@fluid-app/fluid-cli-theme-dev 0.1.65 → 0.1.67
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.
- package/README.md +85 -1
- package/dist/dist-cli-preview/.vite/manifest.json +30 -30
- package/dist/dist-cli-preview/account-screens.js +1 -1
- package/dist/dist-cli-preview/chunks/{ContactsScreen-gIiy4u7c.js → ContactsScreen-IsIZ_lQ0.js} +19 -19
- package/dist/dist-cli-preview/chunks/{MemberManagementProviders-C1bwc3tB.js → MemberManagementProviders-DiQkiWzc.js} +1 -1
- package/dist/dist-cli-preview/chunks/{OrdersScreen-C-b6i5jB.js → OrdersScreen-DkltVCtr.js} +2 -2
- package/dist/dist-cli-preview/chunks/{OrdersScreen-D9HfcTaa.js → OrdersScreen-DrZ3SwEu.js} +1 -1
- package/dist/dist-cli-preview/chunks/{ProfileScreen-DwXozaLs.js → ProfileScreen-Dxspc-6p.js} +4 -4
- package/dist/dist-cli-preview/chunks/{SubscriptionsScreen-CIdYNpWT.js → SubscriptionsScreen-Be0C29uw.js} +2 -2
- package/dist/dist-cli-preview/chunks/{cli-preview-BT_ARKjk.js → cli-preview-BNmwafsA.js} +5 -5
- package/dist/dist-cli-preview/chunks/{countries-api-adapter-lxSE748x.js → countries-api-adapter-InKUzF1q.js} +16 -16
- package/dist/dist-cli-preview/chunks/{create-card-entry-container-D6kSC2Aw.js → create-card-entry-container-C9z7JILL.js} +2 -2
- package/dist/dist-cli-preview/chunks/{mount-account-screen-C_CdoGir.js → mount-account-screen-4rReAofI.js} +1 -1
- package/dist/index.mjs +5565 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -5
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["delay","isRecord","isRecord","themes.getApplicationThemeAvailableThemeables","settingTypesJson.types","escapeHtml","escapeHtml","isRecord","parseDocument","isRecord","records","isRecord","themes.listThemeResources","themes.getThemeAssets","isRecord","themes.createFileResource","themes.destroyFileResource","themes.updateThemeResource","themes.deleteThemeResource","isRecord","themes.listApplicationThemes","themes.getApplicationTheme","themes.cloneApplicationThemeForDevelopment","themes.createApplicationTheme","themes.getApplicationTheme","themes.createApplicationTheme","themes.getApplicationTheme","themes.publishApplicationTheme"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api.ts","../src/plugin-state.ts","../src/theme/dev-server/member-preview-contract.ts","../src/theme/navigation-members.ts","../../../api-clients/themes/src/namespaces/v0.ts","../src/commands/navigate.ts","../src/theme-config.ts","../src/theme/mime-type.ts","../src/theme/resource-key.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../src/theme/file.ts","../src/theme/fluid-ignore.ts","../src/theme/root.ts","../src/theme/dev-server/member-screen-assets.ts","../src/theme/dev-server/hot-reload.ts","../src/theme/dev-server/member-preview-html.ts","../src/theme/dev-server/member-preview.ts","../src/theme/dev-server/sse.ts","../src/theme/dev-server/template-overrides.ts","../src/theme/dev-server/proxy.ts","../src/theme/format-error.ts","../src/theme/dev-server/watcher.ts","../src/theme/background-pull-guard.ts","../src/theme/case-collisions.ts","../src/theme/asset-manifest.ts","../src/theme/stylesheet-keys.ts","../src/theme/member-dependencies.ts","../src/theme/member-configuration.ts","../src/theme/syncer.ts","../src/theme/liquid-delimiters.ts","../src/theme/dev-server/port-preflight.ts","../src/theme/dev-server/index.ts","../src/theme/dev-remote-baseline.ts","../src/theme/shadow-repo.ts","../src/theme-picker.ts","../src/workspace.ts","../src/commands/dev.ts","../src/theme/development-push.ts","../src/theme/merge-push.ts","../src/theme/auto-baseline.ts","../src/theme/legacy-migration.ts","../src/theme/sync-identity.ts","../src/commands/push.ts","../src/theme/background-pull.ts","../src/theme/member-transfer.ts","../src/theme/merge-pull.ts","../src/commands/pull.ts","../src/commands/lint.ts","../src/commands/init.ts","../src/skills/install.ts","../src/commands/skills.ts","../src/commands/theme.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * `??` rather than `||` so an explicitly empty `errors` (`\"\"`, `0`, `false`)\n * is preserved instead of silently falling back to the whole envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n return (body.errors ?? body) as ApiErrorData;\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport type ApiClient = FetchClient;\n\n/** Base URL for all API calls. Set FLUID_API_BASE to route through a BFF. */\nexport function getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\n// Only the desktop's unattended worker opts in. Gate auth resolution to pace\n// even concurrent identity/resource requests within a background theme pull.\nlet backgroundGate = Promise.resolve();\nasync function backgroundToken(tokenOverride?: string): Promise<string | null> {\n backgroundGate = backgroundGate.then(\n () => new Promise((resolve) => setTimeout(resolve, 1_000)),\n );\n await backgroundGate;\n return tokenOverride ?? getAuthToken() ?? null;\n}\n\nexport function createApiClient(tokenOverride?: string): ApiClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () =>\n process.env[\"FLUID_BACKGROUND_SYNC\"] === \"1\"\n ? backgroundToken(tokenOverride)\n : (tokenOverride ?? getAuthToken() ?? null),\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n // Only retry reads: a failed write response may follow a successful\n // server mutation. Preserve the final API error and request ID.\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n\nexport function requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\"Not logged in. Run `fluid login` first.\");\n process.exit(1);\n }\n return token;\n}\n","import { existsSync } from \"node:fs\";\nimport { readConfig, updateConfig } from \"@fluid-app/fluid-cli\";\n\nexport interface DevThemeRef {\n id: number;\n name: string;\n /** Theme that was pulled when this isolated dev target was created. */\n sourceThemeId?: number;\n}\n\ninterface ThemeDevState {\n /**\n * Dev themes keyed per project, so `theme dev` in one working copy never\n * reuses (and clobbers) another project's sandbox theme. See `devThemeKey`.\n * Entries are pruned once their theme directory no longer exists, so the map\n * can't grow without bound as projects (and one-off/temp dirs) come and go.\n */\n devThemes?: Record<string, DevThemeRef>;\n /** Most recently started dev theme — `navigate`'s default target. */\n lastDevThemeId?: number;\n /**\n * Legacy single global dev theme id. Older CLI versions stored one dev theme\n * here regardless of project. Read once for migration (see `getDevTheme`),\n * then dropped in favour of `devThemes`.\n */\n devThemeId?: number;\n /** Legacy companion to `devThemeId`. */\n devThemeName?: string;\n}\n\nconst PLUGIN_KEY = \"theme-dev\";\n\nfunction getState(): ThemeDevState {\n const config = readConfig();\n return (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n}\n\n/** Extract the absolute theme root from a `company:themeRoot` key. */\nfunction themeRootFromKey(key: string): string {\n const sep = key.indexOf(\":\");\n return sep === -1 ? key : key.slice(sep + 1);\n}\n\n/**\n * Set `key` to `theme`, dropping any entries whose theme directory no longer\n * exists. Tying an entry's lifetime to its directory keeps the map bounded —\n * abandoned/deleted projects fall out the next time `theme dev` runs anywhere.\n */\nfunction withDevTheme(\n existing: Record<string, DevThemeRef> | undefined,\n key: string,\n theme: DevThemeRef,\n): Record<string, DevThemeRef> {\n const next: Record<string, DevThemeRef> = {};\n for (const [k, v] of Object.entries(existing ?? {})) {\n if (existsSync(themeRootFromKey(k))) next[k] = v;\n }\n next[key] = theme;\n return next;\n}\n\n/**\n * Stable key identifying a dev theme's owning project: the Fluid company\n * (subdomains are globally unique) plus the absolute theme root. Two working\n * copies — or the same copy pulled from two companies — get distinct keys.\n */\nexport function devThemeKey(\n company: string | undefined,\n themeRoot: string,\n): string {\n return `${company ?? \"default\"}:${themeRoot}`;\n}\n\n/**\n * The dev theme stored for a project key, if any. Falls back once to the legacy\n * global `devThemeId` (older CLI versions) and adopts it for this key — clearing\n * the legacy fields so a second project can't adopt the same theme and collide.\n */\nexport function getDevTheme(key: string): DevThemeRef | undefined {\n const state = getState();\n const existing = state.devThemes?.[key];\n if (existing) return existing;\n\n if (state.devThemeId) {\n const migrated: DevThemeRef = {\n id: state.devThemeId,\n name: state.devThemeName ?? `Development #${state.devThemeId}`,\n };\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const { devThemeId: _id, devThemeName: _name, ...rest } = current;\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...rest,\n devThemes: withDevTheme(rest.devThemes, key, migrated),\n lastDevThemeId: migrated.id,\n },\n },\n };\n });\n return migrated;\n }\n\n return undefined;\n}\n\n/** Store (or refresh) the dev theme for a project key and mark it most-recent. */\nexport function setDevTheme(key: string, theme: DevThemeRef): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...current,\n devThemes: withDevTheme(current.devThemes, key, theme),\n lastDevThemeId: theme.id,\n },\n },\n };\n });\n}\n\n/** Forget a project's dev theme (it was deleted remotely or is no longer a dev theme). */\nexport function clearDevTheme(key: string): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const removed = current.devThemes?.[key];\n if (!removed) return config;\n const { [key]: _removed, ...rest } = current.devThemes ?? {};\n const next: ThemeDevState = { ...current, devThemes: rest };\n // Don't leave `navigate` pointing at a theme we just forgot.\n if (current.lastDevThemeId === removed.id) {\n next.lastDevThemeId = undefined;\n }\n return {\n ...config,\n plugins: { ...config.plugins, [PLUGIN_KEY]: next },\n };\n });\n}\n\n/**\n * Mark a theme as the most recently started dev server (`navigate`'s default)\n * without recording it as a project's dev theme — used for the `--theme`\n * escape hatch, which may target an arbitrary (non-dev) theme.\n */\nexport function setLastDevThemeId(id: number): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: { ...current, lastDevThemeId: id },\n },\n };\n });\n}\n\n/**\n * The dev theme to target by default in `navigate` — the most recently started\n * dev server. Falls back to the legacy global id for users who haven't yet run\n * the per-project `theme dev`.\n */\nexport function getLastDevThemeId(): number | undefined {\n const state = getState();\n return state.lastDevThemeId ?? state.devThemeId;\n}\n","/** A deliberately small projection of the Rails metadata and render contracts. */\nexport interface MemberPreviewPage {\n id: number;\n title: string;\n path: string;\n published: boolean;\n}\nexport interface MemberPreviewSite {\n id: number;\n name: string;\n pages: MemberPreviewPage[];\n}\nexport interface MemberPreviewMetadata {\n members_sites: MemberPreviewSite[];\n system_screens: Array<{ key: string }>;\n}\nexport interface MemberPreviewSelection {\n members_site_id: number;\n country_id?: number;\n language_id?: number;\n member_type_id?: string;\n}\nexport class MemberPreviewError extends Error {\n constructor(\n message: string,\n readonly status: number = 502,\n ) {\n super(message);\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nexport function isId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value > 0;\n}\n/** The metadata API still exposes storefront-era /accounts paths. Account hosts omit it. */\nexport function accountPath(path: string): string {\n return path === \"/accounts\"\n ? \"/\"\n : path.startsWith(\"/accounts/\")\n ? path.slice(\"/accounts\".length)\n : path;\n}\nexport function isProductionPath(value: unknown): value is string {\n if (\n typeof value !== \"string\" ||\n !value.startsWith(\"/\") ||\n value.startsWith(\"//\") ||\n value.includes(\"\\\\\")\n )\n return false;\n const parsed = new URL(value, \"http://preview.invalid\");\n return parsed.pathname === value && !value.startsWith(\"/_members\");\n}\nexport function parseMemberMetadata(value: unknown): MemberPreviewMetadata {\n if (\n !isRecord(value) ||\n !Array.isArray(value.members_sites) ||\n !Array.isArray(value.system_screens)\n ) {\n throw new MemberPreviewError(\n \"This server does not provide compatible member preview metadata.\",\n );\n }\n const sites = value.members_sites.map((site): MemberPreviewSite => {\n if (\n !isRecord(site) ||\n !isId(site.id) ||\n typeof site.name !== \"string\" ||\n !Array.isArray(site.pages)\n )\n throw new MemberPreviewError(\"Invalid member site metadata.\");\n const pages = site.pages.map((page): MemberPreviewPage => {\n if (\n !isRecord(page) ||\n !isId(page.id) ||\n typeof page.title !== \"string\" ||\n !isProductionPath(page.path) ||\n typeof page.published !== \"boolean\"\n )\n throw new MemberPreviewError(\"Invalid member page metadata.\");\n return {\n id: page.id,\n title: page.title,\n path: accountPath(page.path),\n published: page.published,\n };\n });\n if (new Set(pages.map((p) => p.path)).size !== pages.length)\n throw new MemberPreviewError(\"Ambiguous member page paths.\");\n return { id: site.id, name: site.name, pages };\n });\n if (new Set(sites.map((s) => s.id)).size !== sites.length)\n throw new MemberPreviewError(\"Duplicate member site identifiers.\");\n const screens = value.system_screens.map((screen): { key: string } => {\n if (\n !isRecord(screen) ||\n typeof screen.key !== \"string\" ||\n !/^[a-z][a-z0-9_-]*$/.test(screen.key)\n )\n throw new MemberPreviewError(\"Invalid system screen metadata.\");\n return { key: screen.key };\n });\n return { members_sites: sites, system_screens: screens };\n}\nexport function parseSelection(\n params: URLSearchParams,\n): MemberPreviewSelection {\n const site = numericParam(params, \"members_site_id\");\n if (!site)\n throw new MemberPreviewError(\n \"Select a member site at /_members first.\",\n 400,\n );\n const memberType = params.get(\"member_type_id\");\n if (\n params.getAll(\"member_type_id\").length > 1 ||\n (memberType !== null && !/^[\\w-]{1,100}$/.test(memberType))\n )\n throw new MemberPreviewError(\"Invalid member type selection.\", 400);\n return {\n members_site_id: site,\n country_id: numericParam(params, \"country_id\"),\n language_id: numericParam(params, \"language_id\"),\n ...(memberType ? { member_type_id: memberType } : {}),\n };\n}\nfunction numericParam(\n params: URLSearchParams,\n key: string,\n): number | undefined {\n const value = params.get(key);\n if (value === null) return undefined;\n if (\n params.getAll(key).length !== 1 ||\n !/^[1-9]\\d*$/.test(value) ||\n !isId(Number(value))\n )\n throw new MemberPreviewError(`Invalid ${key}.`, 400);\n return Number(value);\n}\nexport function memberPreviewUrl(\n path: string,\n selection: MemberPreviewSelection,\n): string {\n const url = new URL(path, \"http://preview.invalid\");\n if (url.origin !== \"http://preview.invalid\")\n throw new MemberPreviewError(\"Expected a local member path.\", 400);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n url.pathname = `/_members${url.pathname}`;\n for (const key of [\n \"members_site_id\",\n \"country_id\",\n \"language_id\",\n \"member_type_id\",\n ] as const) {\n const value = selection[key];\n if (value === undefined) url.searchParams.delete(key);\n else url.searchParams.set(key, String(value));\n }\n return url.pathname + url.search + url.hash;\n}\nexport function parseMemberRender(\n value: unknown,\n themeId: number,\n selection: MemberPreviewSelection,\n pageId: number | null,\n screenKey: string | null = null,\n): { html: string; styles: string; templateStyles: string } {\n const preview = isRecord(value) ? value.preview : undefined;\n if (\n !isRecord(preview) ||\n preview.contract_version !== 1 ||\n preview.mode !== \"sample\" ||\n preview.theme_id !== themeId ||\n preview.members_site_id !== selection.members_site_id ||\n preview.members_page_id !== pageId ||\n preview.system_screen_key !== screenKey ||\n typeof preview.html_content !== \"string\" ||\n !(\n preview.page_stylesheet === null ||\n typeof preview.page_stylesheet === \"string\"\n ) ||\n !(\n preview.template_stylesheet === null ||\n typeof preview.template_stylesheet === \"string\"\n )\n ) {\n throw new MemberPreviewError(\n \"Incompatible member preview response. Update the server and CLI together.\",\n );\n }\n return {\n html: preview.html_content,\n styles: preview.page_stylesheet ?? \"\",\n templateStyles: preview.template_stylesheet ?? \"\",\n };\n}\n","import {\n isRecord,\n parseMemberMetadata,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./dev-server/member-preview-contract.js\";\n\ninterface NavigatorMetadata extends MemberPreviewMetadata {\n supported_system_screens: string[];\n}\n\n/** Read the running server's acknowledged route map, without sending credentials. */\nexport async function readPreviewMetadata(\n address: string,\n themeId: number,\n): Promise<NavigatorMetadata | undefined> {\n let value: unknown;\n try {\n const response = await fetch(`${address}/_members/_metadata`, {\n redirect: \"error\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new Error(\"Member metadata unavailable\");\n value = await response.json();\n } catch {\n console.warn(\n \"Member preview is unavailable. Start an updated theme dev server to list member pages.\",\n );\n return undefined;\n }\n if (!isRecord(value) || value.mode !== \"sample\") {\n throw new Error(\n \"The running server does not provide compatible preview metadata.\",\n );\n }\n if (value.theme_id !== themeId) {\n throw new Error(\n `The running server is not serving theme #${themeId}. Use its theme ID or restart theme dev.`,\n );\n }\n if (\n !Array.isArray(value.supported_system_screens) ||\n !value.supported_system_screens.every(\n (key): key is string => typeof key === \"string\",\n )\n ) {\n throw new Error(\n \"The running server does not provide preview capabilities.\",\n );\n }\n return {\n ...parseMemberMetadata(value),\n supported_system_screens: value.supported_system_screens,\n };\n}\n\nexport function memberChoices(\n metadata: NavigatorMetadata | undefined,\n): Array<{ title: string; value: string }> {\n if (!metadata) return [];\n return metadata.members_sites.flatMap((site) => {\n const prefix = `Member · ${site.name} (#${site.id}) · `;\n const selection = { members_site_id: site.id };\n return [\n ...site.pages.map((page) => ({\n title: `${prefix}${page.title}${page.published ? \"\" : \" (draft)\"} — ${page.path}`,\n value: memberPreviewUrl(page.path, selection),\n })),\n ...metadata.system_screens\n .filter((screen) =>\n metadata.supported_system_screens.includes(screen.key),\n )\n .map((screen) => {\n return {\n title: `${prefix}${screen.key}`,\n value: memberPreviewUrl(`/${screen.key}`, selection),\n };\n }),\n ];\n });\n}\n\n/** A watcher update may remove a page while the prompt is open. */\nexport async function validateMemberDestination(\n address: string,\n themeId: number,\n path: string,\n): Promise<void> {\n const metadata = await readPreviewMetadata(address, themeId);\n if (!memberChoices(metadata).some((choice) => choice.value === path)) {\n throw new Error(\n \"This member page is no longer available. Run the navigator again to refresh the choices.\",\n );\n }\n}\n","/**\n * Generated API client functions for v0\n *\n * DO NOT EDIT THIS FILE DIRECTLY\n * This file is auto-generated. To update:\n * 1. Update the OpenAPI spec file\n * 2. Run: pnpm generate\n */\n\nimport type { FetchClient } from \"../lib/fetch-client\";\nimport type { operations } from \"../generated/v0\";\n\n// ============================================================================\n// applicationthemetemplates\n// ============================================================================\n\n/**\n * Lists all theme templates\n * Lists the templates available to a theme: the theme's own templates, global templates it has not overridden, and — when you filter by `themeable_type` — active templates from installed droplets. Requires a company admin user or company API token with the `application_themes` view permission. The list is not paginated.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeTemplates(\n client: FetchClient,\n params?: operations[\"listThemeTemplates\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeTemplates\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates`, params);\n}\n\n/**\n * Creates a theme template\n * Creates a template in a theme and publishes it immediately. Pass the theme in the `application_theme_id` query parameter. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, are refused with 422. When the company has moved template stylesheets to theme-level assets, a request that includes `stylesheet` is also refused with 422.\n *\n * @param client - Fetch client instance\n * @param body - body\n * @param params - params\n */\nexport async function createThemeTemplate(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n params: operations[\"createThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"createThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates`, body, { params });\n}\n\n/**\n * List all mysite themes\n * List all mysite themes\n *\n * @param client - Fetch client instance\n\n */\nexport async function listMysiteThemes(\n client: FetchClient,\n): Promise<\n operations[\"listMysiteThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/mysite_themes`);\n}\n\n/**\n * Retrieves a theme template\n * Returns a theme template with details. For section templates whose schema\ndeclares `@theme` or named standalone block references, the response\nincludes an `available_theme_blocks` array with the resolved block schemas\n(name, settings, presets). Private blocks (underscore-prefixed) are excluded\nfrom `@theme` results but included when explicitly referenced by name.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Updates a theme template\n * Updates a template's attributes. Send `publish: true` to publish the saved changes in the same call. Requires the `application_themes` update permission. Global templates and templates provided by a droplet cannot be edited (403). Changing a template into or out of a page type managed by a dedicated endpoint, such as `members_page`, is refused with 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplate(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_theme_templates/${id}`, body);\n}\n\n/**\n * Deletes a theme template\n * Deletes a template, along with any region rules that point at it. If it was the default for its type, another template is promoted to default. Requires the `application_themes` update permission. Global templates, droplet-provided templates, and page types managed by a dedicated endpoint are refused with 403; a template still referenced elsewhere returns 409.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Returns all available themeables for theme template\n * Lists the company's records of the template's type (for example products or pages) that can be assigned to this template, with the template currently assigned to each. Records already using this template are listed first, then by title. Requires the `application_themes` view permission. Paginated with `page` and `per_page` (default 20); only shareable template types are supported (400 otherwise).\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Get available variables for a theme template\n * Get available variables that can be used in the theme template\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableVariables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableVariables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableVariables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_variables`,\n params,\n );\n}\n\n/**\n * Clones a theme template\n * Copies a template within its theme. The copy is named after the original with a ` (Clone)` suffix, is not the default, and gets its own copy of the template's translations. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, cannot be cloned (422).\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/clone`);\n}\n\n/**\n * Publishes the template\n * Publishes the template's current version, or the version given in `version`, so the storefront renders it. Requires the `application_themes` update permission. Global templates cannot be published (403). If the theme is connected to a Git repository, the change is also pushed there in the background.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function publishThemeTemplate(\n client: FetchClient,\n id: string | number,\n params?: operations[\"publishThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"publishThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/publish`, {\n params,\n });\n}\n\n/**\n * Render a system screen in the selected member site's theme\n * Uses the same layout, settings, localization, and GlobalEmbeds pipeline as template previews. The screen body is sealed and never persisted. Unsaved shared settings and layout overrides are accepted; screen body, stylesheet, variables, resource and version overrides are rejected. The real SDK element uses read-only sample data in the builder without member credentials or live member API calls. The builder displays the response in its preview canvas.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function renderThemeSystemScreenPage(\n client: FetchClient,\n body: NonNullable<\n operations[\"renderThemeSystemScreenPage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeSystemScreenPage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/render_page`, body);\n}\n\n/**\n * Renders a page for a theme template\n * Renders the template to HTML for the theme builder's preview, optionally with unsaved content, a specific version, sample resources, a country and language, and a preview audience. Nothing is saved. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function renderThemeTemplatePage(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"renderThemeTemplatePage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeTemplatePage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_theme_templates/${id}/render_page`,\n body,\n );\n}\n\n/**\n * Renders a section template\n * Renders a section template on its own, inside the theme's layout, and returns the HTML for the theme builder's preview. The `id` must be a template of type `sections`. Results are cached briefly per section version. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function renderThemeTemplateSection(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"renderThemeTemplateSection\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/render_section`);\n}\n\n/**\n * Sets a theme template as default\n * Makes this template the default for its type in its theme, so resources without an assigned template render with it. Requires the `application_themes` update permission. Returns the template without a wrapping key.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function setDefaultThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"setDefaultThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/set_default`);\n}\n\n/**\n * Updates themeable records to be used by the specified template\n * Assigns records of the template's type to this template (`added_ids`) and unassigns others (`removed_ids`) in one transaction. Supported for product, medium, enrollment pack, library, page, collection, post, category, and member page templates. Send at least one of the two lists. Requires the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplateThemeables(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplateThemeables\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplateThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_theme_templates/${id}/themeables_update`,\n body,\n );\n}\n\n// ============================================================================\n// application-themes\n// ============================================================================\n\n/**\n * List application themes\n * Get all application themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listApplicationThemes(\n client: FetchClient,\n params?: operations[\"listApplicationThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes`, params);\n}\n\n/**\n * Create an application theme\n * Creates a new theme for your company. Requires a company Bearer token with permission to update themes. The theme starts in `draft` status unless you pass another `status`. Pass `cover_image_url` to set the cover image. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422. A Git repository is provisioned for the theme in the background when Git sync is available.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createApplicationTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createApplicationTheme\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes`, body);\n}\n\n/**\n * Get current active application theme\n * Returns your company's currently active theme with its templates, versions, images, and Git repositories. Requires a company Bearer token with permission to view themes. Returns 404 when no theme is active.\n *\n * @param client - Fetch client instance\n\n */\nexport async function getActiveApplicationTheme(\n client: FetchClient,\n): Promise<\n operations[\"getActiveApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/active`);\n}\n\n/**\n * Import an application theme from zip file\n * Starts importing a theme from a zip archive at a URL you provide. Requires a company Bearer token with permission to update themes. A placeholder theme is created immediately with status `importing`, and the archive is processed in the background; the theme moves to `draft` when the import succeeds or to `error` when it fails. `theme_type` defaults to `fluid`; pass `shopify` to import a Shopify theme archive. The response only confirms that the import started.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function importApplicationThemeFromZip(\n client: FetchClient,\n body: NonNullable<\n operations[\"importApplicationThemeFromZip\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"importApplicationThemeFromZip\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/import_zip`, body);\n}\n\n/**\n * Get an application theme\n * Returns one of your company's themes with its templates (including full Liquid content), versions, images, and Git repositories. Pass `version` to read the theme as it was at a past version. Requires a company Bearer token with permission to view themes. Themes belonging to another company return 404.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationTheme(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationTheme\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}`, params);\n}\n\n/**\n * Update an application theme\n * Updates a theme's attributes. Requires a company Bearer token with permission to update themes. Setting `active` to true makes the theme `active`; false sets it to `draft`. Send `publish` as `\"true\"` to publish the theme and sync it to its Git repository in the same request. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateApplicationTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_themes/${id}`, body);\n}\n\n/**\n * Delete an application theme\n * Permanently deletes one of your company's themes along with its templates and region rules. Any Git repositories connected to the theme are archived afterwards. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_themes/${id}`);\n}\n\n/**\n * Returns available themeables for a given type scoped to the theme's company\n * Lists the company resources of one type (for example `product`, `page`, `collection`, or `enrollment_pack`) that templates in this theme can be assigned to, sorted by title. Pass `themeable` to choose the type; unsupported types return 400. Pass `search` to filter by title. Results are paginated, with page details in `meta.pagination`, 20 per page by default. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationThemeAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationThemeAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Clone an application theme\n * Creates a copy of one of your company's themes, including its templates, images, assets, and member configuration. The copy keeps the source theme's name and starts in `draft` status. Requires a company Bearer token with permission to update themes. Returns 422 when the source theme's member configuration cannot be copied.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/clone`);\n}\n\n/**\n * Create a development reference clone of an application theme\n * Creates an isolated development theme while preserving existing DAM and ImageKit references without transferring asset bytes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function cloneApplicationThemeForDevelopment(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"cloneApplicationThemeForDevelopment\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"cloneApplicationThemeForDevelopment\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${id}/clone_for_development`,\n body,\n );\n}\n\n/**\n * Get a short-lived download URL for the theme at a past version\n * Returns a pre-signed URL for a tarball of the theme's connected\nrepository at one commit. The URL is fetched **directly from the Git\nprovider by the client** and needs no authentication of its own — Fluid\nmints it and never proxies the bytes, so repository credentials stay\nserver-side.\n\nOne request returns the entire tree, binary assets included as real\nbytes. That matters for restoring a past version: an asset's current\nURL points at its current content, so anything built from per-file\nlinks would silently fail to revert images.\n\nRead-only. This exposes a version; it does not apply one. Clients with\na local checkout restore by writing the archive into their theme\nfolder, then pushing — the same local-then-publish flow as the Mist and\nPortal rollbacks. A client without one (the admin editor) uses the\ngit_restore operation instead, which applies a version to the theme's\ndraft records server-side.\n\nThe URL is short-lived. Re-request it rather than caching it.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param params - params\n */\nexport async function getApplicationThemeGitArchiveUrl(\n client: FetchClient,\n id: string | number,\n params: operations[\"getApplicationThemeGitArchiveUrl\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeGitArchiveUrl\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_archive_url`, params);\n}\n\n/**\n * Connect the theme to a Git repository, provisioning one if needed\n * Ensures the theme has a connected Git repository, creating and\nprovisioning one when it doesn't already have it.\n\nIdempotent — an already-connected theme returns its repository\nunchanged, and one whose earlier provisioning failed has it retried.\nSafe to call twice.\n\nProvisioning the upstream repository happens in the background, so a\nfreshly created connection reports `status: provisioning` and becomes\n`connected` shortly after. Poll the git_versions operation, or retry\nthis one, to observe the change.\n\nThis connects themes that automatic provisioning deliberately skips —\ndevelopment themes among them — because an explicit request is a\ndifferent intent from \"provision every theme on creation\".\n\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function connectApplicationThemeToGit(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"connectApplicationThemeToGit\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_connect`);\n}\n\n/**\n * Restore the theme's draft content to a past published version\n * Applies one published version back to the theme, for a client that has\nno local checkout to stage it in. The git_archive_url operation is the\nequivalent for one that does.\n\nPublishes as it writes: every record goes through the theme's own write\nseam, which publishes it. Publishing is per record and there is no\nwhole-theme publish, so a restore that only touched drafts would leave\nthe caller publishing every template by hand. For an **active** theme\nthis therefore goes live.\n\nIt remains undoable: the version being left behind is still in the\nhistory this restored from.\n\nThe version is read as a single archive rather than file by file, and\napplied in one transaction: a tree that cannot be written leaves the\ntheme as it was rather than half-restored.\n\nFiles the target version does not contain are removed, which is what\nmakes this a restore rather than a merge — a template added after that\nversion would otherwise survive into a tree that never had it.\n\nUndoing a restore is another restore: the version you were on is still\nin the history you restored from.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function restoreApplicationThemeGitVersion(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"restoreApplicationThemeGitVersion\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"restoreApplicationThemeGitVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_restore`, body);\n}\n\n/**\n * List a theme's published version history from its connected Git repo\n * Returns the recent commits on the theme's connected Git repository,\nnewest first. Themes are DB-canonical: Fluid writes one commit per\npublish, so this log is the theme's published version history.\n\n`author_name` is the committing identity, which for anything Fluid\npushed is the Fluid GitHub App. The person who published is named in\n`message`.\n\nRead-only — this endpoint never provisions or repairs a repository.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function listApplicationThemeGitVersions(\n client: FetchClient,\n id: string | number,\n params?: operations[\"listApplicationThemeGitVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemeGitVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_versions`, params);\n}\n\n/**\n * Import an application theme\n * Copies a theme from the Fluid theme catalog into your company. The copy is made in the background, so the response only confirms that the import started; the new theme appears in your theme list when it finishes. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function importApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.post(`/api/application_themes/${id}/import`);\n}\n\n/**\n * Publishes the theme\n * Publishes the theme so the storefront serves its latest content, then syncs it to any connected Git repository. Pass the optional `version` query parameter to publish a specific past version instead; an unknown version returns 404. Requires a company Bearer token with permission to update themes. Returns 422 when the theme's member configuration references a template or navigation item that does not exist.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function publishApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"publishApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/publish`);\n}\n\n/**\n * Get theme assets\n * Lists the files (images, stylesheets, scripts, and other assets) uploaded to one of your company's themes. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeAssets(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeAssets\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/theme_assets`);\n}\n\n// ============================================================================\n// applicationthemeresources\n// ============================================================================\n\n/**\n * Lists all theme resources\n * Lists resources for a theme owned by the current company, including the canonical members_sites.json snapshot. Themes without member sites receive an explicit empty version 2 document. Companyless root themes are not accessible through this endpoint. The content field remains JSON-encoded text.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function listThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"listThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${application_theme_id}/resources`,\n );\n}\n\n/**\n * Updates a theme resource\n * Submit member template dependencies together with members_sites.json using member_files so navigation is captured before any file changes. The files and member graph apply atomically; a rejected dependency rolls back the bundle. Existing single-resource writes remain supported. Missing manifests preserve existing configuration; deleting the file is refused.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function updateThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"updateThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_themes/${application_theme_id}/resources`,\n body,\n );\n}\n\n/**\n * Deletes a theme resource\n * Deletes one theme file, identified by its `key`, as the theme CLI does when a local file is removed. Send the `base_sha` from your last pull to guard against overwriting newer server changes (409 when stale). The member site manifest `members_sites.json` cannot be deleted this way. Requires a company admin token with the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function deleteThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"deleteThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"deleteThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/application_themes/${application_theme_id}/resources`,\n { body },\n );\n}\n\n/**\n * Commits the theme's current state as one version\n * Called once by the CLI AFTER its per-file push loop, and the only thing that turns a push into a commit — the per-file writes themselves just mark the theme changed. This is the other half of check_push, which runs once before the loop. Omitting it is safe: the server sweeps anything left unsynced, so an older CLI lags by at most a sweep rather than going unsynced.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function syncThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"syncThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/sync`,\n );\n}\n\n/**\n * Check a theme push before uploading\n * Merge-aware push preflight: CLI checks its stored base_sha matches the server's current content_version_sha before starting the per-file push loop. Enforcement lives on the per-file PUT/DELETE too; this endpoint is UX so the CLI can fail fast before touching any state.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function checkPushThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"checkPushThemeResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"checkPushThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/check_push`,\n body,\n );\n}\n\n// ============================================================================\n// file-resources\n// ============================================================================\n\n/**\n * Returns a list of file resources\n * Lists the company's uploaded files (images, stylesheets, scripts, and other theme assets), newest first by default. Available to company admins with the `file_resources` view permission and to reps. Supports `search_query`, `sorted_by`, `with_types` (partial content-type match, such as `image` or `css`), and `size_greater_than` / `size_less_than` in megabytes. Paginated with `page` and `per_page` (default 10); totals are returned in `meta`.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listFileResources(\n client: FetchClient,\n params?: operations[\"listFileResources\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources`, params);\n}\n\n/**\n * Creates a file resource\n * Uploads a file to the company's asset library. Provide exactly one source: `content` for a text file, a multipart `file` upload, or `dam_asset_code` to link an existing digital asset; otherwise supply `url` with `filename`, `content_type`, and `content_size`. To attach the file to a theme or template, send both `relateable_type` (`ApplicationTheme` or `ApplicationThemeTemplate`) and `relateable_id`. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFileResource(\n client: FetchClient,\n body: NonNullable<\n operations[\"createFileResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFileResource\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources`, body);\n}\n\n/**\n * Creates multiple file resources\n * Creates several files in one transaction; if any file fails, none are created (422). Each entry accepts the same source fields as creating a single file (`content`, `file`, or `url` with its metadata). Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkCreateFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkCreateFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkCreateFileResources\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources/bulk_create`, body);\n}\n\n/**\n * Deletes multiple file resources\n * Deletes the listed files that belong to the company. IDs that do not match a company file are ignored; `meta.destroyed_count` reports how many were deleted, and `ids` echoes the request. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkDestroyFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkDestroyFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkDestroyFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/bulk_destroy`, { body });\n}\n\n/**\n * Shows a file resource\n * Returns one of the company's uploaded files. Available to company admins with the `file_resources` view permission and to reps. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function showFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"showFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources/${id}`);\n}\n\n/**\n * Deletes a file resource\n * Deletes one of the company's uploaded files and returns its ID. Requires the `file_resources` update permission. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function destroyFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"destroyFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/${id}`);\n}\n\n// ============================================================================\n// root-themes\n// ============================================================================\n\n/**\n * List the company's root themes\n * Get all root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listRootThemes(\n client: FetchClient,\n params?: operations[\"listRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes`, params);\n}\n\n/**\n * Create a root theme\n * Submits a new theme to the Fluid theme library for review. The theme is created\nunder your company with an initial `1.0` version in `draft` status, built from the\nzip archive at `zip_file_url`. The version stays in draft until a Fluid platform\nadministrator approves or rejects it.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. The response carries only a confirmation message; call\n`GET /api/root_themes/my` to see the new theme and its versions. A failure while\ncreating the theme returns `422`.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createRootTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes`, body);\n}\n\n/**\n * List company root themes\n * Get all company root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listCompanyRootThemes(\n client: FetchClient,\n params?: operations[\"listCompanyRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listCompanyRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes/my`, params);\n}\n\n/**\n * Update a root theme\n * Updates a theme your company submitted to the Fluid theme library. You can change\nits `name`, `description`, `demo_url`, and `status`. When you pass a\n`zip_file_url` that differs from the latest version's archive, any pending\n`draft` versions are marked `superseded` and a new draft version with the next\nmajor version number (for example `2.0`) is created for review, using\n`changelog` as its changelog. Passing the same archive URL again does not create\na new version.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. You can only update themes that belong to your company; any other\n`id` returns `404`. The response carries only a confirmation message.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/root_themes/${id}`, body);\n}\n\n/**\n * Delete a root theme\n * Permanently deletes a theme your company submitted to the Fluid theme library,\ntogether with its versions and any theme region rules attached to it. Requires a\ncompany admin Bearer token with the `application_themes` update permission. You\ncan only delete themes that belong to your company; any other `id` returns\n`404`. If the theme cannot be deleted, the request returns `422`.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteRootTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/root_themes/${id}`);\n}\n\n/**\n * Update a root theme status\n * Approves or rejects one version of a theme in the Fluid theme library. Restricted\nto Fluid platform administrators; any other caller receives `403`.\n\nSet `approved` to `true` to publish a `draft` version, or to re-publish a\npreviously `approved` version (for example, to roll back). The version moves to\n`publishing` and is imported in the background. Set `approved` to `false` to\nreject a `draft` version. Any other transition, such as rejecting an approved\nversion, returns `422`. An unknown theme `id` or a `theme_version_id` that does\nnot belong to the theme returns `404`.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootThemeStatus(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootThemeStatus\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootThemeStatus\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes/${id}/status`, body);\n}\n\n// ============================================================================\n// theme-region-rules\n// ============================================================================\n\n/**\n * List theme region rules\n * Retrieve a list of theme region rules for the current company\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeRegionRules(\n client: FetchClient,\n params?: operations[\"listThemeRegionRules\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeRegionRules\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules`, params);\n}\n\n/**\n * Create theme region rule\n * Create a new theme region rule\n\nA region rule controls what visitors from one region see at a storefront route:\na specific theme template (`redirect_type: template`), a redirect to\n`redirect_url` (`redirect`), or no page at all (`disabled`). Optionally point the\nrule at a specific resource with `resource_type` and `resource_id`.\n\nIf `application_theme_id` is omitted, the rule is attached to your company's\nactive theme. Requires a company admin or rep Bearer token with the\n`application_themes` update permission.\n\nThis operation is an upsert keyed on theme, `route_path`, and `region_code`. When\nno matching rule exists it creates one and returns `201`. When one exists, the\nrequest body replaces its state and the response is `200`; omitted fields fall\nback to their defaults (`priority` 0, `active` true, `redirect_type`\n`template`). To change only some fields of an existing rule, use the update\noperation instead.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createThemeRegionRule(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/theme_region_rules`, body);\n}\n\n/**\n * Show theme region rule\n * Retrieve a specific theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules/${id}`);\n}\n\n/**\n * Update theme region rule\n * Update an existing theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeRegionRule(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/theme_region_rules/${id}`, body);\n}\n\n/**\n * Delete theme region rule\n * Delete a theme region rule\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.delete(`/api/theme_region_rules/${id}`);\n}\n","import { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { getLastDevThemeId } from \"../plugin-state.js\";\nimport {\n memberChoices,\n readPreviewMetadata,\n validateMemberDestination,\n} from \"../theme/navigation-members.js\";\nimport { themes } from \"@fluid-app/themes-api-client\";\n\nfunction localSuggest(\n input: string,\n choices: prompts.Choice[],\n): prompts.Choice[] {\n if (!input) return choices;\n const lower = input.toLowerCase();\n return choices.filter((c) => c.title.toLowerCase().includes(lower));\n}\n\ninterface ThemeTemplate {\n id: number;\n name: string;\n themeable_type: string;\n default: boolean;\n}\n\ninterface TemplatesResponse {\n templates: ThemeTemplate[];\n}\n\nconst THEMEABLE_TYPE_MAP: Record<string, string> = {\n \"/home\": \"home_page\",\n \"/home/shop\": \"shop_page\",\n \"/home/join\": \"join_page\",\n \"/cart\": \"cart_page\",\n \"/home/blog\": \"post_page\",\n \"/home/categories\": \"category_page\",\n \"/home/collections\": \"collection_page\",\n};\n\nconst STATIC_ROUTES = [\n { label: \"Home\", path: \"/home\" },\n { label: \"Shop\", path: \"/home/shop\" },\n { label: \"Join / Sign Up\", path: \"/home/join\" },\n { label: \"Cart\", path: \"/cart\" },\n { label: \"Blog\", path: \"/home/blog\" },\n { label: \"Categories (all)\", path: \"/home/categories\" },\n { label: \"Collections (all)\", path: \"/home/collections\" },\n] as const;\n\nconst RESOURCE_ROUTES = [\n {\n label: \"Category\",\n type: \"category\",\n template: \"/home/categories/%s\",\n fallback: \"/home/categories\",\n },\n {\n label: \"Collection\",\n type: \"collection\",\n template: \"/home/collections/%s\",\n fallback: \"/home/collections\",\n },\n {\n label: \"Product\",\n type: \"product\",\n template: \"/home/products/%s\",\n fallback: \"/home/shop\",\n },\n {\n label: \"Library\",\n type: \"library\",\n template: \"/home/libraries/%s\",\n fallback: \"/home/libraries\",\n },\n {\n label: \"Post\",\n type: \"post\",\n template: \"/home/posts/%s\",\n fallback: \"/home/blog\",\n },\n {\n label: \"Media\",\n type: \"medium\",\n template: \"/home/media/%s\",\n fallback: \"/home/media\",\n },\n {\n label: \"Enrollment Pack\",\n type: \"enrollment_pack\",\n template: \"/home/enrollments/%s\",\n fallback: \"/home/join\",\n },\n {\n label: \"Page\",\n type: \"page\",\n template: \"/home/pages/%s\",\n fallback: \"/home/pages\",\n },\n] as const;\n\nasync function fetchTemplatesForType(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n): Promise<ThemeTemplate[]> {\n const params = new URLSearchParams({\n application_theme_id: String(themeId),\n themeable_type: themeableType,\n published: \"true\",\n });\n const body = await api.get<TemplatesResponse>(\n `/api/application_theme_templates?${params}`,\n );\n return body.templates ?? [];\n}\n\nasync function selectTemplate(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n onCancel: () => void,\n): Promise<number | null> {\n const templates = await fetchTemplatesForType(api, themeId, themeableType);\n if (templates.length <= 1) return null;\n\n const templateChoices = templates.map((t) => ({\n title: `${t.name}${t.default ? \" (default)\" : \"\"}`,\n value: t.id,\n }));\n const { templateId } = await prompts(\n {\n type: \"autocomplete\",\n name: \"templateId\",\n message: \"Select a template\",\n choices: templateChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n return templateId ?? null;\n}\n\nexport function createNavigateCommand(): Command {\n return new Command(\"navigate\")\n .description(\"Interactively navigate to a route in the dev server browser\")\n .option(\"--host <host>\", \"Dev server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Dev server port\", \"9292\")\n .option(\"-t, --theme <id>\", \"Theme ID (defaults to active dev theme)\")\n .action(async (opts: { host: string; port: string; theme?: string }) => {\n requireToken();\n\n const themeId = opts.theme ? Number(opts.theme) : getLastDevThemeId();\n\n if (!themeId) {\n console.error(\n \"No active dev theme. Run `fluid theme dev` first, or pass --theme <id>.\",\n );\n process.exit(1);\n }\n\n const address = `http://${opts.host}:${opts.port}`;\n\n await navigateTheme(createApiClient(), themeId, address);\n });\n}\n\n/** Shared by theme navigate and theme dev --navigate. */\nexport async function navigateTheme(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n address: string,\n): Promise<void> {\n type Choice = {\n title: string;\n value:\n | string\n | {\n resourceType: string;\n template: string;\n fallback: string;\n label: string;\n };\n };\n const choices: Choice[] = [\n ...STATIC_ROUTES.map((r) => ({ title: r.label, value: r.path })),\n ...RESOURCE_ROUTES.map((r) => ({\n title: `${r.label} (select specific)`,\n value: {\n resourceType: r.type,\n template: r.template,\n fallback: r.fallback,\n label: r.label,\n },\n })),\n ];\n\n let cancelled = false;\n const onCancel = () => {\n cancelled = true;\n return false;\n };\n\n const metadata = await readPreviewMetadata(address, themeId);\n choices.push(...memberChoices(metadata));\n const unavailable = metadata?.system_screens.filter(\n (screen) => !metadata.supported_system_screens.includes(screen.key),\n );\n if (unavailable?.length)\n console.log(\n `System-screen preview unavailable: ${unavailable.map((screen) => screen.key).join(\", \")}.`,\n );\n\n const { dest } = await prompts(\n {\n type: \"autocomplete\",\n name: \"dest\",\n message: \"Select a route\",\n choices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n if (!dest) return;\n\n let path: string;\n let themeableType: string | undefined;\n\n if (typeof dest === \"string\") {\n if (dest.startsWith(\"/_members/\")) {\n await validateMemberDestination(address, themeId, dest);\n }\n path = dest;\n themeableType = THEMEABLE_TYPE_MAP[dest];\n } else {\n themeableType = dest.resourceType;\n const body = await themes.getApplicationThemeAvailableThemeables(\n api,\n themeId,\n { themeable: dest.resourceType, per_page: 50 },\n );\n const resources = body.available_themeables ?? [];\n\n if (!resources.length) {\n console.log(`No ${dest.label} resources found, using listing page.`);\n path = dest.fallback;\n } else {\n const resourceChoices = resources.map((r) => ({\n title: r.title ?? r.slug ?? \"Untitled\",\n value: r.slug,\n }));\n const { slug } = await prompts(\n {\n type: \"autocomplete\",\n name: \"slug\",\n message: `Select a ${dest.label.toLowerCase()}`,\n choices: resourceChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n if (cancelled || !slug) return;\n path = dest.template.replace(\"%s\", encodeURIComponent(slug as string));\n }\n }\n\n let templateParam = \"\";\n if (themeableType) {\n const templateId = await selectTemplate(\n api,\n themeId,\n themeableType,\n onCancel,\n );\n if (templateId) {\n templateParam = `?theme_template_id=${templateId}`;\n }\n }\n\n if (cancelled) return;\n const url = `${address}${path}${templateParam}`;\n console.log(`\\nNavigating to: ${url}\\n`);\n const open = (await import(\"open\")).default;\n await open(url);\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface ThemeConfig {\n themeId: number;\n themeName: string;\n company: string;\n /**\n * Server's `content_version_sha` at the moment of the last pull.\n * Sent back to the server as `base_sha` on push — the server rejects\n * with 409 if it no longer matches, so a push against stale data\n * can't silently clobber. Optional so a `.fluid-theme.json` written\n * by an older CLI (checksums-era) still deserializes; when absent,\n * the CLI falls back to the pre-merge-aware behavior (no server-side\n * check).\n */\n baseSha?: string;\n /** Digest of the managed ImageKit references captured with this baseline. */\n assetManifestSha?: string;\n}\n\n/**\n * Shape of a `.fluid-theme.json` written by the pre-shadow-repo CLI.\n * We read it only to migrate the shadow repo forward on first\n * new-CLI use; we never write this shape back.\n */\nexport interface LegacyThemeConfig extends ThemeConfig {\n lastPulledAt?: string | null;\n checksums?: Record<string, string>;\n}\n\nconst CONFIG_FILE = \".fluid-theme.json\";\n\n/**\n * `company` must be the bare subdomain slug — the dev server builds\n * `<company>.fluid.app` from it, so a full domain here produces an\n * unreachable `<slug>.fluid.app.fluid.app` host. Some external tooling\n * writes the full domain (or a URL); accept those and reduce them to\n * the subdomain.\n */\nfunction normalizeCompany(company: string): string {\n const host = company\n .replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\")\n .replace(/[/?#].*$/, \"\");\n return host.replace(/\\.fluid\\.app$/i, \"\");\n}\n\nfunction configPath(themeRoot: string): string {\n return join(themeRoot, CONFIG_FILE);\n}\n\n/** Read `.fluid-theme.json` from a theme directory, or null if it doesn't exist. */\nexport function readThemeConfig(themeRoot: string): ThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n const config = JSON.parse(raw) as ThemeConfig;\n if (typeof config.company === \"string\") {\n config.company = normalizeCompany(config.company);\n }\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Read a legacy config (pre-shadow-repo). Used only by the migration\n * path on first new-CLI pull to seed shadow HEAD from files whose\n * local content still matches their stored sha256 checksum.\n */\nexport function readLegacyThemeConfig(\n themeRoot: string,\n): LegacyThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw) as LegacyThemeConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a push or pull to `theme` may use this directory's own sync state\n * and rebind it to `theme`.\n *\n * Development themes are scratch copies `fluid theme dev` creates and syncs.\n * Using one here would move the project's sync baseline to the scratch copy\n * and rebind the project, so a later Publish would update the copy instead of\n * the theme the project was pulled from. Push syncs such a theme separately;\n * pull refuses. An explicit `--theme` to any other theme still rebinds.\n */\nexport function mayBindTheme(\n existing: ThemeConfig | null,\n theme: { id: number; status?: string | null },\n): boolean {\n return (\n !existing || existing.themeId === theme.id || theme.status !== \"development\"\n );\n}\n\n/** Why `mayBindTheme` refused, with the way forward. */\nexport function developmentThemeRefusal(\n existing: ThemeConfig,\n theme: { id: number; name: string },\n): string {\n return (\n `${theme.name} (#${theme.id}) is a development theme, and this project is bound to ${existing.themeName} (#${existing.themeId}). ` +\n \"Pulling it here would replace this project's sync state. Use `fluid theme dev` or `fluid theme push -t` to update the development theme, or pull it into a separate directory with --root.\"\n );\n}\n\n/** Write `.fluid-theme.json` to a theme directory. */\nexport function writeThemeConfig(themeRoot: string, config: ThemeConfig): void {\n const path = configPath(themeRoot);\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n","const TEXT_TYPES: Record<string, string> = {\n \".liquid\": \"text/x-liquid\",\n \".json\": \"application/json\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".html\": \"text/html\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".svg\": \"image/svg+xml\",\n};\n\nconst BINARY_TYPES: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".otf\": \"font/otf\",\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n};\n\nexport interface MimeType {\n name: string;\n isText: boolean;\n}\n\nexport function mimeTypeFor(ext: string): MimeType {\n const text = TEXT_TYPES[ext];\n if (text) return { name: text, isText: true };\n\n const binary = BINARY_TYPES[ext];\n if (binary) return { name: binary, isText: false };\n\n return { name: \"application/octet-stream\", isText: false };\n}\n","const THEME_LEVEL_RESOURCE_KEYS = new Set([\n \"global_styles.css\",\n \"styles.css\",\n \"variables.json\",\n \"members_sites.json\",\n]);\n\n/**\n * Template types Fluid has retired. The resources API answers 404 for them, so\n * a theme that still carries their folders must not try to upload them: member\n * home pages are `members_page` templates on the `/` route, sign-in is hosted,\n * and account pages became member pages.\n */\nexport const RETIRED_TEMPLATE_TYPES: ReadonlySet<string> = new Set([\n \"account_page\",\n \"members_dashboard\",\n \"members_login\",\n]);\n\nexport function isRetiredTemplateKey(relativePath: string): boolean {\n const [prefix] = normalizeThemeResourceKey(relativePath).split(\"/\");\n return prefix !== undefined && RETIRED_TEMPLATE_TYPES.has(prefix);\n}\n\nconst COMPOSITE_RESOURCE_FILE_NAMES = new Set([\n \"index.liquid\",\n \"styles.css\",\n \"variables.json\",\n]);\n\nfunction hasSafeSegments(key: string): boolean {\n const segments = key.split(\"/\");\n return segments.every(\n (segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\",\n );\n}\n\nexport function normalizeThemeResourceKey(value: string): string {\n return value.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Whether a relative file path is a resource key accepted by the Fluid themes\n * resource API. Local project files (package manifests, QA evidence, scripts,\n * source baselines, and similar agent artifacts) must never be uploaded.\n */\nexport function isThemeResourceKey(relativePath: string): boolean {\n const key = normalizeThemeResourceKey(relativePath);\n if (!hasSafeSegments(key)) return false;\n if (THEME_LEVEL_RESOURCE_KEYS.has(key)) return true;\n if (isRetiredTemplateKey(key)) return false;\n\n const segments = key.split(\"/\");\n const prefix = segments[0];\n const fileName = segments.at(-1);\n if (!prefix || !fileName || segments.length < 2) return false;\n\n if (\n prefix === \"assets\" ||\n prefix === \"config\" ||\n prefix === \"locales\" ||\n prefix === \"layouts\"\n ) {\n return true;\n }\n\n return segments.length >= 3 && COMPOSITE_RESOURCE_FILE_NAMES.has(fileName);\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n // Duplicate \"blocks\" keys\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return RESERVED_SECTION_TYPES.has(type) || EXTENSION_URI_PATTERN.test(type);\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension URIs are never flagged (see `isNonLocalSectionType`).\n * Emits one `error` diagnostic per missing section type per template.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: \"error\",\n message: `references missing section '${reference.type}'`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","import {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n statSync,\n} from \"node:fs\";\nimport { extname, basename, relative, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { mimeTypeFor, type MimeType } from \"./mime-type.js\";\nimport { normalizeThemeResourceKey } from \"./resource-key.js\";\nimport {\n validateSchemaText,\n type Diagnostic,\n type BlocksSchemaType,\n} from \"@fluid-app/theme-schema\";\n\n// Top-level theme folders that are not page templates. Everything else at the\n// top level (home_page, product, page, footer, navbar, …) is a page template.\nconst NON_TEMPLATE_DIRS = new Set([\n \"sections\",\n \"blocks\",\n \"components\",\n \"layouts\",\n \"config\",\n \"assets\",\n \"locales\",\n]);\n\nexport class ThemeFile {\n readonly absolutePath: string;\n readonly relativePath: string;\n readonly mime: MimeType;\n\n constructor(absolutePath: string, root: string) {\n this.absolutePath = absolutePath;\n this.relativePath = normalizeThemeResourceKey(relative(root, absolutePath));\n this.mime = mimeTypeFor(extname(absolutePath).toLowerCase());\n }\n\n get name(): string {\n return basename(this.absolutePath);\n }\n\n get isText(): boolean {\n return this.mime.isText;\n }\n\n get isLiquid(): boolean {\n return this.absolutePath.endsWith(\".liquid\");\n }\n\n get isJson(): boolean {\n return this.absolutePath.endsWith(\".json\");\n }\n\n get exists(): boolean {\n return existsSync(this.absolutePath);\n }\n\n read(): string {\n return readFileSync(this.absolutePath, \"utf-8\");\n }\n\n readBinary(): Buffer {\n return readFileSync(this.absolutePath);\n }\n\n write(content: string | Buffer): void {\n mkdirSync(dirname(this.absolutePath), { recursive: true });\n if (typeof content === \"string\") {\n writeFileSync(this.absolutePath, content, \"utf-8\");\n } else {\n writeFileSync(this.absolutePath, content);\n }\n }\n\n checksum(): string {\n const content = this.isText ? this.read() : this.readBinary();\n return createHash(\"sha256\").update(content).digest(\"hex\");\n }\n\n size(): number {\n return statSync(this.absolutePath).size;\n }\n\n get isTemplate(): boolean {\n // Page templates (home_page, product, footer, navbar, …) live in top-level\n // page-type folders and expect blocks as objects. The reserved categories\n // below either expect blocks as arrays (sections, blocks, components) or\n // carry no block schema (layouts, config, assets, locales).\n const parts = this.relativePath.split(/[/\\\\]/);\n return parts.length >= 2 && !NON_TEMPLATE_DIRS.has(parts[0]!);\n }\n\n validateSchema(): Diagnostic[] {\n if (!this.isLiquid) return [];\n\n const blocksSchemaType: BlocksSchemaType = this.isTemplate\n ? \"object\"\n : \"array\";\n\n return validateSchemaText(this.read(), { blocksSchemaType });\n }\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\n\nconst IGNORE_FILE = \".fluidignore\";\n\ninterface Pattern {\n negated: boolean;\n pattern: string;\n}\n\nexport class FluidIgnore {\n private patterns: Pattern[];\n\n constructor(root: string) {\n this.patterns = this.parse(join(root, IGNORE_FILE));\n }\n\n ignore(relativePath: string): boolean {\n let result = false;\n for (const { negated, pattern } of this.patterns) {\n if (this.match(pattern, relativePath)) {\n result = !negated;\n }\n }\n return result;\n }\n\n private parse(filePath: string): Pattern[] {\n if (!existsSync(filePath)) return [];\n return readFileSync(filePath, \"utf-8\")\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l && !l.startsWith(\"#\"))\n .map((l) => {\n const negated = l.startsWith(\"!\");\n let pattern = negated ? l.slice(1) : l;\n if (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n return { negated, pattern };\n });\n }\n\n private match(pattern: string, path: string): boolean {\n if (pattern.endsWith(\"/\")) {\n return path.startsWith(pattern) || path === pattern.slice(0, -1);\n }\n if (pattern.includes(\"/\")) {\n return this.fnmatch(pattern, path);\n }\n return this.fnmatch(pattern, path) || this.fnmatch(pattern, basename(path));\n }\n\n private fnmatch(pattern: string, str: string): boolean {\n const re = pattern\n .split(\"**\")\n .map((p) =>\n p\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\"),\n )\n .join(\".*\");\n return new RegExp(`^${re}$`).test(str);\n }\n}\n","import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { ThemeFile } from \"./file.js\";\nimport { FluidIgnore } from \"./fluid-ignore.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nconst THEME_MARKERS = [\"templates\", \"assets\", \"config\"];\nconst THEME_ASSET_MANIFEST = \".fluid-assets.json\";\n\nexport class ThemeRoot {\n readonly root: string;\n readonly ignore: FluidIgnore;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.ignore = new FluidIgnore(this.root);\n }\n\n isValid(): boolean {\n return (\n existsSync(join(this.root, THEME_ASSET_MANIFEST)) ||\n THEME_MARKERS.some((m) => {\n try {\n return statSync(join(this.root, m)).isDirectory();\n } catch {\n return false;\n }\n })\n );\n }\n\n files(): ThemeFile[] {\n return this.glob(this.root).filter(\n (f) =>\n isThemeResourceKey(f.relativePath) &&\n !this.ignore.ignore(f.relativePath),\n );\n }\n\n isResourcePath(pathOrFile: string | ThemeFile): boolean {\n const file = this.file(pathOrFile);\n return isThemeResourceKey(file.relativePath);\n }\n\n file(pathOrFile: string | ThemeFile): ThemeFile {\n if (pathOrFile instanceof ThemeFile) return pathOrFile;\n const abs = isAbsolute(pathOrFile)\n ? pathOrFile\n : join(this.root, pathOrFile);\n return new ThemeFile(abs, this.root);\n }\n\n private glob(dir: string): ThemeFile[] {\n const results: ThemeFile[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\")) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\") continue;\n results.push(...this.glob(full));\n } else if (entry.isFile()) {\n results.push(new ThemeFile(full, this.root));\n }\n }\n return results;\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The runtime is shipped with this CLI, not taken from a mutable theme/CDN URL. */\nexport function findMemberScreenAssets(): string | undefined {\n let directory = dirname(fileURLToPath(import.meta.url));\n while (true) {\n for (const candidate of [\n join(directory, \"dist-cli-preview\"),\n join(directory, \"dist\", \"dist-cli-preview\"),\n ]) {\n if (existsSync(join(candidate, \"capabilities.json\"))) return candidate;\n }\n const parent = dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\nexport class MemberScreenAssets {\n readonly screens: string[];\n constructor(private readonly directory = findMemberScreenAssets()) {\n this.screens = [];\n if (!directory) return;\n try {\n const value: unknown = JSON.parse(\n readFileSync(join(directory, \"capabilities.json\"), \"utf8\"),\n );\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"version\" in value) ||\n value.version !== 1 ||\n !(\"screens\" in value) ||\n !Array.isArray(value.screens)\n )\n return;\n if (\n !value.screens.every(\n (screen: unknown) =>\n typeof screen === \"string\" && /^[a-z][a-z-]*$/.test(screen),\n )\n )\n return;\n if (!existsSync(join(directory, \"account-screens.js\"))) return;\n this.screens = value.screens;\n } catch {\n /* An incomplete build must not advertise screen support. */\n }\n }\n read(path: string): Buffer | undefined {\n if (\n !this.directory ||\n !/^(?:account-screens\\.js|chunks\\/[a-zA-Z0-9_.-]+\\.js)$/.test(path)\n )\n return undefined;\n try {\n return readFileSync(join(this.directory, path));\n } catch {\n return undefined;\n }\n }\n}\n","export function buildHotReloadScript(mode: \"full-page\" | \"off\"): string {\n return `\n<script>\n(() => {\n window.__FLUID_CLI_ENV__ = ${JSON.stringify({ mode })};\n\n class HotReload {\n static reloadMode() { return window.__FLUID_CLI_ENV__.mode; }\n static isActive() { return HotReload.reloadMode() !== \"off\"; }\n static setHotReloadCookie(files) {\n const expires = new Date(Date.now() + 3000).toUTCString();\n document.cookie = \\`hot_reload_files=\\${files.join(\",\")};expires=\\${expires};path=/\\`;\n }\n static refresh(files) {\n HotReload.setHotReloadCookie(files);\n console.log(\"[HotReload] Refreshing page\");\n window.location.reload();\n }\n }\n\n class SSEClient {\n constructor(url, handler) {\n if (typeof EventSource === \"undefined\") {\n console.error(\"[HotReload] EventSource not supported in this browser.\");\n return;\n }\n console.log(\"[HotReload] Initializing…\");\n this.url = url;\n this.handler = handler;\n }\n connect() {\n const es = new EventSource(this.url);\n es.onopen = () => console.log(\"[HotReload] SSE connected.\");\n es.onerror = () => {\n console.log(\"[HotReload] SSE closed. Reconnecting in 5s…\");\n es.close();\n setTimeout(() => this.connect(), 5000);\n };\n es.onmessage = (msg) => {\n const data = JSON.parse(msg.data);\n if (data.reload_page) { HotReload.refresh([]); return; }\n this.handler(data);\n };\n }\n }\n\n if (HotReload.isActive()) {\n new SSEClient(\"/hot-reload\", (data) => {\n if (data.modified) HotReload.refresh(data.modified);\n }).connect();\n }\n})();\n</script>`;\n}\n\nexport function injectHotReload(\n html: string,\n mode: \"full-page\" | \"off\",\n): string {\n const script = buildHotReloadScript(mode);\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", `${script}\\n</body>`);\n }\n return html + script;\n}\n","import {\n parse,\n parseFragment,\n serialize,\n type DefaultTreeAdapterMap,\n} from \"parse5\";\nimport type {\n MemberPreviewMetadata,\n MemberPreviewSelection,\n} from \"./member-preview-contract.js\";\nimport { memberPreviewUrl, accountPath } from \"./member-preview-contract.js\";\nimport { injectHotReload } from \"./hot-reload.js\";\n\ntype Element = DefaultTreeAdapterMap[\"element\"];\ntype Parent = DefaultTreeAdapterMap[\"parentNode\"];\n\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (char) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[\n char\n ] ?? char,\n );\n}\n/** Use the configured Rails environment, never a browser-supplied upstream. */\nexport function memberOrigin(apiBase: string, company: string): string {\n const url = new URL(apiBase);\n if (!/^[a-z0-9-]+$/i.test(company))\n throw new Error(\"Invalid company subdomain.\");\n if (url.hostname.startsWith(\"api.\"))\n url.hostname = `${company}.account.${url.hostname.slice(4)}`;\n return url.origin;\n}\nexport interface MemberHtmlOptions {\n origin: string;\n localOrigin: string;\n path: string;\n selection: MemberPreviewSelection;\n metadata: MemberPreviewMetadata;\n reloadMode: \"full-page\" | \"off\";\n screenKey?: string;\n}\nexport function renderMemberHtml(\n render: { html: string; styles: string; templateStyles: string },\n opts: MemberHtmlOptions,\n): string {\n const document = parse(render.html);\n const elements: Element[] = [];\n function visit(parent: Parent): void {\n for (const child of parent.childNodes) {\n if (\"tagName\" in child) {\n elements.push(child);\n visit(child);\n }\n }\n }\n visit(document);\n const head = elements.find((el) => el.tagName === \"head\")!;\n // Remove renderer/editor bases, retaining one environment-correct base for\n // scripts, fonts, images, CSS url() and imports. Stylesheet URLs keep their own base.\n for (const el of elements) {\n if (el.tagName === \"base\" && el.parentNode)\n el.parentNode.childNodes.splice(el.parentNode.childNodes.indexOf(el), 1);\n // Do not load a theme's live account runtime in a CLI sample preview.\n if (el.tagName === \"script\") {\n const src = el.attrs.find((attr) => attr.name === \"src\")?.value ?? \"\";\n if (\n /\\/(?:account-screens(?:\\/|\\.)|member-storefront-sdk\\/)/.test(src) &&\n el.parentNode\n )\n el.parentNode.childNodes.splice(\n el.parentNode.childNodes.indexOf(el),\n 1,\n );\n }\n if (\n el.tagName === \"fluid-account-screen\" ||\n el.tagName === \"fluid-messaging\"\n ) {\n el.attrs = el.attrs.filter(\n (attr) => attr.name !== \"preview\" && attr.name !== \"path\",\n );\n el.attrs.push({ name: \"preview\", value: \"true\" });\n if (opts.screenKey)\n el.attrs.push({\n name: \"path\",\n value: new URL(opts.path, opts.origin).pathname.slice(1),\n });\n }\n if (el.tagName === \"form\") {\n el.attrs = el.attrs.filter((attr) => attr.name !== \"action\");\n el.attrs.push({\n name: \"action\",\n value: `${opts.localOrigin}/_members/_actions`,\n });\n }\n const formAction = el.attrs.find((attr) => attr.name === \"formaction\");\n if (formAction) formAction.value = `${opts.localOrigin}/_members/_actions`;\n if (el.tagName === \"a\" || el.tagName === \"area\") {\n const href = el.attrs.find((a) => a.name === \"href\");\n if (href) href.value = previewLink(href.value, opts);\n }\n }\n const runtime = parseFragment(\n `<script type=\"module\" src=\"${escapeHtml(opts.localOrigin)}/_members/_runtime/account-screens.js\"></script>`,\n );\n head.childNodes.unshift(...runtime.childNodes);\n const base = parseFragment(\n `<base href=\"${escapeHtml(new URL(opts.path, opts.origin).href)}\">`,\n );\n head.childNodes.unshift(...base.childNodes);\n // Both strings are trusted output from the authenticated theme renderer. The\n // template stylesheet is raw CSS, whereas page_stylesheet is HTML link/style tags.\n const styles = parseFragment(render.styles);\n head.childNodes.push(...styles.childNodes);\n const style = parseFragment(\"<style></style>\").childNodes[0];\n if (style && \"childNodes\" in style) {\n style.childNodes.push({\n nodeName: \"#text\",\n value: render.templateStyles,\n parentNode: style,\n });\n head.childNodes.push(style);\n }\n // Form submissions are not part of the sample preview contract. In particular\n // do not let a theme's live sign-in/sign-out form inherit the upstream base.\n const guard = parseFragment(\n `<script>document.addEventListener(\"submit\",function(event){event.preventDefault();},true);</script>`,\n );\n head.childNodes.unshift(...guard.childNodes);\n const context = JSON.stringify(opts.selection).replace(/</g, \"\\\\u003c\");\n const navigation = parseFragment(`<script>\n document.addEventListener(\"fluid-account-navigate\",function(event){\n event.preventDefault(); event.stopImmediatePropagation();\n var slug=event.detail && event.detail.slug;\n if(typeof slug!==\"string\" || !/^[a-z][a-z0-9_-]*(?:[/?#]|$)/.test(slug) || slug.includes(\"\\\\\\\\\")) return;\n var next=new URL(\"/_members/\"+slug,window.location.origin);\n if(next.origin!==window.location.origin || !next.pathname.startsWith(\"/_members/\")) return;\n var context=${context};\n [\"members_site_id\",\"country_id\",\"language_id\",\"member_type_id\"].forEach(function(key){\n if(context[key]===undefined) next.searchParams.delete(key); else next.searchParams.set(key,context[key]);\n });\n window.location.assign(next.href);\n },true);\n </script>`);\n head.childNodes.unshift(...navigation.childNodes);\n return injectHotReload(serialize(document), opts.reloadMode);\n}\nexport function previewLink(href: string, opts: MemberHtmlOptions): string {\n if (href.startsWith(\"#\"))\n return (\n opts.localOrigin + memberPreviewUrl(opts.path, opts.selection) + href\n );\n let target: URL;\n try {\n target = new URL(href, new URL(opts.path, opts.origin));\n } catch {\n return href;\n }\n if (![\"http:\", \"https:\"].includes(target.protocol)) return href;\n const absolute = /^[a-z][a-z\\d+.-]*:/i.test(href) || href.startsWith(\"//\");\n const accountLink = absolute && target.origin === opts.origin;\n if (absolute && !accountLink && target.origin !== opts.localOrigin)\n return href;\n target.pathname = accountPath(target.pathname);\n const isMember =\n accountLink ||\n /^\\/(auth(?:\\/|$)|login$|logout$)/.test(target.pathname) ||\n target.pathname.startsWith(\"/_members/\") ||\n opts.metadata.members_sites.some((site) =>\n site.pages.some((p) => p.path === target.pathname),\n ) ||\n opts.metadata.system_screens.some(\n (screen) =>\n target.pathname === `/${screen.key}` ||\n target.pathname.startsWith(`/${screen.key}/`),\n );\n const path = target.pathname + target.search + target.hash;\n return (\n opts.localOrigin +\n (isMember ? memberPreviewUrl(path, opts.selection) : path)\n );\n}\n","import { MemberScreenAssets } from \"./member-screen-assets.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ApiClient } from \"../../api.js\";\nimport { getApiBase } from \"../../api.js\";\nimport {\n MemberPreviewError,\n isRecord,\n parseMemberMetadata,\n parseMemberRender,\n parseSelection,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./member-preview-contract.js\";\nimport {\n escapeHtml,\n memberOrigin,\n renderMemberHtml,\n} from \"./member-preview-html.js\";\n\n/** Deny DNS rebinding and cross-origin access before using Node's CLI credential. */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n )\n return false;\n if (req.headers.origin && req.headers.origin !== url.origin) return false;\n return req.headers[\"sec-fetch-site\"] !== \"cross-site\";\n}\n\nexport class MemberPreview {\n private metadata: MemberPreviewMetadata | undefined;\n private loading: Promise<MemberPreviewMetadata> | undefined;\n private readonly origin: string;\n constructor(\n private readonly api: ApiClient,\n private readonly theme: { id: number; company: string },\n private readonly reloadMode: \"full-page\" | \"off\",\n private readonly screenAssets = new MemberScreenAssets(),\n ) {\n this.origin = memberOrigin(getApiBase(), theme.company);\n }\n /** Only call after every upload in a watcher batch has been acknowledged. */\n async refreshAfterSync(): Promise<void> {\n if (!this.metadata && !this.loading) return;\n if (this.loading) await this.loading;\n this.metadata = undefined;\n const next = await this.loadMetadata();\n this.metadata = next;\n }\n private async loadMetadata(): Promise<MemberPreviewMetadata> {\n return parseMemberMetadata(\n await this.api.get(\"/api/company/members_sites\", {\n application_theme_id: this.theme.id,\n }),\n );\n }\n private async getMetadata(): Promise<MemberPreviewMetadata> {\n if (this.metadata) return this.metadata;\n this.loading ??= this.loadMetadata();\n try {\n this.metadata = await this.loading;\n return this.metadata;\n } finally {\n this.loading = undefined;\n }\n }\n async handle(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n return false;\n res.setHeader(\"Cache-Control\", \"private, no-store\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n try {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n res.setHeader(\"Allow\", \"GET, HEAD\");\n throw new MemberPreviewError(\"Member preview is read-only.\", 405);\n }\n if (url.pathname.startsWith(\"/_members/_runtime/\")) {\n const asset = this.screenAssets.read(\n url.pathname.slice(\"/_members/_runtime/\".length),\n );\n if (!asset)\n throw new MemberPreviewError(\n \"Preview runtime asset unavailable. Rebuild or update the CLI.\",\n 404,\n );\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : asset);\n return true;\n }\n const metadata = await this.getMetadata();\n if (url.pathname === \"/_members/_metadata\") {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : JSON.stringify({\n theme_id: this.theme.id,\n mode: \"sample\",\n ...metadata,\n supported_system_screens: metadata.system_screens\n .filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n )\n .map((screen) => screen.key),\n }),\n );\n return true;\n }\n if (\n (url.pathname === \"/_members\" || url.pathname === \"/_members/\") &&\n !url.searchParams.has(\"members_site_id\")\n ) {\n const screens = metadata.system_screens.filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n );\n const links = metadata.members_sites\n .map((site) => {\n const destinations = [\n ...site.pages.map((page) => ({\n path: page.path,\n title: page.title + (page.published ? \"\" : \" (draft)\"),\n })),\n ...screens.map((screen) => ({\n path: `/${screen.key}`,\n title: screen.key + \" (sample)\",\n })),\n ];\n return `<h2>${escapeHtml(site.name)}</h2><ul>${destinations.map((page) => `<li><a href=\"${escapeHtml(memberPreviewUrl(page.path, { members_site_id: site.id }))}\">${escapeHtml(page.title)}</a></li>`).join(\"\")}</ul>`;\n })\n .join(\"\");\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : `<!doctype html><meta charset=\"utf-8\"><title>Member preview</title><h1>Member preview</h1><p>Sample data. Select a page to preview its site's layout.</p>${links || \"<p>No member pages in this theme.</p>\"}`,\n );\n return true;\n }\n const selection = parseSelection(url.searchParams);\n const site = metadata.members_sites.find(\n (s) => s.id === selection.members_site_id,\n );\n if (!site)\n throw new MemberPreviewError(\n \"This site no longer belongs to the preview theme. Select a page at /_members.\",\n 404,\n );\n const path = url.pathname.slice(\"/_members\".length) || \"/\";\n const screen = metadata.system_screens.find(\n (s) => path === `/${s.key}` || path.startsWith(`/${s.key}/`),\n );\n const page = screen ? undefined : site.pages.find((p) => p.path === path);\n if (!page && !screen)\n throw new MemberPreviewError(\n \"This member page is unavailable in the selected site. Select a page at /_members.\",\n 404,\n );\n if (screen && !this.screenAssets.screens.includes(screen.key))\n throw new MemberPreviewError(\n \"This system screen has no packaged sample preview. Rebuild or update the CLI.\",\n 501,\n );\n const response = await this.api.post(\n `/api/v202604/themes/${this.theme.id}/member-preview`,\n {\n ...selection,\n ...(page\n ? { members_page_id: page.id }\n : { system_screen_key: screen!.key }),\n },\n );\n const render = parseMemberRender(\n response,\n this.theme.id,\n selection,\n page?.id ?? null,\n screen?.key ?? null,\n );\n const html = renderMemberHtml(render, {\n origin: this.origin,\n localOrigin: url.origin,\n path: path + url.search,\n selection,\n metadata,\n reloadMode: this.reloadMode,\n screenKey: screen?.key,\n });\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : html);\n } catch (error) {\n // Never echo upstream envelopes, credentials, or internal diagnostics into HTML.\n const status =\n error instanceof MemberPreviewError\n ? error.status\n : isRecord(error) &&\n typeof error.status === \"number\" &&\n [401, 403, 404, 422].includes(error.status)\n ? error.status\n : 502;\n const message =\n error instanceof MemberPreviewError\n ? error.message\n : status === 401 || status === 403\n ? \"Member preview authorization failed. Check your CLI login and theme permissions.\"\n : \"Member preview could not be rendered. Check the selected page and server preview support.\";\n res.writeHead(status, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(req.method === \"HEAD\" ? undefined : message);\n }\n return true;\n }\n}\n","import type { ServerResponse } from \"node:http\";\n\nexport class SSEStream {\n private responses = new Set<ServerResponse>();\n\n add(res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\":\\n\\n\");\n this.responses.add(res);\n res.on(\"close\", () => this.responses.delete(res));\n }\n\n broadcast(data: string): void {\n const payload = `data: ${data}\\n\\n`;\n for (const res of this.responses) {\n try {\n res.write(payload);\n } catch {\n this.responses.delete(res);\n }\n }\n }\n\n close(): void {\n for (const res of this.responses) {\n try {\n res.end();\n } catch {\n // ignore\n }\n }\n this.responses.clear();\n }\n\n get size(): number {\n return this.responses.size;\n }\n}\n","import type { ApiClient } from \"../../api.js\";\n\nexport const TEMPLATE_OVERRIDE_PARAM = \"theme_template_id\";\n\ninterface TemplateRecord {\n id: number;\n name: string;\n themeable_type: string;\n application_theme_id: number | null;\n}\n\nexport type TemplateOverride =\n | { kind: \"use\"; id: string }\n | {\n kind: \"unresolved\";\n /** No dev template matches, or several do and none can be preferred. */\n reason: \"missing\" | \"ambiguous\";\n requestedId: string;\n themeableType: string;\n name: string;\n candidateIds: number[];\n };\n\nexport interface TemplateOverrideResolver {\n resolve(requestedId: string): Promise<TemplateOverride>;\n /** Forget cached mappings — templates were added to or removed from the dev theme. */\n clear(): void;\n}\n\n/**\n * Maps an explicit `theme_template_id` onto the dev theme the server renders.\n *\n * Mist, `theme navigate` and bookmarked URLs carry template ids from the\n * theme the project was pulled from. Forwarded unchanged next to\n * `x-fluid-theme: <dev theme>`, the storefront renders that original\n * template and the local edits never show. Resolve such an id to the dev\n * theme's template with the same themeable type and name instead. Ids that\n * already belong to the dev theme and global and extension templates (no\n * owning theme) pass through untouched. A failed lookup also passes through,\n * with a warning, since the page may then show the original template.\n */\nexport function createTemplateOverrideResolver(\n api: ApiClient,\n devThemeId: number,\n): TemplateOverrideResolver {\n // Only successful mappings are cached: a missing counterpart may appear on\n // the next sync, and a failed lookup should be retried.\n const cache = new Map<string, string>();\n\n const warnLookupFailed = (requestedId: string, error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `[Templates] Could not map template #${requestedId} to dev theme #${devThemeId} (${message}); rendering it unchanged, which may show the original theme's template.`,\n );\n };\n\n /** `null` means the lookup failed: forward the id as-is, uncached. */\n const lookup = async (\n requestedId: string,\n ): Promise<TemplateOverride | null> => {\n let requested: TemplateRecord | undefined;\n try {\n const body = await api.get<{\n application_theme_template?: TemplateRecord;\n }>(`/api/application_theme_templates/${encodeURIComponent(requestedId)}`);\n requested = body.application_theme_template;\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n if (!requested) return null;\n const { name, themeable_type: themeableType } = requested;\n const sourceThemeId = requested.application_theme_id;\n if (!sourceThemeId || sourceThemeId === devThemeId) {\n return { kind: \"use\", id: requestedId };\n }\n\n const params = new URLSearchParams({\n application_theme_id: String(devThemeId),\n themeable_type: themeableType,\n });\n let candidates: TemplateRecord[];\n try {\n const body = await api.get<{ templates?: TemplateRecord[] }>(\n `/api/application_theme_templates?${params}`,\n );\n candidates = body.templates ?? [];\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n\n const matches = candidates.filter(\n (t) =>\n t.application_theme_id === devThemeId &&\n t.themeable_type === themeableType &&\n t.name === name,\n );\n const [counterpart] = matches;\n if (matches.length === 1 && counterpart) {\n return { kind: \"use\", id: String(counterpart.id) };\n }\n // Templates carry no link back to the one they were cloned from, so a\n // duplicated type and name has no right answer — picking one would let\n // an unrelated edit change what the preview renders.\n return {\n kind: \"unresolved\",\n reason: matches.length === 0 ? \"missing\" : \"ambiguous\",\n requestedId,\n themeableType,\n name,\n candidateIds: matches.map((t) => t.id),\n };\n };\n\n return {\n async resolve(requestedId) {\n const cached = cache.get(requestedId);\n if (cached) return { kind: \"use\", id: cached };\n const result = await lookup(requestedId);\n if (!result) return { kind: \"use\", id: requestedId };\n if (result.kind === \"use\") cache.set(requestedId, result.id);\n return result;\n },\n clear() {\n cache.clear();\n },\n };\n}\n","import https from \"node:https\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { injectHotReload } from \"./hot-reload.js\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport {\n TEMPLATE_OVERRIDE_PARAM,\n type TemplateOverride,\n type TemplateOverrideResolver,\n} from \"./template-overrides.js\";\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"content-security-policy\",\n]);\n\nexport interface ProxyOptions {\n company: string;\n themeId: number;\n reloadMode: \"full-page\" | \"off\";\n pendingFiles?: () => Array<{ relativePath: string; read: () => string }>;\n /** Maps explicit template overrides onto the dev theme's copies. */\n templateOverrides?: TemplateOverrideResolver;\n}\n\nexport async function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: ProxyOptions,\n): Promise<void> {\n const companyHost = `${opts.company}.fluid.app`;\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && typeof v === \"string\") {\n headers[k] = v;\n }\n }\n headers[\"host\"] = companyHost;\n headers[\"x-fluid-theme\"] = String(opts.themeId);\n headers[\"user-agent\"] = \"Fluid CLI\";\n headers[\"accept-encoding\"] = \"identity\";\n\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n url.searchParams.set(\"_fd\", \"0\");\n url.searchParams.set(\"pb\", \"0\");\n\n const requestedTemplateId = url.searchParams.get(TEMPLATE_OVERRIDE_PARAM);\n if (requestedTemplateId && opts.templateOverrides) {\n const override = await opts.templateOverrides.resolve(requestedTemplateId);\n if (override.kind === \"use\") {\n url.searchParams.set(TEMPLATE_OVERRIDE_PARAM, override.id);\n } else if (isPageRequest(req)) {\n // Rendering the original theme's template here would silently show\n // content without the local edits — fail loudly instead. API calls\n // that carry the page's id (e.g. the member SDK) still go upstream.\n const html = injectHotReload(\n unresolvedTemplatePage(override, opts.themeId),\n opts.reloadMode,\n );\n res.writeHead(404, {\n \"content-type\": \"text/html; charset=utf-8\",\n \"content-length\": String(Buffer.byteLength(html)),\n });\n res.end(html);\n return;\n }\n }\n\n const pending = opts.pendingFiles?.() ?? [];\n const isGet = req.method === \"GET\" || req.method === \"HEAD\";\n let method = req.method ?? \"GET\";\n let body: string | Buffer | undefined;\n\n if (pending.length > 0 && isGet) {\n method = \"POST\";\n const params = new URLSearchParams();\n params.set(\"_method\", req.method ?? \"GET\");\n for (const f of pending) {\n params.set(`replace_templates[${f.relativePath}]`, f.read());\n }\n const token = getAuthToken();\n if (token) headers[\"authorization\"] = `Bearer ${token}`;\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = params.toString();\n headers[\"content-length\"] = String(Buffer.byteLength(body));\n } else if (!isGet) {\n body = await readBody(req);\n if (body.length > 0) {\n headers[\"content-length\"] = String(body.length);\n }\n }\n\n return new Promise((resolve, reject) => {\n const options: https.RequestOptions = {\n hostname: companyHost,\n port: 443,\n path: url.pathname + (url.search || \"\"),\n method,\n headers,\n };\n\n const proxyReq = https.request(options, (proxyRes) => {\n const contentType = proxyRes.headers[\"content-type\"] ?? \"\";\n const isHtml = contentType.includes(\"text/html\");\n\n const responseHeaders: Record<string, string | string[]> = {};\n for (const [k, v] of Object.entries(proxyRes.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && v !== undefined) {\n responseHeaders[k] = v as string | string[];\n }\n }\n\n if (isHtml) {\n const chunks: Buffer[] = [];\n proxyRes.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on(\"end\", () => {\n let html = Buffer.concat(chunks).toString(\"utf-8\");\n html = injectHotReload(html, opts.reloadMode);\n responseHeaders[\"content-length\"] = String(Buffer.byteLength(html));\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n res.end(html);\n resolve();\n });\n } else {\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n proxyRes.pipe(res);\n proxyRes.on(\"end\", resolve);\n }\n });\n\n proxyReq.on(\"error\", (err) => {\n reject(err);\n });\n\n if (body) proxyReq.write(body);\n proxyReq.end();\n });\n}\n\nconst PAGE_FETCH_DESTS = new Set([\"document\", \"iframe\", \"frame\"]);\n\n/** Browser navigations to a storefront page, as opposed to API or asset calls. */\nfunction isPageRequest(req: IncomingMessage): boolean {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return false;\n const path = (req.url ?? \"/\").split(\"?\")[0] ?? \"/\";\n if (path.startsWith(\"/api/\")) return false;\n const dest = req.headers[\"sec-fetch-dest\"];\n // Mist and the web editor render the preview in an iframe.\n if (typeof dest === \"string\") return PAGE_FETCH_DESTS.has(dest);\n const accept = req.headers[\"accept\"];\n return typeof accept === \"string\" && accept.includes(\"text/html\");\n}\n\nfunction unresolvedTemplatePage(\n override: Extract<TemplateOverride, { kind: \"unresolved\" }>,\n devThemeId: number,\n): string {\n const template =\n `Template #${escapeHtml(override.requestedId)} ` +\n `(${escapeHtml(override.themeableType)} / ${escapeHtml(override.name)})`;\n const problem =\n override.reason === \"missing\"\n ? `dev theme #${devThemeId} has no template with that type and name. Sync or create it locally, or`\n : `dev theme #${devThemeId} has several templates with that type and name ` +\n `(#${override.candidateIds.join(\", #\")}), so the edited copy is ambiguous. Remove the duplicates, or`;\n return (\n `<!doctype html><title>Template not in dev theme</title>` +\n `<p>${template} belongs to another theme, and ${problem} ` +\n `remove <code>${TEMPLATE_OVERRIDE_PARAM}</code> to use the default.</p>`\n );\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n","import { isApiError } from \"@fluid-app/themes-api-client\";\n\n// Name of the bundled skill that walks the caller through migrating\n// per-template `styles.css` files to theme-level assets. See\n// `skills/template-stylesheet-to-asset-migration/SKILL.md` in this\n// package.\nexport const STYLESHEET_MIGRATION_SKILL =\n \"template-stylesheet-to-asset-migration\";\n\n// Detect the backend's 422 rejection for legacy stylesheet keys\n// (`ApplicationThemeResources::UpdateAction#stylesheet_rejected_response`\n// on the Rails side). Both the message and the errors payload identify\n// the rejection uniquely; match on either so a future wording tweak on\n// one surface does not silently drop the hint.\nfunction isStylesheetKeyRejection(error: {\n status: number;\n message: string;\n data: unknown;\n}): boolean {\n if (error.status !== 422) return false;\n if (/stylesheet.*no longer accepted/i.test(error.message)) return true;\n if (error.data && typeof error.data === \"object\") {\n const resourceErrors = (\n error.data as { application_theme_resource?: { key?: unknown } }\n ).application_theme_resource;\n if (\n resourceErrors &&\n typeof resourceErrors.key === \"string\" &&\n /stylesheet.*no longer accepted/i.test(resourceErrors.key)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Extract a human-readable message from a caught error. ApiError's default\n * `toString` prefixes with `ApiError:` and the class name; this returns the\n * API's own `error_message` verbatim so the CLI surfaces messages like\n * \"This stylesheet key is no longer accepted. Upload stylesheets as\n * theme-level assets.\" directly.\n *\n * When the error is the legacy stylesheet key rejection specifically,\n * a hint is appended pointing to the bundled migration skill — the\n * caller needs to move the per-template `styles.css` bytes to `assets/`\n * and stop pushing the deprecated column key.\n */\nexport function formatError(e: unknown): string {\n if (isApiError(e)) {\n const status = e.status ? ` [${e.status}]` : \"\";\n const hint = isStylesheetKeyRejection(e)\n ? `\\n ↳ Run \\`fluid theme skills install\\` — the bundled \\`${STYLESHEET_MIGRATION_SKILL}\\` skill can help you migrate this to a theme-level asset.`\n : \"\";\n return `${e.message}${status}${hint}`;\n }\n if (e instanceof Error) return e.message;\n return String(e);\n}\n","import { relative, sep } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ThemeFile } from \"../file.js\";\nimport { formatError } from \"../format-error.js\";\n\nexport type FileChangeHandler = (\n modified: ThemeFile[],\n added: ThemeFile[],\n removed: ThemeFile[],\n /** When the filesystem event arrived, not when the handler got to run.\n * Handlers are serialized behind awaited uploads, so a queued event can\n * start long after it happened — anything deciding where one edit ends and\n * the next begins has to use this, or it measures upload duration. */\n arrivedAt: number,\n) => Promise<void>;\n\nfunction relativeThemePath(root: ThemeRoot, filePath: string): string {\n return relative(root.root, filePath).split(sep).join(\"/\");\n}\n\nexport function watchTheme(\n root: ThemeRoot,\n handler: FileChangeHandler,\n): () => Promise<void> {\n const watcher = chokidar.watch(root.root, {\n ignoreInitial: true,\n ignored: (filePath: string) => {\n if (filePath.includes(\"node_modules\")) return true;\n try {\n const rel = relativeThemePath(root, filePath);\n const basename = rel.split(/[\\\\/]/).pop() ?? \"\";\n return basename.startsWith(\".\") || root.ignore.ignore(rel);\n } catch {\n return false;\n }\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n\n let pending = Promise.resolve();\n const enqueue = (fn: () => Promise<void>) => {\n // The change handler has its own internal try/catch around\n // per-file uploads/deletes, but anything that throws outside\n // that (e.g. `root.file()`, `validateSchema()`) must still be\n // surfaced — a bare `.catch(() => {})` here previously swallowed\n // it and left watch mode silently stuck.\n pending = pending.then(fn).catch((e) => {\n console.error(` [Watcher] change handling failed: ${formatError(e)}`);\n });\n };\n\n watcher.on(\"change\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([root.file(filePath)], [], [], arrivedAt));\n });\n\n watcher.on(\"add\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [root.file(filePath)], [], arrivedAt));\n });\n\n watcher.on(\"unlink\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [], [root.file(filePath)], arrivedAt));\n });\n\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport { lstatSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nconst METADATA = [\".fluid-assets.json\", \".fluid-theme.json\", \".fluidignore\"];\n\nexport class BackgroundPullChangedError extends Error {\n constructor() {\n super(\n \"Local files changed during background pull; remaining updates skipped.\",\n );\n }\n}\n\n/** Keep the preflight snapshot across asynchronous download/merge work. Each\n * mutation checks its original bytes synchronously and records only our writes.\n * This prevents an edit made after preflight from becoming a merge input or\n * being overwritten by a deferred write, asset cleanup, or metadata update. */\nexport class BackgroundPullGuard {\n private readonly root: string;\n private readonly expected: Map<string, string | null>;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.expected = this.snapshot();\n }\n\n assertUnchanged(): void {\n const current = this.snapshot();\n for (const key of new Set([...current.keys(), ...this.expected.keys()])) {\n if ((current.get(key) ?? null) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n }\n\n mutate(keys: readonly string[], action: () => void): void {\n for (const key of keys) {\n if (this.fingerprint(key) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n action();\n for (const key of keys) this.expected.set(key, this.fingerprint(key));\n }\n\n private fingerprint(key: string): string | null {\n const path = resolve(this.root, key);\n if (!path.startsWith(this.root + sep))\n throw new BackgroundPullChangedError();\n // Fail closed for a parent replaced by a symlink while awaiting Git/network.\n for (let cursor = path; ; cursor = dirname(cursor)) {\n try {\n if (lstatSync(cursor).isSymbolicLink())\n throw new BackgroundPullChangedError();\n } catch (error) {\n if (\n !(\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n )\n throw error;\n }\n if (cursor === this.root) break;\n }\n try {\n return createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n return null;\n throw error;\n }\n }\n\n private snapshot(): Map<string, string | null> {\n const result = new Map<string, string | null>();\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n continue;\n if (entry.isSymbolicLink()) throw new BackgroundPullChangedError();\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else {\n const key = relative(this.root, path).split(sep).join(\"/\");\n result.set(key, this.fingerprint(key));\n }\n }\n };\n visit(this.root);\n for (const key of METADATA) result.set(key, this.fingerprint(key));\n return result;\n }\n}\n","import { normalizeThemeResourceKey } from \"./resource-key.js\";\n\nexport class CaseCollisionError extends Error {\n constructor(readonly collisions: readonly (readonly string[])[]) {\n super(\n `Theme contains paths that differ only by letter case and cannot be synchronized safely:\\n${collisions\n .map((paths) => ` ${paths.join(\", \")}`)\n .join(\"\\n\")}`,\n );\n this.name = \"CaseCollisionError\";\n }\n}\n\n/** Refuse paths a case-insensitive checkout cannot represent independently. */\nexport function assertNoCaseCollisions(paths: readonly string[]): void {\n const pathsByFoldedKey = new Map<string, Set<string>>();\n for (const rawPath of paths) {\n const path = normalizeThemeResourceKey(rawPath);\n const foldedKey = path.toLowerCase();\n const matchingPaths = pathsByFoldedKey.get(foldedKey) ?? new Set<string>();\n matchingPaths.add(path);\n pathsByFoldedKey.set(foldedKey, matchingPaths);\n }\n\n const collisions = [...pathsByFoldedKey.values()]\n .filter((matchingPaths) => matchingPaths.size > 1)\n .map((matchingPaths) => [...matchingPaths].sort())\n .sort(([left = \"\"], [right = \"\"]) => left.localeCompare(right));\n\n if (collisions.length > 0) throw new CaseCollisionError(collisions);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MANIFEST_FILE = \".fluid-assets.json\";\nconst MANIFEST_VERSION = 1;\n\n/**\n * A tiny shadow-repo placeholder for a manifest-backed asset. It preserves\n * the path as a deletion baseline without retaining the asset bytes locally.\n */\nexport const MANAGED_ASSET_SHADOW_SENTINEL = \"fluid-managed-asset\\n\";\n\nexport interface ThemeAssetLink {\n /** Theme that last supplied this asset reference. */\n sourceThemeId: number;\n /** SHA-256 checksum of the source resource, when the API provides one. */\n checksum?: string;\n /** ImageKit URL saved into themes that consume this asset. */\n url?: string;\n /** MIME type required to create a URL-backed FileResource. */\n contentType?: string;\n /** File size required to create a URL-backed FileResource. */\n contentSize?: number;\n /** Optional display metadata copied with the ImageKit URL. */\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n /** A dev-only source that must survive pull until a push makes it durable. */\n pending?: boolean;\n /** DAM asset identity retained as provenance for CLI-uploaded assets. */\n damAssetCode?: string;\n}\n\ninterface ThemeAssetManifestDocument {\n version: number;\n assets: Record<string, ThemeAssetLink>;\n}\n\n/**\n * Tracks binary theme assets that deliberately live only on the server.\n *\n * The manifest is a dotfile so it is excluded from theme uploads and file\n * watching. It is written before the corresponding local file is removed,\n * which prevents `delete: true` from mistaking the removed byte source for a\n * request to delete its remote FileResource.\n */\nexport class ThemeAssetManifest {\n private assets: Record<string, ThemeAssetLink>;\n private readonly path: string;\n\n constructor(themeRoot: string) {\n this.path = join(themeRoot, MANIFEST_FILE);\n this.assets = readDocument(this.path).assets;\n }\n\n reload(): void {\n this.assets = readDocument(this.path).assets;\n }\n\n keys(): string[] {\n return Object.keys(this.assets);\n }\n\n entries(): Array<[string, ThemeAssetLink]> {\n return Object.entries(this.assets).map(([key, link]) => [\n key,\n copyLink(link),\n ]);\n }\n\n /**\n * Stable digest of the URL references represented by this manifest.\n * Pull baselines exclude pending entries because those belong only to an\n * existing dev target and are deliberately absent from the pulled source.\n */\n fingerprint(opts: { excludePending?: boolean } = {}): string {\n const entries = this.entries()\n .filter(([, link]) => !opts.excludePending || !link.pending)\n .toSorted(([left], [right]) =>\n left < right ? -1 : left > right ? 1 : 0,\n );\n return createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n }\n\n has(key: string): boolean {\n return this.assets[key] !== undefined;\n }\n\n get(key: string): ThemeAssetLink | undefined {\n const link = this.assets[key];\n return link ? copyLink(link) : undefined;\n }\n\n set(key: string, link: ThemeAssetLink): void {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(link)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n this.assets[key] = copyLink(link);\n }\n\n delete(key: string): void {\n delete this.assets[key];\n }\n\n write(): void {\n const document: ThemeAssetManifestDocument = {\n version: MANIFEST_VERSION,\n assets: copyAssets(this.assets),\n };\n const tempPath = `${this.path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(tempPath, JSON.stringify(document, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, this.path);\n } catch (error) {\n try {\n unlinkSync(tempPath);\n } catch {\n // The temporary file may not have been written yet.\n }\n throw error;\n }\n }\n}\n\nfunction readDocument(path: string): ThemeAssetManifestDocument {\n if (!existsSync(path)) return emptyDocument();\n\n try {\n return parseDocument(JSON.parse(readFileSync(path, \"utf-8\")));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Could not read ${MANIFEST_FILE}: ${message}`);\n }\n}\n\nfunction parseDocument(value: unknown): ThemeAssetManifestDocument {\n if (!isRecord(value) || value[\"version\"] !== MANIFEST_VERSION) {\n throw new Error(`expected version ${MANIFEST_VERSION}`);\n }\n\n const rawAssets = value[\"assets\"];\n if (!isRecord(rawAssets)) throw new Error(\"expected an assets object\");\n\n const assets: Record<string, ThemeAssetLink> = {};\n for (const [key, rawLink] of Object.entries(rawAssets)) {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(rawLink)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n assets[key] = copyLink(rawLink);\n }\n\n return { version: MANIFEST_VERSION, assets };\n}\n\nfunction emptyDocument(): ThemeAssetManifestDocument {\n return { version: MANIFEST_VERSION, assets: {} };\n}\n\nfunction copyAssets(\n assets: Record<string, ThemeAssetLink>,\n): Record<string, ThemeAssetLink> {\n return Object.fromEntries(\n Object.entries(assets).map(([key, link]) => [key, copyLink(link)]),\n );\n}\n\nfunction copyLink(link: ThemeAssetLink): ThemeAssetLink {\n return {\n sourceThemeId: link.sourceThemeId,\n ...(typeof link.checksum === \"string\" ? { checksum: link.checksum } : {}),\n ...(typeof link.url === \"string\" ? { url: link.url } : {}),\n ...(typeof link.contentType === \"string\"\n ? { contentType: link.contentType }\n : {}),\n ...(typeof link.contentSize === \"number\"\n ? { contentSize: link.contentSize }\n : {}),\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n ...(link.pending === true ? { pending: true } : {}),\n ...(typeof link.damAssetCode === \"string\"\n ? { damAssetCode: link.damAssetCode }\n : {}),\n };\n}\n\nfunction isThemeAssetLink(value: unknown): value is ThemeAssetLink {\n return (\n isRecord(value) &&\n typeof value[\"sourceThemeId\"] === \"number\" &&\n Number.isInteger(value[\"sourceThemeId\"]) &&\n value[\"sourceThemeId\"] > 0 &&\n (value[\"checksum\"] === undefined ||\n (typeof value[\"checksum\"] === \"string\" &&\n value[\"checksum\"].length > 0)) &&\n (value[\"url\"] === undefined ||\n (typeof value[\"url\"] === \"string\" && value[\"url\"].length > 0)) &&\n (value[\"contentType\"] === undefined ||\n (typeof value[\"contentType\"] === \"string\" &&\n value[\"contentType\"].length > 0)) &&\n (value[\"contentSize\"] === undefined ||\n (typeof value[\"contentSize\"] === \"number\" &&\n Number.isInteger(value[\"contentSize\"]) &&\n value[\"contentSize\"] > 0)) &&\n (value[\"previewImageUrl\"] === undefined ||\n (typeof value[\"previewImageUrl\"] === \"string\" &&\n value[\"previewImageUrl\"].length > 0)) &&\n (value[\"altText\"] === undefined || typeof value[\"altText\"] === \"string\") &&\n (value[\"handle\"] === undefined ||\n (typeof value[\"handle\"] === \"string\" && value[\"handle\"].length > 0)) &&\n (value[\"pending\"] === undefined || typeof value[\"pending\"] === \"boolean\") &&\n (value[\"damAssetCode\"] === undefined ||\n (typeof value[\"damAssetCode\"] === \"string\" &&\n value[\"damAssetCode\"].length > 0))\n );\n}\n\nexport function isThemeAssetKey(key: string): boolean {\n if (key.includes(\"\\\\\") || key.includes(\"\\0\")) return false;\n\n const segments = key.split(\"/\");\n return (\n segments[0] === \"assets\" &&\n segments.length === 2 &&\n segments[1] !== undefined &&\n segments[1].length > 0 &&\n segments[1] !== \".\" &&\n segments[1] !== \"..\"\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Resource keys that the backend can hide from the resources index\n// when `STYLESHEET_STRICT_INPUT` is enabled on the owning company —\n// theme-level `styles.css` / `global_styles.css` and per-template\n// composite `{type}/{name}/styles.css`. See `Themes::Resource.find_by`\n// + `Themes::Theme#theme_stylesheet_columns_hidden?` /\n// `Themes::Template#stylesheet_column_hidden?` on the Rails side.\n//\n// A pull that used the \"delete files missing on remote\" behavior would\n// otherwise wipe a merchant's local stylesheets the first time the\n// flag flips on — the resource just stops appearing in the index, not\n// the file itself. Skip these keys from the delete pass so local\n// content stays intact regardless of the flag state.\n\nconst STYLESHEET_KEY_PATTERN =\n /^(styles\\.css|global_styles\\.css|[^/]+\\/[^/]+\\/styles\\.css)$/;\n\nexport function isStylesheetKey(key: string): boolean {\n return STYLESHEET_KEY_PATTERN.test(key);\n}\n","import { isRetiredTemplateKey } from \"./resource-key.js\";\n\n/** The same file graph is used for uploads and incoming omission protection.\n * Rails remains responsible for resolving company-specific roles and handles.\n * Retired template types are never synced, so they are never dependencies. */\nexport function memberDependencyPaths(\n document: Record<string, unknown>,\n availablePaths: ReadonlySet<string>,\n deletedPaths: readonly string[] = [],\n changedPaths: ReadonlySet<string> = availablePaths,\n): Set<string> {\n const dependencies = new Set<string>();\n for (const entry of [\n ...records(document.templates),\n ...records(document.menu_references),\n ]) {\n if (typeof entry.path === \"string\") dependencies.add(entry.path);\n }\n for (const site of records(document.sites)) {\n for (const page of records(site.pages)) {\n if (typeof page.template_path === \"string\")\n dependencies.add(page.template_path);\n }\n const layout = site.layout_name;\n if (layout === \"none\") continue;\n if (\n typeof layout === \"string\" &&\n layout &&\n ![\"member\", \"theme\"].includes(layout)\n ) {\n dependencies.add(`layouts/${layout}.liquid`);\n continue;\n }\n const fallback = [layout, \"member\", \"theme\"]\n .filter(\n (name): name is string => typeof name === \"string\" && name.length > 0,\n )\n .map((name) => `layouts/${name}.liquid`)\n .find((path) => availablePaths.has(path) || deletedPaths.includes(path));\n if (fallback) dependencies.add(fallback);\n }\n\n // Variables are a separate resource, but their menu selectors belong to the\n // same template and must not replace the previous reference before import.\n for (const path of dependencies) {\n if (!path.endsWith(\"/index.liquid\")) continue;\n for (const suffix of [\"variables.json\", \"styles.css\"]) {\n const sibling = path.replace(/index\\.liquid$/, suffix);\n const candidates =\n suffix === \"styles.css\" ? changedPaths : availablePaths;\n if (candidates.has(sibling) || deletedPaths.includes(sibling))\n dependencies.add(sibling);\n }\n }\n\n for (const path of dependencies) {\n if (isRetiredTemplateKey(path)) dependencies.delete(path);\n }\n return dependencies;\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n const candidates: unknown[] = Array.isArray(entries) ? entries : [];\n return candidates.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === \"object\" && entry !== null && !Array.isArray(entry),\n );\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isRetiredTemplateKey } from \"./resource-key.js\";\n\nexport const MEMBER_CONFIGURATION_KEY = \"members_sites.json\";\n\nexport interface ThemeUpload {\n file: ThemeFile;\n memberConfiguration?: {\n content: string;\n files: ThemeFile[];\n };\n}\n\n/** Apply member selectors and their document together, before losing old references. */\nexport function planThemeUploads(\n themeRoot: ThemeRoot,\n changedFiles: readonly ThemeFile[],\n deletedPaths: readonly string[] = [],\n): ThemeUpload[] {\n const localFiles = themeRoot.files();\n const manifest = localFiles.find(\n (file) => file.relativePath === MEMBER_CONFIGURATION_KEY,\n );\n if (!manifest || themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY)) {\n return individualUploads(changedFiles);\n }\n\n const manifestContent = manifest.read();\n const parsed: unknown = JSON.parse(manifestContent);\n // Legacy arrays do not carry the V2 dependency graph. The server's existing\n // importer still accepts them through the ordinary manifest resource.\n if (Array.isArray(parsed)) return individualUploads(changedFiles);\n if (!isRecord(parsed)) {\n throw new Error(\"members_sites.json must contain a JSON object.\");\n }\n const { document, removed } = withoutRetiredTemplates(parsed);\n const content =\n removed.length > 0\n ? `${JSON.stringify(document, null, 2)}\\n`\n : manifestContent;\n\n const dependencies = memberDependencyPaths(\n document,\n new Set(localFiles.map((file) => file.relativePath)),\n deletedPaths,\n new Set(changedFiles.map((file) => file.relativePath)),\n );\n\n for (const path of deletedPaths) {\n if (dependencies.has(path)) {\n throw new Error(\n `Member dependency is still declared and cannot be deleted: ${path}`,\n );\n }\n }\n\n const memberChanged = changedFiles.some(\n (file) =>\n file.relativePath === MEMBER_CONFIGURATION_KEY ||\n dependencies.has(file.relativePath),\n );\n // A retired-reference cleanup has to reach the remote in the same push as\n // the local rewrite, so it rides along with any push, but only when the\n // bundle can be built: it must never block an unrelated change.\n if (!memberChanged && removed.length === 0) {\n return changedFiles.map((file) => ({ file }));\n }\n\n const availableFiles = new Map(\n localFiles.map((file) => [file.relativePath, file]),\n );\n const unavailable = [...dependencies].find((path) => {\n const file = availableFiles.get(path);\n return !file || !file.isText || themeRoot.ignore.ignore(path);\n });\n if (unavailable) {\n if (!memberChanged) return changedFiles.map((file) => ({ file }));\n throw new Error(\n `Member dependency must be an available text file: ${unavailable}`,\n );\n }\n const memberFiles = [...dependencies].map(\n (path) => availableFiles.get(path)!,\n );\n if (removed.length > 0) {\n // Rewrite the local file with what is uploaded. A filtered upload beside\n // an unfiltered file would differ from the acknowledged remote forever,\n // so every later sync would upload it again.\n manifest.write(content);\n }\n const uploads: ThemeUpload[] = changedFiles\n .filter(\n (file) =>\n file.relativePath !== MEMBER_CONFIGURATION_KEY &&\n !dependencies.has(file.relativePath),\n )\n .map((file) => ({ file }));\n uploads.push({\n file: manifest,\n memberConfiguration: { content, files: memberFiles },\n });\n return uploads;\n}\n\n/**\n * Drop references to retired template types. The server rejects the whole\n * document for one, which would also hold back every member site and menu it\n * carries.\n *\n * Only `templates` and `menu_references` can name one: a site page's template\n * must be a `members_page`. A menu left without any reference is dropped with\n * its reference, because the server rejects unreferenced menus too.\n */\nexport function withoutRetiredTemplates(document: Record<string, unknown>): {\n document: Record<string, unknown>;\n removed: string[];\n} {\n const removed = new Set<string>();\n const orphanCandidates = new Set<unknown>();\n const keep = (entry: unknown) => {\n if (!isRecord(entry) || typeof entry.path !== \"string\") return true;\n if (!isRetiredTemplateKey(entry.path)) return true;\n removed.add(entry.path);\n if (\"menu_key\" in entry) orphanCandidates.add(entry.menu_key);\n return false;\n };\n\n const next: Record<string, unknown> = { ...document };\n for (const key of [\"templates\", \"menu_references\"]) {\n if (Array.isArray(next[key])) next[key] = next[key].filter(keep);\n }\n if (removed.size === 0) return { document, removed: [] };\n\n if (orphanCandidates.size > 0 && Array.isArray(next.menus)) {\n const used = new Set<unknown>([\n ...records(next.sites).map((site) => site.menu_key),\n ...records(next.menu_references).map((entry) => entry.menu_key),\n ]);\n next.menus = next.menus.filter(\n (menu) =>\n !isRecord(menu) ||\n !orphanCandidates.has(menu.key) ||\n used.has(menu.key),\n );\n }\n return { document: next, removed: [...removed] };\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n return Array.isArray(entries) ? entries.filter(isRecord) : [];\n}\n\nfunction individualUploads(files: readonly ThemeFile[]): ThemeUpload[] {\n return [...files]\n .sort(\n (left, right) =>\n Number(left.relativePath === MEMBER_CONFIGURATION_KEY) -\n Number(right.relativePath === MEMBER_CONFIGURATION_KEY),\n )\n .map((file) => ({ file }));\n}\n\nfunction isRecord(entry: unknown): entry is Record<string, unknown> {\n return typeof entry === \"object\" && entry !== null && !Array.isArray(entry);\n}\n\n/** Removing an older file is not an instruction to erase a member site. */\nexport function assertMemberConfigurationRetained(\n deletedPaths: readonly string[],\n): void {\n if (deletedPaths.includes(MEMBER_CONFIGURATION_KEY)) {\n throw new Error(\n \"Keep members_sites.json. To remove member configuration, upload an explicit empty versioned document instead of deleting or renaming the file.\",\n );\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport {\n isApiError,\n themes,\n type components,\n} from \"@fluid-app/themes-api-client\";\nimport type { ApiClient } from \"../api.js\";\nimport { formatError } from \"./format-error.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport {\n isThemeAssetKey,\n ThemeAssetManifest,\n type ThemeAssetLink,\n} from \"./asset-manifest.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n MEMBER_CONFIGURATION_KEY,\n} from \"./member-configuration.js\";\nimport type {\n DevRemoteState,\n RemoteResourceState,\n} from \"./dev-remote-baseline.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\ninterface RemoteAssetMetadata {\n url: string;\n contentType: string;\n contentSize: number;\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n}\n\ninterface ManagedAssetPlan {\n key: string;\n link: ThemeAssetLink;\n targetResource?: RemoteResource;\n metadata?: RemoteAssetMetadata;\n}\n\ntype UploadedBinaryResource = RemoteResource & {\n damAssetCode?: string;\n assetMetadata?: RemoteAssetMetadata;\n};\n\nconst ASSET_REFERENCE_CONCURRENCY = 6;\n\nexport interface SyncResult {\n uploaded: number;\n downloaded: number;\n linked: number;\n deleted: number;\n errors: string[];\n validationFailed: boolean;\n}\n\n/**\n * Server rejected the push because the CLI's `base_sha` no longer\n * matches the theme's current `content_version_sha` (someone else\n * wrote to the theme since our last pull). Callers surface a\n * \"pull first\" message; no partial state has been written when this\n * throws from the preflight, and the per-file variant preserves the\n * atomicity guarantee mid-loop by short-circuiting the remaining\n * files on the first conflicting response.\n */\nexport class PushConflictError extends Error {\n constructor(public readonly remoteSha: string | null) {\n super(\n remoteSha\n ? `Your local is behind the server. Server is at ${remoteSha}; local is stale.`\n : \"Your local is behind the server.\",\n );\n this.name = \"PushConflictError\";\n }\n}\n\nexport class Syncer {\n private checksumIndex = new Map<string, string>();\n private memberAcknowledgments = new Map<string, string>();\n private rawRemoteResources = new Map<string, RemoteResource>();\n private remoteResourceGroups = new Map<string, RemoteResource[]>();\n private remoteResourceIndex = new Map<string, RemoteResource>();\n private remoteIndexesDirty = false;\n private remoteResourcesLoaded = false;\n private lastKnownRemoteSha: string | null = null;\n private assetManifestInstance: ThemeAssetManifest | undefined;\n\n constructor(\n private api: ApiClient,\n private themeId: number,\n private themeRoot: ThemeRoot,\n assetManifest?: ThemeAssetManifest,\n ) {\n this.assetManifestInstance = assetManifest;\n }\n\n private get assetManifest(): ThemeAssetManifest {\n this.assetManifestInstance ??= new ThemeAssetManifest(this.themeRoot.root);\n return this.assetManifestInstance;\n }\n\n // ─── Checksum Management ──────────────────────────────────────────────────\n\n async fetchChecksums(): Promise<void> {\n // `content_version_sha` on the resources index is a Phase 003a\n // server addition — older servers don't emit it. Cast through\n // `unknown` because the typed API client hasn't been regenerated\n // against the new OpenAPI spec yet; regeneration is a follow-up.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n this.updateChecksums(body.application_theme_resources ?? []);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n }\n\n /**\n * Server's `content_version_sha` captured on the last `fetchChecksums()`\n * or `downloadAll()`. `null` when talking to a pre-003a server.\n */\n remoteSha(): string | null {\n return this.lastKnownRemoteSha;\n }\n\n private updateChecksums(resources: RemoteResource[]): void {\n assertNoCaseCollisions(\n resources.flatMap((resource) => (resource.key ? [resource.key] : [])),\n );\n this.rawRemoteResources.clear();\n this.remoteResourceGroups.clear();\n for (const resource of resources) {\n if (!resource.key) continue;\n\n this.rawRemoteResources.set(resource.key, resource);\n const group = this.remoteResourceGroups.get(resource.key) ?? [];\n group.push(resource);\n this.remoteResourceGroups.set(resource.key, group);\n }\n this.remoteIndexesDirty = true;\n }\n\n private setRemoteResource(resource: RemoteResource): void {\n if (!resource.key) return;\n this.rawRemoteResources.set(resource.key, resource);\n this.remoteResourceGroups.set(resource.key, [resource]);\n this.remoteIndexesDirty = true;\n }\n\n private removeRemoteResource(relativePath: string): void {\n this.rawRemoteResources.delete(relativePath);\n this.remoteResourceGroups.delete(relativePath);\n this.remoteIndexesDirty = true;\n }\n\n // Rebuilding on every setRemoteResource/removeRemoteResource made bulk\n // upload loops O(n²) over the remote resource map, so mutations only mark\n // the indexes dirty and the next read rebuilds once.\n private get checksums(): Map<string, string> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.checksumIndex;\n }\n\n private get remoteResources(): Map<string, RemoteResource> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.remoteResourceIndex;\n }\n\n private rebuildRemoteIndexes(): void {\n this.remoteIndexesDirty = false;\n this.checksumIndex.clear();\n this.remoteResourceIndex.clear();\n\n for (const [key, resource] of this.rawRemoteResources) {\n // The backend may return both a legacy key and its .liquid equivalent.\n // Preserve the explicit .liquid resource when both are present.\n if (this.rawRemoteResources.has(`${key}.liquid`)) continue;\n\n this.remoteResourceIndex.set(key, resource);\n if (resource.checksum) this.checksumIndex.set(key, resource.checksum);\n }\n }\n\n hasChanged(file: ThemeFile): boolean {\n return file.checksum() !== this.checksums.get(file.relativePath);\n }\n\n remoteKeys(): string[] {\n return [...this.remoteResources.keys()];\n }\n\n /** A null-content resource has no possible working-tree counterpart. */\n private canDeleteRemoteResource(key: string): boolean {\n const resource = this.remoteResources.get(key);\n return resource?.content != null || isManagedAssetResource(resource);\n }\n\n /** Snapshot of remote checksums (key → sha256). Available after fetchChecksums() or downloadAll(). */\n remoteChecksums(): Record<string, string> {\n return Object.fromEntries(this.checksums);\n }\n\n /** URL-backed assets keyed by their exact theme resource path. */\n remoteAssetUrls(): Record<string, string> {\n const urls: Record<string, string> = {};\n for (const [key, resource] of this.remoteResources) {\n if (!isManagedAssetResource(resource)) continue;\n const url = resource.url;\n if (typeof url === \"string\" && url.length > 0) urls[key] = url;\n }\n return urls;\n }\n\n /** Compact, complete resource state paired with its acknowledged dev SHA. */\n devRemoteState(assetManifestSha: string): DevRemoteState | null {\n if (!this.remoteResourcesLoaded || !this.lastKnownRemoteSha) return null;\n\n return {\n themeId: this.themeId,\n remoteSha: this.lastKnownRemoteSha,\n assetManifestSha,\n resources: [...this.remoteResourceGroups.values()]\n .flat()\n .map(remoteResourceState),\n };\n }\n\n useDevRemoteState(state: DevRemoteState): void {\n if (state.themeId !== this.themeId) {\n throw new Error(\n `Dev remote state belongs to theme #${state.themeId}, not #${this.themeId}`,\n );\n }\n this.updateChecksums(state.resources.map(remoteResourceFromState));\n this.lastKnownRemoteSha = state.remoteSha;\n this.remoteResourcesLoaded = true;\n }\n\n private async ensureRemoteResourcesLoaded(): Promise<void> {\n if (!this.remoteResourcesLoaded) await this.fetchChecksums();\n }\n\n /**\n * Adds URL-backed FileResources for manifest assets without transferring\n * their bytes. The target stores the source asset's ImageKit URL.\n */\n async linkManagedAssets(opts: { replace?: boolean } = {}): Promise<number> {\n this.assetManifest.reload();\n await this.ensureRemoteResourcesLoaded();\n\n const plans: ManagedAssetPlan[] = [];\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n // A local binary is the recovery source after a failed metadata pull or\n // an intentional replacement. Let the normal upload path handle it;\n // never recreate the older manifest URL over those bytes first.\n if (this.themeRoot.file(key).exists) continue;\n\n const targetResource =\n this.managedAssetResourceForLink(key, link) ??\n this.remoteResources.get(key);\n if (\n targetResource &&\n !this.managedAssetNeedsRefresh(targetResource, link)\n ) {\n continue;\n }\n if (targetResource && !opts.replace) continue;\n\n const metadata = assetMetadataFromLink(link);\n plans.push({\n key,\n link,\n ...(targetResource ? { targetResource } : {}),\n ...(metadata ? { metadata } : {}),\n });\n }\n\n if (plans.length > 0) {\n await this.resolveAssetMetadata(plans);\n await this.createAssetReferences(plans);\n await this.fetchChecksums();\n }\n if (opts.replace && (await this.pruneDuplicateManagedAssetReferences())) {\n await this.fetchChecksums();\n }\n this.ensureManagedAssetsAreResolved();\n\n return plans.length;\n }\n\n private async resolveAssetMetadata(plans: ManagedAssetPlan[]): Promise<void> {\n const bySourceTheme = new Map<number, ManagedAssetPlan[]>();\n for (const plan of plans) {\n if (plan.metadata) continue;\n const sourcePlans = bySourceTheme.get(plan.link.sourceThemeId) ?? [];\n sourcePlans.push(plan);\n bySourceTheme.set(plan.link.sourceThemeId, sourcePlans);\n }\n\n let manifestChanged = false;\n for (const [sourceThemeId, sourcePlans] of bySourceTheme) {\n let sourceAssets: Map<string, RemoteAssetMetadata>;\n try {\n sourceAssets = await this.fetchThemeAssetMetadata(sourceThemeId);\n } catch (error) {\n throw new Error(\n `Could not read asset metadata from theme #${sourceThemeId}: ${formatError(error)}`,\n );\n }\n\n for (const plan of sourcePlans) {\n const sourceMetadata = sourceAssets.get(assetFilename(plan.key));\n if (!sourceMetadata) {\n throw new Error(\n `Could not find usable metadata for ${plan.key} in theme #${sourceThemeId}`,\n );\n }\n\n // Preserve a pulled URL while enriching an older manifest. A remote\n // source update must not silently change this checkout's asset URL.\n const metadata: RemoteAssetMetadata = {\n ...sourceMetadata,\n ...(typeof plan.link.url === \"string\" ? { url: plan.link.url } : {}),\n };\n plan.metadata = metadata;\n this.assetManifest.set(plan.key, {\n ...plan.link,\n ...metadata,\n });\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) this.assetManifest.write();\n }\n\n private async fetchThemeAssetMetadata(\n sourceThemeId: number,\n ): Promise<Map<string, RemoteAssetMetadata>> {\n const body = await themes.getThemeAssets(this.api, sourceThemeId);\n if (!isRecord(body) || !Array.isArray(body[\"file_resources\"])) {\n throw new Error(\"Theme assets response did not include file_resources\");\n }\n\n const assets = new Map<string, RemoteAssetMetadata>();\n for (const value of body[\"file_resources\"]) {\n const asset = parseThemeAssetMetadata(value);\n if (asset) assets.set(asset.filename, asset.metadata);\n }\n return assets;\n }\n\n private async createAssetReferences(\n plans: ManagedAssetPlan[],\n ): Promise<void> {\n const errors: string[] = [];\n let nextPlan = 0;\n\n const worker = async () => {\n while (nextPlan < plans.length) {\n const plan = plans[nextPlan];\n nextPlan += 1;\n if (!plan || !plan.metadata) continue;\n\n try {\n await this.createAssetReference(plan);\n } catch (error) {\n errors.push(`${plan.key}: ${formatError(error)}`);\n }\n }\n };\n\n await Promise.all(\n Array.from(\n { length: Math.min(ASSET_REFERENCE_CONCURRENCY, plans.length) },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not save ${errors.length} ImageKit URL reference(s) (this requires File Resources update access): ${errors.join(\"; \")}`,\n );\n }\n }\n\n private async createAssetReference(plan: ManagedAssetPlan): Promise<void> {\n const metadata = plan.metadata;\n if (!metadata) throw new Error(\"asset metadata was not resolved\");\n\n const targetResourceId = plan.targetResource?.resource_id;\n if (plan.targetResource && typeof targetResourceId !== \"number\") {\n throw new Error(\"existing target asset has no resource ID\");\n }\n\n const body = await themes.createFileResource(this.api, {\n file_resource: {\n url: metadata.url,\n filename: assetFilename(plan.key),\n content_type: metadata.contentType,\n content_size: metadata.contentSize,\n ...(metadata.previewImageUrl\n ? { preview_image_url: metadata.previewImageUrl }\n : {}),\n ...(metadata.altText !== undefined\n ? { alt_text: metadata.altText }\n : {}),\n ...(metadata.handle ? { handle: metadata.handle } : {}),\n relateable_id: this.themeId,\n relateable_type: \"ApplicationTheme\",\n },\n });\n\n const createdResourceId = createdFileResourceId(body);\n if (!createdResourceId) {\n throw new Error(\"create response did not include a FileResource ID\");\n }\n\n if (typeof targetResourceId !== \"number\") return;\n\n try {\n await themes.destroyFileResource(this.api, targetResourceId);\n } catch (error) {\n if (isNotFoundError(error)) return;\n try {\n await themes.destroyFileResource(this.api, createdResourceId);\n } catch {\n // Keep the original error; a later sync can repair any duplicate.\n }\n throw error;\n }\n }\n\n /**\n * Returns every resource for an exact logical key. A legacy `foo` resource\n * is hidden when `foo.liquid` exists, matching the normal remote index.\n */\n private resourcesForLogicalKey(key: string): RemoteResource[] {\n if (this.rawRemoteResources.has(`${key}.liquid`)) return [];\n return this.remoteResourceGroups.get(key) ?? [];\n }\n\n private managedAssetResourceForLink(\n key: string,\n link: ThemeAssetLink,\n ): RemoteResource | undefined {\n return this.resourcesForLogicalKey(key).find(\n (resource) =>\n isManagedAssetResource(resource) &&\n !this.managedAssetNeedsRefresh(resource, link),\n );\n }\n\n /** Make interrupted reference replacement converge to one FileResource. */\n private async pruneDuplicateManagedAssetReferences(): Promise<boolean> {\n const resourcesToDelete: Array<{ key: string; resourceId: number }> = [];\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key) || !link.url) continue;\n\n const resources = this.resourcesForLogicalKey(key).filter(\n isManagedAssetResource,\n );\n if (resources.length < 2) continue;\n\n const matchingResources = resources.filter(\n (resource) => resource.url === link.url,\n );\n if (matchingResources.length === 0) continue;\n\n const keeper = this.lowestResourceId(matchingResources, key);\n const keeperId = this.resourceIdForAssetReference(keeper, key);\n for (const resource of resources) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId !== keeperId)\n resourcesToDelete.push({ key, resourceId });\n }\n }\n\n if (resourcesToDelete.length === 0) return false;\n\n const errors: string[] = [];\n let nextResource = 0;\n const worker = async () => {\n while (nextResource < resourcesToDelete.length) {\n const resource = resourcesToDelete[nextResource];\n nextResource += 1;\n if (!resource) continue;\n\n try {\n await themes.destroyFileResource(this.api, resource.resourceId);\n } catch (error) {\n if (!isNotFoundError(error)) {\n errors.push(`${resource.key}: ${formatError(error)}`);\n }\n }\n }\n };\n\n await Promise.all(\n Array.from(\n {\n length: Math.min(\n ASSET_REFERENCE_CONCURRENCY,\n resourcesToDelete.length,\n ),\n },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not remove ${errors.length} duplicate ImageKit URL reference(s): ${errors.join(\"; \")}`,\n );\n }\n\n return true;\n }\n\n private lowestResourceId(\n resources: RemoteResource[],\n key: string,\n ): RemoteResource {\n let lowest = resources[0];\n if (!lowest) throw new Error(`No asset resources found for ${key}`);\n\n let lowestId = this.resourceIdForAssetReference(lowest, key);\n for (const resource of resources.slice(1)) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId < lowestId) {\n lowest = resource;\n lowestId = resourceId;\n }\n }\n return lowest;\n }\n\n private resourceIdForAssetReference(\n resource: RemoteResource,\n key: string,\n ): number {\n const resourceId = positiveInteger(resource.resource_id);\n if (!resourceId) {\n throw new Error(`Existing target asset has no resource ID: ${key}`);\n }\n return resourceId;\n }\n\n /** Makes this target the provenance source for assets it now resolves. */\n repointManagedAssetsToCurrentTheme(): void {\n this.assetManifest.reload();\n let changed = false;\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n if (this.themeRoot.file(key).exists) continue;\n\n const resource = this.remoteResources.get(key);\n if (!isManagedAssetResource(resource)) {\n throw new Error(`Managed asset is missing from target theme: ${key}`);\n }\n\n const nextLink: ThemeAssetLink = { ...link, sourceThemeId: this.themeId };\n delete nextLink.pending;\n delete nextLink.checksum;\n Object.assign(nextLink, resourceLink(resource));\n this.assetManifest.set(key, nextLink);\n changed = true;\n }\n\n if (changed) this.assetManifest.write();\n }\n\n // ─── Upload ───────────────────────────────────────────────────────────────\n\n /**\n * Uploads one file. Resolves with the exact text content that was sent to\n * the server (null for binary files) so callers can run diagnostics against\n * the same bytes instead of re-reading a file that may have changed on disk\n * while the request was in flight.\n */\n async uploadFile(\n file: ThemeFile,\n baseSha?: string | null,\n opts: { pendingAsset?: boolean } = {},\n ): Promise<string | null> {\n if (file.isText) {\n const content = file.read();\n const resource = await this.putResource(\n { key: file.relativePath, content },\n baseSha,\n );\n this.setRemoteResource({\n ...resource,\n key: file.relativePath,\n content,\n checksum: resource.checksum ?? file.checksum(),\n });\n return content;\n }\n\n if (isNestedBinaryThemeAsset(file)) {\n throw new Error(\n `Binary assets must be directly inside assets/: ${file.relativePath}`,\n );\n }\n\n const resource = await this.uploadBinaryFile(file, baseSha);\n this.setRemoteResource(resource);\n if (isThemeAssetKey(file.relativePath)) {\n this.externalizeBinaryFile(file, resource, opts.pendingAsset);\n }\n return null;\n }\n\n /** Canonical member bytes accepted by the server during this transfer. */\n acknowledgedMemberContents(): ReadonlyMap<string, string> {\n return this.memberAcknowledgments;\n }\n\n /** Upload one member document and its changed dependencies as one transaction. */\n async uploadMemberConfiguration(\n upload: ThemeUpload,\n baseSha?: string | null,\n ): Promise<Array<{ file: ThemeFile; content: string }>> {\n if (!upload.memberConfiguration) {\n throw new Error(\"A member configuration upload is required.\");\n }\n const { content, files } = upload.memberConfiguration;\n const capturedFiles = files.map((file) => ({ file, content: file.read() }));\n const resource = await this.putResource(\n {\n key: MEMBER_CONFIGURATION_KEY,\n content,\n member_files: capturedFiles.map(\n ({ file, content: capturedContent }) => ({\n key: file.relativePath,\n content: capturedContent,\n }),\n ),\n },\n baseSha,\n capturedFiles.map(({ file }) => file.relativePath),\n );\n // The server may allocate new menu handles and rewrite selectors. Keep its\n // acknowledged contents/checksums, never claim that the local bytes landed.\n this.setRemoteResource(resource);\n if (resource.key && resource.content != null) {\n this.memberAcknowledgments.set(\n resource.key,\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content),\n );\n }\n return [{ file: upload.file, content }, ...capturedFiles];\n }\n\n /**\n * Wraps the generated `updateThemeResource` client with the two\n * merge-aware Phase 003 additions: sending `base_sha` on the request\n * and reading the server's fresh `content_version_sha` off the\n * response so the caller can thread it forward on the next PUT.\n *\n * Accepts any `application_theme_resource` shape — text uploads pass\n * `{ key, content }`; binary uploads (after DAM + ImageKit\n * orchestration) pass `{ key, dam_asset: { ... } }`. Both must route\n * through here so `lastKnownRemoteSha` stays in lockstep with every\n * write the server has ack'd, mixed text/binary pushes included.\n *\n * The typed client hasn't been regenerated against the new OpenAPI\n * spec yet, so `base_sha` is threaded through as an extra property\n * (server accepts unknown fields on this endpoint) and the response\n * is cast to read the extra `content_version_sha`. Regeneration is\n * a follow-up; that PR will drop these casts.\n *\n * `PushConflictError` is thrown on a 409 so callers can distinguish\n * \"server rejected because of stale base\" from generic upload\n * failures.\n */\n private async putResource(\n resource: Record<string, unknown>,\n baseSha: string | null | undefined,\n expectedMemberKeys?: readonly string[],\n ): Promise<RemoteResource> {\n const body: Record<string, unknown> = {\n application_theme_resource: resource,\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.updateThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as {\n application_theme_resource?: RemoteResource;\n member_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n if (expectedMemberKeys) {\n const acknowledged = new Map(\n (response.member_resources ?? []).map((entry) => [entry.key, entry]),\n );\n const manifest = response.application_theme_resource;\n if (\n manifest?.key !== MEMBER_CONFIGURATION_KEY ||\n manifest.content == null ||\n !manifest.checksum ||\n expectedMemberKeys.some(\n (key) =>\n acknowledged.get(key)?.content == null ||\n !acknowledged.get(key)?.checksum,\n )\n ) {\n throw new Error(\n \"Fluid did not acknowledge every bundled member file. Update the server before retrying this push.\",\n );\n }\n }\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n for (const memberResource of response.member_resources ?? []) {\n this.setRemoteResource(memberResource);\n if (memberResource.key && memberResource.content != null) {\n this.memberAcknowledgments.set(\n memberResource.key,\n typeof memberResource.content === \"string\"\n ? memberResource.content\n : JSON.stringify(memberResource.content),\n );\n }\n }\n return (\n response.application_theme_resource ?? {\n key: typeof resource[\"key\"] === \"string\" ? resource[\"key\"] : \"\",\n checksum: null,\n }\n );\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n /**\n * Server-side push preflight (Phase 003a). Runs once at the start of\n * a push loop. On 200 the server's fresh `remote_sha` is stashed on\n * the syncer so subsequent per-file PUTs can carry it. On 409 a\n * `PushConflictError` is thrown carrying the server's `remote_sha`\n * from the `meta` payload — the caller renders \"pull first\".\n *\n * Skipped when `baseSha` is null/undefined so old-behavior pushes\n * (no stored `baseSha` in `.fluid-theme.json`) and `--force` pushes\n * short-circuit past the check.\n */\n /**\n * Tell Fluid the push is finished, so it commits the theme's current state\n * as one version.\n *\n * The other half of `preflightPush`. That one runs once before the file\n * loop to reject a stale base; this runs once after it, and is the only\n * thing that turns a push into a commit — the per-file writes just mark the\n * theme changed.\n *\n * It exists because the server cannot see where an operation ends. A push\n * arrives as a hundred-odd independent requests, and every way of inferring\n * \"these belong together\" either merges two publishes that happened to land\n * close together or splits one push across several commits. The client\n * knows; this says so.\n *\n * Returns false rather than throwing when the request fails. Callers must\n * retain their previous baseline until finalization succeeds; the server\n * can reject an incomplete member graph even after individual files land.\n *\n * Returns whether Fluid took the request, because a caller tracking edit\n * boundaries needs to know. A failed ask leaves the previous edit\n * uncommitted, and uploading the next one over it merges the two into\n * whichever commit eventually lands.\n */\n async requestSync(): Promise<boolean> {\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/sync`,\n {},\n )) as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n return true;\n } catch (e) {\n console.warn(\n ` ⚠ couldn't ask Fluid to commit this push (${formatError(e)}). Retry the push to finalize it.`,\n );\n return false;\n }\n }\n\n async preflightPush(baseSha: string | null | undefined): Promise<void> {\n if (!baseSha) return;\n\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/check_push`,\n { base_sha: baseSha },\n )) as { remote_sha?: string };\n if (response.remote_sha) this.lastKnownRemoteSha = response.remote_sha;\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n private rethrowIfConflict(e: unknown): unknown {\n const status =\n (e as { status?: number; response?: { status?: number } })?.status ??\n (e as { response?: { status?: number } })?.response?.status;\n if (status !== 409) return e;\n\n // `ApiError` from api-client-core stores the parsed response body\n // under `.data` (not `.body`). Reading the wrong key silently\n // dropped `remote_sha` in production so the \"server is at X\"\n // hint in the CLI output went missing.\n const meta = (e as { data?: { meta?: { remote_sha?: string } } })?.data\n ?.meta;\n return new PushConflictError(meta?.remote_sha ?? null);\n }\n\n private async uploadBinaryFile(\n file: ThemeFile,\n baseSha: string | null | undefined,\n ): Promise<UploadedBinaryResource> {\n // Step 1: Create DAM placeholder\n const placeholderBody = await this.api.post<{\n asset: { id: number; canonical_path: string };\n }>(\"/api/dam/assets\", {\n placeholder_asset: {\n description: `Uploaded via Fluid CLI: ${file.name}`,\n mime_type: file.mime.name,\n name: file.name,\n },\n });\n const asset = placeholderBody.asset;\n\n // Step 2: Get ImageKit auth token\n const authBody = await this.api.post<{\n token: string;\n signature: string;\n expire: number;\n }>(\"/api/dam/assets/imagekit_auth\", {});\n\n // Step 3: Upload to ImageKit via multipart\n const folder = this.canonicalPathToImageKitFolder(asset.canonical_path);\n const formData = new FormData();\n const blob = new Blob([file.readBinary() as unknown as ArrayBuffer], {\n type: file.mime.name,\n });\n formData.append(\"file\", blob, file.name);\n formData.append(\"token\", authBody.token);\n formData.append(\"signature\", authBody.signature);\n formData.append(\"expire\", String(authBody.expire));\n formData.append(\"folder\", folder);\n formData.append(\"fileName\", file.name);\n formData.append(\"publicKey\", \"public_j7s4Ih9ETh/OCp41mVQH7tlXBdU=\");\n\n const ikResp = await fetch(\n \"https://upload.imagekit.io/api/v1/files/upload\",\n {\n method: \"POST\",\n body: formData,\n },\n );\n if (!ikResp.ok) throw new Error(`ImageKit upload failed: ${ikResp.status}`);\n const ikBody = (await ikResp.json()) as {\n fileId: string;\n url: string;\n thumbnailUrl: string;\n size: number;\n height?: number;\n width?: number;\n };\n\n // Step 4: Backfill DAM asset\n const backfillPayload: Record<string, unknown> = {\n asset: {\n id: asset.id,\n imagekit_file_id: ikBody.fileId,\n imagekit_url: ikBody.url,\n mime_type: file.mime.name,\n name: file.name,\n file_size: ikBody.size,\n expected_path: asset.canonical_path,\n },\n };\n if (ikBody.height)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"height\"] =\n ikBody.height;\n if (ikBody.width)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"width\"] =\n ikBody.width;\n\n const backfillBody = await this.api.post<{\n asset: { code: string; default_variant_url: string };\n }>(\"/api/dam/assets/backfill_imagekit\", backfillPayload);\n\n // Step 5: Associate with theme resource. Route through `putResource`\n // so `base_sha` gets sent and `lastKnownRemoteSha` advances just\n // like text-file writes — a mixed text+binary push must keep them\n // in a single monotonic chain.\n const update = await this.putResource(\n {\n key: file.relativePath,\n dam_asset: {\n dam_asset_code: backfillBody.asset.code,\n content_type: file.mime.name,\n content_size: ikBody.size,\n filename: file.name,\n handle: backfillBody.asset.code,\n url: backfillBody.asset.default_variant_url,\n preview_image_url: ikBody.thumbnailUrl,\n },\n },\n baseSha,\n );\n\n // Older API responses can omit URL fields. Preserve the known DAM URL so\n // the manifest stays usable after a successful remote write.\n return {\n ...update,\n key: update.key || file.relativePath,\n url: update.url ?? backfillBody.asset.default_variant_url,\n damAssetCode: backfillBody.asset.code,\n assetMetadata: {\n url: backfillBody.asset.default_variant_url,\n contentType: file.mime.name,\n contentSize: ikBody.size,\n previewImageUrl: ikBody.thumbnailUrl,\n altText: file.name,\n handle: backfillBody.asset.code,\n },\n };\n }\n\n private externalizeBinaryFile(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n pending: boolean | undefined,\n ): void {\n // Persist before unlinking. If persistence fails, retain the local bytes so\n // the developer can retry instead of losing the only provenance record.\n this.assetManifest.reload();\n this.assetManifest.set(file.relativePath, {\n sourceThemeId: this.themeId,\n ...resourceLink(resource),\n ...uploadedAssetMetadata(file, resource),\n ...(pending ? { pending: true } : {}),\n ...(typeof resource.damAssetCode === \"string\"\n ? { damAssetCode: resource.damAssetCode }\n : {}),\n });\n this.assetManifest.write();\n unlinkSync(file.absolutePath);\n }\n\n private canonicalPathToImageKitFolder(canonicalPath: string): string {\n const parts = canonicalPath.split(\".\");\n const companyId = parts[0] ?? \"unknown\";\n const category = parts[1] ?? \"files\";\n const assetCode = parts[2] ?? \"unknown\";\n const folderMap: Record<string, string> = {\n images: \"images\",\n videos: \"videos\",\n audio: \"audio\",\n documents: \"documents\",\n files: \"files\",\n };\n return `${companyId}/${folderMap[category] ?? \"files\"}/${assetCode}`;\n }\n\n // ─── Delete ───────────────────────────────────────────────────────────────\n\n async deleteRemoteFile(\n relativePath: string,\n baseSha?: string | null,\n ): Promise<void> {\n assertMemberConfigurationRetained([relativePath]);\n // Chokidar observes the intentional unlink after a binary has been\n // externalized. That unlink must never delete the server-side DAM link.\n this.assetManifest.reload();\n if (this.assetManifest.has(relativePath)) return;\n\n const body: Record<string, unknown> = {\n application_theme_resource: { key: relativePath },\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.deleteThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n } catch (e) {\n if (!isNotFoundError(e)) throw this.rethrowIfConflict(e);\n }\n this.removeRemoteResource(relativePath);\n }\n\n // ─── Download ─────────────────────────────────────────────────────────────\n\n async downloadAll(): Promise<RemoteResource[]> {\n // Same cast rationale as `fetchChecksums`. The additional\n // `content_version_sha` field is stashed on the syncer so callers\n // (pull command) can persist it as the new `baseSha`.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n const resources = body.application_theme_resources ?? [];\n this.updateChecksums(resources);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n return resources;\n }\n\n async downloadBinaryAsset(url: string): Promise<Buffer> {\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`Failed to download asset: ${resp.status}`);\n return Buffer.from(await resp.arrayBuffer());\n }\n\n /**\n * Move directly-addressable binary `assets/*` resources into the local\n * manifest before merge-pull sees them. This prevents a byte download and\n * keeps those paths out of the shadow repository; their canonical state is\n * the ImageKit URL, not a local file.\n */\n async externalizePulledAssets(\n resources: RemoteResource[],\n opts: { delete: boolean; backgroundGuard?: BackgroundPullGuard },\n ): Promise<{ managedKeys: Set<string>; linked: number; errors: string[] }> {\n this.assetManifest.reload();\n\n const managedKeys = new Set<string>();\n const changedManifestKeys = new Set<string>();\n const remoteManagedKeys = new Set<string>();\n const preservedManifestKeys = new Set<string>();\n const filesToRemove = new Map<string, ThemeFile>();\n const errors: string[] = [];\n let manifestChanged = false;\n\n // ApplicationThemeResource deliberately exposes only the URL/checksum.\n // Capture FileResource metadata during the pull too, so a later push can\n // recreate the reference even if this source/development theme has since\n // been deleted. This is one small JSON request, never a binary download.\n const resourcesNeedingMetadata = resources.filter((resource) => {\n const key = resource.key;\n if (!key) return false;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) return false;\n if (!isLinkableBinaryResource(resource, key, file)) return false;\n\n const existing = this.assetManifest.get(key);\n if (this.themeRoot.ignore.ignore(key) || existing?.pending) return false;\n return (\n !existing ||\n existing.url !== resource.url ||\n !assetMetadataFromLink(existing)\n );\n });\n let assetMetadata = new Map<string, RemoteAssetMetadata>();\n const unresolvedMetadataKeys = new Set<string>();\n if (resourcesNeedingMetadata.length > 0) {\n try {\n assetMetadata = await this.fetchThemeAssetMetadata(this.themeId);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Read remote asset metadata: ${formatError(error)}`);\n for (const resource of resourcesNeedingMetadata) {\n if (resource.key) unresolvedMetadataKeys.add(resource.key);\n }\n }\n\n if (unresolvedMetadataKeys.size === 0) {\n for (const resource of resourcesNeedingMetadata) {\n const key = resource.key;\n if (!key || assetMetadata.has(assetFilename(key))) continue;\n unresolvedMetadataKeys.add(key);\n errors.push(\n `Could not find usable metadata for ${key} in theme #${this.themeId}`,\n );\n }\n }\n }\n\n opts.backgroundGuard?.assertUnchanged();\n for (const resource of resources) {\n const key = resource.key;\n if (!key) continue;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) continue;\n if (!isLinkableBinaryResource(resource, key, file)) continue;\n\n remoteManagedKeys.add(key);\n if (this.themeRoot.ignore.ignore(key)) {\n if (this.assetManifest.has(key)) preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // A newly added dev asset has not necessarily been promoted to the\n // pulled source yet. It remains authoritative until a push makes it\n // durable on the selected target.\n if (this.assetManifest.get(key)?.pending) {\n preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // Do not create an incomplete URL-only reference: a later push may need\n // content type and size after this source theme no longer exists. Let\n // merge-pull retain/download just this binary instead.\n if (unresolvedMetadataKeys.has(key)) {\n // The fallback bytes are now the only trustworthy copy. A manifest\n // entry pointing at a URL the remote no longer serves is stale —\n // keeping it would let linkManagedAssets restore the older asset if\n // the local file later disappears.\n const stale = this.assetManifest.get(key);\n if (stale && stale.url !== resource.url) {\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n continue;\n }\n\n try {\n const existing = this.assetManifest.get(key);\n const metadata = assetMetadata.get(assetFilename(key));\n this.assetManifest.set(key, {\n ...existing,\n sourceThemeId: this.themeId,\n ...metadata,\n ...resourceLink(resource),\n });\n manifestChanged = true;\n changedManifestKeys.add(key);\n managedKeys.add(key);\n filesToRemove.set(key, file);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n }\n }\n\n if (opts.delete) {\n for (const [key, link] of this.assetManifest.entries()) {\n if (\n remoteManagedKeys.has(key) ||\n preservedManifestKeys.has(key) ||\n this.themeRoot.ignore.ignore(key) ||\n link.pending\n ) {\n continue;\n }\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) {\n try {\n // Persist all links first. If this fails, preserve the local bytes and\n // let merge-pull materialize the affected resources normally.\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([\".fluid-assets.json\"], () =>\n this.assetManifest.write(),\n );\n else this.assetManifest.write();\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Persist remote asset manifest: ${formatError(error)}`);\n for (const key of changedManifestKeys) managedKeys.delete(key);\n return { managedKeys, linked: 0, errors };\n }\n }\n\n let linked = 0;\n for (const [key, file] of filesToRemove) {\n try {\n if (file.exists) {\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([key], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n }\n linked++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n // The manifest link is safely on disk; retain the resource outside the\n // shadow even if an operating-system lock delays local cleanup.\n }\n }\n\n return { managedKeys, linked, errors };\n }\n\n private isSafeThemeFile(key: string, file: ThemeFile): boolean {\n return (\n !key.includes(\"\\0\") &&\n !key.split(/[\\\\/]/).includes(\"..\") &&\n (file.absolutePath === this.themeRoot.root ||\n file.absolutePath.startsWith(this.themeRoot.root + sep))\n );\n }\n\n private managedAssetNeedsRefresh(\n resource: RemoteResource,\n link: ThemeAssetLink,\n ): boolean {\n if (!isManagedAssetResource(resource)) return true;\n if (link.url !== undefined) return resource.url !== link.url;\n if (link.checksum !== undefined) return resource.checksum !== link.checksum;\n return true;\n }\n\n private ensureManagedAssetsAreResolved(): void {\n const unresolved = this.assetManifest\n .entries()\n .filter(\n ([key]) =>\n !this.themeRoot.ignore.ignore(key) &&\n !this.themeRoot.file(key).exists,\n )\n .map(([key]) => key)\n .filter((key) => !isManagedAssetResource(this.remoteResources.get(key)));\n if (unresolved.length > 0) {\n throw new Error(\n `Managed asset(s) could not be linked: ${unresolved.join(\", \")}`,\n );\n }\n }\n\n // ─── Full Upload ──────────────────────────────────────────────────────────\n\n async uploadTheme(\n opts: {\n delete?: boolean;\n validate?: boolean;\n linkManagedAssets?: { replace?: boolean };\n pendingBinaryAssets?: boolean;\n onProgress?: (done: number, total: number) => void;\n // The SHA `.fluid-theme.json` stored on the last pull. Sent to the\n // server on every PUT/DELETE so a stale local aborts the push\n // instead of clobbering. Null / undefined skips the check entirely\n // (force-push and pre-Phase-003 servers).\n baseSha?: string | null;\n /** The caller already ran the stale-base preflight. */\n skipPreflight?: boolean;\n /** Last acknowledged dev resource index, validated by `baseSha`. */\n remoteState?: DevRemoteState;\n /** Without a stored baseSha, guard writes with the fetched index's SHA\n * so a concurrent change 409s instead of being overwritten. */\n guardFetchedRemote?: boolean;\n } = {},\n ): Promise<SyncResult> {\n const localFiles = this.themeRoot.files();\n assertNoCaseCollisions(localFiles.map((file) => file.relativePath));\n const result: SyncResult = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: 0,\n errors: [],\n validationFailed: false,\n };\n\n // Schema validation pass\n if (opts.validate) {\n for (const file of localFiles) {\n if (!file.isLiquid) continue;\n const diagnostics = file.validateSchema();\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n for (const d of errors) {\n result.errors.push(`${file.relativePath}: ${d.message}`);\n }\n }\n if (result.errors.length > 0) {\n result.validationFailed = true;\n return result;\n }\n }\n\n if (opts.remoteState) {\n this.useDevRemoteState(opts.remoteState);\n } else {\n await this.fetchChecksums();\n }\n\n // Preflight before any state change so a stale local aborts with\n // zero writes. On 409, `PushConflictError` propagates to the CLI\n // and the per-file loop below never runs.\n if (!opts.skipPreflight) await this.preflightPush(opts.baseSha);\n\n // Roll the base_sha forward as the server bumps its version on\n // each successful write. Start from the caller's stored SHA (from\n // pull) and update from every response — the next PUT carries the\n // freshest SHA the server has given us so a concurrent third-party\n // write mid-loop 409s cleanly instead of the CLI overwriting.\n let baseSha =\n opts.baseSha ??\n (opts.guardFetchedRemote && !opts.remoteState ? this.remoteSha() : null);\n\n const localPaths = new Set(localFiles.map((file) => file.relativePath));\n if (opts.delete) {\n for (const key of this.assetManifest.keys()) localPaths.add(key);\n }\n const toDelete = opts.delete\n ? this.remoteKeys().filter(\n (key) =>\n key !== MEMBER_CONFIGURATION_KEY &&\n this.canDeleteRemoteResource(key) &&\n !localPaths.has(key) &&\n !this.themeRoot.ignore.ignore(key),\n )\n : [];\n\n const plannedUploads = planThemeUploads(\n this.themeRoot,\n localFiles.filter((f) => f.exists && this.hasChanged(f)),\n toDelete,\n );\n\n if (opts.linkManagedAssets) {\n result.linked = await this.linkManagedAssets(opts.linkManagedAssets);\n // FileResource writes can change the server's content version. The list\n // after reference creation gives the next normal resource write a fresh\n // base when the server supports Phase 003a.\n baseSha = this.lastKnownRemoteSha ?? baseSha;\n } else if (this.assetManifestInstance) {\n this.assetManifest.reload();\n this.ensureManagedAssetsAreResolved();\n }\n\n // Linking managed assets can acknowledge or externalize local binaries.\n // Recheck ordinary files after that step before attempting an upload.\n const toUpload = plannedUploads.filter(\n ({ file, memberConfiguration }) =>\n memberConfiguration || (file.exists && this.hasChanged(file)),\n );\n const uploadCount = toUpload.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n );\n let done = 0;\n for (const upload of toUpload) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n result.errors.length > 0\n ) {\n result.errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await this.uploadMemberConfiguration(upload, baseSha);\n } else {\n await this.uploadFile(file, baseSha, {\n pendingAsset: opts.pendingBinaryAssets,\n });\n }\n // `putResource` updates `lastKnownRemoteSha` from the server's\n // response. Thread it forward so the next iteration carries\n // the current SHA — this is how mid-push races surface.\n baseSha = this.lastKnownRemoteSha;\n result.uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n done += 1 + (upload.memberConfiguration?.files.length ?? 0);\n opts.onProgress?.(done, uploadCount);\n }\n\n if (opts.delete && result.errors.length === 0) {\n for (const key of toDelete) {\n try {\n await this.deleteRemoteFile(key, baseSha);\n baseSha = this.lastKnownRemoteSha;\n result.deleted++;\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n }\n }\n\n return result;\n }\n\n // ─── Full Download ────────────────────────────────────────────────────────\n\n async downloadTheme(\n opts: {\n delete?: boolean;\n skip?: Set<string>;\n onProgress?: (done: number, total: number) => void;\n } = {},\n ): Promise<SyncResult & { skipped: number }> {\n const resources = await this.downloadAll();\n const externalizedAssets = await this.externalizePulledAssets(resources, {\n delete: opts.delete ?? false,\n });\n const result: SyncResult & { skipped: number } = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: externalizedAssets.linked,\n skipped: 0,\n errors: [...externalizedAssets.errors],\n validationFailed: false,\n };\n\n let done = 0;\n for (const resource of resources) {\n if (externalizedAssets.managedKeys.has(resource.key)) {\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n if (opts.skip?.has(resource.key)) {\n result.skipped++;\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n const file = this.themeRoot.file(resource.key);\n\n // Guard against path traversal from malicious API responses\n if (!this.isSafeThemeFile(resource.key, file)) {\n result.errors.push(`Download ${resource.key}: path traversal detected`);\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n try {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n const buf = await this.downloadBinaryAsset(resource.url);\n file.write(buf);\n } else if (\n resource.content !== undefined &&\n resource.content !== null\n ) {\n const content =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n file.write(content);\n }\n result.downloaded++;\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${formatError(e)}`);\n }\n opts.onProgress?.(++done, resources.length);\n }\n\n if (opts.delete) {\n const remoteKeys = new Set(resources.map((r) => r.key));\n for (const file of this.themeRoot.files()) {\n if (remoteKeys.has(file.relativePath)) continue;\n // Preserve local stylesheets that the backend hides under\n // STYLESHEET_STRICT_INPUT — their absence from the resources\n // index is intentional (theme-level styles.css /\n // global_styles.css and per-template composite\n // {type}/{name}/styles.css), not a signal that the merchant\n // deleted them.\n if (isStylesheetKey(file.relativePath)) continue;\n\n try {\n unlinkSync(file.absolutePath);\n result.deleted++;\n } catch {\n // ignore\n }\n }\n }\n\n return result;\n }\n}\n\nfunction isLinkableBinaryResource(\n resource: RemoteResource,\n key: string,\n file: ThemeFile,\n): boolean {\n return (\n isThemeAssetKey(key) && isManagedAssetResource(resource) && !file.isText\n );\n}\n\nfunction isNestedBinaryThemeAsset(file: ThemeFile): boolean {\n return (\n !file.isText &&\n file.relativePath.startsWith(\"assets/\") &&\n !isThemeAssetKey(file.relativePath)\n );\n}\n\nfunction isManagedAssetResource(\n resource: RemoteResource | undefined,\n): resource is RemoteResource {\n return (\n resource?.resource_type === \"FileResource\" &&\n typeof resource.url === \"string\" &&\n resource.url.length > 0\n );\n}\n\nfunction remoteResourceFromState(state: RemoteResourceState): RemoteResource {\n return {\n key: state.key,\n checksum: state.checksum,\n content: state.contentPresent ? \"\" : null,\n resource_type: state.resourceType,\n resource_id: state.resourceId,\n url: state.url,\n };\n}\n\nfunction remoteResourceState(resource: RemoteResource): RemoteResourceState {\n return {\n key: resource.key,\n checksum: resource.checksum,\n contentPresent: resource.content != null,\n resourceType: resource.resource_type,\n resourceId: resource.resource_id,\n url: resource.url,\n };\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return isApiError(error) && error.status === 404;\n}\n\nfunction resourceLink(\n resource: RemoteResource,\n): Pick<ThemeAssetLink, \"checksum\" | \"url\"> {\n return {\n ...(typeof resource.checksum === \"string\" && resource.checksum.length > 0\n ? { checksum: resource.checksum }\n : {}),\n ...(typeof resource.url === \"string\" && resource.url.length > 0\n ? { url: resource.url }\n : {}),\n };\n}\n\nfunction assetFilename(key: string): string {\n return key.slice(\"assets/\".length);\n}\n\nfunction assetMetadataFromLink(\n link: ThemeAssetLink,\n): RemoteAssetMetadata | undefined {\n if (\n typeof link.url !== \"string\" ||\n link.url.length === 0 ||\n typeof link.contentType !== \"string\" ||\n link.contentType.length === 0 ||\n typeof link.contentSize !== \"number\" ||\n !Number.isInteger(link.contentSize) ||\n link.contentSize <= 0\n ) {\n return undefined;\n }\n\n return {\n url: link.url,\n contentType: link.contentType,\n contentSize: link.contentSize,\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n };\n}\n\nfunction uploadedAssetMetadata(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n): RemoteAssetMetadata {\n if (resource.assetMetadata) return resource.assetMetadata;\n if (!resource.url) {\n throw new Error(`Uploaded asset has no URL: ${file.relativePath}`);\n }\n\n return {\n url: resource.url,\n contentType: file.mime.name,\n contentSize: file.size(),\n altText: file.name,\n ...(typeof resource.damAssetCode === \"string\"\n ? { handle: resource.damAssetCode }\n : {}),\n };\n}\n\nfunction parseThemeAssetMetadata(\n value: unknown,\n): { filename: string; metadata: RemoteAssetMetadata } | undefined {\n if (!isRecord(value)) return undefined;\n\n const filename = nonEmptyString(value[\"filename\"]);\n const url = nonEmptyString(value[\"url\"]);\n const contentType = nonEmptyString(value[\"content_type\"]);\n const contentSize = positiveInteger(value[\"content_size\"]);\n if (!filename || !url || !contentType || !contentSize) return undefined;\n\n const previewImageUrl = nonEmptyString(value[\"preview_image_url\"]);\n const altText = optionalString(value[\"alt_text\"]);\n const handle = nonEmptyString(value[\"handle\"]);\n return {\n filename,\n metadata: {\n url,\n contentType,\n contentSize,\n ...(previewImageUrl ? { previewImageUrl } : {}),\n ...(altText !== undefined ? { altText } : {}),\n ...(handle ? { handle } : {}),\n },\n };\n}\n\nfunction createdFileResourceId(value: unknown): number | undefined {\n if (!isRecord(value) || !isRecord(value[\"file_resource\"])) {\n return undefined;\n }\n return positiveInteger(value[\"file_resource\"][\"id\"]);\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isInteger(value) && value > 0) {\n return value;\n }\n if (typeof value !== \"string\" || !/^\\d+$/.test(value)) return undefined;\n\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * Heuristic-only check for obviously unbalanced liquid delimiters\n * (`{% %}` and `{{ }}`). This is NOT a liquid parser — it only tracks\n * opening delimiters until they are closed. It exists to give watch-mode users a signal\n * when a save is liquid-syntax-broken: the server accepts\n * syntax-broken liquid silently on upload, and the storefront\n * renderer then serves stale content for that section with no error\n * anywhere else in the pipeline.\n *\n * Closing-looking tokens without a preceding Liquid opener are intentionally\n * ignored. Liquid files commonly contain CSS such as `width:100%}` or adjacent\n * block braces (`}}`), so treating every close token as Liquid creates noisy\n * false warnings on valid theme files.\n *\n * Known false positive: delimiters written literally inside a\n * `{% raw %}...{% endraw %}` block are still counted and can trip\n * this check even though the liquid is valid. Acceptable for a\n * warn-only heuristic — a real fix requires parsing liquid, which is\n * out of scope here (see the server-side validation note in the PR).\n */\nexport function hasUnbalancedLiquidDelimiters(content: string): boolean {\n let unclosedTags = 0;\n let unclosedOutputs = 0;\n\n for (const token of content.matchAll(/\\{%|%\\}|\\{\\{|\\}\\}/g)) {\n switch (token[0]) {\n case \"{%\":\n unclosedTags += 1;\n break;\n case \"%}\":\n if (unclosedTags > 0) unclosedTags -= 1;\n break;\n case \"{{\":\n unclosedOutputs += 1;\n break;\n case \"}}\":\n if (unclosedOutputs > 0) unclosedOutputs -= 1;\n break;\n }\n }\n\n return unclosedTags > 0 || unclosedOutputs > 0;\n}\n\nexport interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","import net from \"node:net\";\n\n/**\n * The dev command does real work before it ever binds a port: it resolves\n * (or creates) a server-side dev theme and runs a full initial sync, which\n * can take minutes on a large theme. If the requested port is already taken\n * — most commonly by another `fluid theme dev` or the Mist Desktop preview,\n * which both default to 9292 — all of that work is wasted and the process\n * used to die with a raw `EADDRINUSE` stack trace. Call this before any of\n * that work starts so we fail fast with a clear message instead.\n */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(formatPortConflictMessage(host, port));\n this.name = \"PortInUseError\";\n }\n}\n\nexport function formatPortConflictMessage(host: string, port: number): string {\n return (\n `Port ${port} on ${host} is already in use — likely another ` +\n \"`fluid theme dev` or the Mist Desktop preview. Stop the other \" +\n \"server or pass --port <number>.\"\n );\n}\n\n/**\n * Attempt to bind `host:port`, then immediately release it. Resolves if the\n * port is free; rejects with `PortInUseError` on `EADDRINUSE`/`EACCES`, or\n * the raw error for anything else unexpected.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n reject(new PortInUseError(host, port));\n } else {\n reject(err);\n }\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n\n server.listen(port, host);\n });\n}\n","import http from \"node:http\";\nimport { MemberPreview, isLocalPreviewRequest } from \"./member-preview.js\";\nimport { SSEStream } from \"./sse.js\";\nimport { proxyRequest } from \"./proxy.js\";\nimport { createTemplateOverrideResolver } from \"./template-overrides.js\";\nimport { watchTheme } from \"./watcher.js\";\nimport { PushConflictError, Syncer } from \"../syncer.js\";\nimport {\n findLiquidBlockTagDiagnostics,\n hasUnbalancedLiquidDelimiters,\n} from \"../liquid-delimiters.js\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ApiClient } from \"../../api.js\";\nimport { formatPortConflictMessage } from \"./port-preflight.js\";\nimport { assertNoCaseCollisions } from \"../case-collisions.js\";\nimport { isStylesheetKey } from \"../stylesheet-keys.js\";\nimport { ThemeAssetManifest } from \"../asset-manifest.js\";\nimport type { DevRemoteState } from \"../dev-remote-baseline.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n} from \"../member-configuration.js\";\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString(\"en-US\", { hour12: false });\n}\n\nexport interface DevServerOptions {\n host: string;\n port: number;\n reloadMode: \"full-page\" | \"off\";\n /** Cached dev resource index paired with its acknowledged remote version. */\n initialSync?: DevRemoteState;\n /** Persist the complete dev resource baseline and its version atomically. */\n onRemoteState?: (state: DevRemoteState) => void;\n /** Discard persisted trust after the server rejects a watched write. */\n onRemoteStateInvalidated?: () => void;\n}\n\nexport interface DevServerTheme {\n id: number;\n name: string;\n company: string;\n editorUrl?: string;\n}\n\nexport async function startDevServer(\n api: ApiClient,\n theme: DevServerTheme,\n themeRoot: ThemeRoot,\n opts: DevServerOptions & { validate?: boolean },\n onReady?: (address: string) => void,\n): Promise<() => void> {\n const sse = new SSEStream();\n const memberPreview = new MemberPreview(api, theme, opts.reloadMode);\n const syncer = new Syncer(api, theme.id, themeRoot);\n const templateOverrides = createTemplateOverrideResolver(api, theme.id);\n let remoteStateSafe = true;\n const invalidateRemoteState = () => {\n if (!remoteStateSafe) return;\n remoteStateSafe = false;\n try {\n opts.onRemoteStateInvalidated?.();\n } catch {\n // The stale version will fail preflight on the next startup.\n }\n };\n const recordRemoteState = () => {\n if (!remoteStateSafe) return;\n if (opts.onRemoteState) {\n const state = syncer.devRemoteState(\n new ThemeAssetManifest(themeRoot.root).fingerprint(),\n );\n if (state) {\n try {\n opts.onRemoteState(state);\n } catch {\n invalidateRemoteState();\n }\n }\n }\n };\n\n const pendingUpdates = new Set<string>();\n\n // ── Initial sync ─────────────────────────────────────────────────────────\n console.log(`\\nSyncing theme ${theme.name} (#${theme.id})…`);\n const progress = (done: number, total: number) => {\n process.stdout.write(`\\r Uploading ${done}/${total} files…`);\n };\n const uploadFromRemoteIndex = () =>\n syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n onProgress: progress,\n });\n let syncResult;\n if (opts.initialSync) {\n let remoteStateIsTrusted = false;\n try {\n syncer.useDevRemoteState(opts.initialSync);\n await syncer.preflightPush(opts.initialSync.remoteSha);\n remoteStateIsTrusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n\n syncResult = remoteStateIsTrusted\n ? await syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n baseSha: syncer.remoteSha(),\n remoteState: opts.initialSync,\n skipPreflight: true,\n onProgress: progress,\n })\n : await uploadFromRemoteIndex();\n } else {\n syncResult = await uploadFromRemoteIndex();\n }\n process.stdout.write(\"\\n\");\n if (syncResult.linked > 0) {\n console.log(` Saved ${syncResult.linked} remote asset reference(s).`);\n }\n if (syncResult.validationFailed) {\n console.error(\n `\\nSchema validation failed (${syncResult.errors.length} error(s)). Use --force to skip.\\n`,\n );\n for (const e of syncResult.errors) console.error(` ${e}`);\n process.exit(1);\n } else if (syncResult.errors.length > 0) {\n invalidateRemoteState();\n for (const e of syncResult.errors) console.error(` ${e}`);\n if (syncResult.uploaded + syncResult.deleted === 0) process.exit(1);\n }\n if (syncResult.errors.length === 0) {\n recordRemoteState();\n }\n\n // ── File watcher ─────────────────────────────────────────────────────────\n //\n // Uploading a file no longer commits anything — Fluid only records that the\n // theme changed, and something has to ask for the commit. `theme push` asks\n // once when its loop ends; the watcher has no such moment, since chokidar\n // reports one event per file and nothing in it says whether this is a lone\n // save or the 200th write of a restore.\n //\n // So ask once the writes stop — but decide where \"the writes\" end by when\n // events ARRIVED, not by when their handlers ran. Handlers are serialized\n // behind awaited uploads, so a genuinely separate edit can queue behind a\n // slow one and start immediately after it; timing the handlers would fold\n // the two into a single commit and erase the boundary between them.\n //\n // An event that arrived within the window continues the operation already\n // in flight, so its pending ask is cancelled and rescheduled. One that\n // arrived after a real pause ends that operation, so the ask goes out NOW\n // rather than being cancelled — otherwise the earlier edit would be\n // swallowed into this one's commit.\n //\n // Asks are also chained against the uploads. A fire-and-forget request can\n // overlap the next batch, committing a half-written operation or leaving\n // the rest of it pending; awaiting the outstanding one before uploading\n // keeps each commit to a settled state.\n //\n // If the server stops with an ask still pending, Fluid's sweeper picks the\n // change up; the cost is a delay, never a lost commit.\n const SYNC_IDLE_MS = 2_000;\n let lastArrivedAt = 0;\n let pendingSync: ReturnType<typeof setTimeout> | null = null;\n let syncInFlight: Promise<void> = Promise.resolve();\n\n // Tracks an ask Fluid did not take. Until it lands the previous edit is\n // uncommitted, and uploading the next one over it merges the two into\n // whichever commit eventually arrives — so the boundary is retried before\n // any further writes go out. A retry that also fails has done what a client\n // can: the content is safe on the server and the sweeper commits it, but\n // the two edits will share a version.\n let askOwed = false;\n let remoteWritesBlocked = false;\n const blockRemoteWrites = (error: PushConflictError): void => {\n if (remoteWritesBlocked) return;\n remoteWritesBlocked = true;\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Remote theme changed outside this dev session (${error.message}). Restart theme dev to compare the current remote state before writing again.`,\n );\n };\n const sendSync = (): void => {\n syncInFlight = syncInFlight.then(async () => {\n const accepted = await syncer.requestSync();\n askOwed = !accepted;\n if (accepted) recordRemoteState();\n });\n };\n const flushSyncNow = (): void => {\n if (!pendingSync) return;\n clearTimeout(pendingSync);\n pendingSync = null;\n sendSync();\n };\n const scheduleSync = (): void => {\n if (pendingSync) clearTimeout(pendingSync);\n pendingSync = setTimeout(() => {\n pendingSync = null;\n sendSync();\n }, SYNC_IDLE_MS);\n };\n\n const stopWatcher = watchTheme(\n themeRoot,\n async (modified, added, removed, arrivedAt) => {\n if (arrivedAt - lastArrivedAt > SYNC_IDLE_MS) {\n // A real pause: whatever was pending belongs to the previous edit.\n flushSyncNow();\n } else if (pendingSync) {\n // Same operation still arriving — hold the ask.\n clearTimeout(pendingSync);\n pendingSync = null;\n }\n lastArrivedAt = arrivedAt;\n // Never upload across an outstanding ask, or it commits a half-written\n // operation.\n await syncInFlight;\n // An ask Fluid refused still owes the edit before this one its own\n // commit. Retry before writing over it.\n if (askOwed) {\n sendSync();\n await syncInFlight;\n }\n if (remoteWritesBlocked) return;\n\n let uploads: ThemeUpload[];\n try {\n assertNoCaseCollisions(\n themeRoot.files().map((file) => file.relativePath),\n );\n assertMemberConfigurationRetained(\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n uploads = planThemeUploads(\n themeRoot,\n [...modified, ...added],\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n } catch (error) {\n console.error(`\\n[Watcher] Sync blocked: ${String(error)}`);\n return;\n }\n\n let wroteRemote = false;\n const memberTransfer = uploads.some(\n (upload) =>\n upload.memberConfiguration ||\n upload.file.relativePath === \"members_sites.json\",\n );\n\n for (const upload of uploads) {\n const { file } = upload;\n const uploadFiles = [\n file,\n ...(upload.memberConfiguration?.files ?? []),\n ];\n // Validate schema on liquid files during dev (warn, don't block)\n if (opts.validate) {\n for (const candidate of uploadFiles) {\n if (!candidate.isLiquid) continue;\n for (const diagnostic of candidate.validateSchema()) {\n const prefix =\n diagnostic.severity === \"error\"\n ? \"Schema error\"\n : \"Schema warning\";\n console.warn(\n `\\n[${prefix}] ${candidate.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n\n for (const pendingFile of uploadFiles)\n pendingUpdates.add(pendingFile.relativePath);\n try {\n // uploadFile() resolves with the exact bytes it sent, so the\n // diagnostic below always describes the uploaded content —\n // never a re-read of a file an editor may have changed,\n // replaced, or deleted while the request was in flight.\n const uploadedFiles = upload.memberConfiguration\n ? await syncer.uploadMemberConfiguration(upload, syncer.remoteSha())\n : [\n {\n file,\n content: await syncer.uploadFile(file, syncer.remoteSha(), {\n pendingAsset: true,\n }),\n },\n ];\n wroteRemote = true;\n for (const {\n file: uploadedFile,\n content: uploadedContent,\n } of uploadedFiles) {\n console.log(\n ` ✓ synced ${uploadedFile.relativePath} (${timestamp()})`,\n );\n // \"synced\" is true of the upload and false of the page. The\n // storefront inlines a stylesheet from a Themes::FileResource that a\n // background job rewrites after the publish commits, so\n // `inline_asset_content` keeps emitting the previous bytes for a\n // moment after the server accepts this write. Say so, or the ✓ reads\n // as \"the rendered CSS is up to date\" (CURRENT-3899).\n if (isStylesheetKey(uploadedFile.relativePath)) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: the storefront inlines this CSS from a published asset, so it serves the previous bytes until the stylesheet asset sync finishes`,\n );\n }\n // Cheap warn-only heuristic: the server accepts syntax-broken\n // liquid silently, and the storefront then serves stale\n // content for the section with no error anywhere. This does\n // not parse liquid — it can false-positive on delimiters\n // written literally inside {% raw %} — but it's the only\n // client-side signal a save is likely broken.\n if (\n uploadedFile.isLiquid &&\n uploadedContent !== null &&\n hasUnbalancedLiquidDelimiters(uploadedContent)\n ) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: unbalanced liquid delimiters — the storefront may silently serve stale content for this section`,\n );\n }\n if (uploadedFile.isLiquid && uploadedContent !== null) {\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n uploadedContent,\n )) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n } catch (e) {\n if (e instanceof PushConflictError) {\n blockRemoteWrites(e);\n break;\n }\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Upload failed: ${file.relativePath}: ${e}`,\n );\n // Keep safe first-write failures retryable, but never finalize a partial batch.\n remoteWritesBlocked = wroteRemote || memberTransfer;\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n } finally {\n for (const pendingFile of uploadFiles)\n pendingUpdates.delete(pendingFile.relativePath);\n }\n }\n\n if (remoteWritesBlocked) return;\n\n for (const file of removed) {\n if (themeRoot.ignore.ignore(file.relativePath)) continue;\n try {\n await syncer.deleteRemoteFile(file.relativePath, syncer.remoteSha());\n wroteRemote = true;\n console.log(` ✓ removed ${file.relativePath}`);\n } catch (error) {\n if (error instanceof PushConflictError) {\n blockRemoteWrites(error);\n break;\n }\n invalidateRemoteState();\n remoteWritesBlocked = wroteRemote || memberTransfer;\n console.error(\n `[Watcher] Delete failed: ${file.relativePath}: ${error}`,\n );\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n }\n }\n\n if (remoteWritesBlocked) return;\n\n if (wroteRemote) {\n recordRemoteState();\n try {\n await memberPreview.refreshAfterSync();\n } catch {\n console.error(\n \"[Member preview] Could not refresh routes after sync. The next member request will retry.\",\n );\n }\n }\n // Added or removed files can create or delete dev-theme templates.\n if (added.length > 0 || removed.length > 0) templateOverrides.clear();\n\n if (removed.length > 0) {\n sse.broadcast(JSON.stringify({ reload_page: true }));\n } else if (uploads.length > 0) {\n sse.broadcast(\n JSON.stringify({\n modified: uploads.flatMap(({ file, memberConfiguration }) =>\n [file, ...(memberConfiguration?.files ?? [])].map(\n (updatedFile) => updatedFile.relativePath,\n ),\n ),\n }),\n );\n }\n\n scheduleSync();\n },\n );\n\n // ── HTTP server ───────────────────────────────────────────────────────────\n const server = http.createServer(async (req, res) => {\n if (!isLocalPreviewRequest(req, opts.host)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Local preview requests only.\");\n return;\n }\n if (await memberPreview.handle(req, res)) return;\n if (req.url === \"/hot-reload\") {\n sse.add(res);\n return;\n }\n\n try {\n await proxyRequest(req, res, {\n company: theme.company,\n themeId: theme.id,\n reloadMode: opts.reloadMode,\n templateOverrides,\n pendingFiles: () =>\n [...pendingUpdates]\n .map((p) => themeRoot.file(p))\n .filter((f) => f.isText)\n .map((f) => ({\n relativePath: f.relativePath,\n read: () => f.read(),\n })),\n });\n } catch (e) {\n console.error(`[Proxy] ${req.method} ${req.url} → ${e}`);\n if (!res.headersSent) {\n // Surface the real upstream failure instead of a bare 502.\n // Every local render round-trips through <company>.fluid.app,\n // so this error is usually environmental — a TLS-inspecting\n // security agent whose root CA the browser trusts via the OS\n // keychain but Node does not, DNS, or a dropped connection —\n // and a naked \"Bad Gateway\" sends humans and QA agents off\n // chasing the theme instead of the network. Diagnosed live:\n // four workflow page steps went needs-review over one masked\n // cert error.\n const message = e instanceof Error ? e.message : String(e);\n res.writeHead(502, { \"content-type\": \"text/plain; charset=utf-8\" });\n res.end(\n `Bad Gateway — the local preview could not reach ${theme.company}.fluid.app: ${message}\\n` +\n \"This is the dev machine's network path to Fluid, not the theme. \" +\n \"Common causes: TLS-inspecting security software (its root CA is in the OS keychain, which Node does not read — set NODE_EXTRA_CA_CERTS to its certificate), DNS, or a proxy. \" +\n \"The same error is logged by the theme dev server process.\",\n );\n }\n }\n });\n\n // The preflight check in `dev.ts` runs before the (potentially minutes-long)\n // initial sync above, but the port could still be grabbed by another\n // process in the window between that check and this `listen()` call.\n // Handle that race the same way: a friendly message and a clean exit\n // instead of an unhandled `EADDRINUSE`/`EACCES` stack trace.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n console.error(formatPortConflictMessage(opts.host, opts.port));\n process.exit(1);\n }\n reject(err);\n });\n server.listen(opts.port, opts.host, () => resolve());\n });\n\n const address = `http://${opts.host}:${opts.port}`;\n onReady?.(address);\n\n // ── Teardown ──────────────────────────────────────────────────────────────\n return function stop() {\n sse.close();\n stopWatcher();\n server.close();\n };\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\n\nconst BASELINE_VERSION = 1;\nconst BASELINE_FILE = join(\".fluid-theme\", \"dev-baseline.json\");\n\nexport interface RemoteResourceState {\n key: string;\n checksum: string | null;\n contentPresent: boolean;\n resourceType?: string | null;\n resourceId?: number | null;\n url?: string | null;\n}\n\nexport interface DevRemoteState {\n themeId: number;\n remoteSha: string;\n assetManifestSha: string;\n resources: RemoteResourceState[];\n}\n\nexport function readDevRemoteBaseline(\n themeRoot: string,\n themeId: number,\n assetManifestSha: string,\n): DevRemoteState | null {\n try {\n const parsed = JSON.parse(\n readFileSync(join(themeRoot, BASELINE_FILE), \"utf-8\"),\n );\n const state = parseDocument(parsed);\n if (state.themeId !== themeId) return null;\n if (state.assetManifestSha !== assetManifestSha) return null;\n return state;\n } catch {\n return null;\n }\n}\n\nexport function writeDevRemoteBaseline(\n themeRoot: string,\n state: DevRemoteState,\n): void {\n const document = { version: BASELINE_VERSION, ...state };\n parseDocument(document);\n\n const path = join(themeRoot, BASELINE_FILE);\n const tempPath = `${path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(tempPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, path);\n } catch (error) {\n rmSync(tempPath, { force: true });\n throw error;\n }\n}\n\nexport function removeDevRemoteBaseline(themeRoot: string): void {\n rmSync(join(themeRoot, BASELINE_FILE), { force: true });\n}\n\nexport async function devRemoteStateFromSourceShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n themeId: number,\n remoteSha: string,\n): Promise<DevRemoteState | null> {\n try {\n if (!(await shadow.hasHead())) return null;\n\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const resources: RemoteResourceState[] = [];\n for (const key of await shadow.headPaths()) {\n const content = await shadow.blobAtHead(key);\n if (!content) return null;\n const asset = assetManifest.get(key);\n if (asset) {\n if (\n asset.pending ||\n !asset.url ||\n !content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL))\n ) {\n return null;\n }\n resources.push({\n key,\n checksum: asset.checksum ?? null,\n contentPresent: false,\n resourceType: \"FileResource\",\n url: asset.url,\n });\n continue;\n }\n\n if (content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL)))\n return null;\n resources.push({\n key,\n checksum: createHash(\"sha256\").update(content).digest(\"hex\"),\n contentPresent: true,\n });\n }\n\n return {\n themeId,\n remoteSha,\n assetManifestSha: assetManifest.fingerprint(),\n resources,\n };\n } catch {\n return null;\n }\n}\n\nfunction parseDocument(document: unknown): DevRemoteState {\n if (!isRecord(document) || document[\"version\"] !== BASELINE_VERSION) {\n throw new Error(`expected version ${BASELINE_VERSION}`);\n }\n\n const themeId = document[\"themeId\"];\n const remoteSha = document[\"remoteSha\"];\n const assetManifestSha = document[\"assetManifestSha\"];\n const rawResources = document[\"resources\"];\n if (!isPositiveInteger(themeId)) throw new Error(\"invalid theme id\");\n if (!isNonEmptyString(remoteSha)) throw new Error(\"invalid remote sha\");\n if (!isNonEmptyString(assetManifestSha)) {\n throw new Error(\"invalid asset manifest sha\");\n }\n if (!Array.isArray(rawResources)) throw new Error(\"invalid resources\");\n\n const resources = rawResources.map(parseResource);\n assertNoCaseCollisions(resources.map((resource) => resource.key));\n return { themeId, remoteSha, assetManifestSha, resources };\n}\n\nfunction parseResource(resource: unknown): RemoteResourceState {\n if (!isRecord(resource)) throw new Error(\"invalid resource\");\n\n const key = resource[\"key\"];\n const checksum = resource[\"checksum\"];\n const contentPresent = resource[\"contentPresent\"];\n const resourceType = resource[\"resourceType\"];\n const resourceId = resource[\"resourceId\"];\n const url = resource[\"url\"];\n if (!isNonEmptyString(key) || key.includes(\"\\0\")) {\n throw new Error(\"invalid resource key\");\n }\n if (checksum !== null && !isNonEmptyString(checksum)) {\n throw new Error(\"invalid resource checksum\");\n }\n if (typeof contentPresent !== \"boolean\") {\n throw new Error(\"invalid resource content marker\");\n }\n if (\n resourceType !== undefined &&\n resourceType !== null &&\n !isNonEmptyString(resourceType)\n ) {\n throw new Error(\"invalid resource type\");\n }\n if (\n resourceId !== undefined &&\n resourceId !== null &&\n !isPositiveInteger(resourceId)\n ) {\n throw new Error(\"invalid resource id\");\n }\n if (url !== undefined && url !== null && !isNonEmptyString(url)) {\n throw new Error(\"invalid resource url\");\n }\n\n return {\n key,\n checksum,\n contentPresent,\n resourceType,\n resourceId,\n url,\n };\n}\n\nfunction isRecord(candidate: unknown): candidate is Record<string, unknown> {\n return typeof candidate === \"object\" && candidate !== null;\n}\n\nfunction isNonEmptyString(candidate: unknown): candidate is string {\n return typeof candidate === \"string\" && candidate.length > 0;\n}\n\nfunction isPositiveInteger(candidate: unknown): candidate is number {\n return (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate > 0\n );\n}\n","import { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * A bare git repo hidden under `.fluid-theme/repo` that stands in for\n * git's index+HEAD when talking to the Fluid server. Every pull commits\n * the incoming remote state onto a single branch (`refs/heads/main`);\n * every successful push commits the outgoing local state onto the same\n * branch. HEAD therefore represents \"the last state the CLI and server\n * agreed on\", and its tree is the natural three-way-merge base for the\n * next pull.\n *\n * We stay in git's plumbing layer — no working tree, no index file\n * next to the theme content — so the shadow repo cannot interfere\n * with the user's own git repo (if they have one) around the theme\n * dir. All state lives inside `.fluid-theme/`.\n *\n * The class only wraps the small handful of plumbing commands we\n * actually need: hash-object, cat-file, write-tree (via a temp index),\n * commit-tree, update-ref, and merge-file. Everything else stays out.\n */\nexport class ShadowRepo {\n // Cached \"does HEAD exist\" result. `pull`/`push` call `blobAtHead`\n // per file (potentially hundreds of times per invocation), and\n // spawning a `git rev-parse` subprocess each time is measurable\n // overhead. HEAD presence only flips one way per instance —\n // `commitState` sets it true after a successful `update-ref` — so\n // the cache stays correct without invalidation.\n private headExists: boolean | undefined = undefined;\n\n private constructor(\n private readonly themeRoot: string,\n private readonly gitDir: string,\n ) {}\n\n /**\n * Return a ShadowRepo bound to `themeId` for the given theme root.\n * The shadow is initialized on first use and re-initialized when\n * the caller passes a themeId different from the one previously\n * recorded — the merge base is only meaningful for the theme it\n * was captured against, so a cross-theme operation (pull A → push\n * B, or two pulls of different themes into one dir) starts from a\n * clean slate rather than pretending B's state matches A's HEAD.\n */\n static async open(themeRoot: string, themeId: number): Promise<ShadowRepo> {\n const shadowDir = join(themeRoot, \".fluid-theme\");\n const gitDir = join(shadowDir, \"repo\");\n const themeIdFile = join(shadowDir, \"theme-id\");\n\n // The stored themeId is written on every commit, so a shadow with\n // a HEAD but no theme-id file is one written by an older CLI —\n // treat it as \"unknown\" and wipe rather than trust it against\n // whatever theme the caller is now operating on.\n if (existsSync(gitDir)) {\n const stored = readStoredThemeId(themeIdFile);\n if (stored !== themeId) {\n rmSync(gitDir, { recursive: true, force: true });\n }\n }\n\n const repo = new ShadowRepo(themeRoot, gitDir);\n\n if (!existsSync(gitDir)) {\n mkdirSync(shadowDir, { recursive: true });\n await repo.git([\"init\", \"--bare\", \"-b\", \"main\", gitDir], {\n cwd: themeRoot,\n });\n }\n\n // Rewrite even when the dir already existed — a fresh init above\n // won't have this file yet, and a preserved dir may have missed a\n // previous write if the process crashed mid-way. Idempotent.\n writeFileSync(themeIdFile, `${themeId}\\n`, \"utf-8\");\n\n // Idempotent: keep `.fluid-theme/` invisible to the user's own git\n // repo. The nested `.gitignore` handles `git add .` inside the\n // shadow dir; the root-level entry handles `git status` on the\n // theme root, which otherwise still lists the untracked shadow\n // directory itself (only its *contents* are ignored by the nested\n // file).\n const shadowIgnore = join(shadowDir, \".gitignore\");\n if (!existsSync(shadowIgnore)) {\n writeFileSync(\n shadowIgnore,\n \"# Fluid CLI shadow repo — internal state, not for version control.\\n*\\n\",\n );\n }\n await ensureRootGitignoreHidesShadow(themeRoot);\n\n return repo;\n }\n\n /** True when the repo has at least one commit on `refs/heads/main`. */\n async hasHead(): Promise<boolean> {\n if (this.headExists !== undefined) return this.headExists;\n try {\n await this.git([\"rev-parse\", \"--verify\", \"HEAD\"]);\n this.headExists = true;\n } catch {\n this.headExists = false;\n }\n return this.headExists;\n }\n\n /** Desktop snapshots deliberately use a different author. They are local\n * recovery points, not proof that those bytes were synced to the server. */\n async hasSyncedHead(): Promise<boolean> {\n if (!(await this.hasHead())) return false;\n const { stdout } = await this.git([\"log\", \"-1\", \"--format=%ae\"]);\n return stdout.toString(\"utf8\").trim() === \"cli@fluid.app\";\n }\n\n /**\n * Every path recorded under HEAD's tree, recursively. Callers use\n * this to detect local deletions (paths in HEAD, absent from the\n * working tree). Returns [] when HEAD has never been committed.\n */\n async headPaths(): Promise<string[]> {\n if (!(await this.hasHead())) return [];\n // -z is required, not cosmetic. Without it git honours core.quotePath\n // (default true) and renders any path containing a non-ASCII byte in\n // its C-quoted display form — `\"assets/caf\\303\\251.css\"`, quotes\n // included. That string matches nothing in the working tree, so\n // diffAgainstShadow reports the file as a local deletion and push\n // sends a DELETE for a key the server has never had. The failed\n // delete then blocks commitPushedState, so shadow HEAD never\n // advances and the phantom delete repeats on every later push.\n // -z also covers the rarer newline-in-path case; splitting on \"\\n\"\n // could not.\n const { stdout } = await this.git([\n \"ls-tree\",\n \"-r\",\n \"-z\",\n \"HEAD\",\n \"--name-only\",\n ]);\n return stdout\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter((line) => line.length > 0);\n }\n\n /**\n * The content of `path` in HEAD's tree, or null when the path does\n * not exist there. Callers use this as the merge base for pull\n * conflict resolution.\n */\n async blobAtHead(path: string): Promise<Buffer | null> {\n if (!(await this.hasHead())) return null;\n try {\n const { stdout } = await this.git([\"cat-file\", \"-p\", `HEAD:${path}`]);\n return stdout;\n } catch {\n return null;\n }\n }\n\n /**\n * Write `content` as a blob in the shadow repo and return its sha.\n * Used by `commitState` to stage each file's content before the\n * `write-tree` call.\n */\n async writeBlob(content: string | Buffer): Promise<string> {\n const buf = typeof content === \"string\" ? Buffer.from(content) : content;\n const { stdout } = await this.git([\"hash-object\", \"-w\", \"--stdin\"], {\n input: buf,\n });\n return stdout.toString(\"utf8\").trim();\n }\n\n /**\n * Commit `files` as HEAD's new tree, threaded onto the current HEAD\n * as the parent. Uses a per-call temp index so a partial run can't\n * corrupt anything reachable from HEAD; the previous commit stays\n * intact until `update-ref` at the end.\n *\n * Returns the new commit sha.\n */\n async commitState(\n files: Array<{ path: string; sha: string }>,\n message: string,\n ): Promise<string> {\n const indexPath = mkdtempSync(join(tmpdir(), \"fluid-shadow-\")) + \"/index\";\n\n try {\n if (files.length > 0) {\n // Keep file lists off argv: large themes exceed Windows' process\n // command-line limit. NUL delimiters preserve paths verbatim.\n await this.git([\"update-index\", \"-z\", \"--index-info\"], {\n input: Buffer.from(\n files.map(({ path, sha }) => `100644 ${sha}\\t${path}\\0`).join(\"\"),\n ),\n env: { GIT_INDEX_FILE: indexPath },\n });\n }\n\n const treeSha = (\n await this.git([\"write-tree\"], { env: { GIT_INDEX_FILE: indexPath } })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n const parent = (await this.hasHead())\n ? (await this.git([\"rev-parse\", \"HEAD\"])).stdout.toString(\"utf8\").trim()\n : null;\n\n const commitArgs = [\"commit-tree\", treeSha, \"-m\", message];\n if (parent) commitArgs.push(\"-p\", parent);\n\n const commitSha = (\n await this.git(commitArgs, {\n env: {\n GIT_AUTHOR_NAME: \"Fluid CLI\",\n GIT_AUTHOR_EMAIL: \"cli@fluid.app\",\n GIT_COMMITTER_NAME: \"Fluid CLI\",\n GIT_COMMITTER_EMAIL: \"cli@fluid.app\",\n },\n })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n await this.git([\"update-ref\", \"refs/heads/main\", commitSha]);\n this.headExists = true;\n return commitSha;\n } finally {\n // Best-effort — a stale index in tmp is harmless.\n try {\n rmSync(indexPath, { force: true });\n rmSync(indexPath.substring(0, indexPath.length - \"/index\".length), {\n recursive: true,\n force: true,\n });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Three-way merge of `local` against `remote` with `base` as the\n * common ancestor. Returns the merged bytes and a flag when\n * `git merge-file` reported unresolved conflicts (i.e. the output\n * contains `<<<<<<<` markers for the reader to resolve).\n *\n * `base` is null when HEAD has never seen this path; we merge\n * against an empty base, which is what git itself does for a new\n * file added on both sides.\n *\n * `favor` maps to `git merge-file`'s `--ours` / `--theirs`: instead\n * of emitting `<<<<<<<` markers, conflicting hunks are resolved to\n * the local (`\"local\"` → `--ours`, local is file1) or remote\n * (`\"remote\"` → `--theirs`) side. The output then never contains\n * markers, so the result is reported conflict-free even when\n * merge-file's exit code still counts the auto-resolved hunks.\n */\n async merge3(\n base: Buffer | null,\n local: Buffer,\n remote: Buffer,\n favor?: \"local\" | \"remote\",\n ): Promise<{ merged: Buffer; hasConflicts: boolean }> {\n const dir = mkdtempSync(join(tmpdir(), \"fluid-merge-\"));\n const localPath = join(dir, \"local\");\n const basePath = join(dir, \"base\");\n const remotePath = join(dir, \"remote\");\n\n try {\n writeFileSync(localPath, local);\n writeFileSync(basePath, base ?? Buffer.alloc(0));\n writeFileSync(remotePath, remote);\n\n // `git merge-file -p` writes the merged result to stdout instead\n // of mutating `localPath` in place, and exits non-zero (= number\n // of unresolved conflicts) when it couldn't reconcile everything.\n // We treat any non-zero exit as \"conflicts present\" as long as\n // stdout is populated — the caller writes it to disk with markers.\n try {\n const { stdout } = await this.git([\n \"merge-file\",\n \"-p\",\n ...(favor === \"local\"\n ? [\"--ours\"]\n : favor === \"remote\"\n ? [\"--theirs\"]\n : []),\n \"-L\",\n \"local\",\n \"-L\",\n \"base\",\n \"-L\",\n \"remote\",\n localPath,\n basePath,\n remotePath,\n ]);\n return { merged: stdout, hasConflicts: false };\n } catch (err) {\n const e = err as {\n code?: number;\n stdout?: Buffer | string;\n stderr?: Buffer | string;\n };\n // `git merge-file` exit codes:\n // 0 clean merge (handled in the try branch above)\n // 1..127 count of unresolved conflict hunks; stdout is the\n // merged content with markers — treat as conflict.\n // >=128 git itself crashed (SIGSEGV, out-of-memory) —\n // stdout is empty, don't clobber the user's file.\n // negative usage error (bad args) — same, don't clobber.\n // Requiring both the conflict-range code AND a non-empty\n // stdout matches the comment above and stops a crash from\n // silently writing 0 bytes to disk.\n const merged =\n e.stdout instanceof Buffer\n ? e.stdout\n : e.stdout != null\n ? Buffer.from(e.stdout)\n : Buffer.alloc(0);\n const inConflictRange =\n typeof e.code === \"number\" && e.code >= 1 && e.code <= 127;\n // With a favor side, merge-file resolved every hunk itself —\n // the exit code still counts them, but the output carries no\n // markers, so it's a finished merge from the caller's view.\n // The stdout-length guard is dropped here: an auto-resolved\n // result can be legitimately empty (favoring a side that\n // deleted all content).\n if (inConflictRange && favor) {\n return { merged, hasConflicts: false };\n }\n if (inConflictRange && merged.length > 0) {\n return { merged, hasConflicts: true };\n }\n throw err;\n }\n } finally {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Snapshot the working-tree copy of `paths` into HEAD as a single\n * commit. Intended for the migration path: on the first pull with\n * the new CLI (no shadow repo yet, but a `.fluid-theme.json` with\n * checksums exists) we seed HEAD with whatever is on disk before\n * running the merge, so unmodified files fast-forward cleanly and\n * modified files show a diff.\n */\n async seedFromWorkingTree(\n files: Array<{ path: string; content: string | Buffer }>,\n message: string,\n ): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n for (const { path, content } of files) {\n entries.push({ path, sha: await this.writeBlob(content) });\n }\n await this.commitState(entries, message);\n }\n\n private async git(\n args: string[],\n opts: {\n cwd?: string;\n input?: Buffer;\n env?: Record<string, string>;\n } = {},\n ): Promise<{ stdout: Buffer; stderr: Buffer }> {\n const fullArgs =\n args[0] === \"init\" ? args : [\"--git-dir\", this.gitDir, ...args];\n const child = spawn(\"git\", fullArgs, {\n cwd: opts.cwd ?? this.themeRoot,\n env: { ...process.env, ...opts.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n return new Promise((resolve, reject) => {\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", reject);\n child.stdin.on(\"error\", reject);\n child.on(\"close\", (code) => {\n const out = Buffer.concat(stdout);\n const err = Buffer.concat(stderr);\n if (code === 0) {\n resolve({ stdout: out, stderr: err });\n } else {\n const e = new Error(\n `git ${args.join(\" \")} exited with ${code}: ${err.toString(\"utf8\")}`,\n ) as Error & { code: number; stdout: Buffer; stderr: Buffer };\n e.code = code ?? -1;\n e.stdout = out;\n e.stderr = err;\n reject(e);\n }\n });\n child.stdin.end(opts.input);\n });\n }\n}\n\n/**\n * Content-type check the pull command uses to decide whether a file\n * is safe to run through `merge3` (line-based) or must fall back to\n * whole-file \"either/or\" resolution (binary).\n */\nexport function looksBinary(content: Buffer): boolean {\n // Same heuristic git uses for `core.autocrlf` detection: any NUL in\n // the first 8000 bytes counts as binary.\n const scan = content.subarray(0, Math.min(content.length, 8000));\n return scan.includes(0);\n}\n\n/** Best-effort readFile that returns null when the file does not exist. */\nexport function readIfExists(path: string): Buffer | null {\n try {\n return readFileSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the numeric theme id from `.fluid-theme/theme-id`. Returns\n * null when the file is missing or the contents don't parse cleanly;\n * `open` treats that as \"unknown theme\" and rebuilds the shadow.\n */\nfunction readStoredThemeId(themeIdFile: string): number | null {\n try {\n const stored = parseInt(readFileSync(themeIdFile, \"utf-8\").trim(), 10);\n return Number.isFinite(stored) ? stored : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Append `.fluid-theme/` to the theme root's `.gitignore` when the\n * theme dir sits inside a git working tree and the entry isn't\n * already there. Skipped when the user isn't in a git repo — no\n * point manufacturing a `.gitignore` for someone who doesn't use\n * git. Idempotent — a second call is a no-op.\n */\nasync function ensureRootGitignoreHidesShadow(\n themeRoot: string,\n): Promise<void> {\n const insideGit = await new Promise<boolean>((resolve) => {\n const child = spawn(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd: themeRoot,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.on(\"close\", (code) => resolve(code === 0));\n child.on(\"error\", () => resolve(false));\n });\n if (!insideGit) return;\n\n const gitignorePath = join(themeRoot, \".gitignore\");\n let existing = \"\";\n try {\n existing = readFileSync(gitignorePath, \"utf-8\");\n } catch {\n existing = \"\";\n }\n\n const lines = existing.split(\"\\n\").map((line) => line.trim());\n // Match both `.fluid-theme/` and `.fluid-theme` — either shape hides\n // the dir from `git status`.\n if (lines.includes(\".fluid-theme/\") || lines.includes(\".fluid-theme\")) {\n return;\n }\n\n const separator =\n existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n writeFileSync(\n gitignorePath,\n `${existing}${separator}.fluid-theme/\\n`,\n \"utf-8\",\n );\n}\n","import chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport type { createApiClient } from \"./api.js\";\nimport { themes, type components } from \"@fluid-app/themes-api-client\";\n\nexport type ApplicationTheme = components[\"schemas\"][\"ApplicationTheme\"];\n\nconst PAGE_SIZE = 50;\nconst LOAD_MORE_VALUE = -1;\n\nfunction themeLabel(t: ApplicationTheme): string {\n const active = t.status === \"active\" ? ` ${chalk.green(\"[active]\")}` : \"\";\n return `${t.name} (#${t.id})${active}`;\n}\n\nfunction themeChoices(\n themeList: ApplicationTheme[],\n hasMore: boolean,\n): prompts.Choice[] {\n const choices: prompts.Choice[] = themeList.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n if (hasMore) {\n choices.push({\n title: chalk.dim(`── Load more themes ──`),\n value: LOAD_MORE_VALUE,\n });\n }\n return choices;\n}\n\nasync function fetchThemesPage(\n api: ReturnType<typeof createApiClient>,\n page: number,\n searchQuery?: string,\n): Promise<{\n themes: ApplicationTheme[];\n hasMore: boolean;\n}> {\n const body = await themes.listApplicationThemes(api, {\n per_page: PAGE_SIZE,\n page,\n ...(searchQuery ? { search_query: searchQuery } : {}),\n });\n const list = body.application_themes ?? [];\n const totalPages = body.meta?.total_pages ?? 1;\n return { themes: list, hasMore: page < totalPages };\n}\n\nexport async function selectTheme(\n api: ReturnType<typeof createApiClient>,\n message: string,\n): Promise<ApplicationTheme> {\n const allThemes: ApplicationTheme[] = [];\n let page = 1;\n let hasMore = true;\n let initialIndex = 0;\n\n // Search cache — persists across suggest calls\n let searchQuery = \"\";\n let searchResults: ApplicationTheme[] = [];\n\n while (true) {\n if (hasMore && allThemes.length < page * PAGE_SIZE) {\n const result = await fetchThemesPage(api, page);\n allThemes.push(...result.themes);\n hasMore = result.hasMore;\n }\n\n if (!allThemes.length) {\n console.error(\"No themes found.\");\n process.exit(1);\n }\n\n const choices = themeChoices(allThemes, hasMore);\n\n const { id } = await prompts(\n {\n type: \"autocomplete\",\n name: \"id\",\n message,\n initial: initialIndex,\n choices,\n suggest: async (input: string, choices: prompts.Choice[]) => {\n if (!input) {\n searchQuery = \"\";\n searchResults = [];\n return choices;\n }\n\n if (input !== searchQuery) {\n searchQuery = input;\n try {\n const result = await fetchThemesPage(api, 1, input);\n searchResults = result.themes;\n } catch {\n searchResults = [];\n }\n }\n\n return searchResults.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n },\n },\n { onCancel: () => process.exit(130) },\n );\n\n if (id === LOAD_MORE_VALUE) {\n initialIndex = allThemes.length;\n page++;\n continue;\n }\n\n if (!id) {\n console.error(\"No theme selected.\");\n process.exit(1);\n }\n\n // Check loaded themes first, then search results\n const found =\n allThemes.find((t) => t.id === id) ??\n searchResults.find((t) => t.id === id);\n if (found) return found;\n\n // Fetch directly by ID as fallback\n const body = await themes.getApplicationTheme(api, id);\n return body.application_theme;\n }\n}\n\nexport async function findTheme(\n api: ReturnType<typeof createApiClient>,\n identifier: string,\n): Promise<ApplicationTheme> {\n // Try ID lookup first\n const idNum = Number(identifier);\n if (Number.isInteger(idNum) && idNum > 0) {\n try {\n const body = await themes.getApplicationTheme(api, idNum);\n if (body.application_theme) return body.application_theme;\n } catch {\n // Not found by ID, fall through to search\n }\n }\n\n // Search by name via API with pagination\n let page = 1;\n let hasMore = true;\n while (hasMore) {\n const result = await fetchThemesPage(api, page, identifier);\n const found = result.themes.find(\n (t) => t.name.toLowerCase() === identifier.toLowerCase(),\n );\n if (found) return found;\n hasMore = result.hasMore;\n page++;\n }\n\n console.error(`No theme found with identifier: ${identifier}`);\n process.exit(1);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nexport interface FluidWorkspace {\n /** Absolute path to the workspace root (where .fluid-workspace.json lives) */\n root: string;\n /** Parsed workspace config */\n config: WorkspaceConfig;\n}\n\ninterface WorkspaceConfig {\n type: string;\n version: number;\n}\n\nconst WORKSPACE_FILE = \".fluid-workspace.json\";\n\n/**\n * Walk up from `startDir` looking for `.fluid-workspace.json`.\n * Returns the workspace info if found, or `null` if not in a workspace.\n */\nexport function findWorkspace(startDir?: string): FluidWorkspace | null {\n let dir = resolve(startDir ?? process.cwd());\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const candidate = join(dir, WORKSPACE_FILE);\n if (existsSync(candidate)) {\n try {\n const raw = readFileSync(candidate, \"utf-8\");\n const config = JSON.parse(raw) as WorkspaceConfig;\n return { root: dir, config };\n } catch {\n return null;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * If cwd is already inside `{workspace}/local/{company}/...`, return that\n * theme root directory. Otherwise return null.\n *\n * Examples (workspace root = /code/fluid-theme-dev):\n * cwd = /code/fluid-theme-dev/local/acme-co → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev/local/acme-co/templates → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev → null\n * cwd = /code/fluid-theme-dev/local → null\n */\nexport function resolveThemeRootFromCwd(\n workspace: FluidWorkspace,\n): string | null {\n const cwd = resolve(process.cwd());\n const localDir = join(workspace.root, \"local\");\n const rel = relative(localDir, cwd);\n\n // Not under local/ at all, or exactly at local/\n if (rel.startsWith(\"..\") || rel === \".\") return null;\n\n // rel is like \"acme-co\" or \"acme-co/templates/subfolder\"\n // The theme root is the first segment: local/{company}\n const firstSegment = rel.split(sep)[0];\n if (!firstSegment) return null;\n\n return join(localDir, firstSegment);\n}\n","import { Command } from \"commander\";\nimport { navigateTheme } from \"./navigate.js\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport {\n devThemeKey,\n getDevTheme,\n setDevTheme,\n setLastDevThemeId,\n clearDevTheme,\n type DevThemeRef,\n} from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { startDevServer } from \"../theme/dev-server/index.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport {\n devRemoteStateFromSourceShadow,\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"../theme/dev-remote-baseline.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n checkPortAvailable,\n PortInUseError,\n} from \"../theme/dev-server/port-preflight.js\";\nimport { isApiError, themes } from \"@fluid-app/themes-api-client\";\nimport { findTheme, type ApplicationTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\n/** Whether this invocation may read and persist the dev remote baseline. */\nexport function devRemoteBaselineEnabled(explicitTheme: boolean): boolean {\n return (\n !explicitTheme && process.env[\"FLUID_THEME_DEV_DISABLE_SHADOW_SYNC\"] !== \"1\"\n );\n}\n\n/**\n * Create the isolated theme used by `theme dev`.\n *\n * A checkout from `theme pull` has a source theme id. New servers clone that\n * source by reference, preserving its DAM/ImageKit assets without moving\n * bytes. A 404/405 keeps older deployments compatible with the established\n * empty-theme flow; other failures must remain visible to the developer.\n */\nexport async function createDevelopmentTheme(\n api: ReturnType<typeof createApiClient>,\n sourceThemeId: number | undefined,\n name: string,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (sourceThemeId !== undefined) {\n try {\n const body = await themes.cloneApplicationThemeForDevelopment(\n api,\n sourceThemeId,\n { application_theme: { name } },\n );\n return { theme: body.application_theme, referenceCloned: true };\n } catch (error) {\n if (\n !isApiError(error) ||\n (error.status !== 404 && error.status !== 405)\n ) {\n throw error;\n }\n\n console.warn(\n \"Server-side theme cloning is unavailable; falling back to an empty development theme. The first sync may take longer.\",\n );\n }\n }\n\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"development\" },\n });\n return { theme: body.application_theme, referenceCloned: false };\n}\n\nasync function ensureDevTheme(\n api: ReturnType<typeof createApiClient>,\n projectKey: string,\n identifier?: string,\n sourceThemeId?: number,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (identifier) {\n const theme = await findTheme(api, identifier);\n // Keep `navigate` pointed at whatever the dev server is actually serving.\n setLastDevThemeId(theme.id);\n return { theme, referenceCloned: false };\n }\n\n // Reuse this project's stored dev theme if it still exists and is still a\n // development theme (a published/promoted theme must not be edited in place).\n const stored = getDevTheme(projectKey);\n // A checkout pulled from a different source needs an isolated sandbox of\n // its own. Older stored entries have no source id, so they are retained only\n // for worktrees that were not pulled from a remote theme.\n if (stored && stored.sourceThemeId === sourceThemeId) {\n try {\n const body = await themes.getApplicationTheme(api, stored.id);\n const existing = body.application_theme;\n if (existing && existing.status === \"development\") {\n console.log(`Using existing dev theme #${existing.id}`);\n // Refresh the stored name and mark it most-recent for `navigate`.\n setDevTheme(projectKey, {\n ...stored,\n id: existing.id,\n name: existing.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n });\n return { theme: existing, referenceCloned: false };\n }\n } catch {\n // Theme no longer exists — fall through to create a new one.\n }\n // Stored theme is gone or no longer a dev theme; forget it.\n clearDevTheme(projectKey);\n }\n\n // Create a new development theme\n const { hostname } = await import(\"node:os\");\n const host = hostname().split(\".\")[0] ?? \"dev\";\n const name =\n `Development (${host}-${Math.random().toString(36).slice(2, 8)})`.slice(\n 0,\n 50,\n );\n\n const creation = await createDevelopmentTheme(api, sourceThemeId, name);\n const { theme } = creation;\n const devTheme: DevThemeRef = {\n id: theme.id,\n name: theme.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n };\n setDevTheme(projectKey, devTheme);\n console.log(`Created dev theme: ${theme.name} (#${theme.id})`);\n return creation;\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\"Start the theme dev server with hot reload\")\n .option(\"--host <host>\", \"Local server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Local server port\", \"9292\")\n .option(\n \"-t, --theme <name-or-id>\",\n \"Use an existing theme instead of dev theme\",\n )\n .option(\"-f, --force\", \"Skip schema validation on upload\")\n .option(\"--live-reload <mode>\", \"Reload mode: full-page | off\", \"full-page\")\n .option(\"--navigate\", \"Open browser navigator after server starts\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n host: string;\n port: string;\n theme?: string;\n force?: boolean;\n liveReload: string;\n navigate?: boolean;\n root: string;\n }) => {\n requireToken();\n\n // If no explicit --root and we're inside a workspace, resolve to the theme root\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const port = Number(opts.port);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error(\n `Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`,\n );\n process.exit(1);\n }\n\n // Fail fast if the port is already taken — everything below this\n // point (resolving/creating the dev theme, the initial sync) is\n // expensive and would otherwise run to completion only to crash\n // with a raw EADDRINUSE stack trace when the server finally binds.\n try {\n await checkPortAvailable(opts.host, port);\n } catch (e) {\n if (e instanceof PortInUseError) {\n console.error(e.message);\n } else {\n console.error(`Failed to check port availability: ${e}`);\n }\n process.exit(1);\n }\n\n const reloadMode = opts.liveReload === \"off\" ? \"off\" : \"full-page\";\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n\n // Use company from .fluid-theme.json if available, otherwise fetch\n let company: string;\n if (config?.company) {\n company = config.company;\n } else {\n const companyRes = await api.get<CompanyMe>(\n \"/api/company/v1/companies/me\",\n );\n company = companyRes.data?.company?.subdomain ?? \"\";\n if (!company) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n }\n\n // Always iterate on an isolated dev theme: reuse the stored one or\n // create a fresh `development` theme. `--theme` is the explicit\n // escape hatch for targeting an existing theme. A pulled theme id is\n // never a sync target: it only seeds a new isolated reference clone.\n const projectKey = devThemeKey(company, themeRoot.root);\n const devTarget = opts.theme\n ? await ensureDevTheme(api, projectKey, opts.theme)\n : await ensureDevTheme(api, projectKey, undefined, config?.themeId);\n const { theme } = devTarget;\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const baselineEnabled = devRemoteBaselineEnabled(Boolean(opts.theme));\n let initialRemoteState = baselineEnabled\n ? readDevRemoteBaseline(\n themeRoot.root,\n theme.id,\n assetManifest.fingerprint(),\n )\n : null;\n if (\n !initialRemoteState &&\n baselineEnabled &&\n devTarget.referenceCloned &&\n config?.themeId &&\n config.baseSha\n ) {\n try {\n const sourceShadow = await ShadowRepo.open(\n themeRoot.root,\n config.themeId,\n );\n initialRemoteState = await devRemoteStateFromSourceShadow(\n themeRoot,\n sourceShadow,\n theme.id,\n config.baseSha,\n );\n if (initialRemoteState) {\n writeDevRemoteBaseline(themeRoot.root, initialRemoteState);\n }\n } catch {\n initialRemoteState = null;\n }\n }\n const editorUrl = `https://admin.fluid.app/themes/${theme.id}/editor`;\n\n let stop: (() => void) | undefined;\n\n const cleanup = () => {\n stop?.();\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n stop = await startDevServer(\n api,\n {\n id: theme.id,\n name: theme.name,\n company,\n editorUrl,\n },\n themeRoot,\n {\n host: opts.host,\n port,\n reloadMode,\n validate: !opts.force,\n ...(initialRemoteState ? { initialSync: initialRemoteState } : {}),\n ...(baselineEnabled\n ? {\n onRemoteState: (state) =>\n writeDevRemoteBaseline(themeRoot.root, state),\n onRemoteStateInvalidated: () =>\n removeDevRemoteBaseline(themeRoot.root),\n }\n : {}),\n },\n (address) => {\n console.log(`\\n Dev server: ${address}`);\n console.log(` Web editor: ${editorUrl}`);\n console.log(\"\\n Watching for file changes…\\n\");\n\n if (opts.navigate) {\n void navigateTheme(api, theme.id, address).catch(\n (error: unknown) => {\n console.error(\n error instanceof Error\n ? error.message\n : \"Could not open the preview navigator.\",\n );\n },\n );\n }\n },\n );\n\n // Keep process alive\n await new Promise(() => {});\n },\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport {\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"./dev-remote-baseline.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { PushConflictError, Syncer } from \"./syncer.js\";\n\nexport interface DevelopmentPushResult {\n uploaded: number;\n deleted: number;\n linked: number;\n errors: string[];\n validationFailed: boolean;\n /** The remote changed during the push; nothing further was written. */\n conflict: boolean;\n}\n\n/**\n * Push a project to a development theme without touching the project's own\n * sync state.\n *\n * A project directory keeps one shadow repo and one `.fluid-theme.json`, both\n * for the theme it is bound to. Opening the shadow repo for another theme\n * wipes it, so an ordinary push here would cost the bound theme its baseline:\n * the next Publish could no longer detect deletions or stale remotes. This\n * syncs the way `fluid theme dev` does instead: against the development\n * theme's own remote index and `.fluid-theme/dev-baseline.json`, which it\n * keeps current for the next dev session.\n */\nexport async function pushToDevelopmentTheme(\n api: ApiClient,\n themeId: number,\n themeRoot: ThemeRoot,\n opts: {\n validate: boolean;\n /** Remove remote files that no longer exist locally (`--nodelete` off). */\n delete: boolean;\n onProgress?: (done: number, total: number) => void;\n },\n): Promise<DevelopmentPushResult> {\n const syncer = new Syncer(api, themeId, themeRoot);\n const fingerprint = () =>\n new ThemeAssetManifest(themeRoot.root).fingerprint();\n const common = {\n delete: opts.delete,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n ...(opts.onProgress ? { onProgress: opts.onProgress } : {}),\n } as const;\n\n // Trust the saved dev baseline only while the remote still matches it;\n // otherwise compare against the remote index, as `theme dev` does.\n const saved = readDevRemoteBaseline(themeRoot.root, themeId, fingerprint());\n let trusted = false;\n if (saved) {\n try {\n syncer.useDevRemoteState(saved);\n await syncer.preflightPush(saved.remoteSha);\n trusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n }\n let result;\n try {\n result =\n trusted && saved\n ? await syncer.uploadTheme({\n ...common,\n baseSha: syncer.remoteSha(),\n remoteState: saved,\n skipPreflight: true,\n })\n : await syncer.uploadTheme({ ...common, guardFetchedRemote: true });\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n removeDevRemoteBaseline(themeRoot.root);\n return {\n uploaded: 0,\n deleted: 0,\n linked: 0,\n errors: [\n \"The development theme changed during this push. Run the push again to compare against its current state.\",\n ],\n validationFailed: false,\n conflict: true,\n };\n }\n\n const errors = [...result.errors];\n if (result.validationFailed || errors.length > 0) {\n removeDevRemoteBaseline(themeRoot.root);\n } else if (!(await syncer.requestSync())) {\n // Always ask, even with nothing uploaded: a retry after a rejected\n // finalization finds the files already remote, and some writes (asset\n // reference cleanup) never show in the counts.\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push to commit it.\",\n );\n } else {\n const state = syncer.devRemoteState(fingerprint());\n if (state) writeDevRemoteBaseline(themeRoot.root, state);\n }\n\n return {\n uploaded: result.uploaded,\n deleted: result.deleted,\n linked: result.linked,\n errors,\n validationFailed: result.validationFailed,\n conflict: false,\n };\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, readIfExists } from \"./shadow-repo.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\n\nexport interface DiffSet {\n /** Files whose local content differs from shadow HEAD — the ones to send. */\n changed: ThemeFile[];\n /** Paths present in shadow HEAD but no longer on disk — deletions to send. */\n deleted: string[];\n}\n\n/**\n * Compute what changed locally since the last time the shadow repo\n * committed a state. Replaces the sha256 `checksums` map: shadow HEAD\n * is the source of truth for \"what the CLI last saw the server have\".\n *\n * Files whose local bytes are byte-identical to their HEAD blob are\n * skipped; anything else — new, modified, or a locally-deleted path\n * that HEAD still has — is included.\n */\nexport async function diffAgainstShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<DiffSet> {\n const changed: ThemeFile[] = [];\n const deleted: string[] = [];\n\n const localFiles = themeRoot.files();\n const localByKey = new Map<string, ThemeFile>();\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let headPaths: string[];\n try {\n headPaths = await shadow.headPaths();\n } catch (error) {\n throw new Error(\"Could not read the local theme shadow\", { cause: error });\n }\n\n assertNoCaseCollisions([\n ...localFiles.map((file) => file.relativePath),\n ...headPaths,\n ]);\n const headPathSet = new Set(headPaths);\n\n for (const file of localFiles) {\n if (!file.exists) continue;\n localByKey.set(file.relativePath, file);\n\n const headBlob = await shadow.blobAtHead(file.relativePath);\n if (!headBlob && headPathSet.has(file.relativePath)) {\n throw new Error(\n `Could not read the local theme shadow: ${file.relativePath}`,\n );\n }\n const localBuf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n if (headBlob && headBlob.equals(localBuf)) continue;\n\n changed.push(file);\n }\n\n for (const key of headPaths) {\n if (localByKey.has(key)) continue;\n if (themeRoot.ignore.ignore(key)) continue;\n // A manifest-backed asset is deliberately absent from disk. Its URL is\n // still authoritative and must never be emitted as a remote deletion.\n if (assetManifest.has(key)) continue;\n if (isStylesheetKey(key)) continue; // hidden from the API surface\n deleted.push(key);\n }\n\n return { changed, deleted };\n}\n\n/**\n * Refuse a push when any working file still contains a conflict marker\n * from a previous pull. Mirrors git's \"you have unresolved conflicts;\n * fix them and re-run\" behavior — the whole point of writing markers\n * on pull was to hand resolution to the user, so we can't send them\n * upstream.\n */\nexport function findUnresolvedConflicts(files: ThemeFile[]): string[] {\n const flagged: string[] = [];\n for (const file of files) {\n if (!file.isText) continue;\n const buf = readIfExists(file.absolutePath);\n if (!buf) continue;\n if (containsConflictMarker(buf)) flagged.push(file.relativePath);\n }\n return flagged;\n}\n\n/**\n * Stable, machine-readable one-liner for non-interactive callers\n * (Mist Desktop's publish flow parses push output). Uploading marker-\n * bearing files to a live theme is never acceptable, so `--auto-\n * baseline` pushes still refuse — but they emit this line so the\n * desktop can surface WHICH files block the publish instead of a\n * dead-end wall of prose. Format:\n *\n * FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=a.liquid,b.json\n */\nexport function conflictMarkerBlockLine(files: string[]): string {\n return `FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=${files.join(\",\")}`;\n}\n\nconst CONFLICT_START = Buffer.from(\"<<<<<<<\");\nconst CONFLICT_MID = Buffer.from(\"=======\");\nconst CONFLICT_END = Buffer.from(\">>>>>>>\");\n\n/**\n * A file counts as unresolved when it contains all three marker\n * shapes: `<<<<<<<`, `=======`, and `>>>>>>>`. Requiring all three\n * avoids false positives — a line of equals signs alone (e.g. inside\n * an ASCII table in a template comment) doesn't trip the guard.\n */\nfunction containsConflictMarker(buf: Buffer): boolean {\n return (\n buf.includes(CONFLICT_START) &&\n buf.includes(CONFLICT_MID) &&\n buf.includes(CONFLICT_END)\n );\n}\n\n/**\n * Commit the pushed state to shadow HEAD. Server-acknowledged contents\n * override local bytes for normalized member files, including files edited\n * or deleted locally during upload. Local files remain untouched so those\n * later changes still differ from the next pull's merge base.\n */\nexport async function commitPushedState(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n message: string,\n acknowledgedContents?: ReadonlyMap<string, string>,\n): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const recordedKeys = new Set<string>();\n for (const [path, content] of new Map(acknowledgedContents)) {\n entries.push({ path, sha: await shadow.writeBlob(content) });\n recordedKeys.add(path);\n }\n for (const file of themeRoot.files()) {\n if (!file.exists || recordedKeys.has(file.relativePath)) continue;\n recordedKeys.add(file.relativePath);\n // A manifest entry normally has no file at all. If bytes are present (for\n // example after a metadata fallback), they remain authoritative until a\n // successful upload can externalize them again.\n const buf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n entries.push({ path: file.relativePath, sha: await shadow.writeBlob(buf) });\n }\n let managedAssetSentinelSha: string | undefined;\n for (const key of assetManifest.keys()) {\n if (recordedKeys.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n entries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // Commit an empty tree too when HEAD exists: a successful final deletion\n // must clear its old path/sentinel so later pushes do not repeat it.\n if (entries.length > 0 || (await shadow.hasHead())) {\n await shadow.commitState(entries, message);\n }\n}\n\n/**\n * A target can already have every URL-backed FileResource (for example from a\n * reference clone), while this checkout's manifest still names its old source\n * theme. Adopt the target and clear any legacy binary from shadow even though\n * no remote write was necessary.\n */\nexport async function finalizeManifestOnlyPush(\n syncer: { repointManagedAssetsToCurrentTheme(): void },\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<void> {\n syncer.repointManagedAssetsToCurrentTheme();\n await commitPushedState(\n themeRoot,\n shadow,\n `push @ ${new Date().toISOString()}`,\n );\n}\n","import { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { FetchBinary } from \"./merge-pull.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\nexport interface SeedBaselineResult {\n /** False when a baseline already existed (HEAD present) — e.g. the\n * legacy checksum-era migration already seeded it. The migration\n * always wins over server seeding: a migrated HEAD carries the\n * local \"unmodified since last sync\" blobs, which is a better\n * merge base than the server's current state. */\n seeded: boolean;\n /** Paths recorded into the baseline commit. */\n recorded: string[];\n /** Server paths with no local counterpart, deliberately left OUT of\n * the baseline (see invariant below). */\n serverOnly: string[];\n /** Non-fatal per-file problems (e.g. a binary download failed). */\n errors: string[];\n}\n\n/**\n * `fluid theme push --auto-baseline`: when a theme directory has no\n * shadow baseline (scaffold that never pulled, or a dir last synced\n * by the checksum-era CLI whose migration had nothing to seed),\n * record the server's CURRENT state as the baseline commit so the\n * normal push diff (local vs baseline) can run.\n *\n * Invariants:\n *\n * 1. **The working tree is never touched.** Baseline recording writes\n * blobs into the bare shadow repo only; not a single byte on disk\n * changes. Local files identical to the server simply won't diff;\n * files that differ (or exist only locally) will push.\n *\n * 2. **Server-only files are never deleted.** Paths that exist on the\n * server but not locally are deliberately excluded from the\n * baseline commit. `diffAgainstShadow` reports deletions as \"in\n * HEAD but not on disk\", so putting server-only paths into HEAD\n * would mark them for remote deletion — on THIS push (or worse, a\n * later one) — for files the user never had. Excluding them makes\n * the diff structurally unable to delete them; the next `pull`\n * materializes them locally and records them for real.\n *\n * Bandwidth note: a server file whose sha256 checksum matches the\n * local file's is recorded from the LOCAL bytes (identical by\n * definition), so binary assets that are already in sync are never\n * downloaded just to seed the baseline.\n *\n * No-op (`seeded: false`) when HEAD already exists.\n */\nexport async function seedBaselineFromServer(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n message: string;\n}): Promise<SeedBaselineResult> {\n const { shadow, themeRoot, remote, fetchBinary, message } = input;\n const result: SeedBaselineResult = {\n seeded: false,\n recorded: [],\n serverOnly: [],\n errors: [],\n };\n\n if (await shadow.hasHead()) return result;\n\n const entries: Array<{ path: string; sha: string }> = [];\n\n for (const resource of remote) {\n const file = themeRoot.file(resource.key);\n\n // Same traversal guard as mergePull — never trust remote keys.\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Baseline ${resource.key}: path traversal detected`);\n continue;\n }\n\n if (!file.exists) {\n result.serverOnly.push(resource.key);\n continue;\n }\n\n let content: Buffer | null;\n if (resource.checksum && file.checksum() === resource.checksum) {\n // Identical to the server — local bytes ARE the server bytes.\n content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n } else {\n try {\n content = await materializeRemote(resource, fetchBinary);\n } catch (e) {\n result.errors.push(\n `Baseline ${resource.key}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n }\n if (content == null) {\n // Nothing to record (empty resource) — the local file will be\n // treated as new and pushed, which is the honest outcome.\n continue;\n }\n\n entries.push({ path: resource.key, sha: await shadow.writeBlob(content) });\n result.recorded.push(resource.key);\n }\n\n if (entries.length > 0) {\n await shadow.commitState(entries, message);\n result.seeded = true;\n }\n\n return result;\n}\n\nasync function materializeRemote(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n","import { readLegacyThemeConfig } from \"../theme-config.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\n\n/**\n * On first pull after upgrading from a checksum-era CLI, the shadow\n * repo starts with no HEAD. `mergePull` would then run every diverged\n * file through a null-base merge — even files the user never touched\n * locally — producing spurious `<<<<<<<` markers for every file the\n * server updated since the last pull.\n *\n * Recover a real merge base by trusting the legacy sha256 checksums:\n * any local file whose content still matches its stored checksum is\n * \"unmodified since last pull\" and can be committed as HEAD. Files\n * whose local sha256 diverges from the stored checksum stay\n * unseeded — we don't have their pre-modification content, so a\n * null-base merge (marker-first UX) is the honest fallback for them.\n *\n * Idempotent: no-op when HEAD already exists, when there is no\n * legacy config, when the config is for a different theme, or when\n * the checksums map is empty. Runs before `mergePull` so its base\n * lookups see the seeded tree.\n */\nexport async function migrateLegacyChecksumsIntoShadow(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n absoluteRoot: string;\n themeId: number;\n}): Promise<void> {\n const { shadow, themeRoot, absoluteRoot, themeId } = input;\n\n if (await shadow.hasHead()) return;\n\n const legacy = readLegacyThemeConfig(absoluteRoot);\n if (!legacy) return;\n if (legacy.themeId !== themeId) return;\n if (!legacy.checksums || Object.keys(legacy.checksums).length === 0) return;\n\n const seed: Array<{ path: string; content: Buffer }> = [];\n for (const file of themeRoot.files()) {\n if (!file.exists) continue;\n const stored = legacy.checksums[file.relativePath];\n if (!stored) continue;\n if (file.checksum() !== stored) continue;\n\n const content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n seed.push({ path: file.relativePath, content });\n }\n\n if (seed.length === 0) return;\n\n await shadow.seedFromWorkingTree(\n seed,\n `migrate from checksum-era CLI @ ${new Date().toISOString()}`,\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport type { ChangeEntry, GitSyncActor } from \"@fluid-app/fluid-cli\";\nimport {\n gitSyncActorFromMe,\n gitSyncCommitSubject,\n resolveGitSyncActor,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\n\n/**\n * User-stamped shadow-repo commit subjects, in the canonical GitSync\n * format shared with the mist / portal / widget CLIs and the Ruby\n * adapters (see `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts):\n *\n * Mike Tingey: Push theme Aurora · 2026-07-22 05:10:33 (0182d21e-…)\n *\n * When the actor is unknown (offline, /api/me down, token weirdness) the\n * commit is attributed to `Fluid` in the same shape, so consumers need\n * one grammar rather than two:\n *\n * Fluid: Push theme Aurora · 2026-07-22 05:10:33\n *\n * Shadow repos written by older CLIs still hold the pre-format machine\n * wording (`push @ <iso>`). We no longer emit it, but Mist Desktop's\n * History popover still parses it so those repos keep rendering.\n */\n\nexport type SyncVerb =\n | \"Push\"\n | \"Pull\"\n | \"Baseline from server\"\n | \"Snapshot before pull\";\n\n/** Human action per verb, used as the canonical subject's action. */\nconst BASE_MESSAGE: Record<SyncVerb, string> = {\n Push: \"Push from Fluid CLI\",\n Pull: \"Pull from Fluid CLI\",\n \"Baseline from server\": \"Baseline from server\",\n \"Snapshot before pull\": \"Snapshot before pull\",\n};\n\n/** Re-exported under the local name the theme CLI has always used. */\nexport type SyncActor = GitSyncActor;\n\n/**\n * Build a shadow-repo commit subject.\n *\n * `changes` (when supplied) replaces the per-verb constant with a\n * description of what the sync actually carried, so the shadow log reads\n * `Update 3 files in templates` rather than `Push from Fluid CLI` on\n * every entry. Falls back to the constant for an empty change set — a\n * baseline or a no-op sync still deserves a sensible subject.\n */\nexport function syncCommitSubject(\n verb: SyncVerb,\n actor: SyncActor | null,\n when: Date = new Date(),\n changes: readonly ChangeEntry[] = [],\n): string {\n const action = summarizeChanges(changes) ?? BASE_MESSAGE[verb];\n return gitSyncCommitSubject(action, actor, when);\n}\n\n/** Resolve a `/api/me` body to a commit actor. Exported for tests.\n * Delegates to the shared resolver so the name fallback chain\n * (full_name → first+last → email → `user-<id>`) can't drift between\n * CLIs. Null when the body carries no usable identity — the caller then\n * writes a `Fluid:` system commit. */\nexport function actorFromMe(\n raw: Parameters<typeof gitSyncActorFromMe>[0],\n): SyncActor | null {\n return gitSyncActorFromMe(raw);\n}\n\n/**\n * Best-effort fetch of the signed-in user for commit stamping. Races\n * `/api/me` against a short timeout and NEVER rejects — a sync must\n * not block or fail because the identity lookup did. Callers kick\n * this off early and await it only at commit time.\n */\nexport async function fetchSyncActor(\n api: ApiClient,\n timeoutMs = 5_000,\n): Promise<SyncActor | null> {\n // Served from the profile cache after the first sync; /api/me only\n // fires on a miss.\n return resolveGitSyncActor(() => requestSyncActor(api, timeoutMs));\n}\n\nasync function requestSyncActor(\n api: ApiClient,\n timeoutMs: number,\n): Promise<SyncActor | null> {\n try {\n const raw = await Promise.race([\n api.get<Record<string, unknown>>(\"/api/me\"),\n new Promise<never>((_, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n // Don't keep the process alive just for the stamp timeout.\n timer.unref?.();\n }),\n ]);\n return actorFromMe(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(\n ` (skipping user-stamp on sync commit — couldn't fetch /api/me: ${reason})`,\n );\n return null;\n }\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { pushToDevelopmentTheme } from \"../theme/development-push.js\";\nimport { devThemeKey, getDevTheme } from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { PushConflictError, Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n commitPushedState,\n conflictMarkerBlockLine,\n diffAgainstShadow,\n finalizeManifestOnlyPush,\n findUnresolvedConflicts,\n} from \"../theme/merge-push.js\";\nimport { seedBaselineFromServer } from \"../theme/auto-baseline.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport {\n fetchSyncActor,\n syncCommitSubject,\n type SyncActor,\n} from \"../theme/sync-identity.js\";\nimport type { ChangeEntry } from \"@fluid-app/fluid-cli\";\nimport { themes } from \"@fluid-app/themes-api-client\";\nimport {\n selectTheme,\n findTheme,\n type ApplicationTheme,\n} from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { formatError } from \"../theme/format-error.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n MEMBER_CONFIGURATION_KEY,\n} from \"../theme/member-configuration.js\";\n\nexport function createPushCommand(): Command {\n return new Command(\"push\")\n .description(\"Push local theme files to a remote theme\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to push to\")\n .option(\"-n, --nodelete\", \"Do not delete remote files missing locally\")\n .option(\n \"-f, --force\",\n \"Skip local Liquid validation and the server-side merge check\",\n )\n .option(\"-p, --publish\", \"Publish the theme after pushing\")\n .option(\n \"-u, --unpublished\",\n \"Create a new unpublished theme and push to it\",\n )\n .option(\n \"--auto-baseline\",\n \"When no local baseline exists, record the server's current state \" +\n \"as the baseline and push only what differs locally (never \" +\n \"modifies local files, never deletes server-only files)\",\n )\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n force?: boolean;\n publish?: boolean;\n unpublished?: boolean;\n autoBaseline?: boolean;\n root: string;\n }) => {\n requireToken();\n\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n let theme: ApplicationTheme;\n\n if (opts.unpublished) {\n const { name } = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Name for the new theme\",\n },\n { onCancel: () => process.exit(130) },\n );\n if (!name) {\n console.error(\"Theme name is required.\");\n process.exit(1);\n }\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"draft\" },\n });\n theme = body.application_theme;\n console.log(\n `Created unpublished theme: ${theme.name} (#${theme.id})`,\n );\n } else if (opts.theme) {\n theme = await findTheme(api, opts.theme);\n } else if (config) {\n console.log(\n ` Using theme from .fluid-theme.json: ${chalk.bold(config.themeName)} (#${config.themeId})`,\n );\n const body = await themes.getApplicationTheme(api, config.themeId);\n theme = body.application_theme;\n if (theme.status === \"development\") {\n console.warn(\n chalk.yellow(\n ` ${theme.name} (#${theme.id}) is a development theme: a scratch copy made by \\`fluid theme dev\\`, not a theme your storefront serves. ` +\n `This push updates only that copy. To update the real theme, run \\`fluid theme pull -t <theme-id>\\` here to bind the project to it, or pass --theme.`,\n ),\n );\n }\n } else {\n theme = await selectTheme(api, \"Select a theme to push to\");\n }\n\n if (config && !mayBindTheme(config, theme)) {\n // Mirroring overwrites the target, so only the development theme\n // `fluid theme dev` made for this directory may be pushed, never a\n // teammate's or another project's scratch copy.\n const own = getDevTheme(devThemeKey(config.company, themeRoot.root));\n if (own?.id !== theme.id) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme, but not this project's` +\n (own ? ` (that is #${own.id})` : \"\") +\n `. Pushing would overwrite it with this directory. Run \\`fluid theme dev\\` here to sync this project's own development theme, or \\`fluid theme dev --theme ${theme.id}\\` to sync this one deliberately.`,\n );\n process.exit(1);\n }\n const unresolvedDev = findUnresolvedConflicts(themeRoot.files());\n if (unresolvedDev.length > 0) {\n console.error(\n `✗ ${unresolvedDev.length} file(s) still contain unresolved conflict markers: ${unresolvedDev.join(\", \")}. Resolve them before pushing.`,\n );\n process.exit(1);\n }\n if (opts.publish) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme; --publish would make that scratch copy the live theme. Push without --publish, or publish ${config.themeName} (#${config.themeId}).`,\n );\n process.exit(1);\n }\n const devSpinner = ora(\n `Pushing to development theme ${theme.name} (#${theme.id})…`,\n ).start();\n const result = await pushToDevelopmentTheme(\n api,\n theme.id,\n themeRoot,\n {\n validate: !opts.force,\n // --auto-baseline promises to leave server-only files alone.\n delete: !opts.nodelete && !opts.autoBaseline,\n onProgress: (done, total) => {\n devSpinner.text = `Pushing ${done}/${total} files to ${theme.name} (#${theme.id})…`;\n },\n },\n );\n if (result.validationFailed) {\n devSpinner.fail(\n `Schema validation failed (${result.errors.length} error(s)). Use --force to skip.`,\n );\n } else if (result.errors.length > 0) {\n devSpinner.warn(`Pushed with ${result.errors.length} error(s).`);\n } else {\n devSpinner.succeed(\n `Pushed ${result.uploaded} file(s), removed ${result.deleted}, to development theme #${theme.id}.`,\n );\n }\n for (const e of result.errors) console.error(` ${e}`);\n console.log(\n ` This project stays bound to ${config.themeName} (#${config.themeId}); Publish still pushes there.`,\n );\n if (result.errors.length > 0) process.exitCode = 1;\n return;\n }\n\n const shadow = await ShadowRepo.open(themeRoot.root, theme.id);\n\n // Legacy checksum-era dirs (pre-shadow CLI) migrate here — on\n // push as well as pull, so a dir that never pulled with the\n // new CLI still recovers its merge base instead of dead-ending\n // on the baseline check below. Idempotent; the migration wins\n // over `--auto-baseline` server seeding because a migrated\n // HEAD holds the local \"unmodified since last sync\" blobs.\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot: themeRoot.root,\n themeId: theme.id,\n });\n\n const localFiles = themeRoot.files().filter((f) => f.exists);\n\n // Lazy, memoized identity lookup for commit stamping — only\n // fired when we actually commit, never blocks or fails a push.\n let actorPromise: Promise<SyncActor | null> | null = null;\n const getActor = (): Promise<SyncActor | null> =>\n (actorPromise ??= fetchSyncActor(api));\n\n // Refuse to push files that still contain `<<<<<<< / >>>>>>>`\n // markers from a previous merge — the whole point of writing\n // them was to hand resolution to the user; sending them\n // upstream would ship broken content. This guard is absolute:\n // `--auto-baseline` (non-interactive) refuses too, but emits a\n // machine-readable line the desktop can surface well.\n const unresolved = findUnresolvedConflicts(localFiles);\n if (unresolved.length > 0) {\n console.log();\n console.log(\n chalk.red(\n `✗ ${unresolved.length} file(s) still contain unresolved conflict markers:`,\n ),\n );\n for (const key of unresolved) console.log(` ${key}`);\n console.log();\n console.log(\n ` Edit each file to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} sections,`,\n );\n console.log(` then re-run ${chalk.cyan(\"fluid theme push\")}.`);\n console.log();\n if (opts.autoBaseline) {\n console.error(conflictMarkerBlockLine(unresolved));\n }\n process.exit(1);\n }\n\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // baseSha — server-side merge check on every write. Skipped\n // when `--force`, when the config was written by an older CLI\n // that didn't yet capture the pull's `content_version_sha`,\n // or when we're pushing to a different theme than the one the\n // config was written for (a `--unpublished` create, or\n // `--theme` pointing somewhere else). `baseSha` is per-theme —\n // reusing one theme's sha as the base of another theme's push\n // always 409s.\n const configMatchesTheme = config?.themeId === theme.id;\n let baseSha =\n opts.force || !configMatchesTheme ? null : (config?.baseSha ?? null);\n\n // A baseline exists when either half is present: shadow HEAD\n // (the local diff base — written by pull, a legacy migration,\n // or a previous auto-baseline) or a stored `baseSha` (the\n // server-side race check). HEAD alone is enough to diff\n // honestly; a missing baseSha only skips the server preflight.\n const hasBaseline = baseSha != null || (await shadow.hasHead());\n\n // No baseline at all: without `--auto-baseline`, refuse —\n // otherwise the CLI silently clobbers whatever the server\n // currently has, with no way for the user to see the\n // divergence first. `--force` opts out (explicit clobber);\n // `--unpublished` skips because the theme was created empty\n // in this same command and has nothing to protect.\n if (!opts.force && !opts.unpublished && !hasBaseline) {\n if (!opts.autoBaseline) {\n console.error();\n console.error(\n chalk.red(\n `No local baseline for theme \"${theme.name}\" (#${theme.id}).`,\n ),\n );\n console.error();\n console.error(\n ` Run ${chalk.cyan(`fluid theme pull -t ${theme.id}`)} first to sync down the current server state,`,\n );\n console.error(\n ` then push — this way you see what would change before it goes live.`,\n );\n console.error();\n console.error(\n ` Or re-run with ${chalk.cyan(\"--auto-baseline\")} to record the server's current state`,\n );\n console.error(\n ` as the baseline and push only what differs locally (local files are never modified).`,\n );\n console.error();\n console.error(\n ` Or, if you know what you're doing and want to overwrite the server's current`,\n );\n console.error(\n ` contents wholesale, re-run with ${chalk.cyan(\"--force\")}.`,\n );\n console.error();\n process.exit(1);\n }\n\n // `--auto-baseline`: record the server's current state as\n // the baseline WITHOUT touching a single local file, then\n // fall through to the normal diff. Server-only files are\n // deliberately kept out of the baseline so they can never\n // be reported as local deletions (see auto-baseline.ts).\n const baselineSpinner = ora(\n `No local baseline — recording current server state for ${theme.name} (#${theme.id})…`,\n ).start();\n try {\n const resources = await syncer.downloadAll();\n const seedResult = await seedBaselineFromServer({\n shadow,\n themeRoot,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n message: syncCommitSubject(\n \"Baseline from server\",\n await getActor(),\n ),\n });\n baseSha = syncer.remoteSha() ?? null;\n const parts = [`recorded ${seedResult.recorded.length} file(s)`];\n if (seedResult.serverOnly.length > 0) {\n parts.push(\n `left ${seedResult.serverOnly.length} server-only file(s) untouched`,\n );\n }\n baselineSpinner.succeed(`Baseline recorded — ${parts.join(\", \")}.`);\n for (const err of seedResult.errors) {\n console.warn(` ${chalk.yellow(\"warn\")} ${err}`);\n }\n } catch (e) {\n baselineSpinner.fail(\n `Could not record a baseline from the server: ${formatError(e)}`,\n );\n process.exit(1);\n }\n }\n\n // Diff against shadow HEAD is the source of truth for \"what\n // changed since last sync\" — replaces the sha256 checksum map.\n const { changed, deleted } = await diffAgainstShadow(themeRoot, shadow);\n if (!opts.nodelete) assertMemberConfigurationRetained(deleted);\n const managedAssetCount = new ThemeAssetManifest(themeRoot.root).keys()\n .length;\n\n if (\n changed.length === 0 &&\n deleted.length === 0 &&\n managedAssetCount === 0\n ) {\n console.log(\"Nothing to push — local matches the last synced state.\");\n await persistConfig();\n return;\n }\n\n const spinner = ora(`Pushing to ${theme.name} (#${theme.id})…`).start();\n\n // Validate before linking URL references so a schema failure cannot\n // mutate the target theme at all.\n if (!opts.force) {\n const validationErrors: string[] = [];\n for (const file of changed) {\n if (!file.isLiquid) continue;\n for (const diagnostic of file.validateSchema()) {\n if (diagnostic.severity === \"error\") {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n file.read(),\n )) {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n if (validationErrors.length > 0) {\n spinner.fail(\n `Liquid validation failed (${validationErrors.length} error(s)). Use --force to skip.`,\n );\n for (const error of validationErrors) console.error(` ${error}`);\n process.exit(1);\n }\n }\n try {\n await syncer.preflightPush(baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (e) {\n if (e instanceof PushConflictError) {\n renderPullFirst(spinner);\n process.exit(1);\n }\n throw e;\n }\n\n const uploads = planThemeUploads(\n themeRoot,\n changed,\n opts.nodelete ? [] : deleted,\n );\n let linked = 0;\n try {\n linked = await syncer.linkManagedAssets({ replace: true });\n // Reference writes are outside the ordinary resource PUT path. The\n // syncer refreshes its remote SHA afterwards so following PUTs keep\n // the existing Phase-003a conflict chain intact.\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (error) {\n spinner.fail(\n `Could not save remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n\n if (changed.length === 0 && deleted.length === 0 && linked === 0) {\n try {\n await finalizeManifestOnlyPush(syncer, themeRoot, shadow);\n } catch (error) {\n spinner.fail(\n `Could not finalize remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n spinner.succeed(\"Nothing to push — local matches the remote theme.\");\n await persistConfig();\n return;\n }\n\n let uploaded = 0;\n let deletedCount = 0;\n const errors: string[] = [];\n let progress = 0;\n const total =\n uploads.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n ) + (opts.nodelete ? 0 : deleted.length);\n\n for (const upload of uploads) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n errors.length > 0\n ) {\n errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await syncer.uploadMemberConfiguration(upload, baseSha);\n } else {\n await syncer.uploadFile(file, baseSha);\n }\n baseSha = syncer.remoteSha() ?? baseSha;\n uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n progress += 1 + (upload.memberConfiguration?.files.length ?? 0);\n spinner.text = `Pushing ${progress}/${total} files…`;\n }\n\n if (!opts.nodelete && errors.length === 0) {\n for (const key of deleted) {\n try {\n await syncer.deleteRemoteFile(key, baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n deletedCount++;\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n spinner.text = `Pushing ${++progress}/${total} files…`;\n }\n }\n\n if (errors.length === 0) {\n try {\n // The selected target now owns every manifest reference, so a\n // later deleted dev sandbox cannot force a byte upload again.\n syncer.repointManagedAssetsToCurrentTheme();\n } catch (error) {\n errors.push(`Save asset provenance: ${formatError(error)}`);\n }\n }\n\n // Every write is on the server; ask Fluid to commit them as one\n // version. The writes themselves only mark the theme changed — this\n // one call is what makes a 120-file push a single commit instead of\n // 120, and it is the reason the server no longer has to guess where\n // an operation ended.\n //\n // A partial transfer may contain unresolved member dependencies.\n // Managed-asset-only pushes still finalize once every write succeeds.\n if (\n errors.length === 0 &&\n (uploaded > 0 || deletedCount > 0 || linked > 0)\n ) {\n if (!(await syncer.requestSync())) {\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push before publishing.\",\n );\n }\n }\n\n if (errors.length) {\n spinner.warn(`Pushed with ${errors.length} error(s).`);\n for (const err of errors) console.error(` ${err}`);\n // The live theme is missing every rejected file, so this is a\n // failure. Mist's lifecycle retry runs `fluid theme push\n // --auto-baseline` and keys off the exit code, and every other\n // failure in this command already exits 1 — exiting 0 here\n // reported a publish that did not happen.\n //\n // exitCode rather than process.exit(): the command still has\n // output to flush, and an immediate exit can truncate it.\n process.exitCode = 1;\n } else {\n spinner.succeed(\n `Pushed ${uploaded} file(s), saved ${linked} remote asset reference(s)` +\n (deletedCount > 0\n ? `, deleted ${deletedCount} remote file(s).`\n : \".\"),\n );\n }\n\n // Roll shadow HEAD forward only when every write succeeded —\n // committing the working tree on a partial push would make\n // `diffAgainstShadow` treat the failed files as \"already\n // synced\" on the next push, silently swallowing the retry.\n // Leave HEAD stale on failure and let the next push re-diff\n // and re-attempt.\n if (errors.length === 0) {\n // Describe what the push carried rather than stamping every\n // entry \"Push from Fluid CLI\". `changed` is the uploaded set\n // and `deleted` the removed keys — exactly what landed, since\n // we only get here when no write failed.\n const pushedChanges: ChangeEntry[] = [\n ...changed.map((file) => ({\n status: \"modified\" as const,\n path: file.relativePath,\n })),\n ...(opts.nodelete\n ? []\n : deleted.map((key) => ({\n status: \"deleted\" as const,\n path: key,\n }))),\n ];\n try {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\n \"Push\",\n await getActor(),\n new Date(),\n pushedChanges,\n ),\n syncer.acknowledgedMemberContents(),\n );\n } catch (error) {\n console.error(\n `Theme content was uploaded to the server, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Do not repeat the push with --force. Review the remote theme and run a normal pull to recover the baseline.`,\n );\n if (opts.publish) console.error(\"Publishing was not attempted.\");\n process.exitCode = 1;\n return;\n }\n }\n\n await persistConfig();\n\n /**\n * Persist `.fluid-theme.json`. When a config already exists,\n * this is the pre-existing baseSha refresh. When it doesn't\n * (a scaffold's first `--auto-baseline` push), bind the dir\n * to the theme now — best-effort, since the company subdomain\n * needs one more API call — so the next push/pull needs no\n * interactive picker.\n */\n async function persistConfig(): Promise<void> {\n if (config) {\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: config.company,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n return;\n }\n if (!opts.autoBaseline) return;\n try {\n const res = await api.get<{\n data?: { company?: { subdomain?: string } };\n }>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) return;\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n } catch {\n // Best-effort — the shadow baseline alone is enough for\n // the next push to diff correctly.\n }\n }\n\n if (opts.publish && errors.length === 0) {\n const pubSpinner = ora(\"Publishing theme…\").start();\n try {\n await themes.publishApplicationTheme(api, theme.id);\n pubSpinner.succeed(\"Theme published.\");\n } catch (e) {\n pubSpinner.fail(`Publish failed: ${e}`);\n // Content uploaded but the theme is still a draft. Without\n // this the caller is told the publish succeeded.\n process.exitCode = 1;\n }\n }\n },\n );\n}\n\nfunction renderPullFirst(spinner: ReturnType<typeof ora>): void {\n spinner.fail(\"Server has changed since your last pull. Push aborted.\");\n console.log();\n console.log(\n ` ${chalk.cyan(\"Run `fluid theme pull` first\")} to merge the remote changes,`,\n );\n console.log(\n ` then re-run push. Use ${chalk.cyan(\"fluid theme push --force\")} to overwrite anyway.`,\n );\n console.log();\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport { diffAgainstShadow } from \"./merge-push.js\";\nimport { ThemeRoot } from \"./root.js\";\nimport { ShadowRepo } from \"./shadow-repo.js\";\n\nfunction containsSymlink(directory: string): boolean {\n return readdirSync(directory, { withFileTypes: true }).some((entry) => {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n return false;\n return (\n entry.isSymbolicLink() ||\n (entry.isDirectory() && containsSymlink(join(directory, entry.name)))\n );\n });\n}\n\n/** Fail closed for unattended updates. Desktop snapshots can advance shadow\n * HEAD with local work, so only a CLI sync baseline is eligible. */\nexport async function canPullThemeInBackground(\n root: string,\n themeId: number,\n): Promise<boolean> {\n try {\n if (containsSymlink(root)) return false;\n const config = readThemeConfig(root);\n if (\n !config ||\n config.themeId !== themeId ||\n !existsSync(join(root, \".fluid-theme\", \"repo\", \"HEAD\"))\n )\n return false;\n const storedId = readFileSync(\n join(root, \".fluid-theme\", \"theme-id\"),\n \"utf8\",\n ).trim();\n if (storedId !== String(themeId)) return false;\n const manifestPath = join(root, \".fluid-assets.json\");\n if (existsSync(manifestPath)) {\n const raw: unknown = JSON.parse(readFileSync(manifestPath, \"utf8\"));\n if (\n !raw ||\n typeof raw !== \"object\" ||\n !(\"version\" in raw) ||\n raw.version !== 1 ||\n !(\"assets\" in raw) ||\n !raw.assets ||\n typeof raw.assets !== \"object\" ||\n Array.isArray(raw.assets)\n )\n return false;\n const manifest = new ThemeAssetManifest(root);\n if (manifest.entries().some(([, link]) => link.pending)) return false;\n if (\n !config.assetManifestSha ||\n config.assetManifestSha !== manifest.fingerprint()\n )\n return false;\n } else if (\n config.assetManifestSha &&\n config.assetManifestSha !== new ThemeAssetManifest(root).fingerprint()\n )\n return false;\n const shadow = await ShadowRepo.open(root, themeId);\n if (!(await shadow.hasSyncedHead())) return false;\n const diff = await diffAgainstShadow(new ThemeRoot(root), shadow);\n return diff.changed.length === 0 && diff.deleted.length === 0;\n } catch {\n return false;\n }\n}\n","import { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nexport interface MemberOmissionInput {\n /** Both accepted and edited documents: omission must not erase either graph. */\n previousDocuments: readonly (Buffer | null)[];\n availablePaths: ReadonlySet<string>;\n incomingPaths: ReadonlySet<string>;\n ignored: (path: string) => boolean;\n}\n\n/** Missing/ignored is not empty. Return paths to retain without acknowledging\n * their local bytes as remotely accepted. Used before pull writes or pruning. */\nexport function planOmittedMemberFiles(\n input: MemberOmissionInput,\n): Set<string> {\n if (\n input.incomingPaths.has(MEMBER_CONFIGURATION_KEY) &&\n !input.ignored(MEMBER_CONFIGURATION_KEY)\n )\n return new Set();\n\n const protectedPaths = new Set<string>();\n for (const content of input.previousDocuments) {\n if (content === null) continue;\n protectedPaths.add(MEMBER_CONFIGURATION_KEY);\n if (content.byteLength > 2 * 1024 * 1024) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json exceeds 2 MB.\",\n );\n }\n const document: unknown = JSON.parse(content.toString(\"utf8\"));\n if (!isRecord(document) || document.version !== 2) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json must be a version 2 document.\",\n );\n }\n for (const field of [\"sites\", \"menus\", \"menu_references\"]) {\n if (!Array.isArray(document[field])) {\n throw new Error(\n `Cannot preserve member files: members_sites.json ${field} must be an array.`,\n );\n }\n }\n assertDependencyShape(document);\n // An omitted manifest cannot authorize pruning shared composition files.\n // Their dependency relationships live in the template contents, not the manifest.\n if ((document.sites as unknown[]).length > 0) {\n for (const path of input.availablePaths) {\n if (/^(sections|blocks|components|snippets|navbar|footer)\\//.test(path))\n protectedPaths.add(path);\n }\n }\n for (const path of memberDependencyPaths(document, input.availablePaths)) {\n if (\n !isThemeResourceKey(path) ||\n path.includes(\"\\\\\") ||\n [...path].some((character) => character.charCodeAt(0) < 32) ||\n path === \"config/members_sites.json\"\n ) {\n throw new Error(\n `Cannot preserve member files: invalid dependency path ${path}.`,\n );\n }\n protectedPaths.add(path);\n }\n }\n return new Set(\n [...protectedPaths].filter(\n (path) => !input.incomingPaths.has(path) || input.ignored(path),\n ),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Reject incomplete dependency declarations rather than silently pruning files. */\nfunction assertDependencyShape(document: Record<string, unknown>): void {\n const entries = (value: unknown): Record<string, unknown>[] => {\n if (!Array.isArray(value) || !value.every(isRecord)) {\n throw new Error(\n \"Cannot preserve member files: malformed dependency declaration.\",\n );\n }\n return value;\n };\n for (const entry of [\n ...entries(document.templates ?? []),\n ...entries(document.menu_references),\n ]) {\n if (typeof entry.path !== \"string\" || !entry.path) {\n throw new Error(\n \"Cannot preserve member files: dependency path is missing.\",\n );\n }\n }\n for (const site of entries(document.sites)) {\n if (site.layout_name !== null && typeof site.layout_name !== \"string\") {\n throw new Error(\"Cannot preserve member files: invalid layout name.\");\n }\n for (const page of entries(site.pages)) {\n if (\n page.template_path !== null &&\n typeof page.template_path !== \"string\"\n ) {\n throw new Error(\n \"Cannot preserve member files: invalid page template path.\",\n );\n }\n }\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, looksBinary, readIfExists } from \"./shadow-repo.js\";\nimport { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { planOmittedMemberFiles } from \"./member-transfer.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { commitPushedState } from \"./merge-push.js\";\nimport { syncCommitSubject, type SyncActor } from \"./sync-identity.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\n/** `write(..., RESOLVES_CONFLICT)`: this write is the chosen side of a conflict. */\nconst RESOLVES_CONFLICT = true;\n\n/**\n * Appended to every per-file write failure. Resource keys are verbatim\n * relative paths, so a template named `Sale 11/14/2025, 12:30 PM` asks\n * the filesystem for directories that Windows (and any path already\n * occupied by a file) refuses. Renaming the template in the admin is\n * the only fix the user owns.\n */\nconst RENAME_REMEDY =\n 'Rename the template in the admin visual builder to remove / \\\\ : * ? \" < > | from its name, then pull again.';\n\nexport interface MergePullResult {\n /** Files written from the remote without any local content to merge. */\n written: number;\n /** Files that went through a clean three-way merge with no markers. */\n merged: number;\n /** Files where markers remain — the caller must surface these. */\n conflicts: string[];\n /** Conflicting files auto-resolved to one side via `resolve`. */\n autoResolved: string[];\n /** Files removed locally because remote no longer emitted them. */\n deleted: number;\n /** Files that needed no change (already in sync). */\n skipped: number;\n /** Non-fatal per-file errors. */\n errors: string[];\n /** The remote revision must not be accepted as the new sync baseline. */\n baselinePreserved?: boolean;\n}\n\nexport type FetchBinary = (url: string) => Promise<Buffer>;\n\nexport interface MergePullInput {\n themeRoot: ThemeRoot;\n backgroundGuard?: BackgroundPullGuard;\n shadow: ShadowRepo;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n delete: boolean;\n /**\n * When true, skip the merge and take remote wholesale for every\n * file. Used by `fluid theme pull --force` when the user wants a\n * clean slate. HEAD still advances to reflect the state we wrote.\n */\n force?: boolean;\n /**\n * Remote resources already represented by `.fluid-assets.json`. They are\n * intentionally not materialized; ShadowRepo retains only a tiny path\n * sentinel, never their binary bytes.\n */\n skipRemoteKeys?: ReadonlySet<string>;\n /**\n * Non-interactive conflict resolution (`fluid theme pull --resolve\n * <local|remote>`): conflicting hunks are resolved to the chosen\n * side via `git merge-file --ours/--theirs` instead of writing\n * `<<<<<<<` markers; binary conflicts keep the whole chosen side.\n * Because \"remote\" resolution discards local hunks that live\n * NOWHERE else (shadow HEAD only holds the last-synced base, not\n * the user's edits), the pre-merge working tree is committed to the\n * shadow repo first — so the discarded state stays restorable.\n * Undefined = current behavior (markers).\n */\n resolve?: \"local\" | \"remote\";\n /**\n * Signed-in user for commit stamping (best-effort — null falls back\n * to the legacy machine-flavored subjects).\n */\n actor?: SyncActor | null;\n onProgress?: (done: number, total: number) => void;\n}\n\n/**\n * Reconcile the just-downloaded remote tree against the working tree,\n * using the shadow repo's HEAD as the merge base. Text files that\n * diverge on both sides get run through `git merge-file`; the result\n * — clean or with `<<<<<<<` markers — is written to disk. Binary\n * files fall back to \"take remote\" because there is no principled\n * three-way merge for them.\n *\n * HEAD advances to reflect the remote state we just materialized,\n * even when unresolved conflict markers remain in the working tree.\n * The user resolves markers in their editor and re-runs push; push\n * refuses to send files whose content still starts with a marker,\n * so a half-resolved push cannot silently ship broken content.\n */\nexport async function mergePull(\n input: MergePullInput,\n): Promise<MergePullResult> {\n const { themeRoot, shadow, remote, fetchBinary, onProgress } = input;\n const doDelete = input.delete;\n const result: MergePullResult = {\n written: 0,\n merged: 0,\n conflicts: [],\n autoResolved: [],\n deleted: 0,\n skipped: 0,\n errors: [],\n };\n\n // In `resolve` mode, working-tree writes are DEFERRED until after\n // the merge loop: if any conflict got auto-resolved, the pre-merge\n // working tree (still untouched at that point) is committed to the\n // shadow repo first, so the side we discard remains restorable.\n const pendingWrites: Array<{\n key: string;\n file: ThemeFile;\n content: Buffer;\n record: () => void;\n resolvesConflict: boolean;\n }> = [];\n // Keys whose bytes never reached disk — refused by the traversal\n // guard, or failed on the write. They must be kept out of the shadow\n // HEAD commit below, exactly like a failed download: HEAD is what\n // push diffs against, so recording content we did not write would\n // turn a tolerated failure into a remote DELETE. Refused keys have a\n // second reason — git rejects paths like `../x` outright, and the\n // commit takes the whole pull down with it.\n const unwrittenKeys = new Set<string>();\n const tryWrite = (key: string, file: ThemeFile, content: Buffer): boolean => {\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([key], () => file.write(content));\n else file.write(content);\n return true;\n } catch (e) {\n if (e instanceof BackgroundPullChangedError) throw e;\n unwrittenKeys.add(key);\n result.errors.push(`Reconcile ${key}: ${errMsg(e)}. ${RENAME_REMEDY}`);\n return false;\n }\n };\n /**\n * Write `content`, then apply `record` — the bookkeeping that says\n * which bucket of the summary this file counted as. The two travel\n * together, including into the deferred flush: bookkeeping applied\n * when the write was merely QUEUED would report a file the flush\n * never managed to write as both written and errored.\n */\n const write = (\n key: string,\n file: ThemeFile,\n content: Buffer,\n record: () => void,\n resolvesConflict = false,\n ): void => {\n if (input.resolve) {\n pendingWrites.push({ key, file, content, record, resolvesConflict });\n return;\n }\n if (tryWrite(key, file, content)) record();\n };\n\n const remoteContent = new Map<string, Buffer>();\n const remoteKeys = new Set<string>();\n let done = 0;\n for (const resource of remote) {\n remoteKeys.add(resource.key);\n if (input.skipRemoteKeys?.has(resource.key)) {\n onProgress?.(++done, remote.length);\n continue;\n }\n try {\n const buf = await materialize(resource, fetchBinary);\n if (buf) remoteContent.set(resource.key, buf);\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${errMsg(e)}`);\n }\n onProgress?.(++done, remote.length);\n }\n\n input.backgroundGuard?.assertUnchanged();\n let retainedMemberPaths: Set<string>;\n try {\n if (\n remoteKeys.has(MEMBER_CONFIGURATION_KEY) &&\n !themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY) &&\n !remoteContent.has(MEMBER_CONFIGURATION_KEY)\n ) {\n throw new Error(\n \"members_sites.json was listed but could not be downloaded; previous member state retained.\",\n );\n }\n retainedMemberPaths = planOmittedMemberFiles({\n previousDocuments: [\n readIfExists(themeRoot.file(MEMBER_CONFIGURATION_KEY).absolutePath),\n await shadow.blobAtHead(MEMBER_CONFIGURATION_KEY),\n ],\n availablePaths: new Set([\n ...themeRoot.files().map((file) => file.relativePath),\n ...(await shadow.headPaths()),\n ]),\n incomingPaths: remoteKeys,\n ignored: (path) => themeRoot.ignore.ignore(path),\n });\n } catch (error) {\n result.errors.push(errMsg(error));\n result.baselinePreserved = true;\n return result;\n }\n for (const path of retainedMemberPaths) remoteContent.delete(path);\n\n for (const [key, remoteBuf] of remoteContent) {\n const file = themeRoot.file(key);\n\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Reconcile ${key}: path traversal detected`);\n unwrittenKeys.add(key);\n continue;\n }\n\n if (input.force) {\n // `--force`: clobber everything with the remote content. HEAD\n // still advances below so the next pull's merge base is right.\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n const localBuf = readIfExists(file.absolutePath);\n const baseBuf = await shadow.blobAtHead(key);\n\n if (localBuf == null) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (localBuf.equals(remoteBuf)) {\n result.skipped++;\n continue;\n }\n\n if (baseBuf && localBuf.equals(baseBuf)) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (baseBuf && remoteBuf.equals(baseBuf)) {\n // A prior unresolved pull advances shadow HEAD to the clean remote\n // state while leaving markers in the working tree. A later\n // `--resolve remote` must be able to finish that interrupted clone;\n // treating the marker-filled file as an ordinary local edit traps\n // every retry in the same broken state.\n if (\n input.resolve === \"remote\" &&\n !looksBinary(localBuf) &&\n hasGeneratedConflictMarkers(localBuf)\n ) {\n write(\n key,\n file,\n remoteBuf,\n () => result.autoResolved.push(`${key} (cleared conflict markers)`),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n // Server didn't move; keep the user's local edits.\n result.skipped++;\n continue;\n }\n\n const isBinary =\n looksBinary(localBuf) ||\n looksBinary(remoteBuf) ||\n (baseBuf ? looksBinary(baseBuf) : false);\n\n if (isBinary) {\n // No textual merge for binary files: whole-file either/or.\n if (input.resolve === \"local\") {\n // Keep the local side untouched — nothing to write.\n result.autoResolved.push(`${key} (binary — kept local)`);\n continue;\n }\n // Take remote. In `--resolve remote` that's the chosen side;\n // otherwise flag it so the user can compare against the shadow\n // repo (`git show HEAD:...`) and reapply local intent by hand.\n write(\n key,\n file,\n remoteBuf,\n () => {\n if (input.resolve === \"remote\") {\n result.autoResolved.push(`${key} (binary — kept remote)`);\n } else {\n result.conflicts.push(`${key} (binary — kept remote)`);\n }\n },\n input.resolve === \"remote\",\n );\n continue;\n }\n\n // First merge WITHOUT a favor side so we know whether this file\n // actually conflicts — that drives honest bookkeeping (merged vs\n // autoResolved) and the pre-merge snapshot decision.\n const { merged, hasConflicts } = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n );\n if (hasConflicts && input.resolve) {\n const favored = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n input.resolve,\n );\n write(\n key,\n file,\n favored.merged,\n () => result.autoResolved.push(key),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n write(key, file, merged, () => {\n if (hasConflicts) result.conflicts.push(key);\n else result.merged++;\n });\n }\n\n // Flush deferred writes (resolve mode only). When any conflict was\n // auto-resolved, snapshot the pre-merge working tree first — it is\n // the ONLY place the losing side of each resolution still exists.\n if (input.resolve) {\n if (pendingWrites.some((w) => w.resolvesConflict)) {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\"Snapshot before pull\", input.actor ?? null),\n );\n }\n input.backgroundGuard?.assertUnchanged();\n for (const { key, file, content, record } of pendingWrites) {\n if (tryWrite(key, file, content)) record();\n }\n }\n\n if (doDelete && (await shadow.hasHead())) {\n for (const file of themeRoot.files()) {\n if (\n remoteKeys.has(file.relativePath) ||\n retainedMemberPaths.has(file.relativePath)\n )\n continue;\n // Stylesheet-hidden files are intentionally absent from the\n // resources index — deleting them locally would fight the\n // STYLESHEET_STRICT_INPUT feature, not honor a real deletion.\n if (isStylesheetKey(file.relativePath)) continue;\n\n const baseBuf = await shadow.blobAtHead(file.relativePath);\n if (!baseBuf) continue; // never seen by shadow — leave alone\n const localBuf = readIfExists(file.absolutePath);\n if (!localBuf) continue;\n if (!localBuf.equals(baseBuf)) continue; // user has local edits\n\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([file.relativePath], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n result.deleted++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n // ignore best-effort deletion\n }\n }\n }\n\n // Advance HEAD to the remote state we just downloaded. Even when\n // some files carry unresolved conflict markers, HEAD reflects the\n // server's clean bytes — the next push's diff-against-HEAD is what\n // surfaces the markers as \"unresolved\".\n //\n // Preserve failed-download paths from the previous HEAD so a\n // transient network error doesn't quietly drop them from the merge\n // base. If a file was in HEAD before the pull and its download\n // just failed, the next pull needs the previous blob as the base\n // for its merge — otherwise `blobAtHead` returns null next time and\n // a server-side change fires a spurious null-base conflict on a\n // file the user never touched.\n //\n // Keys whose write failed take the same route: their bytes are on\n // the server but not on disk, so recording the remote content here\n // would claim a state this checkout never had.\n try {\n const commitEntries: Array<{ path: string; sha: string }> = [];\n for (const [key, buf] of remoteContent) {\n if (unwrittenKeys.has(key)) continue;\n commitEntries.push({ path: key, sha: await shadow.writeBlob(buf) });\n }\n for (const key of new Set([...remoteKeys, ...retainedMemberPaths])) {\n if (input.skipRemoteKeys?.has(key) && !retainedMemberPaths.has(key))\n continue;\n // Downloaded AND written — already added above.\n if (remoteContent.has(key) && !unwrittenKeys.has(key)) continue;\n const prevBlob = await shadow.blobAtHead(key);\n if (prevBlob == null) continue; // wasn't in HEAD to begin with\n commitEntries.push({ path: key, sha: await shadow.writeBlob(prevBlob) });\n }\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let managedAssetSentinelSha: string | undefined;\n for (const key of input.skipRemoteKeys ?? []) {\n // Ignored resources without a manifest link remain outside the shadow\n // entirely. Otherwise removing an ignore rule could make a skipped file\n // look like a local deletion on the next push.\n if (!remoteKeys.has(key) || !assetManifest.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n commitEntries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // A pull containing only manifest-backed assets still needs a shadow commit:\n // sentinel entries replace any legacy binary blobs while keeping a baseline\n // for an intentional manifest-entry removal on a later push.\n // When the server becomes empty, record an empty tree rather than retaining\n // stale paths from HEAD as a future merge/delete baseline — but only when\n // the server really is empty. Empty entries with a non-empty remote mean\n // every write failed, and HEAD must survive as the next pull's merge base.\n if (\n commitEntries.length > 0 ||\n (remoteKeys.size === 0 && (await shadow.hasHead()))\n ) {\n input.backgroundGuard?.assertUnchanged();\n await shadow.commitState(\n commitEntries,\n syncCommitSubject(\"Pull\", input.actor ?? null),\n );\n }\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n result.baselinePreserved = true;\n result.errors.push(\n `Downloaded changes have already been applied locally, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Review the files and retry a normal pull before pushing; do not use --force to bypass the missing baseline.`,\n );\n }\n\n return result;\n}\n\nfunction hasGeneratedConflictMarkers(content: Buffer): boolean {\n const text = content.toString(\"utf8\");\n return (\n /^<<<<<<< local\\r?$/m.test(text) &&\n /^=======\\r?$/m.test(text) &&\n /^>>>>>>> remote\\r?$/m.test(text)\n );\n}\n\nasync function materialize(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","import {\n BackgroundPullChangedError,\n BackgroundPullGuard,\n} from \"../theme/background-pull-guard.js\";\nimport { canPullThemeInBackground } from \"../theme/background-pull.js\";\nimport { join, resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n developmentThemeRefusal,\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport { mergePull } from \"../theme/merge-pull.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport { fetchSyncActor } from \"../theme/sync-identity.js\";\nimport { selectTheme, findTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { CaseCollisionError } from \"../theme/case-collisions.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\nasync function fetchCompanySubdomain(\n api: ReturnType<typeof createApiClient>,\n): Promise<string> {\n const res = await api.get<CompanyMe>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n return subdomain;\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\"Pull a remote theme to your local directory\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to pull\")\n .option(\"-n, --nodelete\", \"Do not delete local files missing on remote\")\n .option(\"--root <path>\", \"Theme root directory\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .option(\n \"--only-if-clean\",\n \"Skip with exit code 3 when local work cannot be proven synced\",\n )\n .option(\n \"-f, --force\",\n \"Overwrite local without merging (skip conflict markers)\",\n )\n .option(\n \"--resolve <side>\",\n \"Auto-resolve merge conflicts to one side instead of writing \" +\n \"conflict markers: 'local' keeps your files' hunks, 'remote' \" +\n \"takes the server's (for non-interactive use)\",\n )\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n root?: string;\n yes?: boolean;\n force?: boolean;\n resolve?: string;\n onlyIfClean?: boolean;\n }) => {\n let spinner: ReturnType<typeof ora> | undefined;\n try {\n requireToken();\n\n if (\n opts.resolve !== undefined &&\n opts.resolve !== \"local\" &&\n opts.resolve !== \"remote\"\n ) {\n console.error(\n `Invalid --resolve value \"${opts.resolve}\" — use \"local\" or \"remote\".`,\n );\n process.exit(1);\n }\n const resolveSide = opts.resolve as \"local\" | \"remote\" | undefined;\n\n const api = createApiClient();\n const workspace = findWorkspace();\n\n const theme = opts.theme\n ? await findTheme(api, opts.theme)\n : await selectTheme(api, \"Select a theme to pull\");\n\n const subdomain = await fetchCompanySubdomain(api);\n let root: string;\n if (opts.root) {\n root = opts.root;\n } else if (workspace) {\n root =\n resolveThemeRootFromCwd(workspace) ??\n join(workspace.root, \"local\", subdomain);\n } else {\n root = `.`;\n }\n\n const absoluteRoot = resolve(root);\n const existingConfig = readThemeConfig(absoluteRoot);\n if (existingConfig && !mayBindTheme(existingConfig, theme)) {\n // Background preparation treats 3 as a safe skip; 1 would pause\n // its queue and retry a directory that belongs to another theme.\n if (opts.onlyIfClean) {\n console.log(\n `Skipped: this directory is bound to ${existingConfig.themeName} (#${existingConfig.themeId}), not development theme #${theme.id}.`,\n );\n process.exit(3);\n }\n console.error(developmentThemeRefusal(existingConfig, theme));\n process.exit(1);\n }\n const backgroundGuard = opts.onlyIfClean\n ? new BackgroundPullGuard(absoluteRoot)\n : undefined;\n\n console.log();\n console.log(` Theme: ${chalk.bold(theme.name)} (#${theme.id})`);\n console.log(` Company: ${chalk.bold(subdomain)}`);\n console.log(` Target: ${chalk.bold(absoluteRoot)}`);\n console.log();\n\n if (!opts.yes) {\n const { confirmed } = await prompts(\n {\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Pull theme to this directory?\",\n initial: true,\n },\n { onCancel: () => process.exit(130) },\n );\n if (!confirmed) {\n console.log(\"Aborted.\");\n process.exit(0);\n }\n }\n\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n console.log(\"Skipped: local changes or an unknown sync baseline.\");\n process.exit(3);\n }\n backgroundGuard?.assertUnchanged();\n const themeRoot = new ThemeRoot(root);\n const shadow = await ShadowRepo.open(absoluteRoot, theme.id);\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot,\n themeId: theme.id,\n });\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // Fire the identity lookup alongside the download — it never\n // rejects, and mergePull only awaits it at commit time.\n const actorPromise = fetchSyncActor(api);\n\n spinner = ora(`Pulling ${theme.name} (#${theme.id})…`).start();\n const resources = await syncer\n .downloadAll()\n .catch((error: unknown) => {\n if (!(error instanceof CaseCollisionError)) throw error;\n if (spinner) spinner.fail(error.message);\n process.exit(1);\n });\n // Recheck after network I/O and before any asset or content writes.\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n spinner.stop();\n console.log(\"Skipped: local files changed while downloading.\");\n process.exit(3);\n }\n const externalizedAssets = await syncer.externalizePulledAssets(\n resources,\n {\n delete: !opts.nodelete,\n backgroundGuard,\n },\n );\n\n const result = await mergePull({\n themeRoot,\n shadow,\n backgroundGuard,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n delete: !opts.nodelete,\n force: opts.force ?? false,\n skipRemoteKeys: externalizedAssets.managedKeys,\n resolve: resolveSide,\n actor: await actorPromise,\n onProgress: (done, total) => {\n if (spinner) spinner.text = `Downloading ${done}/${total} files…`;\n },\n });\n result.errors.push(...externalizedAssets.errors);\n\n const parts: string[] = [];\n if (result.written > 0) parts.push(`wrote ${result.written} file(s)`);\n if (result.merged > 0)\n parts.push(`merged ${result.merged} file(s) cleanly`);\n if (externalizedAssets.linked > 0) {\n parts.push(\n `kept ${externalizedAssets.linked} binary asset(s) remote`,\n );\n }\n if (result.autoResolved.length > 0)\n parts.push(\n `auto-resolved ${result.autoResolved.length} conflict(s) (kept ${resolveSide})`,\n );\n if (result.deleted > 0)\n parts.push(`deleted ${result.deleted} local file(s)`);\n if (result.skipped > 0)\n parts.push(`${result.skipped} already in sync`);\n\n if (result.errors.length) {\n spinner.warn(\n `Pulled with ${result.errors.length} error(s): ${parts.join(\", \")}.`,\n );\n for (const e of result.errors) console.error(` ${e}`);\n // A failed file or restore point leaves the checkout incomplete\n // or without a safe sync baseline. Conflicts already exit 1\n // below; this branch used to fall through at 0.\n //\n // Mist spawns `fluid theme pull -t <id> --root <path> --yes` to\n // clone a theme, and only records a lifecycle failure when the\n // exit code is non-zero — so a partial clone never reached the\n // agent's context and it went on to edit a checkout that was\n // missing files.\n //\n // exitCode rather than an immediate exit: the per-file error\n // list is printed right here and `.fluid-theme.json` is written\n // below, both of which still need to happen.\n process.exitCode = 1;\n }\n // Conflicts still need to be reported when snapshot saving failed;\n // Mist parses these paths to guide the user through resolution.\n if (result.conflicts.length > 0) {\n spinner.warn(\n `${result.conflicts.length} conflict(s) — resolve markers before pushing: ${parts.join(\", \")}.`,\n );\n console.log();\n for (const c of result.conflicts) {\n console.log(` ${chalk.yellow(\"CONFLICT\")} ${c}`);\n }\n console.log();\n console.log(\n ` Edit each file above to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} markers,`,\n );\n console.log(\n ` then run ${chalk.cyan(\"fluid theme push\")} once your resolution is in place.`,\n );\n console.log();\n } else if (result.errors.length === 0) {\n spinner.succeed(parts.join(\", \") || \"Already up to date.\");\n }\n\n // A rejected transfer or failed snapshot did not accept the revision.\n if (result.baselinePreserved) return;\n\n // Update .fluid-theme.json — baseSha is the server-side handle\n // the push preflight uses; the merge base itself lives in the\n // shadow repo.\n const remoteSha = syncer.remoteSha();\n backgroundGuard?.assertUnchanged();\n const updateConfig = () =>\n writeThemeConfig(absoluteRoot, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: remoteSha ?? existingConfig?.baseSha,\n assetManifestSha: new ThemeAssetManifest(\n absoluteRoot,\n ).fingerprint({\n excludePending: true,\n }),\n });\n\n if (backgroundGuard)\n backgroundGuard.mutate([\".fluid-theme.json\"], updateConfig);\n else updateConfig();\n if (result.conflicts.length > 0) process.exit(1);\n } catch (error) {\n if (!(error instanceof BackgroundPullChangedError)) throw error;\n spinner?.stop();\n console.log(error.message);\n process.exitCode = 3;\n }\n },\n );\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport {\n findMissingSectionReferences,\n validateSchemaText,\n VALID_SETTING_TYPES,\n type BlocksSchemaType,\n type Diagnostic,\n type TemplateInput,\n} from \"@fluid-app/theme-schema\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface FileDiagnostics {\n path: string;\n diagnostics: Diagnostic[];\n}\n\n// A theme section is defined by a liquid file under the top-level `sections/`\n// directory. Returns the section name a `{% section %}` tag would reference, or\n// null if the file is not a section definition. Handles both the flat layout\n// (`sections/hero.liquid`) and the nested one (`sections/hero/index.liquid`).\nfunction sectionNameOf(relativePath: string): string | null {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts[0] === \"sections\" && parts.length >= 2) {\n return parts[1]!.replace(/\\.liquid$/, \"\");\n }\n return null;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\"Validate theme files locally (read-only — no upload)\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .option(\"--json\", \"Output results as compact JSON\")\n .action(async (opts: { root: string; json?: boolean }) => {\n // Resolve the theme root the same way push/dev do: when left at the\n // default, prefer the workspace's theme root if we're inside one.\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n const message = `'${rootPath}' does not look like a theme directory.`;\n if (opts.json) {\n console.log(JSON.stringify({ ok: false, error: message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const files = themeRoot.files();\n // Read each liquid file once and reuse the content for both passes\n // (validateSchemaText and the section scan) to avoid a double disk read.\n const liquidFiles = files\n .filter((f) => f.isLiquid)\n .map((f) => ({ file: f, content: f.read() }));\n\n const byFile = new Map<string, Diagnostic[]>();\n const record = (path: string, diagnostic: Diagnostic): void => {\n const existing = byFile.get(path);\n if (existing) existing.push(diagnostic);\n else byFile.set(path, [diagnostic]);\n };\n\n // ── Schema pass — the same {% schema %} validation `fluid theme push`\n // runs. blocksSchemaType mirrors ThemeFile.validateSchema: page/layout\n // templates use object blocks, sections use array blocks.\n for (const { file, content } of liquidFiles) {\n const blocksSchemaType: BlocksSchemaType = file.isTemplate\n ? \"object\"\n : \"array\";\n for (const diagnostic of validateSchemaText(content, {\n blocksSchemaType,\n })) {\n record(file.relativePath, diagnostic);\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(content)) {\n record(file.relativePath, diagnostic);\n }\n }\n\n // ── Section pass — flag `{% section 'x' %}` references to a section\n // that has no definition on disk. Section definitions and assets are\n // not themselves referrers, so they are excluded from the scan.\n const existingSectionNames = new Set<string>();\n for (const { file } of liquidFiles) {\n const name = sectionNameOf(file.relativePath);\n if (name) existingSectionNames.add(name);\n }\n const referrers: TemplateInput[] = liquidFiles\n .filter(({ file }) => sectionNameOf(file.relativePath) === null)\n .map(({ file, content }) => ({ path: file.relativePath, content }));\n for (const missing of findMissingSectionReferences(\n referrers,\n existingSectionNames,\n )) {\n record(missing.templatePath, missing.diagnostic);\n }\n\n const results: FileDiagnostics[] = [...byFile.entries()]\n .map(([path, diagnostics]) => ({ path, diagnostics }))\n .sort((a, b) => a.path.localeCompare(b.path));\n\n let errors = 0;\n let warnings = 0;\n for (const { diagnostics } of results) {\n for (const d of diagnostics) {\n if (d.severity === \"error\") errors++;\n else warnings++;\n }\n }\n\n // Surface the canonical setting types once (not in every diagnostic) so a\n // consumer fixing an \"Invalid settings type\" error has the valid set to\n // hand without it bloating each message.\n const hasInvalidSettingType = results.some(({ diagnostics }) =>\n diagnostics.some(\n (d) =>\n d.target?.kind === \"setting\" &&\n d.target.field === \"type\" &&\n d.target.settingType !== undefined,\n ),\n );\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n ok: errors === 0,\n errors,\n warnings,\n filesChecked: liquidFiles.length,\n ...(hasInvalidSettingType\n ? { validSettingTypes: VALID_SETTING_TYPES }\n : {}),\n files: results,\n }),\n );\n } else {\n printText(results, errors, warnings, liquidFiles.length);\n }\n\n process.exit(errors > 0 ? 1 : 0);\n });\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction printText(\n results: FileDiagnostics[],\n errors: number,\n warnings: number,\n filesChecked: number,\n): void {\n for (const { path, diagnostics } of results) {\n console.log(chalk.bold(path));\n for (const d of diagnostics) {\n const label =\n d.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n // Only the first line — a few messages (e.g. the `Invalid JSON:` parse\n // error) carry a multi-line body that `--json` preserves in full.\n const message = d.message.split(\"\\n\")[0];\n console.log(` ${label} ${message}`);\n }\n }\n\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n if (errors > 0) {\n console.log(\n `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else if (warnings > 0) {\n console.log(\n `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else {\n console.log(`${chalk.green(\"✓ No problems found\")} ${suffix}`);\n }\n}\n","import { Command } from \"commander\";\nimport { execFileSync } from \"node:child_process\";\nimport { rmSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport prompts from \"prompts\";\n\nconst DEFAULT_CLONE_URL = \"git@github.com:fluid-commerce/base-theme.git\";\n\nconst SAFE_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/;\n\nexport function createInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Initialize a new theme by cloning the base theme\")\n .argument(\"[name]\", \"Directory name for the new theme\")\n .option(\"-u, --clone-url <url>\", \"Git URL to clone from\", DEFAULT_CLONE_URL)\n .action(async (name: string | undefined, opts: { cloneUrl: string }) => {\n if (!name) {\n const res = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Theme name\",\n },\n { onCancel: () => process.exit(130) },\n );\n name = res.name as string;\n if (!name) {\n console.error(\"No name provided.\");\n process.exit(1);\n }\n }\n\n if (!SAFE_NAME_RE.test(name)) {\n console.error(\n `Invalid theme name: '${name}'. Use only letters, numbers, hyphens, underscores, and dots.`,\n );\n process.exit(1);\n }\n\n console.log(`Cloning theme from ${opts.cloneUrl} into ${name}…`);\n execFileSync(\"git\", [\"clone\", opts.cloneUrl, name], { stdio: \"inherit\" });\n\n for (const dir of [\".git\", \".github\"]) {\n const path = join(name, dir);\n if (existsSync(path)) rmSync(path, { recursive: true, force: true });\n }\n\n console.log(`\\nTheme initialized in ./${name}`);\n console.log(`Next steps:\\n cd ${name}\\n fluid theme push`);\n });\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\n// A skill is a directory containing a SKILL.md. The bundled skills directory\n// holds one such directory per skill (e.g. `themes-review/`).\nexport function listSkillNames(skillsDir: string): string[] {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(skillsDir, name, \"SKILL.md\")))\n .sort();\n}\n\nexport interface InstallSkillsOptions {\n /** Directory holding the bundled skills (one sub-directory per skill). */\n sourceDir: string;\n /** Directory the skills are copied into (one sub-directory per skill). */\n targetRoot: string;\n /** Overwrite existing skills without asking. */\n force: boolean;\n /**\n * Asked once per skill that already exists when not forcing. Return true to\n * overwrite, false to leave the existing copy untouched.\n */\n confirmOverwrite: (name: string) => Promise<boolean>;\n /**\n * Called immediately for each install that left a temporary backup directory\n * behind (it could not be removed). Reported as it happens — not via the\n * return value — so the warning isn't lost if a later skill throws mid-loop.\n */\n onLeftover: (path: string) => void;\n}\n\nexport interface InstallSkillsResult {\n readonly installed: readonly string[];\n readonly skipped: readonly string[];\n}\n\n// Copy each bundled skill into `targetRoot/<name>`. Existing skills are only\n// replaced with `force` or an affirmative `confirmOverwrite`; everything else is\n// reported as skipped so the caller can summarize what happened.\nexport async function installSkills(\n options: InstallSkillsOptions,\n): Promise<InstallSkillsResult> {\n const { sourceDir, targetRoot, force, confirmOverwrite, onLeftover } =\n options;\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n mkdirSync(targetRoot, { recursive: true });\n\n for (const name of listSkillNames(sourceDir)) {\n const from = join(sourceDir, name);\n const to = join(targetRoot, name);\n const exists = existsSync(to);\n\n if (exists && !force && !(await confirmOverwrite(name))) {\n skipped.push(name);\n continue;\n }\n\n // Atomic copy + swap: a failed copy never destroys an existing install, and\n // the whole-directory replace prunes files removed from the bundled skill.\n // Report any leftover backup immediately so the warning isn't lost if a\n // later skill throws mid-loop.\n const leftover = replaceDirectory(from, to);\n if (leftover !== null) onLeftover(leftover);\n installed.push(name);\n }\n\n return { installed, skipped };\n}\n\n/**\n * Replace `target` with a fresh copy of `source` without ever leaving `target`\n * missing or partially written.\n *\n * Filesystem copies are not atomic, so a naive \"delete then copy\" loses the\n * original if the copy fails (permissions, no disk space, an interrupted\n * process). This stages the copy in a sibling directory and only swaps it into\n * place once it has fully succeeded; an existing `target` is moved aside to a\n * sibling backup first and restored if the swap fails. Because the whole\n * directory is replaced, files removed or renamed in `source` do not linger.\n *\n * Staging and backup directories live beside `target`, so its parent must\n * already exist and be on the same filesystem — that keeps the swap a cheap,\n * atomic rename rather than a cross-device copy.\n *\n * Not safe against a second process racing on the same `target`; intended for\n * single-process CLI use.\n *\n * @returns the path of a leftover backup directory that could not be removed\n * after an otherwise-successful replace (the previous contents are retained\n * there for manual cleanup), or `null` when nothing was left behind. The caller\n * should surface a non-null result so the leftover isn't silently hidden.\n */\nexport function replaceDirectory(\n source: string,\n target: string,\n): string | null {\n const staging = reserveSiblingPath(target, \"staging\");\n try {\n cpSync(source, staging, { recursive: true });\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n\n // No existing target: a single rename moves the staged copy into place.\n if (!existsSync(target)) {\n return swapIntoPlace(staging, target, null);\n }\n\n // Existing target: move it aside first so the swap stays reversible.\n const backup = reserveSiblingPath(target, \"backup\");\n try {\n renameSync(target, backup);\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n return swapIntoPlace(staging, target, backup);\n}\n\n// Move the staged copy into `target`. If the move fails, restore the original\n// from `backup` (when there was one) *before* any cleanup, so a failed staging\n// removal can never leave the caller without a directory at `target`; then\n// best-effort discard the staged copy and rethrow. On success, returns the\n// backup path if it could not be removed (a leftover the caller should report),\n// or `null`.\nfunction swapIntoPlace(\n staging: string,\n target: string,\n backup: string | null,\n): string | null {\n try {\n renameSync(staging, target);\n } catch (error) {\n if (backup !== null) restoreBackup(backup, target, error);\n removeQuietly(staging);\n throw error;\n }\n return removeQuietly(backup);\n}\n\n// Best-effort restore of the original directory. If even this fails, surface an\n// error pointing at the backup so the user can recover their data by hand.\nfunction restoreBackup(backup: string, target: string, cause: unknown): void {\n try {\n renameSync(backup, target);\n } catch {\n throw new Error(\n `Failed to replace ${target}; its previous contents are preserved at ${backup}.`,\n { cause },\n );\n }\n}\n\n// Best-effort removal of a temporary staging/backup directory. Cleanup must\n// never throw: a failure here should not mask the real outcome or strand the\n// caller. Returns the path when removal failed (so the caller can surface the\n// leftover), or `null` when the directory was removed or there was none.\nfunction removeQuietly(path: string | null): string | null {\n if (path === null) return null;\n try {\n rmSync(path, { recursive: true, force: true });\n return null;\n } catch {\n return path;\n }\n}\n\n// Pick a sibling path of `basePath` that does not exist yet, so renaming onto it\n// is a clean create on every platform (Windows rejects a rename onto an existing\n// directory). Deterministic — no randomness.\nfunction reserveSiblingPath(basePath: string, label: string): string {\n let candidate = `${basePath}.${label}`;\n for (let n = 1; existsSync(candidate); n += 1) {\n candidate = `${basePath}.${label}.${n}`;\n }\n return candidate;\n}\n","import { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { installSkills, listSkillNames } from \"../skills/install.js\";\n\n// Where skills land by default — `.agents/skills/` is the tool-neutral\n// convention for agent skills.\nconst DEFAULT_TARGET_DIR = \".agents/skills\";\n\n// The bundled skills ship beside the compiled CLI at `dist/skills/` and live at\n// the package root `skills/` in source. Resolve relative to this module so the\n// lookup works whether the code runs from the published bundle or from source.\nfunction resolveBundledSkillsDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, \"skills\"), // dist/skills (bundled build output)\n join(here, \"..\", \"skills\"), // package root from dist/index.mjs\n join(here, \"..\", \"..\", \"skills\"), // package root from src/commands/\n ];\n for (const dir of candidates) {\n if (listSkillNames(dir).length > 0) return dir;\n }\n // Last resort: walk up looking for a skills/ dir that holds a skill.\n let dir = here;\n for (let depth = 0; depth < 6; depth++) {\n const candidate = join(dir, \"skills\");\n if (listSkillNames(candidate).length > 0) return candidate;\n dir = dirname(dir);\n }\n throw new Error(\n \"Could not locate the bundled theme skills — this is a packaging bug.\",\n );\n}\n\n// Skills stay out of `fluid theme push/pull/dev/lint` only when some path\n// segment is a dot-directory — that is the sole thing ThemeRoot.glob() skips.\n// Returns true when installing into a *visible* dir inside a theme, where the\n// files would be uploaded as theme content instead of kept local.\nfunction skillsWouldShipWithTheme(cwd: string, targetRoot: string): boolean {\n if (!new ThemeRoot(cwd).isValid()) return false; // not a theme dir — irrelevant\n const rel = relative(cwd, targetRoot);\n if (rel.startsWith(\"..\")) return false; // outside the theme root — never scanned\n return !rel.split(sep).some((segment) => segment.startsWith(\".\"));\n}\n\nexport function createSkillsCommand(): Command {\n const skills = new Command(\"skills\").description(\n \"Manage the bundled Fluid theme AI skills\",\n );\n\n skills\n .command(\"install\")\n .description(\n \"Copy the bundled theme skills into the current directory (default: .agents/skills/)\",\n )\n .option(\"-d, --dir <path>\", \"Directory to install into\", DEFAULT_TARGET_DIR)\n .option(\"-f, --force\", \"Overwrite existing skills without prompting\")\n .action(async (opts: { dir: string; force?: boolean }) => {\n const sourceDir = resolveBundledSkillsDir();\n if (listSkillNames(sourceDir).length === 0) {\n console.error(\"No bundled skills found to install.\");\n process.exit(1);\n }\n\n const targetRoot = resolve(process.cwd(), opts.dir);\n if (skillsWouldShipWithTheme(process.cwd(), targetRoot)) {\n console.log(\n `${chalk.yellow(\"⚠\")} ${chalk.bold(opts.dir)} is not a hidden directory — ` +\n `'fluid theme push' will upload these skills as theme files. ` +\n `Install into a dot-directory like ${chalk.cyan(DEFAULT_TARGET_DIR)} to keep them local.`,\n );\n }\n\n const { installed, skipped } = await installSkills({\n sourceDir,\n targetRoot,\n force: Boolean(opts.force),\n confirmOverwrite: async (name) => {\n const res = await prompts(\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `${chalk.yellow(name)} already exists in ${opts.dir}. Overwrite?`,\n initial: false,\n },\n { onCancel: () => process.exit(130) },\n );\n return Boolean(res.overwrite);\n },\n // Warn as soon as a leftover is found, so it's reported even if a later\n // skill fails before the install finishes.\n onLeftover: (path) => {\n console.log(\n `${chalk.yellow(\"⚠\")} kept the previous copy at ${path} (couldn't remove it — delete it manually)`,\n );\n },\n });\n\n for (const name of installed) {\n console.log(`${chalk.green(\"✓\")} ${name} → ${join(opts.dir, name)}`);\n }\n for (const name of skipped) {\n console.log(`${chalk.dim(`· skipped ${name} (kept existing)`)}`);\n }\n\n const parts = [\n installed.length > 0 ? `${installed.length} installed` : null,\n skipped.length > 0 ? `${skipped.length} skipped` : null,\n ].filter(Boolean);\n console.log(\n `\\n${chalk.bold(parts.join(\", \") || \"Nothing to do\")} in ${targetRoot}`,\n );\n if (installed.length > 0) {\n console.log(\n chalk.dim(\"Restart your agent session to pick up the new skills.\"),\n );\n }\n });\n\n return skills;\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createPushCommand } from \"./push.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createInitCommand } from \"./init.js\";\nimport { createNavigateCommand } from \"./navigate.js\";\nimport { createSkillsCommand } from \"./skills.js\";\n\nexport function registerThemeCommand(ctx: PluginContext): void {\n const cmd = new Command(\"theme\").description(\n \"Theme developer workflow — dev server, push, pull, lint, init, skills\",\n );\n\n cmd.addCommand(createDevCommand());\n cmd.addCommand(createPushCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createInitCommand());\n cmd.addCommand(createNavigateCommand());\n cmd.addCommand(createSkillsCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerThemeCommand } from \"./commands/theme.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-theme-dev\",\n version: \"0.1.0\",\n register(ctx: PluginContext) {\n registerThemeCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["delay","isRecord","isRecord","themes.getApplicationThemeAvailableThemeables","settingTypesJson.types","escapeHtml","escapeHtml","isRecord","parseDocument","isRecord","records","isRecord","themes.listThemeResources","themes.getThemeAssets","isRecord","themes.createFileResource","themes.destroyFileResource","themes.updateThemeResource","themes.deleteThemeResource","isRecord","themes.listApplicationThemes","themes.getApplicationTheme","themes.cloneApplicationThemeForDevelopment","themes.createApplicationTheme","themes.getApplicationTheme","themes.createApplicationTheme","themes.getApplicationTheme","themes.publishApplicationTheme","undiciFetch","dnsLookup","REDIRECT_STATUSES","sha256","isInside"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api.ts","../src/plugin-state.ts","../src/theme/dev-server/member-preview-contract.ts","../src/theme/navigation-members.ts","../../../api-clients/themes/src/namespaces/v0.ts","../src/commands/navigate.ts","../src/theme-config.ts","../src/theme/mime-type.ts","../src/theme/resource-key.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../src/theme/file.ts","../src/theme/fluid-ignore.ts","../src/theme/root.ts","../src/theme/dev-server/member-screen-assets.ts","../src/theme/dev-server/hot-reload.ts","../src/theme/dev-server/member-preview-html.ts","../src/theme/dev-server/member-preview.ts","../src/theme/dev-server/sse.ts","../src/theme/dev-server/template-overrides.ts","../src/theme/dev-server/proxy.ts","../src/theme/format-error.ts","../src/theme/dev-server/watcher.ts","../src/theme/background-pull-guard.ts","../src/theme/case-collisions.ts","../src/theme/asset-manifest.ts","../src/theme/stylesheet-keys.ts","../src/theme/member-dependencies.ts","../src/theme/member-configuration.ts","../src/theme/syncer.ts","../src/theme/liquid-delimiters.ts","../src/theme/dev-server/port-preflight.ts","../src/theme/dev-server/index.ts","../src/theme/dev-remote-baseline.ts","../src/theme/shadow-repo.ts","../src/theme-picker.ts","../src/workspace.ts","../src/commands/dev.ts","../src/theme/development-push.ts","../src/theme/merge-push.ts","../src/theme/auto-baseline.ts","../src/theme/legacy-migration.ts","../src/theme/sync-identity.ts","../src/commands/push.ts","../src/theme/background-pull.ts","../src/theme/member-transfer.ts","../src/theme/merge-pull.ts","../src/commands/pull.ts","../src/commands/lint.ts","../src/commands/init.ts","../src/skills/install.ts","../src/commands/skills.ts","../../../mist/core/src/pinned-public-fetch.ts","../../../mist/core/src/public-url-policy.ts","../../../mist/core/src/source-stylesheets.ts","../../../mist/core/src/source-page-evidence.ts","../../../mist/core/src/crawl-evidence.ts","../src/page-clone/crawl-transport.ts","../../../mist/core/src/firecrawl-request.ts","../src/page-clone/image.ts","../src/page-clone/run-crawl.ts","../src/commands/crawl.ts","../src/page-clone/preview-browser.ts","../../../mist/core/src/preview-comparison.ts","../../../mist/core/src/source-baseline-admissibility.ts","../../../mist/core/src/source-comparison-report.ts","../src/page-clone/source-bundle.ts","../src/page-clone/run-compare.ts","../src/commands/compare.ts","../src/commands/theme.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * `??` rather than `||` so an explicitly empty `errors` (`\"\"`, `0`, `false`)\n * is preserved instead of silently falling back to the whole envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n return (body.errors ?? body) as ApiErrorData;\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nexport type ApiClient = FetchClient;\n\n/** Base URL for all API calls. Set FLUID_API_BASE to route through a BFF. */\nexport function getApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\n// Only the desktop's unattended worker opts in. Gate auth resolution to pace\n// even concurrent identity/resource requests within a background theme pull.\nlet backgroundGate = Promise.resolve();\nasync function backgroundToken(tokenOverride?: string): Promise<string | null> {\n backgroundGate = backgroundGate.then(\n () => new Promise((resolve) => setTimeout(resolve, 1_000)),\n );\n await backgroundGate;\n return tokenOverride ?? getAuthToken() ?? null;\n}\n\nexport function createApiClient(tokenOverride?: string): ApiClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () =>\n process.env[\"FLUID_BACKGROUND_SYNC\"] === \"1\"\n ? backgroundToken(tokenOverride)\n : (tokenOverride ?? getAuthToken() ?? null),\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n // Only retry reads: a failed write response may follow a successful\n // server mutation. Preserve the final API error and request ID.\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n\nexport function requireToken(): string {\n const token = getAuthToken();\n if (!token) {\n console.error(\"Not logged in. Run `fluid login` first.\");\n process.exit(1);\n }\n return token;\n}\n","import { existsSync } from \"node:fs\";\nimport { readConfig, updateConfig } from \"@fluid-app/fluid-cli\";\n\nexport interface DevThemeRef {\n id: number;\n name: string;\n /** Theme that was pulled when this isolated dev target was created. */\n sourceThemeId?: number;\n}\n\ninterface ThemeDevState {\n /**\n * Dev themes keyed per project, so `theme dev` in one working copy never\n * reuses (and clobbers) another project's sandbox theme. See `devThemeKey`.\n * Entries are pruned once their theme directory no longer exists, so the map\n * can't grow without bound as projects (and one-off/temp dirs) come and go.\n */\n devThemes?: Record<string, DevThemeRef>;\n /** Most recently started dev theme — `navigate`'s default target. */\n lastDevThemeId?: number;\n /**\n * Legacy single global dev theme id. Older CLI versions stored one dev theme\n * here regardless of project. Read once for migration (see `getDevTheme`),\n * then dropped in favour of `devThemes`.\n */\n devThemeId?: number;\n /** Legacy companion to `devThemeId`. */\n devThemeName?: string;\n}\n\nconst PLUGIN_KEY = \"theme-dev\";\n\nfunction getState(): ThemeDevState {\n const config = readConfig();\n return (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n}\n\n/** Extract the absolute theme root from a `company:themeRoot` key. */\nfunction themeRootFromKey(key: string): string {\n const sep = key.indexOf(\":\");\n return sep === -1 ? key : key.slice(sep + 1);\n}\n\n/**\n * Set `key` to `theme`, dropping any entries whose theme directory no longer\n * exists. Tying an entry's lifetime to its directory keeps the map bounded —\n * abandoned/deleted projects fall out the next time `theme dev` runs anywhere.\n */\nfunction withDevTheme(\n existing: Record<string, DevThemeRef> | undefined,\n key: string,\n theme: DevThemeRef,\n): Record<string, DevThemeRef> {\n const next: Record<string, DevThemeRef> = {};\n for (const [k, v] of Object.entries(existing ?? {})) {\n if (existsSync(themeRootFromKey(k))) next[k] = v;\n }\n next[key] = theme;\n return next;\n}\n\n/**\n * Stable key identifying a dev theme's owning project: the Fluid company\n * (subdomains are globally unique) plus the absolute theme root. Two working\n * copies — or the same copy pulled from two companies — get distinct keys.\n */\nexport function devThemeKey(\n company: string | undefined,\n themeRoot: string,\n): string {\n return `${company ?? \"default\"}:${themeRoot}`;\n}\n\n/**\n * The dev theme stored for a project key, if any. Falls back once to the legacy\n * global `devThemeId` (older CLI versions) and adopts it for this key — clearing\n * the legacy fields so a second project can't adopt the same theme and collide.\n */\nexport function getDevTheme(key: string): DevThemeRef | undefined {\n const state = getState();\n const existing = state.devThemes?.[key];\n if (existing) return existing;\n\n if (state.devThemeId) {\n const migrated: DevThemeRef = {\n id: state.devThemeId,\n name: state.devThemeName ?? `Development #${state.devThemeId}`,\n };\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const { devThemeId: _id, devThemeName: _name, ...rest } = current;\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...rest,\n devThemes: withDevTheme(rest.devThemes, key, migrated),\n lastDevThemeId: migrated.id,\n },\n },\n };\n });\n return migrated;\n }\n\n return undefined;\n}\n\n/** Store (or refresh) the dev theme for a project key and mark it most-recent. */\nexport function setDevTheme(key: string, theme: DevThemeRef): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: {\n ...current,\n devThemes: withDevTheme(current.devThemes, key, theme),\n lastDevThemeId: theme.id,\n },\n },\n };\n });\n}\n\n/** Forget a project's dev theme (it was deleted remotely or is no longer a dev theme). */\nexport function clearDevTheme(key: string): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n const removed = current.devThemes?.[key];\n if (!removed) return config;\n const { [key]: _removed, ...rest } = current.devThemes ?? {};\n const next: ThemeDevState = { ...current, devThemes: rest };\n // Don't leave `navigate` pointing at a theme we just forgot.\n if (current.lastDevThemeId === removed.id) {\n next.lastDevThemeId = undefined;\n }\n return {\n ...config,\n plugins: { ...config.plugins, [PLUGIN_KEY]: next },\n };\n });\n}\n\n/**\n * Mark a theme as the most recently started dev server (`navigate`'s default)\n * without recording it as a project's dev theme — used for the `--theme`\n * escape hatch, which may target an arbitrary (non-dev) theme.\n */\nexport function setLastDevThemeId(id: number): void {\n updateConfig((config) => {\n const current = (config.plugins[PLUGIN_KEY] as ThemeDevState) ?? {};\n return {\n ...config,\n plugins: {\n ...config.plugins,\n [PLUGIN_KEY]: { ...current, lastDevThemeId: id },\n },\n };\n });\n}\n\n/**\n * The dev theme to target by default in `navigate` — the most recently started\n * dev server. Falls back to the legacy global id for users who haven't yet run\n * the per-project `theme dev`.\n */\nexport function getLastDevThemeId(): number | undefined {\n const state = getState();\n return state.lastDevThemeId ?? state.devThemeId;\n}\n","/** A deliberately small projection of the Rails metadata and render contracts. */\nexport interface MemberPreviewPage {\n id: number;\n title: string;\n path: string;\n published: boolean;\n}\nexport interface MemberPreviewSite {\n id: number;\n name: string;\n pages: MemberPreviewPage[];\n}\nexport interface MemberPreviewMetadata {\n members_sites: MemberPreviewSite[];\n system_screens: Array<{ key: string }>;\n}\nexport interface MemberPreviewSelection {\n members_site_id: number;\n country_id?: number;\n language_id?: number;\n member_type_id?: string;\n}\nexport class MemberPreviewError extends Error {\n constructor(\n message: string,\n readonly status: number = 502,\n ) {\n super(message);\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\nexport function isId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value > 0;\n}\n/** The metadata API still exposes storefront-era /accounts paths. Account hosts omit it. */\nexport function accountPath(path: string): string {\n return path === \"/accounts\"\n ? \"/\"\n : path.startsWith(\"/accounts/\")\n ? path.slice(\"/accounts\".length)\n : path;\n}\nexport function isProductionPath(value: unknown): value is string {\n if (\n typeof value !== \"string\" ||\n !value.startsWith(\"/\") ||\n value.startsWith(\"//\") ||\n value.includes(\"\\\\\")\n )\n return false;\n const parsed = new URL(value, \"http://preview.invalid\");\n return parsed.pathname === value && !value.startsWith(\"/_members\");\n}\nexport function parseMemberMetadata(value: unknown): MemberPreviewMetadata {\n if (\n !isRecord(value) ||\n !Array.isArray(value.members_sites) ||\n !Array.isArray(value.system_screens)\n ) {\n throw new MemberPreviewError(\n \"This server does not provide compatible member preview metadata.\",\n );\n }\n const sites = value.members_sites.map((site): MemberPreviewSite => {\n if (\n !isRecord(site) ||\n !isId(site.id) ||\n typeof site.name !== \"string\" ||\n !Array.isArray(site.pages)\n )\n throw new MemberPreviewError(\"Invalid member site metadata.\");\n const pages = site.pages.map((page): MemberPreviewPage => {\n if (\n !isRecord(page) ||\n !isId(page.id) ||\n typeof page.title !== \"string\" ||\n !isProductionPath(page.path) ||\n typeof page.published !== \"boolean\"\n )\n throw new MemberPreviewError(\"Invalid member page metadata.\");\n return {\n id: page.id,\n title: page.title,\n path: accountPath(page.path),\n published: page.published,\n };\n });\n if (new Set(pages.map((p) => p.path)).size !== pages.length)\n throw new MemberPreviewError(\"Ambiguous member page paths.\");\n return { id: site.id, name: site.name, pages };\n });\n if (new Set(sites.map((s) => s.id)).size !== sites.length)\n throw new MemberPreviewError(\"Duplicate member site identifiers.\");\n const screens = value.system_screens.map((screen): { key: string } => {\n if (\n !isRecord(screen) ||\n typeof screen.key !== \"string\" ||\n !/^[a-z][a-z0-9_-]*$/.test(screen.key)\n )\n throw new MemberPreviewError(\"Invalid system screen metadata.\");\n return { key: screen.key };\n });\n return { members_sites: sites, system_screens: screens };\n}\nexport function parseSelection(\n params: URLSearchParams,\n): MemberPreviewSelection {\n const site = numericParam(params, \"members_site_id\");\n if (!site)\n throw new MemberPreviewError(\n \"Select a member site at /_members first.\",\n 400,\n );\n const memberType = params.get(\"member_type_id\");\n if (\n params.getAll(\"member_type_id\").length > 1 ||\n (memberType !== null && !/^[\\w-]{1,100}$/.test(memberType))\n )\n throw new MemberPreviewError(\"Invalid member type selection.\", 400);\n return {\n members_site_id: site,\n country_id: numericParam(params, \"country_id\"),\n language_id: numericParam(params, \"language_id\"),\n ...(memberType ? { member_type_id: memberType } : {}),\n };\n}\nfunction numericParam(\n params: URLSearchParams,\n key: string,\n): number | undefined {\n const value = params.get(key);\n if (value === null) return undefined;\n if (\n params.getAll(key).length !== 1 ||\n !/^[1-9]\\d*$/.test(value) ||\n !isId(Number(value))\n )\n throw new MemberPreviewError(`Invalid ${key}.`, 400);\n return Number(value);\n}\nexport function memberPreviewUrl(\n path: string,\n selection: MemberPreviewSelection,\n): string {\n const url = new URL(path, \"http://preview.invalid\");\n if (url.origin !== \"http://preview.invalid\")\n throw new MemberPreviewError(\"Expected a local member path.\", 400);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n url.pathname = `/_members${url.pathname}`;\n for (const key of [\n \"members_site_id\",\n \"country_id\",\n \"language_id\",\n \"member_type_id\",\n ] as const) {\n const value = selection[key];\n if (value === undefined) url.searchParams.delete(key);\n else url.searchParams.set(key, String(value));\n }\n return url.pathname + url.search + url.hash;\n}\nexport function parseMemberRender(\n value: unknown,\n themeId: number,\n selection: MemberPreviewSelection,\n pageId: number | null,\n screenKey: string | null = null,\n): { html: string; styles: string; templateStyles: string } {\n const preview = isRecord(value) ? value.preview : undefined;\n if (\n !isRecord(preview) ||\n preview.contract_version !== 1 ||\n preview.mode !== \"sample\" ||\n preview.theme_id !== themeId ||\n preview.members_site_id !== selection.members_site_id ||\n preview.members_page_id !== pageId ||\n preview.system_screen_key !== screenKey ||\n typeof preview.html_content !== \"string\" ||\n !(\n preview.page_stylesheet === null ||\n typeof preview.page_stylesheet === \"string\"\n ) ||\n !(\n preview.template_stylesheet === null ||\n typeof preview.template_stylesheet === \"string\"\n )\n ) {\n throw new MemberPreviewError(\n \"Incompatible member preview response. Update the server and CLI together.\",\n );\n }\n return {\n html: preview.html_content,\n styles: preview.page_stylesheet ?? \"\",\n templateStyles: preview.template_stylesheet ?? \"\",\n };\n}\n","import {\n isRecord,\n parseMemberMetadata,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./dev-server/member-preview-contract.js\";\n\ninterface NavigatorMetadata extends MemberPreviewMetadata {\n supported_system_screens: string[];\n}\n\n/** Read the running server's acknowledged route map, without sending credentials. */\nexport async function readPreviewMetadata(\n address: string,\n themeId: number,\n): Promise<NavigatorMetadata | undefined> {\n let value: unknown;\n try {\n const response = await fetch(`${address}/_members/_metadata`, {\n redirect: \"error\",\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) throw new Error(\"Member metadata unavailable\");\n value = await response.json();\n } catch {\n console.warn(\n \"Member preview is unavailable. Start an updated theme dev server to list member pages.\",\n );\n return undefined;\n }\n if (!isRecord(value) || value.mode !== \"sample\") {\n throw new Error(\n \"The running server does not provide compatible preview metadata.\",\n );\n }\n if (value.theme_id !== themeId) {\n throw new Error(\n `The running server is not serving theme #${themeId}. Use its theme ID or restart theme dev.`,\n );\n }\n if (\n !Array.isArray(value.supported_system_screens) ||\n !value.supported_system_screens.every(\n (key): key is string => typeof key === \"string\",\n )\n ) {\n throw new Error(\n \"The running server does not provide preview capabilities.\",\n );\n }\n return {\n ...parseMemberMetadata(value),\n supported_system_screens: value.supported_system_screens,\n };\n}\n\nexport function memberChoices(\n metadata: NavigatorMetadata | undefined,\n): Array<{ title: string; value: string }> {\n if (!metadata) return [];\n return metadata.members_sites.flatMap((site) => {\n const prefix = `Member · ${site.name} (#${site.id}) · `;\n const selection = { members_site_id: site.id };\n return [\n ...site.pages.map((page) => ({\n title: `${prefix}${page.title}${page.published ? \"\" : \" (draft)\"} — ${page.path}`,\n value: memberPreviewUrl(page.path, selection),\n })),\n ...metadata.system_screens\n .filter((screen) =>\n metadata.supported_system_screens.includes(screen.key),\n )\n .map((screen) => {\n return {\n title: `${prefix}${screen.key}`,\n value: memberPreviewUrl(`/${screen.key}`, selection),\n };\n }),\n ];\n });\n}\n\n/** A watcher update may remove a page while the prompt is open. */\nexport async function validateMemberDestination(\n address: string,\n themeId: number,\n path: string,\n): Promise<void> {\n const metadata = await readPreviewMetadata(address, themeId);\n if (!memberChoices(metadata).some((choice) => choice.value === path)) {\n throw new Error(\n \"This member page is no longer available. Run the navigator again to refresh the choices.\",\n );\n }\n}\n","/**\n * Generated API client functions for v0\n *\n * DO NOT EDIT THIS FILE DIRECTLY\n * This file is auto-generated. To update:\n * 1. Update the OpenAPI spec file\n * 2. Run: pnpm generate\n */\n\nimport type { FetchClient } from \"../lib/fetch-client\";\nimport type { operations } from \"../generated/v0\";\n\n// ============================================================================\n// applicationthemetemplates\n// ============================================================================\n\n/**\n * Lists all theme templates\n * Lists the templates available to a theme: the theme's own templates, global templates it has not overridden, and — when you filter by `themeable_type` — active templates from installed droplets. Requires a company admin user or company API token with the `application_themes` view permission. The list is not paginated.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeTemplates(\n client: FetchClient,\n params?: operations[\"listThemeTemplates\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeTemplates\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates`, params);\n}\n\n/**\n * Creates a theme template\n * Creates a template in a theme and publishes it immediately. Pass the theme in the `application_theme_id` query parameter. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, are refused with 422. When the company has moved template stylesheets to theme-level assets, a request that includes `stylesheet` is also refused with 422.\n *\n * @param client - Fetch client instance\n * @param body - body\n * @param params - params\n */\nexport async function createThemeTemplate(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n params: operations[\"createThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"createThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates`, body, { params });\n}\n\n/**\n * List all mysite themes\n * List all mysite themes\n *\n * @param client - Fetch client instance\n\n */\nexport async function listMysiteThemes(\n client: FetchClient,\n): Promise<\n operations[\"listMysiteThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/mysite_themes`);\n}\n\n/**\n * Retrieves a theme template\n * Returns a theme template with details. For section templates whose schema\ndeclares `@theme` or named standalone block references, the response\nincludes an `available_theme_blocks` array with the resolved block schemas\n(name, settings, presets). Private blocks (underscore-prefixed) are excluded\nfrom `@theme` results but included when explicitly referenced by name.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Updates a theme template\n * Updates a template's attributes. Send `publish: true` to publish the saved changes in the same call. Requires the `application_themes` update permission. Global templates and templates provided by a droplet cannot be edited (403). Changing a template into or out of a page type managed by a dedicated endpoint, such as `members_page`, is refused with 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplate(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplate\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_theme_templates/${id}`, body);\n}\n\n/**\n * Deletes a theme template\n * Deletes a template, along with any region rules that point at it. If it was the default for its type, another template is promoted to default. Requires the `application_themes` update permission. Global templates, droplet-provided templates, and page types managed by a dedicated endpoint are refused with 403; a template still referenced elsewhere returns 409.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_theme_templates/${id}`);\n}\n\n/**\n * Returns all available themeables for theme template\n * Lists the company's records of the template's type (for example products or pages) that can be assigned to this template, with the template currently assigned to each. Records already using this template are listed first, then by title. Requires the `application_themes` view permission. Paginated with `page` and `per_page` (default 20); only shareable template types are supported (400 otherwise).\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Get available variables for a theme template\n * Get available variables that can be used in the theme template\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getThemeTemplateAvailableVariables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getThemeTemplateAvailableVariables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getThemeTemplateAvailableVariables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_theme_templates/${id}/available_variables`,\n params,\n );\n}\n\n/**\n * Clones a theme template\n * Copies a template within its theme. The copy is named after the original with a ` (Clone)` suffix, is not the default, and gets its own copy of the template's translations. Requires the `application_themes` update permission. Page types managed by a dedicated endpoint, such as `members_page`, cannot be cloned (422).\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneThemeTemplate\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/clone`);\n}\n\n/**\n * Publishes the template\n * Publishes the template's current version, or the version given in `version`, so the storefront renders it. Requires the `application_themes` update permission. Global templates cannot be published (403). If the theme is connected to a Git repository, the change is also pushed there in the background.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function publishThemeTemplate(\n client: FetchClient,\n id: string | number,\n params?: operations[\"publishThemeTemplate\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"publishThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/publish`, {\n params,\n });\n}\n\n/**\n * Render a system screen in the selected member site's theme\n * Uses the same layout, settings, localization, and GlobalEmbeds pipeline as template previews. The screen body is sealed and never persisted. Unsaved shared settings and layout overrides are accepted; screen body, stylesheet, variables, resource and version overrides are rejected. The real SDK element uses read-only sample data in the builder without member credentials or live member API calls. The builder displays the response in its preview canvas.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function renderThemeSystemScreenPage(\n client: FetchClient,\n body: NonNullable<\n operations[\"renderThemeSystemScreenPage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeSystemScreenPage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/render_page`, body);\n}\n\n/**\n * Renders a page for a theme template\n * Renders the template to HTML for the theme builder's preview, optionally with unsaved content, a specific version, sample resources, a country and language, and a preview audience. Nothing is saved. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function renderThemeTemplatePage(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"renderThemeTemplatePage\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"renderThemeTemplatePage\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_theme_templates/${id}/render_page`,\n body,\n );\n}\n\n/**\n * Renders a section template\n * Renders a section template on its own, inside the theme's layout, and returns the HTML for the theme builder's preview. The `id` must be a template of type `sections`. Results are cached briefly per section version. Requires the `application_themes` view permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function renderThemeTemplateSection(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"renderThemeTemplateSection\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/render_section`);\n}\n\n/**\n * Sets a theme template as default\n * Makes this template the default for its type in its theme, so resources without an assigned template render with it. Requires the `application_themes` update permission. Returns the template without a wrapping key.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function setDefaultThemeTemplate(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"setDefaultThemeTemplate\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_theme_templates/${id}/set_default`);\n}\n\n/**\n * Updates themeable records to be used by the specified template\n * Assigns records of the template's type to this template (`added_ids`) and unassigns others (`removed_ids`) in one transaction. Supported for product, medium, enrollment pack, library, page, collection, post, category, and member page templates. Send at least one of the two lists. Requires the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeTemplateThemeables(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeTemplateThemeables\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeTemplateThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_theme_templates/${id}/themeables_update`,\n body,\n );\n}\n\n// ============================================================================\n// application-themes\n// ============================================================================\n\n/**\n * List application themes\n * Get all application themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listApplicationThemes(\n client: FetchClient,\n params?: operations[\"listApplicationThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes`, params);\n}\n\n/**\n * Create an application theme\n * Creates a new theme for your company. Requires a company Bearer token with permission to update themes. The theme starts in `draft` status unless you pass another `status`. Pass `cover_image_url` to set the cover image. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422. A Git repository is provisioned for the theme in the background when Git sync is available.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createApplicationTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createApplicationTheme\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes`, body);\n}\n\n/**\n * Get current active application theme\n * Returns your company's currently active theme with its templates, versions, images, and Git repositories. Requires a company Bearer token with permission to view themes. Returns 404 when no theme is active.\n *\n * @param client - Fetch client instance\n\n */\nexport async function getActiveApplicationTheme(\n client: FetchClient,\n): Promise<\n operations[\"getActiveApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/active`);\n}\n\n/**\n * Import an application theme from zip file\n * Starts importing a theme from a zip archive at a URL you provide. Requires a company Bearer token with permission to update themes. A placeholder theme is created immediately with status `importing`, and the archive is processed in the background; the theme moves to `draft` when the import succeeds or to `error` when it fails. `theme_type` defaults to `fluid`; pass `shopify` to import a Shopify theme archive. The response only confirms that the import started.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function importApplicationThemeFromZip(\n client: FetchClient,\n body: NonNullable<\n operations[\"importApplicationThemeFromZip\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"importApplicationThemeFromZip\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/import_zip`, body);\n}\n\n/**\n * Get an application theme\n * Returns one of your company's themes with its templates (including full Liquid content), versions, images, and Git repositories. Pass `version` to read the theme as it was at a past version. Requires a company Bearer token with permission to view themes. Themes belonging to another company return 404.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationTheme(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationTheme\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}`, params);\n}\n\n/**\n * Update an application theme\n * Updates a theme's attributes. Requires a company Bearer token with permission to update themes. Setting `active` to true makes the theme `active`; false sets it to `draft`. Send `publish` as `\"true\"` to publish the theme and sync it to its Git repository in the same request. When the company has moved theme stylesheets to theme-level assets, sending `custom_stylesheet` or `global_stylesheet` returns 422.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateApplicationTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateApplicationTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/application_themes/${id}`, body);\n}\n\n/**\n * Delete an application theme\n * Permanently deletes one of your company's themes along with its templates and region rules. Any Git repositories connected to the theme are archived afterwards. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/application_themes/${id}`);\n}\n\n/**\n * Returns available themeables for a given type scoped to the theme's company\n * Lists the company resources of one type (for example `product`, `page`, `collection`, or `enrollment_pack`) that templates in this theme can be assigned to, sorted by title. Pass `themeable` to choose the type; unsupported types return 400. Pass `search` to filter by title. Results are paginated, with page details in `meta.pagination`, 20 per page by default. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function getApplicationThemeAvailableThemeables(\n client: FetchClient,\n id: string | number,\n params?: operations[\"getApplicationThemeAvailableThemeables\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeAvailableThemeables\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${id}/available_themeables`,\n params,\n );\n}\n\n/**\n * Clone an application theme\n * Creates a copy of one of your company's themes, including its templates, images, assets, and member configuration. The copy keeps the source theme's name and starts in `draft` status. Requires a company Bearer token with permission to update themes. Returns 422 when the source theme's member configuration cannot be copied.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function cloneApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"cloneApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/clone`);\n}\n\n/**\n * Create a development reference clone of an application theme\n * Creates an isolated development theme while preserving existing DAM and ImageKit references without transferring asset bytes.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function cloneApplicationThemeForDevelopment(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"cloneApplicationThemeForDevelopment\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"cloneApplicationThemeForDevelopment\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${id}/clone_for_development`,\n body,\n );\n}\n\n/**\n * Get a short-lived download URL for the theme at a past version\n * Returns a pre-signed URL for a tarball of the theme's connected\nrepository at one commit. The URL is fetched **directly from the Git\nprovider by the client** and needs no authentication of its own — Fluid\nmints it and never proxies the bytes, so repository credentials stay\nserver-side.\n\nOne request returns the entire tree, binary assets included as real\nbytes. That matters for restoring a past version: an asset's current\nURL points at its current content, so anything built from per-file\nlinks would silently fail to revert images.\n\nRead-only. This exposes a version; it does not apply one. Clients with\na local checkout restore by writing the archive into their theme\nfolder, then pushing — the same local-then-publish flow as the Mist and\nPortal rollbacks. A client without one (the admin editor) uses the\ngit_restore operation instead, which applies a version to the theme's\ndraft records server-side.\n\nThe URL is short-lived. Re-request it rather than caching it.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param params - params\n */\nexport async function getApplicationThemeGitArchiveUrl(\n client: FetchClient,\n id: string | number,\n params: operations[\"getApplicationThemeGitArchiveUrl\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"getApplicationThemeGitArchiveUrl\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_archive_url`, params);\n}\n\n/**\n * Connect the theme to a Git repository, provisioning one if needed\n * Ensures the theme has a connected Git repository, creating and\nprovisioning one when it doesn't already have it.\n\nIdempotent — an already-connected theme returns its repository\nunchanged, and one whose earlier provisioning failed has it retried.\nSafe to call twice.\n\nProvisioning the upstream repository happens in the background, so a\nfreshly created connection reports `status: provisioning` and becomes\n`connected` shortly after. Poll the git_versions operation, or retry\nthis one, to observe the change.\n\nThis connects themes that automatic provisioning deliberately skips —\ndevelopment themes among them — because an explicit request is a\ndifferent intent from \"provision every theme on creation\".\n\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function connectApplicationThemeToGit(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"connectApplicationThemeToGit\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_connect`);\n}\n\n/**\n * Restore the theme's draft content to a past published version\n * Applies one published version back to the theme, for a client that has\nno local checkout to stage it in. The git_archive_url operation is the\nequivalent for one that does.\n\nPublishes as it writes: every record goes through the theme's own write\nseam, which publishes it. Publishing is per record and there is no\nwhole-theme publish, so a restore that only touched drafts would leave\nthe caller publishing every template by hand. For an **active** theme\nthis therefore goes live.\n\nIt remains undoable: the version being left behind is still in the\nhistory this restored from.\n\nThe version is read as a single archive rather than file by file, and\napplied in one transaction: a tree that cannot be written leaves the\ntheme as it was rather than half-restored.\n\nFiles the target version does not contain are removed, which is what\nmakes this a restore rather than a merge — a template added after that\nversion would otherwise survive into a tree that never had it.\n\nUndoing a restore is another restore: the version you were on is still\nin the history you restored from.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function restoreApplicationThemeGitVersion(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"restoreApplicationThemeGitVersion\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"restoreApplicationThemeGitVersion\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/git_restore`, body);\n}\n\n/**\n * List a theme's published version history from its connected Git repo\n * Returns the recent commits on the theme's connected Git repository,\nnewest first. Themes are DB-canonical: Fluid writes one commit per\npublish, so this log is the theme's published version history.\n\n`author_name` is the committing identity, which for anything Fluid\npushed is the Fluid GitHub App. The person who published is named in\n`message`.\n\nRead-only — this endpoint never provisions or repairs a repository.\n\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param [params] - params\n */\nexport async function listApplicationThemeGitVersions(\n client: FetchClient,\n id: string | number,\n params?: operations[\"listApplicationThemeGitVersions\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listApplicationThemeGitVersions\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/git_versions`, params);\n}\n\n/**\n * Import an application theme\n * Copies a theme from the Fluid theme catalog into your company. The copy is made in the background, so the response only confirms that the import started; the new theme appears in your theme list when it finishes. Requires a company Bearer token with permission to update themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function importApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.post(`/api/application_themes/${id}/import`);\n}\n\n/**\n * Publishes the theme\n * Publishes the theme so the storefront serves its latest content, then syncs it to any connected Git repository. Pass the optional `version` query parameter to publish a specific past version instead; an unknown version returns 404. Requires a company Bearer token with permission to update themes. Returns 422 when the theme's member configuration references a template or navigation item that does not exist.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function publishApplicationTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"publishApplicationTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/application_themes/${id}/publish`);\n}\n\n/**\n * Get theme assets\n * Lists the files (images, stylesheets, scripts, and other assets) uploaded to one of your company's themes. Requires a company Bearer token with permission to view themes.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeAssets(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeAssets\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/application_themes/${id}/theme_assets`);\n}\n\n// ============================================================================\n// applicationthemeresources\n// ============================================================================\n\n/**\n * Lists all theme resources\n * Lists resources for a theme owned by the current company, including the canonical members_sites.json snapshot. Themes without member sites receive an explicit empty version 2 document. Companyless root themes are not accessible through this endpoint. The content field remains JSON-encoded text.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function listThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"listThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(\n `/api/application_themes/${application_theme_id}/resources`,\n );\n}\n\n/**\n * Updates a theme resource\n * Submit member template dependencies together with members_sites.json using member_files so navigation is captured before any file changes. The files and member graph apply atomically; a rejected dependency rolls back the bundle. Existing single-resource writes remain supported. Missing manifests preserve existing configuration; deleting the file is refused.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function updateThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"updateThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.put(\n `/api/application_themes/${application_theme_id}/resources`,\n body,\n );\n}\n\n/**\n * Deletes a theme resource\n * Deletes one theme file, identified by its `key`, as the theme CLI does when a local file is removed. Send the `base_sha` from your last pull to guard against overwriting newer server changes (409 when stale). The member site manifest `members_sites.json` cannot be deleted this way. Requires a company admin token with the `application_themes` update permission.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function deleteThemeResource(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"deleteThemeResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"deleteThemeResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(\n `/api/application_themes/${application_theme_id}/resources`,\n { body },\n );\n}\n\n/**\n * Commits the theme's current state as one version\n * Called once by the CLI AFTER its per-file push loop, and the only thing that turns a push into a commit — the per-file writes themselves just mark the theme changed. This is the other half of check_push, which runs once before the loop. Omitting it is safe: the server sweeps anything left unsynced, so an older CLI lags by at most a sweep rather than going unsynced.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n */\nexport async function syncThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n): Promise<\n operations[\"syncThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/sync`,\n );\n}\n\n/**\n * Check a theme push before uploading\n * Merge-aware push preflight: CLI checks its stored base_sha matches the server's current content_version_sha before starting the per-file push loop. Enforcement lives on the per-file PUT/DELETE too; this endpoint is UX so the CLI can fail fast before touching any state.\n *\n * @param client - Fetch client instance\n * @param application_theme_id - application_theme_id\n * @param body - body\n */\nexport async function checkPushThemeResources(\n client: FetchClient,\n application_theme_id: string | number,\n body: NonNullable<\n operations[\"checkPushThemeResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"checkPushThemeResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(\n `/api/application_themes/${application_theme_id}/resources/check_push`,\n body,\n );\n}\n\n// ============================================================================\n// file-resources\n// ============================================================================\n\n/**\n * Returns a list of file resources\n * Lists the company's uploaded files (images, stylesheets, scripts, and other theme assets), newest first by default. Available to company admins with the `file_resources` view permission and to reps. Supports `search_query`, `sorted_by`, `with_types` (partial content-type match, such as `image` or `css`), and `size_greater_than` / `size_less_than` in megabytes. Paginated with `page` and `per_page` (default 10); totals are returned in `meta`.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listFileResources(\n client: FetchClient,\n params?: operations[\"listFileResources\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources`, params);\n}\n\n/**\n * Creates a file resource\n * Uploads a file to the company's asset library. Provide exactly one source: `content` for a text file, a multipart `file` upload, or `dam_asset_code` to link an existing digital asset; otherwise supply `url` with `filename`, `content_type`, and `content_size`. To attach the file to a theme or template, send both `relateable_type` (`ApplicationTheme` or `ApplicationThemeTemplate`) and `relateable_id`. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createFileResource(\n client: FetchClient,\n body: NonNullable<\n operations[\"createFileResource\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createFileResource\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources`, body);\n}\n\n/**\n * Creates multiple file resources\n * Creates several files in one transaction; if any file fails, none are created (422). Each entry accepts the same source fields as creating a single file (`content`, `file`, or `url` with its metadata). Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkCreateFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkCreateFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkCreateFileResources\"][\"responses\"][201][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/file_resources/bulk_create`, body);\n}\n\n/**\n * Deletes multiple file resources\n * Deletes the listed files that belong to the company. IDs that do not match a company file are ignored; `meta.destroyed_count` reports how many were deleted, and `ids` echoes the request. Requires the `file_resources` update permission.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function bulkDestroyFileResources(\n client: FetchClient,\n body: NonNullable<\n operations[\"bulkDestroyFileResources\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"bulkDestroyFileResources\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/bulk_destroy`, { body });\n}\n\n/**\n * Shows a file resource\n * Returns one of the company's uploaded files. Available to company admins with the `file_resources` view permission and to reps. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function showFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"showFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/file_resources/${id}`);\n}\n\n/**\n * Deletes a file resource\n * Deletes one of the company's uploaded files and returns its ID. Requires the `file_resources` update permission. Returns 404 when the file does not belong to the company.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function destroyFileResource(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"destroyFileResource\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/file_resources/${id}`);\n}\n\n// ============================================================================\n// root-themes\n// ============================================================================\n\n/**\n * List the company's root themes\n * Get all root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listRootThemes(\n client: FetchClient,\n params?: operations[\"listRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes`, params);\n}\n\n/**\n * Create a root theme\n * Submits a new theme to the Fluid theme library for review. The theme is created\nunder your company with an initial `1.0` version in `draft` status, built from the\nzip archive at `zip_file_url`. The version stays in draft until a Fluid platform\nadministrator approves or rejects it.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. The response carries only a confirmation message; call\n`GET /api/root_themes/my` to see the new theme and its versions. A failure while\ncreating the theme returns `422`.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createRootTheme(\n client: FetchClient,\n body: NonNullable<\n operations[\"createRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes`, body);\n}\n\n/**\n * List company root themes\n * Get all company root themes with optional filters\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listCompanyRootThemes(\n client: FetchClient,\n params?: operations[\"listCompanyRootThemes\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listCompanyRootThemes\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/root_themes/my`, params);\n}\n\n/**\n * Update a root theme\n * Updates a theme your company submitted to the Fluid theme library. You can change\nits `name`, `description`, `demo_url`, and `status`. When you pass a\n`zip_file_url` that differs from the latest version's archive, any pending\n`draft` versions are marked `superseded` and a new draft version with the next\nmajor version number (for example `2.0`) is created for review, using\n`changelog` as its changelog. Passing the same archive URL again does not create\na new version.\n\nRequires a company admin Bearer token with the `application_themes` update\npermission. You can only update themes that belong to your company; any other\n`id` returns `404`. The response carries only a confirmation message.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootTheme(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootTheme\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/root_themes/${id}`, body);\n}\n\n/**\n * Delete a root theme\n * Permanently deletes a theme your company submitted to the Fluid theme library,\ntogether with its versions and any theme region rules attached to it. Requires a\ncompany admin Bearer token with the `application_themes` update permission. You\ncan only delete themes that belong to your company; any other `id` returns\n`404`. If the theme cannot be deleted, the request returns `422`.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteRootTheme(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"deleteRootTheme\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.delete(`/api/root_themes/${id}`);\n}\n\n/**\n * Update a root theme status\n * Approves or rejects one version of a theme in the Fluid theme library. Restricted\nto Fluid platform administrators; any other caller receives `403`.\n\nSet `approved` to `true` to publish a `draft` version, or to re-publish a\npreviously `approved` version (for example, to roll back). The version moves to\n`publishing` and is imported in the background. Set `approved` to `false` to\nreject a `draft` version. Any other transition, such as rejecting an approved\nversion, returns `422`. An unknown theme `id` or a `theme_version_id` that does\nnot belong to the theme returns `404`.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateRootThemeStatus(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateRootThemeStatus\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateRootThemeStatus\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/root_themes/${id}/status`, body);\n}\n\n// ============================================================================\n// theme-region-rules\n// ============================================================================\n\n/**\n * List sitemap country routes (theme region rules)\n * Lists your sitemap country routes, which the API calls theme region rules. Country routes are the per-country overrides on the admin's Sitemap page: each one sets what visitors from one country see at a storefront route. A custom route can also have a default rule, with `region_code` `default`, that applies to every other country.\n *\n * @param client - Fetch client instance\n * @param [params] - params\n */\nexport async function listThemeRegionRules(\n client: FetchClient,\n params?: operations[\"listThemeRegionRules\"][\"parameters\"][\"query\"],\n): Promise<\n operations[\"listThemeRegionRules\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules`, params);\n}\n\n/**\n * Create a sitemap country route (theme region rule)\n * Creates a sitemap country route, which the API calls a theme region rule. Country routes are the per-country overrides on the admin's Sitemap page.\n\nA region rule controls what visitors from one region see at a storefront route:\na specific theme template (`redirect_type: template`), a redirect to\n`redirect_url` (`redirect`), or no page at all (`disabled`). Optionally point the\nrule at a specific resource with `resource_type` and `resource_id`.\n\nIf `application_theme_id` is omitted, the rule is attached to your company's\nactive theme. Requires a company admin or rep Bearer token with the\n`application_themes` update permission.\n\nThis operation is an upsert keyed on theme, `route_path`, and `region_code`. When\nno matching rule exists it creates one and returns `201`. When one exists, the\nrequest body replaces its state and the response is `200`; omitted fields fall\nback to their defaults (`priority` 0, `active` true, `redirect_type`\n`template`). To change only some fields of an existing rule, use the update\noperation instead.\n *\n * @param client - Fetch client instance\n * @param body - body\n */\nexport async function createThemeRegionRule(\n client: FetchClient,\n body: NonNullable<\n operations[\"createThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"createThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.post(`/api/theme_region_rules`, body);\n}\n\n/**\n * Get a sitemap country route (theme region rule)\n * Returns one sitemap country route, which the API calls a theme region rule.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function getThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<\n operations[\"getThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.get(`/api/theme_region_rules/${id}`);\n}\n\n/**\n * Update a sitemap country route (theme region rule)\n * Updates a sitemap country route, which the API calls a theme region rule. Send only the fields you want to change.\n *\n * @param client - Fetch client instance\n * @param id - id\n * @param body - body\n */\nexport async function updateThemeRegionRule(\n client: FetchClient,\n id: string | number,\n body: NonNullable<\n operations[\"updateThemeRegionRule\"][\"requestBody\"]\n >[\"content\"][\"application/json\"],\n): Promise<\n operations[\"updateThemeRegionRule\"][\"responses\"][200][\"content\"][\"application/json\"]\n> {\n return client.patch(`/api/theme_region_rules/${id}`, body);\n}\n\n/**\n * Delete a sitemap country route (theme region rule)\n * Deletes a sitemap country route, which the API calls a theme region rule.\n *\n * @param client - Fetch client instance\n * @param id - id\n */\nexport async function deleteThemeRegionRule(\n client: FetchClient,\n id: string | number,\n): Promise<void> {\n return client.delete(`/api/theme_region_rules/${id}`);\n}\n","import { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { getLastDevThemeId } from \"../plugin-state.js\";\nimport {\n memberChoices,\n readPreviewMetadata,\n validateMemberDestination,\n} from \"../theme/navigation-members.js\";\nimport { themes } from \"@fluid-app/themes-api-client\";\n\nfunction localSuggest(\n input: string,\n choices: prompts.Choice[],\n): prompts.Choice[] {\n if (!input) return choices;\n const lower = input.toLowerCase();\n return choices.filter((c) => c.title.toLowerCase().includes(lower));\n}\n\ninterface ThemeTemplate {\n id: number;\n name: string;\n themeable_type: string;\n default: boolean;\n}\n\ninterface TemplatesResponse {\n templates: ThemeTemplate[];\n}\n\nconst THEMEABLE_TYPE_MAP: Record<string, string> = {\n \"/home\": \"home_page\",\n \"/home/shop\": \"shop_page\",\n \"/home/join\": \"join_page\",\n \"/cart\": \"cart_page\",\n \"/home/blog\": \"post_page\",\n \"/home/categories\": \"category_page\",\n \"/home/collections\": \"collection_page\",\n};\n\nconst STATIC_ROUTES = [\n { label: \"Home\", path: \"/home\" },\n { label: \"Shop\", path: \"/home/shop\" },\n { label: \"Join / Sign Up\", path: \"/home/join\" },\n { label: \"Cart\", path: \"/cart\" },\n { label: \"Blog\", path: \"/home/blog\" },\n { label: \"Categories (all)\", path: \"/home/categories\" },\n { label: \"Collections (all)\", path: \"/home/collections\" },\n] as const;\n\nconst RESOURCE_ROUTES = [\n {\n label: \"Category\",\n type: \"category\",\n template: \"/home/categories/%s\",\n fallback: \"/home/categories\",\n },\n {\n label: \"Collection\",\n type: \"collection\",\n template: \"/home/collections/%s\",\n fallback: \"/home/collections\",\n },\n {\n label: \"Product\",\n type: \"product\",\n template: \"/home/products/%s\",\n fallback: \"/home/shop\",\n },\n {\n label: \"Library\",\n type: \"library\",\n template: \"/home/libraries/%s\",\n fallback: \"/home/libraries\",\n },\n {\n label: \"Post\",\n type: \"post\",\n template: \"/home/posts/%s\",\n fallback: \"/home/blog\",\n },\n {\n label: \"Media\",\n type: \"medium\",\n template: \"/home/media/%s\",\n fallback: \"/home/media\",\n },\n {\n label: \"Enrollment Pack\",\n type: \"enrollment_pack\",\n template: \"/home/enrollments/%s\",\n fallback: \"/home/join\",\n },\n {\n label: \"Page\",\n type: \"page\",\n template: \"/home/pages/%s\",\n fallback: \"/home/pages\",\n },\n] as const;\n\nasync function fetchTemplatesForType(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n): Promise<ThemeTemplate[]> {\n const params = new URLSearchParams({\n application_theme_id: String(themeId),\n themeable_type: themeableType,\n published: \"true\",\n });\n const body = await api.get<TemplatesResponse>(\n `/api/application_theme_templates?${params}`,\n );\n return body.templates ?? [];\n}\n\nasync function selectTemplate(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n themeableType: string,\n onCancel: () => void,\n): Promise<number | null> {\n const templates = await fetchTemplatesForType(api, themeId, themeableType);\n if (templates.length <= 1) return null;\n\n const templateChoices = templates.map((t) => ({\n title: `${t.name}${t.default ? \" (default)\" : \"\"}`,\n value: t.id,\n }));\n const { templateId } = await prompts(\n {\n type: \"autocomplete\",\n name: \"templateId\",\n message: \"Select a template\",\n choices: templateChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n return templateId ?? null;\n}\n\nexport function createNavigateCommand(): Command {\n return new Command(\"navigate\")\n .description(\"Interactively navigate to a route in the dev server browser\")\n .option(\"--host <host>\", \"Dev server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Dev server port\", \"9292\")\n .option(\"-t, --theme <id>\", \"Theme ID (defaults to active dev theme)\")\n .action(async (opts: { host: string; port: string; theme?: string }) => {\n requireToken();\n\n const themeId = opts.theme ? Number(opts.theme) : getLastDevThemeId();\n\n if (!themeId) {\n console.error(\n \"No active dev theme. Run `fluid theme dev` first, or pass --theme <id>.\",\n );\n process.exit(1);\n }\n\n const address = `http://${opts.host}:${opts.port}`;\n\n await navigateTheme(createApiClient(), themeId, address);\n });\n}\n\n/** Shared by theme navigate and theme dev --navigate. */\nexport async function navigateTheme(\n api: ReturnType<typeof createApiClient>,\n themeId: number,\n address: string,\n): Promise<void> {\n type Choice = {\n title: string;\n value:\n | string\n | {\n resourceType: string;\n template: string;\n fallback: string;\n label: string;\n };\n };\n const choices: Choice[] = [\n ...STATIC_ROUTES.map((r) => ({ title: r.label, value: r.path })),\n ...RESOURCE_ROUTES.map((r) => ({\n title: `${r.label} (select specific)`,\n value: {\n resourceType: r.type,\n template: r.template,\n fallback: r.fallback,\n label: r.label,\n },\n })),\n ];\n\n let cancelled = false;\n const onCancel = () => {\n cancelled = true;\n return false;\n };\n\n const metadata = await readPreviewMetadata(address, themeId);\n choices.push(...memberChoices(metadata));\n const unavailable = metadata?.system_screens.filter(\n (screen) => !metadata.supported_system_screens.includes(screen.key),\n );\n if (unavailable?.length)\n console.log(\n `System-screen preview unavailable: ${unavailable.map((screen) => screen.key).join(\", \")}.`,\n );\n\n const { dest } = await prompts(\n {\n type: \"autocomplete\",\n name: \"dest\",\n message: \"Select a route\",\n choices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n\n if (!dest) return;\n\n let path: string;\n let themeableType: string | undefined;\n\n if (typeof dest === \"string\") {\n if (dest.startsWith(\"/_members/\")) {\n await validateMemberDestination(address, themeId, dest);\n }\n path = dest;\n themeableType = THEMEABLE_TYPE_MAP[dest];\n } else {\n themeableType = dest.resourceType;\n const body = await themes.getApplicationThemeAvailableThemeables(\n api,\n themeId,\n { themeable: dest.resourceType, per_page: 50 },\n );\n const resources = body.available_themeables ?? [];\n\n if (!resources.length) {\n console.log(`No ${dest.label} resources found, using listing page.`);\n path = dest.fallback;\n } else {\n const resourceChoices = resources.map((r) => ({\n title: r.title ?? r.slug ?? \"Untitled\",\n value: r.slug,\n }));\n const { slug } = await prompts(\n {\n type: \"autocomplete\",\n name: \"slug\",\n message: `Select a ${dest.label.toLowerCase()}`,\n choices: resourceChoices,\n suggest: (input: string, choices: prompts.Choice[]) =>\n Promise.resolve(localSuggest(input, choices)),\n },\n { onCancel },\n );\n if (cancelled || !slug) return;\n path = dest.template.replace(\"%s\", encodeURIComponent(slug as string));\n }\n }\n\n let templateParam = \"\";\n if (themeableType) {\n const templateId = await selectTemplate(\n api,\n themeId,\n themeableType,\n onCancel,\n );\n if (templateId) {\n templateParam = `?theme_template_id=${templateId}`;\n }\n }\n\n if (cancelled) return;\n const url = `${address}${path}${templateParam}`;\n console.log(`\\nNavigating to: ${url}\\n`);\n const open = (await import(\"open\")).default;\n await open(url);\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface ThemeConfig {\n themeId: number;\n themeName: string;\n company: string;\n /**\n * Server's `content_version_sha` at the moment of the last pull.\n * Sent back to the server as `base_sha` on push — the server rejects\n * with 409 if it no longer matches, so a push against stale data\n * can't silently clobber. Optional so a `.fluid-theme.json` written\n * by an older CLI (checksums-era) still deserializes; when absent,\n * the CLI falls back to the pre-merge-aware behavior (no server-side\n * check).\n */\n baseSha?: string;\n /** Digest of the managed ImageKit references captured with this baseline. */\n assetManifestSha?: string;\n}\n\n/**\n * Shape of a `.fluid-theme.json` written by the pre-shadow-repo CLI.\n * We read it only to migrate the shadow repo forward on first\n * new-CLI use; we never write this shape back.\n */\nexport interface LegacyThemeConfig extends ThemeConfig {\n lastPulledAt?: string | null;\n checksums?: Record<string, string>;\n}\n\nconst CONFIG_FILE = \".fluid-theme.json\";\n\n/**\n * `company` must be the bare subdomain slug — the dev server builds\n * `<company>.fluid.app` from it, so a full domain here produces an\n * unreachable `<slug>.fluid.app.fluid.app` host. Some external tooling\n * writes the full domain (or a URL); accept those and reduce them to\n * the subdomain.\n */\nfunction normalizeCompany(company: string): string {\n const host = company\n .replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\")\n .replace(/[/?#].*$/, \"\");\n return host.replace(/\\.fluid\\.app$/i, \"\");\n}\n\nfunction configPath(themeRoot: string): string {\n return join(themeRoot, CONFIG_FILE);\n}\n\n/** Read `.fluid-theme.json` from a theme directory, or null if it doesn't exist. */\nexport function readThemeConfig(themeRoot: string): ThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n const config = JSON.parse(raw) as ThemeConfig;\n if (typeof config.company === \"string\") {\n config.company = normalizeCompany(config.company);\n }\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Read a legacy config (pre-shadow-repo). Used only by the migration\n * path on first new-CLI pull to seed shadow HEAD from files whose\n * local content still matches their stored sha256 checksum.\n */\nexport function readLegacyThemeConfig(\n themeRoot: string,\n): LegacyThemeConfig | null {\n const path = configPath(themeRoot);\n if (!existsSync(path)) return null;\n try {\n const raw = readFileSync(path, \"utf-8\");\n return JSON.parse(raw) as LegacyThemeConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a push or pull to `theme` may use this directory's own sync state\n * and rebind it to `theme`.\n *\n * Development themes are scratch copies `fluid theme dev` creates and syncs.\n * Using one here would move the project's sync baseline to the scratch copy\n * and rebind the project, so a later Publish would update the copy instead of\n * the theme the project was pulled from. Push syncs such a theme separately;\n * pull refuses. An explicit `--theme` to any other theme still rebinds.\n */\nexport function mayBindTheme(\n existing: ThemeConfig | null,\n theme: { id: number; status?: string | null },\n): boolean {\n return (\n !existing || existing.themeId === theme.id || theme.status !== \"development\"\n );\n}\n\n/** Why `mayBindTheme` refused, with the way forward. */\nexport function developmentThemeRefusal(\n existing: ThemeConfig,\n theme: { id: number; name: string },\n): string {\n return (\n `${theme.name} (#${theme.id}) is a development theme, and this project is bound to ${existing.themeName} (#${existing.themeId}). ` +\n \"Pulling it here would replace this project's sync state. Use `fluid theme dev` or `fluid theme push -t` to update the development theme, or pull it into a separate directory with --root.\"\n );\n}\n\n/** Write `.fluid-theme.json` to a theme directory. */\nexport function writeThemeConfig(themeRoot: string, config: ThemeConfig): void {\n const path = configPath(themeRoot);\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n","const TEXT_TYPES: Record<string, string> = {\n \".liquid\": \"text/x-liquid\",\n \".json\": \"application/json\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".html\": \"text/html\",\n \".txt\": \"text/plain\",\n \".md\": \"text/markdown\",\n \".svg\": \"image/svg+xml\",\n};\n\nconst BINARY_TYPES: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".eot\": \"application/vnd.ms-fontobject\",\n \".otf\": \"font/otf\",\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".mp4\": \"video/mp4\",\n \".webm\": \"video/webm\",\n \".mp3\": \"audio/mpeg\",\n \".wav\": \"audio/wav\",\n};\n\nexport interface MimeType {\n name: string;\n isText: boolean;\n}\n\nexport function mimeTypeFor(ext: string): MimeType {\n const text = TEXT_TYPES[ext];\n if (text) return { name: text, isText: true };\n\n const binary = BINARY_TYPES[ext];\n if (binary) return { name: binary, isText: false };\n\n return { name: \"application/octet-stream\", isText: false };\n}\n","const THEME_LEVEL_RESOURCE_KEYS = new Set([\n \"global_styles.css\",\n \"styles.css\",\n \"variables.json\",\n \"members_sites.json\",\n]);\n\n/**\n * Template types Fluid has retired. The resources API answers 404 for them, so\n * a theme that still carries their folders must not try to upload them: member\n * home pages are `members_page` templates on the `/` route, sign-in is hosted,\n * and account pages became member pages.\n */\nexport const RETIRED_TEMPLATE_TYPES: ReadonlySet<string> = new Set([\n \"account_page\",\n \"members_dashboard\",\n \"members_login\",\n]);\n\nexport function isRetiredTemplateKey(relativePath: string): boolean {\n const [prefix] = normalizeThemeResourceKey(relativePath).split(\"/\");\n return prefix !== undefined && RETIRED_TEMPLATE_TYPES.has(prefix);\n}\n\nconst COMPOSITE_RESOURCE_FILE_NAMES = new Set([\n \"index.liquid\",\n \"styles.css\",\n \"variables.json\",\n]);\n\nfunction hasSafeSegments(key: string): boolean {\n const segments = key.split(\"/\");\n return segments.every(\n (segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\",\n );\n}\n\nexport function normalizeThemeResourceKey(value: string): string {\n return value.replaceAll(\"\\\\\", \"/\");\n}\n\n/**\n * Whether a relative file path is a resource key accepted by the Fluid themes\n * resource API. Local project files (package manifests, QA evidence, scripts,\n * source baselines, and similar agent artifacts) must never be uploaded.\n */\nexport function isThemeResourceKey(relativePath: string): boolean {\n const key = normalizeThemeResourceKey(relativePath);\n if (!hasSafeSegments(key)) return false;\n if (THEME_LEVEL_RESOURCE_KEYS.has(key)) return true;\n if (isRetiredTemplateKey(key)) return false;\n\n const segments = key.split(\"/\");\n const prefix = segments[0];\n const fileName = segments.at(-1);\n if (!prefix || !fileName || segments.length < 2) return false;\n\n if (\n prefix === \"assets\" ||\n prefix === \"config\" ||\n prefix === \"locales\" ||\n prefix === \"layouts\"\n ) {\n return true;\n }\n\n return segments.length >= 3 && COMPOSITE_RESOURCE_FILE_NAMES.has(fileName);\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n // Duplicate \"blocks\" keys\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return RESERVED_SECTION_TYPES.has(type) || EXTENSION_URI_PATTERN.test(type);\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension URIs are never flagged (see `isNonLocalSectionType`).\n * Emits one `error` diagnostic per missing section type per template.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: \"error\",\n message: `references missing section '${reference.type}'`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","import {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n statSync,\n} from \"node:fs\";\nimport { extname, basename, relative, dirname } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { mimeTypeFor, type MimeType } from \"./mime-type.js\";\nimport { normalizeThemeResourceKey } from \"./resource-key.js\";\nimport {\n validateSchemaText,\n type Diagnostic,\n type BlocksSchemaType,\n} from \"@fluid-app/theme-schema\";\n\n// Top-level theme folders that are not page templates. Everything else at the\n// top level (home_page, product, page, footer, navbar, …) is a page template.\nconst NON_TEMPLATE_DIRS = new Set([\n \"sections\",\n \"blocks\",\n \"components\",\n \"layouts\",\n \"config\",\n \"assets\",\n \"locales\",\n]);\n\nexport class ThemeFile {\n readonly absolutePath: string;\n readonly relativePath: string;\n readonly mime: MimeType;\n\n constructor(absolutePath: string, root: string) {\n this.absolutePath = absolutePath;\n this.relativePath = normalizeThemeResourceKey(relative(root, absolutePath));\n this.mime = mimeTypeFor(extname(absolutePath).toLowerCase());\n }\n\n get name(): string {\n return basename(this.absolutePath);\n }\n\n get isText(): boolean {\n return this.mime.isText;\n }\n\n get isLiquid(): boolean {\n return this.absolutePath.endsWith(\".liquid\");\n }\n\n get isJson(): boolean {\n return this.absolutePath.endsWith(\".json\");\n }\n\n get exists(): boolean {\n return existsSync(this.absolutePath);\n }\n\n read(): string {\n return readFileSync(this.absolutePath, \"utf-8\");\n }\n\n readBinary(): Buffer {\n return readFileSync(this.absolutePath);\n }\n\n write(content: string | Buffer): void {\n mkdirSync(dirname(this.absolutePath), { recursive: true });\n if (typeof content === \"string\") {\n writeFileSync(this.absolutePath, content, \"utf-8\");\n } else {\n writeFileSync(this.absolutePath, content);\n }\n }\n\n checksum(): string {\n const content = this.isText ? this.read() : this.readBinary();\n return createHash(\"sha256\").update(content).digest(\"hex\");\n }\n\n size(): number {\n return statSync(this.absolutePath).size;\n }\n\n get isTemplate(): boolean {\n // Page templates (home_page, product, footer, navbar, …) live in top-level\n // page-type folders and expect blocks as objects. The reserved categories\n // below either expect blocks as arrays (sections, blocks, components) or\n // carry no block schema (layouts, config, assets, locales).\n const parts = this.relativePath.split(/[/\\\\]/);\n return parts.length >= 2 && !NON_TEMPLATE_DIRS.has(parts[0]!);\n }\n\n validateSchema(): Diagnostic[] {\n if (!this.isLiquid) return [];\n\n const blocksSchemaType: BlocksSchemaType = this.isTemplate\n ? \"object\"\n : \"array\";\n\n return validateSchemaText(this.read(), { blocksSchemaType });\n }\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\n\nconst IGNORE_FILE = \".fluidignore\";\n\ninterface Pattern {\n negated: boolean;\n pattern: string;\n}\n\nexport class FluidIgnore {\n private patterns: Pattern[];\n\n constructor(root: string) {\n this.patterns = this.parse(join(root, IGNORE_FILE));\n }\n\n ignore(relativePath: string): boolean {\n let result = false;\n for (const { negated, pattern } of this.patterns) {\n if (this.match(pattern, relativePath)) {\n result = !negated;\n }\n }\n return result;\n }\n\n private parse(filePath: string): Pattern[] {\n if (!existsSync(filePath)) return [];\n return readFileSync(filePath, \"utf-8\")\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l && !l.startsWith(\"#\"))\n .map((l) => {\n const negated = l.startsWith(\"!\");\n let pattern = negated ? l.slice(1) : l;\n if (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n return { negated, pattern };\n });\n }\n\n private match(pattern: string, path: string): boolean {\n if (pattern.endsWith(\"/\")) {\n return path.startsWith(pattern) || path === pattern.slice(0, -1);\n }\n if (pattern.includes(\"/\")) {\n return this.fnmatch(pattern, path);\n }\n return this.fnmatch(pattern, path) || this.fnmatch(pattern, basename(path));\n }\n\n private fnmatch(pattern: string, str: string): boolean {\n const re = pattern\n .split(\"**\")\n .map((p) =>\n p\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\"),\n )\n .join(\".*\");\n return new RegExp(`^${re}$`).test(str);\n }\n}\n","import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { ThemeFile } from \"./file.js\";\nimport { FluidIgnore } from \"./fluid-ignore.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nconst THEME_MARKERS = [\"templates\", \"assets\", \"config\"];\nconst THEME_ASSET_MANIFEST = \".fluid-assets.json\";\n\nexport class ThemeRoot {\n readonly root: string;\n readonly ignore: FluidIgnore;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.ignore = new FluidIgnore(this.root);\n }\n\n isValid(): boolean {\n return (\n existsSync(join(this.root, THEME_ASSET_MANIFEST)) ||\n THEME_MARKERS.some((m) => {\n try {\n return statSync(join(this.root, m)).isDirectory();\n } catch {\n return false;\n }\n })\n );\n }\n\n files(): ThemeFile[] {\n return this.glob(this.root).filter(\n (f) =>\n isThemeResourceKey(f.relativePath) &&\n !this.ignore.ignore(f.relativePath),\n );\n }\n\n isResourcePath(pathOrFile: string | ThemeFile): boolean {\n const file = this.file(pathOrFile);\n return isThemeResourceKey(file.relativePath);\n }\n\n file(pathOrFile: string | ThemeFile): ThemeFile {\n if (pathOrFile instanceof ThemeFile) return pathOrFile;\n const abs = isAbsolute(pathOrFile)\n ? pathOrFile\n : join(this.root, pathOrFile);\n return new ThemeFile(abs, this.root);\n }\n\n private glob(dir: string): ThemeFile[] {\n const results: ThemeFile[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\")) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \"node_modules\") continue;\n results.push(...this.glob(full));\n } else if (entry.isFile()) {\n results.push(new ThemeFile(full, this.root));\n }\n }\n return results;\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The runtime is shipped with this CLI, not taken from a mutable theme/CDN URL. */\nexport function findMemberScreenAssets(): string | undefined {\n let directory = dirname(fileURLToPath(import.meta.url));\n while (true) {\n for (const candidate of [\n join(directory, \"dist-cli-preview\"),\n join(directory, \"dist\", \"dist-cli-preview\"),\n ]) {\n if (existsSync(join(candidate, \"capabilities.json\"))) return candidate;\n }\n const parent = dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\nexport class MemberScreenAssets {\n readonly screens: string[];\n constructor(private readonly directory = findMemberScreenAssets()) {\n this.screens = [];\n if (!directory) return;\n try {\n const value: unknown = JSON.parse(\n readFileSync(join(directory, \"capabilities.json\"), \"utf8\"),\n );\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"version\" in value) ||\n value.version !== 1 ||\n !(\"screens\" in value) ||\n !Array.isArray(value.screens)\n )\n return;\n if (\n !value.screens.every(\n (screen: unknown) =>\n typeof screen === \"string\" && /^[a-z][a-z-]*$/.test(screen),\n )\n )\n return;\n if (!existsSync(join(directory, \"account-screens.js\"))) return;\n this.screens = value.screens;\n } catch {\n /* An incomplete build must not advertise screen support. */\n }\n }\n read(path: string): Buffer | undefined {\n if (\n !this.directory ||\n !/^(?:account-screens\\.js|chunks\\/[a-zA-Z0-9_.-]+\\.js)$/.test(path)\n )\n return undefined;\n try {\n return readFileSync(join(this.directory, path));\n } catch {\n return undefined;\n }\n }\n}\n","export function buildHotReloadScript(mode: \"full-page\" | \"off\"): string {\n return `\n<script>\n(() => {\n window.__FLUID_CLI_ENV__ = ${JSON.stringify({ mode })};\n\n class HotReload {\n static reloadMode() { return window.__FLUID_CLI_ENV__.mode; }\n static isActive() { return HotReload.reloadMode() !== \"off\"; }\n static setHotReloadCookie(files) {\n const expires = new Date(Date.now() + 3000).toUTCString();\n document.cookie = \\`hot_reload_files=\\${files.join(\",\")};expires=\\${expires};path=/\\`;\n }\n static refresh(files) {\n HotReload.setHotReloadCookie(files);\n console.log(\"[HotReload] Refreshing page\");\n window.location.reload();\n }\n }\n\n class SSEClient {\n constructor(url, handler) {\n if (typeof EventSource === \"undefined\") {\n console.error(\"[HotReload] EventSource not supported in this browser.\");\n return;\n }\n console.log(\"[HotReload] Initializing…\");\n this.url = url;\n this.handler = handler;\n }\n connect() {\n const es = new EventSource(this.url);\n es.onopen = () => console.log(\"[HotReload] SSE connected.\");\n es.onerror = () => {\n console.log(\"[HotReload] SSE closed. Reconnecting in 5s…\");\n es.close();\n setTimeout(() => this.connect(), 5000);\n };\n es.onmessage = (msg) => {\n const data = JSON.parse(msg.data);\n if (data.reload_page) { HotReload.refresh([]); return; }\n this.handler(data);\n };\n }\n }\n\n if (HotReload.isActive()) {\n new SSEClient(\"/hot-reload\", (data) => {\n if (data.modified) HotReload.refresh(data.modified);\n }).connect();\n }\n})();\n</script>`;\n}\n\nexport function injectHotReload(\n html: string,\n mode: \"full-page\" | \"off\",\n): string {\n const script = buildHotReloadScript(mode);\n if (html.includes(\"</body>\")) {\n return html.replace(\"</body>\", `${script}\\n</body>`);\n }\n return html + script;\n}\n","import {\n parse,\n parseFragment,\n serialize,\n type DefaultTreeAdapterMap,\n} from \"parse5\";\nimport type {\n MemberPreviewMetadata,\n MemberPreviewSelection,\n} from \"./member-preview-contract.js\";\nimport { memberPreviewUrl, accountPath } from \"./member-preview-contract.js\";\nimport { injectHotReload } from \"./hot-reload.js\";\n\ntype Element = DefaultTreeAdapterMap[\"element\"];\ntype Parent = DefaultTreeAdapterMap[\"parentNode\"];\n\nexport function escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (char) =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[\n char\n ] ?? char,\n );\n}\n/** Use the configured Rails environment, never a browser-supplied upstream. */\nexport function memberOrigin(apiBase: string, company: string): string {\n const url = new URL(apiBase);\n if (!/^[a-z0-9-]+$/i.test(company))\n throw new Error(\"Invalid company subdomain.\");\n if (url.hostname.startsWith(\"api.\"))\n url.hostname = `${company}.account.${url.hostname.slice(4)}`;\n return url.origin;\n}\nexport interface MemberHtmlOptions {\n origin: string;\n localOrigin: string;\n path: string;\n selection: MemberPreviewSelection;\n metadata: MemberPreviewMetadata;\n reloadMode: \"full-page\" | \"off\";\n screenKey?: string;\n}\nexport function renderMemberHtml(\n render: { html: string; styles: string; templateStyles: string },\n opts: MemberHtmlOptions,\n): string {\n const document = parse(render.html);\n const elements: Element[] = [];\n function visit(parent: Parent): void {\n for (const child of parent.childNodes) {\n if (\"tagName\" in child) {\n elements.push(child);\n visit(child);\n }\n }\n }\n visit(document);\n const head = elements.find((el) => el.tagName === \"head\")!;\n // Remove renderer/editor bases, retaining one environment-correct base for\n // scripts, fonts, images, CSS url() and imports. Stylesheet URLs keep their own base.\n for (const el of elements) {\n if (el.tagName === \"base\" && el.parentNode)\n el.parentNode.childNodes.splice(el.parentNode.childNodes.indexOf(el), 1);\n // Do not load a theme's live account runtime in a CLI sample preview.\n if (el.tagName === \"script\") {\n const src = el.attrs.find((attr) => attr.name === \"src\")?.value ?? \"\";\n if (\n /\\/(?:account-screens(?:\\/|\\.)|member-storefront-sdk\\/)/.test(src) &&\n el.parentNode\n )\n el.parentNode.childNodes.splice(\n el.parentNode.childNodes.indexOf(el),\n 1,\n );\n }\n if (\n el.tagName === \"fluid-account-screen\" ||\n el.tagName === \"fluid-messaging\"\n ) {\n el.attrs = el.attrs.filter(\n (attr) => attr.name !== \"preview\" && attr.name !== \"path\",\n );\n el.attrs.push({ name: \"preview\", value: \"true\" });\n if (opts.screenKey)\n el.attrs.push({\n name: \"path\",\n value: new URL(opts.path, opts.origin).pathname.slice(1),\n });\n }\n if (el.tagName === \"form\") {\n el.attrs = el.attrs.filter((attr) => attr.name !== \"action\");\n el.attrs.push({\n name: \"action\",\n value: `${opts.localOrigin}/_members/_actions`,\n });\n }\n const formAction = el.attrs.find((attr) => attr.name === \"formaction\");\n if (formAction) formAction.value = `${opts.localOrigin}/_members/_actions`;\n if (el.tagName === \"a\" || el.tagName === \"area\") {\n const href = el.attrs.find((a) => a.name === \"href\");\n if (href) href.value = previewLink(href.value, opts);\n }\n }\n const runtime = parseFragment(\n `<script type=\"module\" src=\"${escapeHtml(opts.localOrigin)}/_members/_runtime/account-screens.js\"></script>`,\n );\n head.childNodes.unshift(...runtime.childNodes);\n const base = parseFragment(\n `<base href=\"${escapeHtml(new URL(opts.path, opts.origin).href)}\">`,\n );\n head.childNodes.unshift(...base.childNodes);\n // Both strings are trusted output from the authenticated theme renderer. The\n // template stylesheet is raw CSS, whereas page_stylesheet is HTML link/style tags.\n const styles = parseFragment(render.styles);\n head.childNodes.push(...styles.childNodes);\n const style = parseFragment(\"<style></style>\").childNodes[0];\n if (style && \"childNodes\" in style) {\n style.childNodes.push({\n nodeName: \"#text\",\n value: render.templateStyles,\n parentNode: style,\n });\n head.childNodes.push(style);\n }\n // Form submissions are not part of the sample preview contract. In particular\n // do not let a theme's live sign-in/sign-out form inherit the upstream base.\n const guard = parseFragment(\n `<script>document.addEventListener(\"submit\",function(event){event.preventDefault();},true);</script>`,\n );\n head.childNodes.unshift(...guard.childNodes);\n const context = JSON.stringify(opts.selection).replace(/</g, \"\\\\u003c\");\n const navigation = parseFragment(`<script>\n document.addEventListener(\"fluid-account-navigate\",function(event){\n event.preventDefault(); event.stopImmediatePropagation();\n var slug=event.detail && event.detail.slug;\n if(typeof slug!==\"string\" || !/^[a-z][a-z0-9_-]*(?:[/?#]|$)/.test(slug) || slug.includes(\"\\\\\\\\\")) return;\n var next=new URL(\"/_members/\"+slug,window.location.origin);\n if(next.origin!==window.location.origin || !next.pathname.startsWith(\"/_members/\")) return;\n var context=${context};\n [\"members_site_id\",\"country_id\",\"language_id\",\"member_type_id\"].forEach(function(key){\n if(context[key]===undefined) next.searchParams.delete(key); else next.searchParams.set(key,context[key]);\n });\n window.location.assign(next.href);\n },true);\n </script>`);\n head.childNodes.unshift(...navigation.childNodes);\n return injectHotReload(serialize(document), opts.reloadMode);\n}\nexport function previewLink(href: string, opts: MemberHtmlOptions): string {\n if (href.startsWith(\"#\"))\n return (\n opts.localOrigin + memberPreviewUrl(opts.path, opts.selection) + href\n );\n let target: URL;\n try {\n target = new URL(href, new URL(opts.path, opts.origin));\n } catch {\n return href;\n }\n if (![\"http:\", \"https:\"].includes(target.protocol)) return href;\n const absolute = /^[a-z][a-z\\d+.-]*:/i.test(href) || href.startsWith(\"//\");\n const accountLink = absolute && target.origin === opts.origin;\n if (absolute && !accountLink && target.origin !== opts.localOrigin)\n return href;\n target.pathname = accountPath(target.pathname);\n const isMember =\n accountLink ||\n /^\\/(auth(?:\\/|$)|login$|logout$)/.test(target.pathname) ||\n target.pathname.startsWith(\"/_members/\") ||\n opts.metadata.members_sites.some((site) =>\n site.pages.some((p) => p.path === target.pathname),\n ) ||\n opts.metadata.system_screens.some(\n (screen) =>\n target.pathname === `/${screen.key}` ||\n target.pathname.startsWith(`/${screen.key}/`),\n );\n const path = target.pathname + target.search + target.hash;\n return (\n opts.localOrigin +\n (isMember ? memberPreviewUrl(path, opts.selection) : path)\n );\n}\n","import { MemberScreenAssets } from \"./member-screen-assets.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ApiClient } from \"../../api.js\";\nimport { getApiBase } from \"../../api.js\";\nimport {\n MemberPreviewError,\n isRecord,\n parseMemberMetadata,\n parseMemberRender,\n parseSelection,\n memberPreviewUrl,\n type MemberPreviewMetadata,\n} from \"./member-preview-contract.js\";\nimport {\n escapeHtml,\n memberOrigin,\n renderMemberHtml,\n} from \"./member-preview-html.js\";\n\n/** Deny DNS rebinding and cross-origin access before using Node's CLI credential. */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n )\n return false;\n if (req.headers.origin && req.headers.origin !== url.origin) return false;\n return req.headers[\"sec-fetch-site\"] !== \"cross-site\";\n}\n\nexport class MemberPreview {\n private metadata: MemberPreviewMetadata | undefined;\n private loading: Promise<MemberPreviewMetadata> | undefined;\n private readonly origin: string;\n constructor(\n private readonly api: ApiClient,\n private readonly theme: { id: number; company: string },\n private readonly reloadMode: \"full-page\" | \"off\",\n private readonly screenAssets = new MemberScreenAssets(),\n ) {\n this.origin = memberOrigin(getApiBase(), theme.company);\n }\n /** Only call after every upload in a watcher batch has been acknowledged. */\n async refreshAfterSync(): Promise<void> {\n if (!this.metadata && !this.loading) return;\n if (this.loading) await this.loading;\n this.metadata = undefined;\n const next = await this.loadMetadata();\n this.metadata = next;\n }\n private async loadMetadata(): Promise<MemberPreviewMetadata> {\n return parseMemberMetadata(\n await this.api.get(\"/api/company/members_sites\", {\n application_theme_id: this.theme.id,\n }),\n );\n }\n private async getMetadata(): Promise<MemberPreviewMetadata> {\n if (this.metadata) return this.metadata;\n this.loading ??= this.loadMetadata();\n try {\n this.metadata = await this.loading;\n return this.metadata;\n } finally {\n this.loading = undefined;\n }\n }\n async handle(req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n if (url.pathname !== \"/_members\" && !url.pathname.startsWith(\"/_members/\"))\n return false;\n res.setHeader(\"Cache-Control\", \"private, no-store\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n try {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n res.setHeader(\"Allow\", \"GET, HEAD\");\n throw new MemberPreviewError(\"Member preview is read-only.\", 405);\n }\n if (url.pathname.startsWith(\"/_members/_runtime/\")) {\n const asset = this.screenAssets.read(\n url.pathname.slice(\"/_members/_runtime/\".length),\n );\n if (!asset)\n throw new MemberPreviewError(\n \"Preview runtime asset unavailable. Rebuild or update the CLI.\",\n 404,\n );\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : asset);\n return true;\n }\n const metadata = await this.getMetadata();\n if (url.pathname === \"/_members/_metadata\") {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : JSON.stringify({\n theme_id: this.theme.id,\n mode: \"sample\",\n ...metadata,\n supported_system_screens: metadata.system_screens\n .filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n )\n .map((screen) => screen.key),\n }),\n );\n return true;\n }\n if (\n (url.pathname === \"/_members\" || url.pathname === \"/_members/\") &&\n !url.searchParams.has(\"members_site_id\")\n ) {\n const screens = metadata.system_screens.filter((screen) =>\n this.screenAssets.screens.includes(screen.key),\n );\n const links = metadata.members_sites\n .map((site) => {\n const destinations = [\n ...site.pages.map((page) => ({\n path: page.path,\n title: page.title + (page.published ? \"\" : \" (draft)\"),\n })),\n ...screens.map((screen) => ({\n path: `/${screen.key}`,\n title: screen.key + \" (sample)\",\n })),\n ];\n return `<h2>${escapeHtml(site.name)}</h2><ul>${destinations.map((page) => `<li><a href=\"${escapeHtml(memberPreviewUrl(page.path, { members_site_id: site.id }))}\">${escapeHtml(page.title)}</a></li>`).join(\"\")}</ul>`;\n })\n .join(\"\");\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(\n req.method === \"HEAD\"\n ? undefined\n : `<!doctype html><meta charset=\"utf-8\"><title>Member preview</title><h1>Member preview</h1><p>Sample data. Select a page to preview its site's layout.</p>${links || \"<p>No member pages in this theme.</p>\"}`,\n );\n return true;\n }\n const selection = parseSelection(url.searchParams);\n const site = metadata.members_sites.find(\n (s) => s.id === selection.members_site_id,\n );\n if (!site)\n throw new MemberPreviewError(\n \"This site no longer belongs to the preview theme. Select a page at /_members.\",\n 404,\n );\n const path = url.pathname.slice(\"/_members\".length) || \"/\";\n const screen = metadata.system_screens.find(\n (s) => path === `/${s.key}` || path.startsWith(`/${s.key}/`),\n );\n const page = screen ? undefined : site.pages.find((p) => p.path === path);\n if (!page && !screen)\n throw new MemberPreviewError(\n \"This member page is unavailable in the selected site. Select a page at /_members.\",\n 404,\n );\n if (screen && !this.screenAssets.screens.includes(screen.key))\n throw new MemberPreviewError(\n \"This system screen has no packaged sample preview. Rebuild or update the CLI.\",\n 501,\n );\n const response = await this.api.post(\n `/api/v202604/themes/${this.theme.id}/member-preview`,\n {\n ...selection,\n ...(page\n ? { members_page_id: page.id }\n : { system_screen_key: screen!.key }),\n },\n );\n const render = parseMemberRender(\n response,\n this.theme.id,\n selection,\n page?.id ?? null,\n screen?.key ?? null,\n );\n const html = renderMemberHtml(render, {\n origin: this.origin,\n localOrigin: url.origin,\n path: path + url.search,\n selection,\n metadata,\n reloadMode: this.reloadMode,\n screenKey: screen?.key,\n });\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : html);\n } catch (error) {\n // Never echo upstream envelopes, credentials, or internal diagnostics into HTML.\n const status =\n error instanceof MemberPreviewError\n ? error.status\n : isRecord(error) &&\n typeof error.status === \"number\" &&\n [401, 403, 404, 422].includes(error.status)\n ? error.status\n : 502;\n const message =\n error instanceof MemberPreviewError\n ? error.message\n : status === 401 || status === 403\n ? \"Member preview authorization failed. Check your CLI login and theme permissions.\"\n : \"Member preview could not be rendered. Check the selected page and server preview support.\";\n res.writeHead(status, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(req.method === \"HEAD\" ? undefined : message);\n }\n return true;\n }\n}\n","import type { ServerResponse } from \"node:http\";\n\nexport class SSEStream {\n private responses = new Set<ServerResponse>();\n\n add(res: ServerResponse): void {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.write(\":\\n\\n\");\n this.responses.add(res);\n res.on(\"close\", () => this.responses.delete(res));\n }\n\n broadcast(data: string): void {\n const payload = `data: ${data}\\n\\n`;\n for (const res of this.responses) {\n try {\n res.write(payload);\n } catch {\n this.responses.delete(res);\n }\n }\n }\n\n close(): void {\n for (const res of this.responses) {\n try {\n res.end();\n } catch {\n // ignore\n }\n }\n this.responses.clear();\n }\n\n get size(): number {\n return this.responses.size;\n }\n}\n","import type { ApiClient } from \"../../api.js\";\n\nexport const TEMPLATE_OVERRIDE_PARAM = \"theme_template_id\";\n\ninterface TemplateRecord {\n id: number;\n name: string;\n themeable_type: string;\n application_theme_id: number | null;\n}\n\nexport type TemplateOverride =\n | { kind: \"use\"; id: string }\n | {\n kind: \"unresolved\";\n /** No dev template matches, or several do and none can be preferred. */\n reason: \"missing\" | \"ambiguous\";\n requestedId: string;\n themeableType: string;\n name: string;\n candidateIds: number[];\n };\n\nexport interface TemplateOverrideResolver {\n resolve(requestedId: string): Promise<TemplateOverride>;\n /** Forget cached mappings — templates were added to or removed from the dev theme. */\n clear(): void;\n}\n\n/**\n * Maps an explicit `theme_template_id` onto the dev theme the server renders.\n *\n * Mist, `theme navigate` and bookmarked URLs carry template ids from the\n * theme the project was pulled from. Forwarded unchanged next to\n * `x-fluid-theme: <dev theme>`, the storefront renders that original\n * template and the local edits never show. Resolve such an id to the dev\n * theme's template with the same themeable type and name instead. Ids that\n * already belong to the dev theme and global and extension templates (no\n * owning theme) pass through untouched. A failed lookup also passes through,\n * with a warning, since the page may then show the original template.\n */\nexport function createTemplateOverrideResolver(\n api: ApiClient,\n devThemeId: number,\n): TemplateOverrideResolver {\n // Only successful mappings are cached: a missing counterpart may appear on\n // the next sync, and a failed lookup should be retried.\n const cache = new Map<string, string>();\n\n const warnLookupFailed = (requestedId: string, error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(\n `[Templates] Could not map template #${requestedId} to dev theme #${devThemeId} (${message}); rendering it unchanged, which may show the original theme's template.`,\n );\n };\n\n /** `null` means the lookup failed: forward the id as-is, uncached. */\n const lookup = async (\n requestedId: string,\n ): Promise<TemplateOverride | null> => {\n let requested: TemplateRecord | undefined;\n try {\n const body = await api.get<{\n application_theme_template?: TemplateRecord;\n }>(`/api/application_theme_templates/${encodeURIComponent(requestedId)}`);\n requested = body.application_theme_template;\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n if (!requested) return null;\n const { name, themeable_type: themeableType } = requested;\n const sourceThemeId = requested.application_theme_id;\n if (!sourceThemeId || sourceThemeId === devThemeId) {\n return { kind: \"use\", id: requestedId };\n }\n\n const params = new URLSearchParams({\n application_theme_id: String(devThemeId),\n themeable_type: themeableType,\n });\n let candidates: TemplateRecord[];\n try {\n const body = await api.get<{ templates?: TemplateRecord[] }>(\n `/api/application_theme_templates?${params}`,\n );\n candidates = body.templates ?? [];\n } catch (error) {\n warnLookupFailed(requestedId, error);\n return null;\n }\n\n const matches = candidates.filter(\n (t) =>\n t.application_theme_id === devThemeId &&\n t.themeable_type === themeableType &&\n t.name === name,\n );\n const [counterpart] = matches;\n if (matches.length === 1 && counterpart) {\n return { kind: \"use\", id: String(counterpart.id) };\n }\n // Templates carry no link back to the one they were cloned from, so a\n // duplicated type and name has no right answer — picking one would let\n // an unrelated edit change what the preview renders.\n return {\n kind: \"unresolved\",\n reason: matches.length === 0 ? \"missing\" : \"ambiguous\",\n requestedId,\n themeableType,\n name,\n candidateIds: matches.map((t) => t.id),\n };\n };\n\n return {\n async resolve(requestedId) {\n const cached = cache.get(requestedId);\n if (cached) return { kind: \"use\", id: cached };\n const result = await lookup(requestedId);\n if (!result) return { kind: \"use\", id: requestedId };\n if (result.kind === \"use\") cache.set(requestedId, result.id);\n return result;\n },\n clear() {\n cache.clear();\n },\n };\n}\n","import https from \"node:https\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { injectHotReload } from \"./hot-reload.js\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport {\n TEMPLATE_OVERRIDE_PARAM,\n type TemplateOverride,\n type TemplateOverrideResolver,\n} from \"./template-overrides.js\";\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"content-security-policy\",\n]);\n\nexport interface ProxyOptions {\n company: string;\n themeId: number;\n reloadMode: \"full-page\" | \"off\";\n pendingFiles?: () => Array<{ relativePath: string; read: () => string }>;\n /** Maps explicit template overrides onto the dev theme's copies. */\n templateOverrides?: TemplateOverrideResolver;\n}\n\nexport async function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: ProxyOptions,\n): Promise<void> {\n const companyHost = `${opts.company}.fluid.app`;\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && typeof v === \"string\") {\n headers[k] = v;\n }\n }\n headers[\"host\"] = companyHost;\n headers[\"x-fluid-theme\"] = String(opts.themeId);\n headers[\"user-agent\"] = \"Fluid CLI\";\n headers[\"accept-encoding\"] = \"identity\";\n\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n url.searchParams.set(\"_fd\", \"0\");\n url.searchParams.set(\"pb\", \"0\");\n\n const requestedTemplateId = url.searchParams.get(TEMPLATE_OVERRIDE_PARAM);\n if (requestedTemplateId && opts.templateOverrides) {\n const override = await opts.templateOverrides.resolve(requestedTemplateId);\n if (override.kind === \"use\") {\n url.searchParams.set(TEMPLATE_OVERRIDE_PARAM, override.id);\n } else if (isPageRequest(req)) {\n // Rendering the original theme's template here would silently show\n // content without the local edits — fail loudly instead. API calls\n // that carry the page's id (e.g. the member SDK) still go upstream.\n const html = injectHotReload(\n unresolvedTemplatePage(override, opts.themeId),\n opts.reloadMode,\n );\n res.writeHead(404, {\n \"content-type\": \"text/html; charset=utf-8\",\n \"content-length\": String(Buffer.byteLength(html)),\n });\n res.end(html);\n return;\n }\n }\n\n const pending = opts.pendingFiles?.() ?? [];\n const isGet = req.method === \"GET\" || req.method === \"HEAD\";\n let method = req.method ?? \"GET\";\n let body: string | Buffer | undefined;\n\n if (pending.length > 0 && isGet) {\n method = \"POST\";\n const params = new URLSearchParams();\n params.set(\"_method\", req.method ?? \"GET\");\n for (const f of pending) {\n params.set(`replace_templates[${f.relativePath}]`, f.read());\n }\n const token = getAuthToken();\n if (token) headers[\"authorization\"] = `Bearer ${token}`;\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = params.toString();\n headers[\"content-length\"] = String(Buffer.byteLength(body));\n } else if (!isGet) {\n body = await readBody(req);\n if (body.length > 0) {\n headers[\"content-length\"] = String(body.length);\n }\n }\n\n return new Promise((resolve, reject) => {\n const options: https.RequestOptions = {\n hostname: companyHost,\n port: 443,\n path: url.pathname + (url.search || \"\"),\n method,\n headers,\n };\n\n const proxyReq = https.request(options, (proxyRes) => {\n const contentType = proxyRes.headers[\"content-type\"] ?? \"\";\n const isHtml = contentType.includes(\"text/html\");\n\n const responseHeaders: Record<string, string | string[]> = {};\n for (const [k, v] of Object.entries(proxyRes.headers)) {\n if (!HOP_BY_HOP.has(k.toLowerCase()) && v !== undefined) {\n responseHeaders[k] = v as string | string[];\n }\n }\n\n if (isHtml) {\n const chunks: Buffer[] = [];\n proxyRes.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on(\"end\", () => {\n let html = Buffer.concat(chunks).toString(\"utf-8\");\n html = injectHotReload(html, opts.reloadMode);\n responseHeaders[\"content-length\"] = String(Buffer.byteLength(html));\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n res.end(html);\n resolve();\n });\n } else {\n res.writeHead(proxyRes.statusCode ?? 200, responseHeaders);\n proxyRes.pipe(res);\n proxyRes.on(\"end\", resolve);\n }\n });\n\n proxyReq.on(\"error\", (err) => {\n reject(err);\n });\n\n if (body) proxyReq.write(body);\n proxyReq.end();\n });\n}\n\nconst PAGE_FETCH_DESTS = new Set([\"document\", \"iframe\", \"frame\"]);\n\n/** Browser navigations to a storefront page, as opposed to API or asset calls. */\nfunction isPageRequest(req: IncomingMessage): boolean {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return false;\n const path = (req.url ?? \"/\").split(\"?\")[0] ?? \"/\";\n if (path.startsWith(\"/api/\")) return false;\n const dest = req.headers[\"sec-fetch-dest\"];\n // Mist and the web editor render the preview in an iframe.\n if (typeof dest === \"string\") return PAGE_FETCH_DESTS.has(dest);\n const accept = req.headers[\"accept\"];\n return typeof accept === \"string\" && accept.includes(\"text/html\");\n}\n\nfunction unresolvedTemplatePage(\n override: Extract<TemplateOverride, { kind: \"unresolved\" }>,\n devThemeId: number,\n): string {\n const template =\n `Template #${escapeHtml(override.requestedId)} ` +\n `(${escapeHtml(override.themeableType)} / ${escapeHtml(override.name)})`;\n const problem =\n override.reason === \"missing\"\n ? `dev theme #${devThemeId} has no template with that type and name. Sync or create it locally, or`\n : `dev theme #${devThemeId} has several templates with that type and name ` +\n `(#${override.candidateIds.join(\", #\")}), so the edited copy is ambiguous. Remove the duplicates, or`;\n return (\n `<!doctype html><title>Template not in dev theme</title>` +\n `<p>${template} belongs to another theme, and ${problem} ` +\n `remove <code>${TEMPLATE_OVERRIDE_PARAM}</code> to use the default.</p>`\n );\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n req.on(\"error\", reject);\n });\n}\n","import { isApiError } from \"@fluid-app/themes-api-client\";\n\n// Name of the bundled skill that walks the caller through migrating\n// per-template `styles.css` files to theme-level assets. See\n// `skills/template-stylesheet-to-asset-migration/SKILL.md` in this\n// package.\nexport const STYLESHEET_MIGRATION_SKILL =\n \"template-stylesheet-to-asset-migration\";\n\n// Detect the backend's 422 rejection for legacy stylesheet keys\n// (`ApplicationThemeResources::UpdateAction#stylesheet_rejected_response`\n// on the Rails side). Both the message and the errors payload identify\n// the rejection uniquely; match on either so a future wording tweak on\n// one surface does not silently drop the hint.\nfunction isStylesheetKeyRejection(error: {\n status: number;\n message: string;\n data: unknown;\n}): boolean {\n if (error.status !== 422) return false;\n if (/stylesheet.*no longer accepted/i.test(error.message)) return true;\n if (error.data && typeof error.data === \"object\") {\n const resourceErrors = (\n error.data as { application_theme_resource?: { key?: unknown } }\n ).application_theme_resource;\n if (\n resourceErrors &&\n typeof resourceErrors.key === \"string\" &&\n /stylesheet.*no longer accepted/i.test(resourceErrors.key)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Extract a human-readable message from a caught error. ApiError's default\n * `toString` prefixes with `ApiError:` and the class name; this returns the\n * API's own `error_message` verbatim so the CLI surfaces messages like\n * \"This stylesheet key is no longer accepted. Upload stylesheets as\n * theme-level assets.\" directly.\n *\n * When the error is the legacy stylesheet key rejection specifically,\n * a hint is appended pointing to the bundled migration skill — the\n * caller needs to move the per-template `styles.css` bytes to `assets/`\n * and stop pushing the deprecated column key.\n */\nexport function formatError(e: unknown): string {\n if (isApiError(e)) {\n const status = e.status ? ` [${e.status}]` : \"\";\n const hint = isStylesheetKeyRejection(e)\n ? `\\n ↳ Run \\`fluid theme skills install\\` — the bundled \\`${STYLESHEET_MIGRATION_SKILL}\\` skill can help you migrate this to a theme-level asset.`\n : \"\";\n return `${e.message}${status}${hint}`;\n }\n if (e instanceof Error) return e.message;\n return String(e);\n}\n","import { relative, sep } from \"node:path\";\nimport chokidar from \"chokidar\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ThemeFile } from \"../file.js\";\nimport { formatError } from \"../format-error.js\";\n\nexport type FileChangeHandler = (\n modified: ThemeFile[],\n added: ThemeFile[],\n removed: ThemeFile[],\n /** When the filesystem event arrived, not when the handler got to run.\n * Handlers are serialized behind awaited uploads, so a queued event can\n * start long after it happened — anything deciding where one edit ends and\n * the next begins has to use this, or it measures upload duration. */\n arrivedAt: number,\n) => Promise<void>;\n\nfunction relativeThemePath(root: ThemeRoot, filePath: string): string {\n return relative(root.root, filePath).split(sep).join(\"/\");\n}\n\nexport function watchTheme(\n root: ThemeRoot,\n handler: FileChangeHandler,\n): () => Promise<void> {\n const watcher = chokidar.watch(root.root, {\n ignoreInitial: true,\n ignored: (filePath: string) => {\n if (filePath.includes(\"node_modules\")) return true;\n try {\n const rel = relativeThemePath(root, filePath);\n const basename = rel.split(/[\\\\/]/).pop() ?? \"\";\n return basename.startsWith(\".\") || root.ignore.ignore(rel);\n } catch {\n return false;\n }\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n\n let pending = Promise.resolve();\n const enqueue = (fn: () => Promise<void>) => {\n // The change handler has its own internal try/catch around\n // per-file uploads/deletes, but anything that throws outside\n // that (e.g. `root.file()`, `validateSchema()`) must still be\n // surfaced — a bare `.catch(() => {})` here previously swallowed\n // it and left watch mode silently stuck.\n pending = pending.then(fn).catch((e) => {\n console.error(` [Watcher] change handling failed: ${formatError(e)}`);\n });\n };\n\n watcher.on(\"change\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([root.file(filePath)], [], [], arrivedAt));\n });\n\n watcher.on(\"add\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [root.file(filePath)], [], arrivedAt));\n });\n\n watcher.on(\"unlink\", (filePath) => {\n const rel = relativeThemePath(root, filePath);\n if (!root.isResourcePath(filePath) || root.ignore.ignore(rel)) return;\n const arrivedAt = Date.now();\n enqueue(() => handler([], [], [root.file(filePath)], arrivedAt));\n });\n\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport { lstatSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nconst METADATA = [\".fluid-assets.json\", \".fluid-theme.json\", \".fluidignore\"];\n\nexport class BackgroundPullChangedError extends Error {\n constructor() {\n super(\n \"Local files changed during background pull; remaining updates skipped.\",\n );\n }\n}\n\n/** Keep the preflight snapshot across asynchronous download/merge work. Each\n * mutation checks its original bytes synchronously and records only our writes.\n * This prevents an edit made after preflight from becoming a merge input or\n * being overwritten by a deferred write, asset cleanup, or metadata update. */\nexport class BackgroundPullGuard {\n private readonly root: string;\n private readonly expected: Map<string, string | null>;\n\n constructor(root: string) {\n this.root = resolve(root);\n this.expected = this.snapshot();\n }\n\n assertUnchanged(): void {\n const current = this.snapshot();\n for (const key of new Set([...current.keys(), ...this.expected.keys()])) {\n if ((current.get(key) ?? null) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n }\n\n mutate(keys: readonly string[], action: () => void): void {\n for (const key of keys) {\n if (this.fingerprint(key) !== (this.expected.get(key) ?? null)) {\n throw new BackgroundPullChangedError();\n }\n }\n action();\n for (const key of keys) this.expected.set(key, this.fingerprint(key));\n }\n\n private fingerprint(key: string): string | null {\n const path = resolve(this.root, key);\n if (!path.startsWith(this.root + sep))\n throw new BackgroundPullChangedError();\n // Fail closed for a parent replaced by a symlink while awaiting Git/network.\n for (let cursor = path; ; cursor = dirname(cursor)) {\n try {\n if (lstatSync(cursor).isSymbolicLink())\n throw new BackgroundPullChangedError();\n } catch (error) {\n if (\n !(\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n )\n throw error;\n }\n if (cursor === this.root) break;\n }\n try {\n return createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n return null;\n throw error;\n }\n }\n\n private snapshot(): Map<string, string | null> {\n const result = new Map<string, string | null>();\n const visit = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n continue;\n if (entry.isSymbolicLink()) throw new BackgroundPullChangedError();\n const path = join(directory, entry.name);\n if (entry.isDirectory()) visit(path);\n else {\n const key = relative(this.root, path).split(sep).join(\"/\");\n result.set(key, this.fingerprint(key));\n }\n }\n };\n visit(this.root);\n for (const key of METADATA) result.set(key, this.fingerprint(key));\n return result;\n }\n}\n","import { normalizeThemeResourceKey } from \"./resource-key.js\";\n\nexport class CaseCollisionError extends Error {\n constructor(readonly collisions: readonly (readonly string[])[]) {\n super(\n `Theme contains paths that differ only by letter case and cannot be synchronized safely:\\n${collisions\n .map((paths) => ` ${paths.join(\", \")}`)\n .join(\"\\n\")}`,\n );\n this.name = \"CaseCollisionError\";\n }\n}\n\n/** Refuse paths a case-insensitive checkout cannot represent independently. */\nexport function assertNoCaseCollisions(paths: readonly string[]): void {\n const pathsByFoldedKey = new Map<string, Set<string>>();\n for (const rawPath of paths) {\n const path = normalizeThemeResourceKey(rawPath);\n const foldedKey = path.toLowerCase();\n const matchingPaths = pathsByFoldedKey.get(foldedKey) ?? new Set<string>();\n matchingPaths.add(path);\n pathsByFoldedKey.set(foldedKey, matchingPaths);\n }\n\n const collisions = [...pathsByFoldedKey.values()]\n .filter((matchingPaths) => matchingPaths.size > 1)\n .map((matchingPaths) => [...matchingPaths].sort())\n .sort(([left = \"\"], [right = \"\"]) => left.localeCompare(right));\n\n if (collisions.length > 0) throw new CaseCollisionError(collisions);\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MANIFEST_FILE = \".fluid-assets.json\";\nconst MANIFEST_VERSION = 1;\n\n/**\n * A tiny shadow-repo placeholder for a manifest-backed asset. It preserves\n * the path as a deletion baseline without retaining the asset bytes locally.\n */\nexport const MANAGED_ASSET_SHADOW_SENTINEL = \"fluid-managed-asset\\n\";\n\nexport interface ThemeAssetLink {\n /** Theme that last supplied this asset reference. */\n sourceThemeId: number;\n /** SHA-256 checksum of the source resource, when the API provides one. */\n checksum?: string;\n /** ImageKit URL saved into themes that consume this asset. */\n url?: string;\n /** MIME type required to create a URL-backed FileResource. */\n contentType?: string;\n /** File size required to create a URL-backed FileResource. */\n contentSize?: number;\n /** Optional display metadata copied with the ImageKit URL. */\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n /** A dev-only source that must survive pull until a push makes it durable. */\n pending?: boolean;\n /** DAM asset identity retained as provenance for CLI-uploaded assets. */\n damAssetCode?: string;\n}\n\ninterface ThemeAssetManifestDocument {\n version: number;\n assets: Record<string, ThemeAssetLink>;\n}\n\n/**\n * Tracks binary theme assets that deliberately live only on the server.\n *\n * The manifest is a dotfile so it is excluded from theme uploads and file\n * watching. It is written before the corresponding local file is removed,\n * which prevents `delete: true` from mistaking the removed byte source for a\n * request to delete its remote FileResource.\n */\nexport class ThemeAssetManifest {\n private assets: Record<string, ThemeAssetLink>;\n private readonly path: string;\n\n constructor(themeRoot: string) {\n this.path = join(themeRoot, MANIFEST_FILE);\n this.assets = readDocument(this.path).assets;\n }\n\n reload(): void {\n this.assets = readDocument(this.path).assets;\n }\n\n keys(): string[] {\n return Object.keys(this.assets);\n }\n\n entries(): Array<[string, ThemeAssetLink]> {\n return Object.entries(this.assets).map(([key, link]) => [\n key,\n copyLink(link),\n ]);\n }\n\n /**\n * Stable digest of the URL references represented by this manifest.\n * Pull baselines exclude pending entries because those belong only to an\n * existing dev target and are deliberately absent from the pulled source.\n */\n fingerprint(opts: { excludePending?: boolean } = {}): string {\n const entries = this.entries()\n .filter(([, link]) => !opts.excludePending || !link.pending)\n .toSorted(([left], [right]) =>\n left < right ? -1 : left > right ? 1 : 0,\n );\n return createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n }\n\n has(key: string): boolean {\n return this.assets[key] !== undefined;\n }\n\n get(key: string): ThemeAssetLink | undefined {\n const link = this.assets[key];\n return link ? copyLink(link) : undefined;\n }\n\n set(key: string, link: ThemeAssetLink): void {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(link)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n this.assets[key] = copyLink(link);\n }\n\n delete(key: string): void {\n delete this.assets[key];\n }\n\n write(): void {\n const document: ThemeAssetManifestDocument = {\n version: MANIFEST_VERSION,\n assets: copyAssets(this.assets),\n };\n const tempPath = `${this.path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n mkdirSync(dirname(this.path), { recursive: true });\n writeFileSync(tempPath, JSON.stringify(document, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, this.path);\n } catch (error) {\n try {\n unlinkSync(tempPath);\n } catch {\n // The temporary file may not have been written yet.\n }\n throw error;\n }\n }\n}\n\nfunction readDocument(path: string): ThemeAssetManifestDocument {\n if (!existsSync(path)) return emptyDocument();\n\n try {\n return parseDocument(JSON.parse(readFileSync(path, \"utf-8\")));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Could not read ${MANIFEST_FILE}: ${message}`);\n }\n}\n\nfunction parseDocument(value: unknown): ThemeAssetManifestDocument {\n if (!isRecord(value) || value[\"version\"] !== MANIFEST_VERSION) {\n throw new Error(`expected version ${MANIFEST_VERSION}`);\n }\n\n const rawAssets = value[\"assets\"];\n if (!isRecord(rawAssets)) throw new Error(\"expected an assets object\");\n\n const assets: Record<string, ThemeAssetLink> = {};\n for (const [key, rawLink] of Object.entries(rawAssets)) {\n if (!isThemeAssetKey(key) || !isThemeAssetLink(rawLink)) {\n throw new Error(`invalid asset entry for ${key}`);\n }\n assets[key] = copyLink(rawLink);\n }\n\n return { version: MANIFEST_VERSION, assets };\n}\n\nfunction emptyDocument(): ThemeAssetManifestDocument {\n return { version: MANIFEST_VERSION, assets: {} };\n}\n\nfunction copyAssets(\n assets: Record<string, ThemeAssetLink>,\n): Record<string, ThemeAssetLink> {\n return Object.fromEntries(\n Object.entries(assets).map(([key, link]) => [key, copyLink(link)]),\n );\n}\n\nfunction copyLink(link: ThemeAssetLink): ThemeAssetLink {\n return {\n sourceThemeId: link.sourceThemeId,\n ...(typeof link.checksum === \"string\" ? { checksum: link.checksum } : {}),\n ...(typeof link.url === \"string\" ? { url: link.url } : {}),\n ...(typeof link.contentType === \"string\"\n ? { contentType: link.contentType }\n : {}),\n ...(typeof link.contentSize === \"number\"\n ? { contentSize: link.contentSize }\n : {}),\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n ...(link.pending === true ? { pending: true } : {}),\n ...(typeof link.damAssetCode === \"string\"\n ? { damAssetCode: link.damAssetCode }\n : {}),\n };\n}\n\nfunction isThemeAssetLink(value: unknown): value is ThemeAssetLink {\n return (\n isRecord(value) &&\n typeof value[\"sourceThemeId\"] === \"number\" &&\n Number.isInteger(value[\"sourceThemeId\"]) &&\n value[\"sourceThemeId\"] > 0 &&\n (value[\"checksum\"] === undefined ||\n (typeof value[\"checksum\"] === \"string\" &&\n value[\"checksum\"].length > 0)) &&\n (value[\"url\"] === undefined ||\n (typeof value[\"url\"] === \"string\" && value[\"url\"].length > 0)) &&\n (value[\"contentType\"] === undefined ||\n (typeof value[\"contentType\"] === \"string\" &&\n value[\"contentType\"].length > 0)) &&\n (value[\"contentSize\"] === undefined ||\n (typeof value[\"contentSize\"] === \"number\" &&\n Number.isInteger(value[\"contentSize\"]) &&\n value[\"contentSize\"] > 0)) &&\n (value[\"previewImageUrl\"] === undefined ||\n (typeof value[\"previewImageUrl\"] === \"string\" &&\n value[\"previewImageUrl\"].length > 0)) &&\n (value[\"altText\"] === undefined || typeof value[\"altText\"] === \"string\") &&\n (value[\"handle\"] === undefined ||\n (typeof value[\"handle\"] === \"string\" && value[\"handle\"].length > 0)) &&\n (value[\"pending\"] === undefined || typeof value[\"pending\"] === \"boolean\") &&\n (value[\"damAssetCode\"] === undefined ||\n (typeof value[\"damAssetCode\"] === \"string\" &&\n value[\"damAssetCode\"].length > 0))\n );\n}\n\nexport function isThemeAssetKey(key: string): boolean {\n if (key.includes(\"\\\\\") || key.includes(\"\\0\")) return false;\n\n const segments = key.split(\"/\");\n return (\n segments[0] === \"assets\" &&\n segments.length === 2 &&\n segments[1] !== undefined &&\n segments[1].length > 0 &&\n segments[1] !== \".\" &&\n segments[1] !== \"..\"\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Resource keys that the backend can hide from the resources index\n// when `STYLESHEET_STRICT_INPUT` is enabled on the owning company —\n// theme-level `styles.css` / `global_styles.css` and per-template\n// composite `{type}/{name}/styles.css`. See `Themes::Resource.find_by`\n// + `Themes::Theme#theme_stylesheet_columns_hidden?` /\n// `Themes::Template#stylesheet_column_hidden?` on the Rails side.\n//\n// A pull that used the \"delete files missing on remote\" behavior would\n// otherwise wipe a merchant's local stylesheets the first time the\n// flag flips on — the resource just stops appearing in the index, not\n// the file itself. Skip these keys from the delete pass so local\n// content stays intact regardless of the flag state.\n\nconst STYLESHEET_KEY_PATTERN =\n /^(styles\\.css|global_styles\\.css|[^/]+\\/[^/]+\\/styles\\.css)$/;\n\nexport function isStylesheetKey(key: string): boolean {\n return STYLESHEET_KEY_PATTERN.test(key);\n}\n","import { isRetiredTemplateKey } from \"./resource-key.js\";\n\n/** The same file graph is used for uploads and incoming omission protection.\n * Rails remains responsible for resolving company-specific roles and handles.\n * Retired template types are never synced, so they are never dependencies. */\nexport function memberDependencyPaths(\n document: Record<string, unknown>,\n availablePaths: ReadonlySet<string>,\n deletedPaths: readonly string[] = [],\n changedPaths: ReadonlySet<string> = availablePaths,\n): Set<string> {\n const dependencies = new Set<string>();\n for (const entry of [\n ...records(document.templates),\n ...records(document.menu_references),\n ]) {\n if (typeof entry.path === \"string\") dependencies.add(entry.path);\n }\n for (const site of records(document.sites)) {\n for (const page of records(site.pages)) {\n if (typeof page.template_path === \"string\")\n dependencies.add(page.template_path);\n }\n const layout = site.layout_name;\n if (layout === \"none\") continue;\n if (\n typeof layout === \"string\" &&\n layout &&\n ![\"member\", \"theme\"].includes(layout)\n ) {\n dependencies.add(`layouts/${layout}.liquid`);\n continue;\n }\n const fallback = [layout, \"member\", \"theme\"]\n .filter(\n (name): name is string => typeof name === \"string\" && name.length > 0,\n )\n .map((name) => `layouts/${name}.liquid`)\n .find((path) => availablePaths.has(path) || deletedPaths.includes(path));\n if (fallback) dependencies.add(fallback);\n }\n\n // Variables are a separate resource, but their menu selectors belong to the\n // same template and must not replace the previous reference before import.\n for (const path of dependencies) {\n if (!path.endsWith(\"/index.liquid\")) continue;\n for (const suffix of [\"variables.json\", \"styles.css\"]) {\n const sibling = path.replace(/index\\.liquid$/, suffix);\n const candidates =\n suffix === \"styles.css\" ? changedPaths : availablePaths;\n if (candidates.has(sibling) || deletedPaths.includes(sibling))\n dependencies.add(sibling);\n }\n }\n\n for (const path of dependencies) {\n if (isRetiredTemplateKey(path)) dependencies.delete(path);\n }\n return dependencies;\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n const candidates: unknown[] = Array.isArray(entries) ? entries : [];\n return candidates.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === \"object\" && entry !== null && !Array.isArray(entry),\n );\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isRetiredTemplateKey } from \"./resource-key.js\";\n\nexport const MEMBER_CONFIGURATION_KEY = \"members_sites.json\";\n\nexport interface ThemeUpload {\n file: ThemeFile;\n memberConfiguration?: {\n content: string;\n files: ThemeFile[];\n };\n}\n\n/** Apply member selectors and their document together, before losing old references. */\nexport function planThemeUploads(\n themeRoot: ThemeRoot,\n changedFiles: readonly ThemeFile[],\n deletedPaths: readonly string[] = [],\n): ThemeUpload[] {\n const localFiles = themeRoot.files();\n const manifest = localFiles.find(\n (file) => file.relativePath === MEMBER_CONFIGURATION_KEY,\n );\n if (!manifest || themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY)) {\n return individualUploads(changedFiles);\n }\n\n const manifestContent = manifest.read();\n const parsed: unknown = JSON.parse(manifestContent);\n // Legacy arrays do not carry the V2 dependency graph. The server's existing\n // importer still accepts them through the ordinary manifest resource.\n if (Array.isArray(parsed)) return individualUploads(changedFiles);\n if (!isRecord(parsed)) {\n throw new Error(\"members_sites.json must contain a JSON object.\");\n }\n const { document, removed } = withoutRetiredTemplates(parsed);\n const content =\n removed.length > 0\n ? `${JSON.stringify(document, null, 2)}\\n`\n : manifestContent;\n\n const dependencies = memberDependencyPaths(\n document,\n new Set(localFiles.map((file) => file.relativePath)),\n deletedPaths,\n new Set(changedFiles.map((file) => file.relativePath)),\n );\n\n for (const path of deletedPaths) {\n if (dependencies.has(path)) {\n throw new Error(\n `Member dependency is still declared and cannot be deleted: ${path}`,\n );\n }\n }\n\n const memberChanged = changedFiles.some(\n (file) =>\n file.relativePath === MEMBER_CONFIGURATION_KEY ||\n dependencies.has(file.relativePath),\n );\n // A retired-reference cleanup has to reach the remote in the same push as\n // the local rewrite, so it rides along with any push, but only when the\n // bundle can be built: it must never block an unrelated change.\n if (!memberChanged && removed.length === 0) {\n return changedFiles.map((file) => ({ file }));\n }\n\n const availableFiles = new Map(\n localFiles.map((file) => [file.relativePath, file]),\n );\n const unavailable = [...dependencies].find((path) => {\n const file = availableFiles.get(path);\n return !file || !file.isText || themeRoot.ignore.ignore(path);\n });\n if (unavailable) {\n if (!memberChanged) return changedFiles.map((file) => ({ file }));\n throw new Error(\n `Member dependency must be an available text file: ${unavailable}`,\n );\n }\n const memberFiles = [...dependencies].map(\n (path) => availableFiles.get(path)!,\n );\n if (removed.length > 0) {\n // Rewrite the local file with what is uploaded. A filtered upload beside\n // an unfiltered file would differ from the acknowledged remote forever,\n // so every later sync would upload it again.\n manifest.write(content);\n }\n const uploads: ThemeUpload[] = changedFiles\n .filter(\n (file) =>\n file.relativePath !== MEMBER_CONFIGURATION_KEY &&\n !dependencies.has(file.relativePath),\n )\n .map((file) => ({ file }));\n uploads.push({\n file: manifest,\n memberConfiguration: { content, files: memberFiles },\n });\n return uploads;\n}\n\n/**\n * Drop references to retired template types. The server rejects the whole\n * document for one, which would also hold back every member site and menu it\n * carries.\n *\n * Only `templates` and `menu_references` can name one: a site page's template\n * must be a `members_page`. A menu left without any reference is dropped with\n * its reference, because the server rejects unreferenced menus too.\n */\nexport function withoutRetiredTemplates(document: Record<string, unknown>): {\n document: Record<string, unknown>;\n removed: string[];\n} {\n const removed = new Set<string>();\n const orphanCandidates = new Set<unknown>();\n const keep = (entry: unknown) => {\n if (!isRecord(entry) || typeof entry.path !== \"string\") return true;\n if (!isRetiredTemplateKey(entry.path)) return true;\n removed.add(entry.path);\n if (\"menu_key\" in entry) orphanCandidates.add(entry.menu_key);\n return false;\n };\n\n const next: Record<string, unknown> = { ...document };\n for (const key of [\"templates\", \"menu_references\"]) {\n if (Array.isArray(next[key])) next[key] = next[key].filter(keep);\n }\n if (removed.size === 0) return { document, removed: [] };\n\n if (orphanCandidates.size > 0 && Array.isArray(next.menus)) {\n const used = new Set<unknown>([\n ...records(next.sites).map((site) => site.menu_key),\n ...records(next.menu_references).map((entry) => entry.menu_key),\n ]);\n next.menus = next.menus.filter(\n (menu) =>\n !isRecord(menu) ||\n !orphanCandidates.has(menu.key) ||\n used.has(menu.key),\n );\n }\n return { document: next, removed: [...removed] };\n}\n\nfunction records(entries: unknown): Array<Record<string, unknown>> {\n return Array.isArray(entries) ? entries.filter(isRecord) : [];\n}\n\nfunction individualUploads(files: readonly ThemeFile[]): ThemeUpload[] {\n return [...files]\n .sort(\n (left, right) =>\n Number(left.relativePath === MEMBER_CONFIGURATION_KEY) -\n Number(right.relativePath === MEMBER_CONFIGURATION_KEY),\n )\n .map((file) => ({ file }));\n}\n\nfunction isRecord(entry: unknown): entry is Record<string, unknown> {\n return typeof entry === \"object\" && entry !== null && !Array.isArray(entry);\n}\n\n/** Removing an older file is not an instruction to erase a member site. */\nexport function assertMemberConfigurationRetained(\n deletedPaths: readonly string[],\n): void {\n if (deletedPaths.includes(MEMBER_CONFIGURATION_KEY)) {\n throw new Error(\n \"Keep members_sites.json. To remove member configuration, upload an explicit empty versioned document instead of deleting or renaming the file.\",\n );\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport {\n isApiError,\n themes,\n type components,\n} from \"@fluid-app/themes-api-client\";\nimport type { ApiClient } from \"../api.js\";\nimport { formatError } from \"./format-error.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport {\n isThemeAssetKey,\n ThemeAssetManifest,\n type ThemeAssetLink,\n} from \"./asset-manifest.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n MEMBER_CONFIGURATION_KEY,\n} from \"./member-configuration.js\";\nimport type {\n DevRemoteState,\n RemoteResourceState,\n} from \"./dev-remote-baseline.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\ninterface RemoteAssetMetadata {\n url: string;\n contentType: string;\n contentSize: number;\n previewImageUrl?: string;\n altText?: string;\n handle?: string;\n}\n\ninterface ManagedAssetPlan {\n key: string;\n link: ThemeAssetLink;\n targetResource?: RemoteResource;\n metadata?: RemoteAssetMetadata;\n}\n\ntype UploadedBinaryResource = RemoteResource & {\n damAssetCode?: string;\n assetMetadata?: RemoteAssetMetadata;\n};\n\nconst ASSET_REFERENCE_CONCURRENCY = 6;\n\nexport interface SyncResult {\n uploaded: number;\n downloaded: number;\n linked: number;\n deleted: number;\n errors: string[];\n validationFailed: boolean;\n}\n\n/**\n * Server rejected the push because the CLI's `base_sha` no longer\n * matches the theme's current `content_version_sha` (someone else\n * wrote to the theme since our last pull). Callers surface a\n * \"pull first\" message; no partial state has been written when this\n * throws from the preflight, and the per-file variant preserves the\n * atomicity guarantee mid-loop by short-circuiting the remaining\n * files on the first conflicting response.\n */\nexport class PushConflictError extends Error {\n constructor(public readonly remoteSha: string | null) {\n super(\n remoteSha\n ? `Your local is behind the server. Server is at ${remoteSha}; local is stale.`\n : \"Your local is behind the server.\",\n );\n this.name = \"PushConflictError\";\n }\n}\n\nexport class Syncer {\n private checksumIndex = new Map<string, string>();\n private memberAcknowledgments = new Map<string, string>();\n private rawRemoteResources = new Map<string, RemoteResource>();\n private remoteResourceGroups = new Map<string, RemoteResource[]>();\n private remoteResourceIndex = new Map<string, RemoteResource>();\n private remoteIndexesDirty = false;\n private remoteResourcesLoaded = false;\n private lastKnownRemoteSha: string | null = null;\n private assetManifestInstance: ThemeAssetManifest | undefined;\n\n constructor(\n private api: ApiClient,\n private themeId: number,\n private themeRoot: ThemeRoot,\n assetManifest?: ThemeAssetManifest,\n ) {\n this.assetManifestInstance = assetManifest;\n }\n\n private get assetManifest(): ThemeAssetManifest {\n this.assetManifestInstance ??= new ThemeAssetManifest(this.themeRoot.root);\n return this.assetManifestInstance;\n }\n\n // ─── Checksum Management ──────────────────────────────────────────────────\n\n async fetchChecksums(): Promise<void> {\n // `content_version_sha` on the resources index is a Phase 003a\n // server addition — older servers don't emit it. Cast through\n // `unknown` because the typed API client hasn't been regenerated\n // against the new OpenAPI spec yet; regeneration is a follow-up.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n this.updateChecksums(body.application_theme_resources ?? []);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n }\n\n /**\n * Server's `content_version_sha` captured on the last `fetchChecksums()`\n * or `downloadAll()`. `null` when talking to a pre-003a server.\n */\n remoteSha(): string | null {\n return this.lastKnownRemoteSha;\n }\n\n private updateChecksums(resources: RemoteResource[]): void {\n assertNoCaseCollisions(\n resources.flatMap((resource) => (resource.key ? [resource.key] : [])),\n );\n this.rawRemoteResources.clear();\n this.remoteResourceGroups.clear();\n for (const resource of resources) {\n if (!resource.key) continue;\n\n this.rawRemoteResources.set(resource.key, resource);\n const group = this.remoteResourceGroups.get(resource.key) ?? [];\n group.push(resource);\n this.remoteResourceGroups.set(resource.key, group);\n }\n this.remoteIndexesDirty = true;\n }\n\n private setRemoteResource(resource: RemoteResource): void {\n if (!resource.key) return;\n this.rawRemoteResources.set(resource.key, resource);\n this.remoteResourceGroups.set(resource.key, [resource]);\n this.remoteIndexesDirty = true;\n }\n\n private removeRemoteResource(relativePath: string): void {\n this.rawRemoteResources.delete(relativePath);\n this.remoteResourceGroups.delete(relativePath);\n this.remoteIndexesDirty = true;\n }\n\n // Rebuilding on every setRemoteResource/removeRemoteResource made bulk\n // upload loops O(n²) over the remote resource map, so mutations only mark\n // the indexes dirty and the next read rebuilds once.\n private get checksums(): Map<string, string> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.checksumIndex;\n }\n\n private get remoteResources(): Map<string, RemoteResource> {\n if (this.remoteIndexesDirty) this.rebuildRemoteIndexes();\n return this.remoteResourceIndex;\n }\n\n private rebuildRemoteIndexes(): void {\n this.remoteIndexesDirty = false;\n this.checksumIndex.clear();\n this.remoteResourceIndex.clear();\n\n for (const [key, resource] of this.rawRemoteResources) {\n // The backend may return both a legacy key and its .liquid equivalent.\n // Preserve the explicit .liquid resource when both are present.\n if (this.rawRemoteResources.has(`${key}.liquid`)) continue;\n\n this.remoteResourceIndex.set(key, resource);\n if (resource.checksum) this.checksumIndex.set(key, resource.checksum);\n }\n }\n\n hasChanged(file: ThemeFile): boolean {\n return file.checksum() !== this.checksums.get(file.relativePath);\n }\n\n remoteKeys(): string[] {\n return [...this.remoteResources.keys()];\n }\n\n /** A null-content resource has no possible working-tree counterpart. */\n private canDeleteRemoteResource(key: string): boolean {\n const resource = this.remoteResources.get(key);\n return resource?.content != null || isManagedAssetResource(resource);\n }\n\n /** Snapshot of remote checksums (key → sha256). Available after fetchChecksums() or downloadAll(). */\n remoteChecksums(): Record<string, string> {\n return Object.fromEntries(this.checksums);\n }\n\n /** URL-backed assets keyed by their exact theme resource path. */\n remoteAssetUrls(): Record<string, string> {\n const urls: Record<string, string> = {};\n for (const [key, resource] of this.remoteResources) {\n if (!isManagedAssetResource(resource)) continue;\n const url = resource.url;\n if (typeof url === \"string\" && url.length > 0) urls[key] = url;\n }\n return urls;\n }\n\n /** Compact, complete resource state paired with its acknowledged dev SHA. */\n devRemoteState(assetManifestSha: string): DevRemoteState | null {\n if (!this.remoteResourcesLoaded || !this.lastKnownRemoteSha) return null;\n\n return {\n themeId: this.themeId,\n remoteSha: this.lastKnownRemoteSha,\n assetManifestSha,\n resources: [...this.remoteResourceGroups.values()]\n .flat()\n .map(remoteResourceState),\n };\n }\n\n useDevRemoteState(state: DevRemoteState): void {\n if (state.themeId !== this.themeId) {\n throw new Error(\n `Dev remote state belongs to theme #${state.themeId}, not #${this.themeId}`,\n );\n }\n this.updateChecksums(state.resources.map(remoteResourceFromState));\n this.lastKnownRemoteSha = state.remoteSha;\n this.remoteResourcesLoaded = true;\n }\n\n private async ensureRemoteResourcesLoaded(): Promise<void> {\n if (!this.remoteResourcesLoaded) await this.fetchChecksums();\n }\n\n /**\n * Adds URL-backed FileResources for manifest assets without transferring\n * their bytes. The target stores the source asset's ImageKit URL.\n */\n async linkManagedAssets(opts: { replace?: boolean } = {}): Promise<number> {\n this.assetManifest.reload();\n await this.ensureRemoteResourcesLoaded();\n\n const plans: ManagedAssetPlan[] = [];\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n // A local binary is the recovery source after a failed metadata pull or\n // an intentional replacement. Let the normal upload path handle it;\n // never recreate the older manifest URL over those bytes first.\n if (this.themeRoot.file(key).exists) continue;\n\n const targetResource =\n this.managedAssetResourceForLink(key, link) ??\n this.remoteResources.get(key);\n if (\n targetResource &&\n !this.managedAssetNeedsRefresh(targetResource, link)\n ) {\n continue;\n }\n if (targetResource && !opts.replace) continue;\n\n const metadata = assetMetadataFromLink(link);\n plans.push({\n key,\n link,\n ...(targetResource ? { targetResource } : {}),\n ...(metadata ? { metadata } : {}),\n });\n }\n\n if (plans.length > 0) {\n await this.resolveAssetMetadata(plans);\n await this.createAssetReferences(plans);\n await this.fetchChecksums();\n }\n if (opts.replace && (await this.pruneDuplicateManagedAssetReferences())) {\n await this.fetchChecksums();\n }\n this.ensureManagedAssetsAreResolved();\n\n return plans.length;\n }\n\n private async resolveAssetMetadata(plans: ManagedAssetPlan[]): Promise<void> {\n const bySourceTheme = new Map<number, ManagedAssetPlan[]>();\n for (const plan of plans) {\n if (plan.metadata) continue;\n const sourcePlans = bySourceTheme.get(plan.link.sourceThemeId) ?? [];\n sourcePlans.push(plan);\n bySourceTheme.set(plan.link.sourceThemeId, sourcePlans);\n }\n\n let manifestChanged = false;\n for (const [sourceThemeId, sourcePlans] of bySourceTheme) {\n let sourceAssets: Map<string, RemoteAssetMetadata>;\n try {\n sourceAssets = await this.fetchThemeAssetMetadata(sourceThemeId);\n } catch (error) {\n throw new Error(\n `Could not read asset metadata from theme #${sourceThemeId}: ${formatError(error)}`,\n );\n }\n\n for (const plan of sourcePlans) {\n const sourceMetadata = sourceAssets.get(assetFilename(plan.key));\n if (!sourceMetadata) {\n throw new Error(\n `Could not find usable metadata for ${plan.key} in theme #${sourceThemeId}`,\n );\n }\n\n // Preserve a pulled URL while enriching an older manifest. A remote\n // source update must not silently change this checkout's asset URL.\n const metadata: RemoteAssetMetadata = {\n ...sourceMetadata,\n ...(typeof plan.link.url === \"string\" ? { url: plan.link.url } : {}),\n };\n plan.metadata = metadata;\n this.assetManifest.set(plan.key, {\n ...plan.link,\n ...metadata,\n });\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) this.assetManifest.write();\n }\n\n private async fetchThemeAssetMetadata(\n sourceThemeId: number,\n ): Promise<Map<string, RemoteAssetMetadata>> {\n const body = await themes.getThemeAssets(this.api, sourceThemeId);\n if (!isRecord(body) || !Array.isArray(body[\"file_resources\"])) {\n throw new Error(\"Theme assets response did not include file_resources\");\n }\n\n const assets = new Map<string, RemoteAssetMetadata>();\n for (const value of body[\"file_resources\"]) {\n const asset = parseThemeAssetMetadata(value);\n if (asset) assets.set(asset.filename, asset.metadata);\n }\n return assets;\n }\n\n private async createAssetReferences(\n plans: ManagedAssetPlan[],\n ): Promise<void> {\n const errors: string[] = [];\n let nextPlan = 0;\n\n const worker = async () => {\n while (nextPlan < plans.length) {\n const plan = plans[nextPlan];\n nextPlan += 1;\n if (!plan || !plan.metadata) continue;\n\n try {\n await this.createAssetReference(plan);\n } catch (error) {\n errors.push(`${plan.key}: ${formatError(error)}`);\n }\n }\n };\n\n await Promise.all(\n Array.from(\n { length: Math.min(ASSET_REFERENCE_CONCURRENCY, plans.length) },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not save ${errors.length} ImageKit URL reference(s) (this requires File Resources update access): ${errors.join(\"; \")}`,\n );\n }\n }\n\n private async createAssetReference(plan: ManagedAssetPlan): Promise<void> {\n const metadata = plan.metadata;\n if (!metadata) throw new Error(\"asset metadata was not resolved\");\n\n const targetResourceId = plan.targetResource?.resource_id;\n if (plan.targetResource && typeof targetResourceId !== \"number\") {\n throw new Error(\"existing target asset has no resource ID\");\n }\n\n const body = await themes.createFileResource(this.api, {\n file_resource: {\n url: metadata.url,\n filename: assetFilename(plan.key),\n content_type: metadata.contentType,\n content_size: metadata.contentSize,\n ...(metadata.previewImageUrl\n ? { preview_image_url: metadata.previewImageUrl }\n : {}),\n ...(metadata.altText !== undefined\n ? { alt_text: metadata.altText }\n : {}),\n ...(metadata.handle ? { handle: metadata.handle } : {}),\n relateable_id: this.themeId,\n relateable_type: \"ApplicationTheme\",\n },\n });\n\n const createdResourceId = createdFileResourceId(body);\n if (!createdResourceId) {\n throw new Error(\"create response did not include a FileResource ID\");\n }\n\n if (typeof targetResourceId !== \"number\") return;\n\n try {\n await themes.destroyFileResource(this.api, targetResourceId);\n } catch (error) {\n if (isNotFoundError(error)) return;\n try {\n await themes.destroyFileResource(this.api, createdResourceId);\n } catch {\n // Keep the original error; a later sync can repair any duplicate.\n }\n throw error;\n }\n }\n\n /**\n * Returns every resource for an exact logical key. A legacy `foo` resource\n * is hidden when `foo.liquid` exists, matching the normal remote index.\n */\n private resourcesForLogicalKey(key: string): RemoteResource[] {\n if (this.rawRemoteResources.has(`${key}.liquid`)) return [];\n return this.remoteResourceGroups.get(key) ?? [];\n }\n\n private managedAssetResourceForLink(\n key: string,\n link: ThemeAssetLink,\n ): RemoteResource | undefined {\n return this.resourcesForLogicalKey(key).find(\n (resource) =>\n isManagedAssetResource(resource) &&\n !this.managedAssetNeedsRefresh(resource, link),\n );\n }\n\n /** Make interrupted reference replacement converge to one FileResource. */\n private async pruneDuplicateManagedAssetReferences(): Promise<boolean> {\n const resourcesToDelete: Array<{ key: string; resourceId: number }> = [];\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key) || !link.url) continue;\n\n const resources = this.resourcesForLogicalKey(key).filter(\n isManagedAssetResource,\n );\n if (resources.length < 2) continue;\n\n const matchingResources = resources.filter(\n (resource) => resource.url === link.url,\n );\n if (matchingResources.length === 0) continue;\n\n const keeper = this.lowestResourceId(matchingResources, key);\n const keeperId = this.resourceIdForAssetReference(keeper, key);\n for (const resource of resources) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId !== keeperId)\n resourcesToDelete.push({ key, resourceId });\n }\n }\n\n if (resourcesToDelete.length === 0) return false;\n\n const errors: string[] = [];\n let nextResource = 0;\n const worker = async () => {\n while (nextResource < resourcesToDelete.length) {\n const resource = resourcesToDelete[nextResource];\n nextResource += 1;\n if (!resource) continue;\n\n try {\n await themes.destroyFileResource(this.api, resource.resourceId);\n } catch (error) {\n if (!isNotFoundError(error)) {\n errors.push(`${resource.key}: ${formatError(error)}`);\n }\n }\n }\n };\n\n await Promise.all(\n Array.from(\n {\n length: Math.min(\n ASSET_REFERENCE_CONCURRENCY,\n resourcesToDelete.length,\n ),\n },\n worker,\n ),\n );\n\n if (errors.length > 0) {\n throw new Error(\n `Could not remove ${errors.length} duplicate ImageKit URL reference(s): ${errors.join(\"; \")}`,\n );\n }\n\n return true;\n }\n\n private lowestResourceId(\n resources: RemoteResource[],\n key: string,\n ): RemoteResource {\n let lowest = resources[0];\n if (!lowest) throw new Error(`No asset resources found for ${key}`);\n\n let lowestId = this.resourceIdForAssetReference(lowest, key);\n for (const resource of resources.slice(1)) {\n const resourceId = this.resourceIdForAssetReference(resource, key);\n if (resourceId < lowestId) {\n lowest = resource;\n lowestId = resourceId;\n }\n }\n return lowest;\n }\n\n private resourceIdForAssetReference(\n resource: RemoteResource,\n key: string,\n ): number {\n const resourceId = positiveInteger(resource.resource_id);\n if (!resourceId) {\n throw new Error(`Existing target asset has no resource ID: ${key}`);\n }\n return resourceId;\n }\n\n /** Makes this target the provenance source for assets it now resolves. */\n repointManagedAssetsToCurrentTheme(): void {\n this.assetManifest.reload();\n let changed = false;\n\n for (const [key, link] of this.assetManifest.entries()) {\n if (this.themeRoot.ignore.ignore(key)) continue;\n if (this.themeRoot.file(key).exists) continue;\n\n const resource = this.remoteResources.get(key);\n if (!isManagedAssetResource(resource)) {\n throw new Error(`Managed asset is missing from target theme: ${key}`);\n }\n\n const nextLink: ThemeAssetLink = { ...link, sourceThemeId: this.themeId };\n delete nextLink.pending;\n delete nextLink.checksum;\n Object.assign(nextLink, resourceLink(resource));\n this.assetManifest.set(key, nextLink);\n changed = true;\n }\n\n if (changed) this.assetManifest.write();\n }\n\n // ─── Upload ───────────────────────────────────────────────────────────────\n\n /**\n * Uploads one file. Resolves with the exact text content that was sent to\n * the server (null for binary files) so callers can run diagnostics against\n * the same bytes instead of re-reading a file that may have changed on disk\n * while the request was in flight.\n */\n async uploadFile(\n file: ThemeFile,\n baseSha?: string | null,\n opts: { pendingAsset?: boolean } = {},\n ): Promise<string | null> {\n if (file.isText) {\n const content = file.read();\n const resource = await this.putResource(\n { key: file.relativePath, content },\n baseSha,\n );\n this.setRemoteResource({\n ...resource,\n key: file.relativePath,\n content,\n checksum: resource.checksum ?? file.checksum(),\n });\n return content;\n }\n\n if (isNestedBinaryThemeAsset(file)) {\n throw new Error(\n `Binary assets must be directly inside assets/: ${file.relativePath}`,\n );\n }\n\n const resource = await this.uploadBinaryFile(file, baseSha);\n this.setRemoteResource(resource);\n if (isThemeAssetKey(file.relativePath)) {\n this.externalizeBinaryFile(file, resource, opts.pendingAsset);\n }\n return null;\n }\n\n /** Canonical member bytes accepted by the server during this transfer. */\n acknowledgedMemberContents(): ReadonlyMap<string, string> {\n return this.memberAcknowledgments;\n }\n\n /** Upload one member document and its changed dependencies as one transaction. */\n async uploadMemberConfiguration(\n upload: ThemeUpload,\n baseSha?: string | null,\n ): Promise<Array<{ file: ThemeFile; content: string }>> {\n if (!upload.memberConfiguration) {\n throw new Error(\"A member configuration upload is required.\");\n }\n const { content, files } = upload.memberConfiguration;\n const capturedFiles = files.map((file) => ({ file, content: file.read() }));\n const resource = await this.putResource(\n {\n key: MEMBER_CONFIGURATION_KEY,\n content,\n member_files: capturedFiles.map(\n ({ file, content: capturedContent }) => ({\n key: file.relativePath,\n content: capturedContent,\n }),\n ),\n },\n baseSha,\n capturedFiles.map(({ file }) => file.relativePath),\n );\n // The server may allocate new menu handles and rewrite selectors. Keep its\n // acknowledged contents/checksums, never claim that the local bytes landed.\n this.setRemoteResource(resource);\n if (resource.key && resource.content != null) {\n this.memberAcknowledgments.set(\n resource.key,\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content),\n );\n }\n return [{ file: upload.file, content }, ...capturedFiles];\n }\n\n /**\n * Wraps the generated `updateThemeResource` client with the two\n * merge-aware Phase 003 additions: sending `base_sha` on the request\n * and reading the server's fresh `content_version_sha` off the\n * response so the caller can thread it forward on the next PUT.\n *\n * Accepts any `application_theme_resource` shape — text uploads pass\n * `{ key, content }`; binary uploads (after DAM + ImageKit\n * orchestration) pass `{ key, dam_asset: { ... } }`. Both must route\n * through here so `lastKnownRemoteSha` stays in lockstep with every\n * write the server has ack'd, mixed text/binary pushes included.\n *\n * The typed client hasn't been regenerated against the new OpenAPI\n * spec yet, so `base_sha` is threaded through as an extra property\n * (server accepts unknown fields on this endpoint) and the response\n * is cast to read the extra `content_version_sha`. Regeneration is\n * a follow-up; that PR will drop these casts.\n *\n * `PushConflictError` is thrown on a 409 so callers can distinguish\n * \"server rejected because of stale base\" from generic upload\n * failures.\n */\n private async putResource(\n resource: Record<string, unknown>,\n baseSha: string | null | undefined,\n expectedMemberKeys?: readonly string[],\n ): Promise<RemoteResource> {\n const body: Record<string, unknown> = {\n application_theme_resource: resource,\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.updateThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as {\n application_theme_resource?: RemoteResource;\n member_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n if (expectedMemberKeys) {\n const acknowledged = new Map(\n (response.member_resources ?? []).map((entry) => [entry.key, entry]),\n );\n const manifest = response.application_theme_resource;\n if (\n manifest?.key !== MEMBER_CONFIGURATION_KEY ||\n manifest.content == null ||\n !manifest.checksum ||\n expectedMemberKeys.some(\n (key) =>\n acknowledged.get(key)?.content == null ||\n !acknowledged.get(key)?.checksum,\n )\n ) {\n throw new Error(\n \"Fluid did not acknowledge every bundled member file. Update the server before retrying this push.\",\n );\n }\n }\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n for (const memberResource of response.member_resources ?? []) {\n this.setRemoteResource(memberResource);\n if (memberResource.key && memberResource.content != null) {\n this.memberAcknowledgments.set(\n memberResource.key,\n typeof memberResource.content === \"string\"\n ? memberResource.content\n : JSON.stringify(memberResource.content),\n );\n }\n }\n return (\n response.application_theme_resource ?? {\n key: typeof resource[\"key\"] === \"string\" ? resource[\"key\"] : \"\",\n checksum: null,\n }\n );\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n /**\n * Server-side push preflight (Phase 003a). Runs once at the start of\n * a push loop. On 200 the server's fresh `remote_sha` is stashed on\n * the syncer so subsequent per-file PUTs can carry it. On 409 a\n * `PushConflictError` is thrown carrying the server's `remote_sha`\n * from the `meta` payload — the caller renders \"pull first\".\n *\n * Skipped when `baseSha` is null/undefined so old-behavior pushes\n * (no stored `baseSha` in `.fluid-theme.json`) and `--force` pushes\n * short-circuit past the check.\n */\n /**\n * Tell Fluid the push is finished, so it commits the theme's current state\n * as one version.\n *\n * The other half of `preflightPush`. That one runs once before the file\n * loop to reject a stale base; this runs once after it, and is the only\n * thing that turns a push into a commit — the per-file writes just mark the\n * theme changed.\n *\n * It exists because the server cannot see where an operation ends. A push\n * arrives as a hundred-odd independent requests, and every way of inferring\n * \"these belong together\" either merges two publishes that happened to land\n * close together or splits one push across several commits. The client\n * knows; this says so.\n *\n * Returns false rather than throwing when the request fails. Callers must\n * retain their previous baseline until finalization succeeds; the server\n * can reject an incomplete member graph even after individual files land.\n *\n * Returns whether Fluid took the request, because a caller tracking edit\n * boundaries needs to know. A failed ask leaves the previous edit\n * uncommitted, and uploading the next one over it merges the two into\n * whichever commit eventually lands.\n */\n async requestSync(): Promise<boolean> {\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/sync`,\n {},\n )) as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n return true;\n } catch (e) {\n console.warn(\n ` ⚠ couldn't ask Fluid to commit this push (${formatError(e)}). Retry the push to finalize it.`,\n );\n return false;\n }\n }\n\n async preflightPush(baseSha: string | null | undefined): Promise<void> {\n if (!baseSha) return;\n\n try {\n const response = (await this.api.post(\n `/api/application_themes/${this.themeId}/resources/check_push`,\n { base_sha: baseSha },\n )) as { remote_sha?: string };\n if (response.remote_sha) this.lastKnownRemoteSha = response.remote_sha;\n } catch (e) {\n throw this.rethrowIfConflict(e);\n }\n }\n\n private rethrowIfConflict(e: unknown): unknown {\n const status =\n (e as { status?: number; response?: { status?: number } })?.status ??\n (e as { response?: { status?: number } })?.response?.status;\n if (status !== 409) return e;\n\n // `ApiError` from api-client-core stores the parsed response body\n // under `.data` (not `.body`). Reading the wrong key silently\n // dropped `remote_sha` in production so the \"server is at X\"\n // hint in the CLI output went missing.\n const meta = (e as { data?: { meta?: { remote_sha?: string } } })?.data\n ?.meta;\n return new PushConflictError(meta?.remote_sha ?? null);\n }\n\n private async uploadBinaryFile(\n file: ThemeFile,\n baseSha: string | null | undefined,\n ): Promise<UploadedBinaryResource> {\n // Step 1: Create DAM placeholder\n const placeholderBody = await this.api.post<{\n asset: { id: number; canonical_path: string };\n }>(\"/api/dam/assets\", {\n placeholder_asset: {\n description: `Uploaded via Fluid CLI: ${file.name}`,\n mime_type: file.mime.name,\n name: file.name,\n },\n });\n const asset = placeholderBody.asset;\n\n // Step 2: Get ImageKit auth token\n const authBody = await this.api.post<{\n token: string;\n signature: string;\n expire: number;\n }>(\"/api/dam/assets/imagekit_auth\", {});\n\n // Step 3: Upload to ImageKit via multipart\n const folder = this.canonicalPathToImageKitFolder(asset.canonical_path);\n const formData = new FormData();\n const blob = new Blob([file.readBinary() as unknown as ArrayBuffer], {\n type: file.mime.name,\n });\n formData.append(\"file\", blob, file.name);\n formData.append(\"token\", authBody.token);\n formData.append(\"signature\", authBody.signature);\n formData.append(\"expire\", String(authBody.expire));\n formData.append(\"folder\", folder);\n formData.append(\"fileName\", file.name);\n formData.append(\"publicKey\", \"public_j7s4Ih9ETh/OCp41mVQH7tlXBdU=\");\n\n const ikResp = await fetch(\n \"https://upload.imagekit.io/api/v1/files/upload\",\n {\n method: \"POST\",\n body: formData,\n },\n );\n if (!ikResp.ok) throw new Error(`ImageKit upload failed: ${ikResp.status}`);\n const ikBody = (await ikResp.json()) as {\n fileId: string;\n url: string;\n thumbnailUrl: string;\n size: number;\n height?: number;\n width?: number;\n };\n\n // Step 4: Backfill DAM asset\n const backfillPayload: Record<string, unknown> = {\n asset: {\n id: asset.id,\n imagekit_file_id: ikBody.fileId,\n imagekit_url: ikBody.url,\n mime_type: file.mime.name,\n name: file.name,\n file_size: ikBody.size,\n expected_path: asset.canonical_path,\n },\n };\n if (ikBody.height)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"height\"] =\n ikBody.height;\n if (ikBody.width)\n (backfillPayload[\"asset\"] as Record<string, unknown>)[\"width\"] =\n ikBody.width;\n\n const backfillBody = await this.api.post<{\n asset: { code: string; default_variant_url: string };\n }>(\"/api/dam/assets/backfill_imagekit\", backfillPayload);\n\n // Step 5: Associate with theme resource. Route through `putResource`\n // so `base_sha` gets sent and `lastKnownRemoteSha` advances just\n // like text-file writes — a mixed text+binary push must keep them\n // in a single monotonic chain.\n const update = await this.putResource(\n {\n key: file.relativePath,\n dam_asset: {\n dam_asset_code: backfillBody.asset.code,\n content_type: file.mime.name,\n content_size: ikBody.size,\n filename: file.name,\n handle: backfillBody.asset.code,\n url: backfillBody.asset.default_variant_url,\n preview_image_url: ikBody.thumbnailUrl,\n },\n },\n baseSha,\n );\n\n // Older API responses can omit URL fields. Preserve the known DAM URL so\n // the manifest stays usable after a successful remote write.\n return {\n ...update,\n key: update.key || file.relativePath,\n url: update.url ?? backfillBody.asset.default_variant_url,\n damAssetCode: backfillBody.asset.code,\n assetMetadata: {\n url: backfillBody.asset.default_variant_url,\n contentType: file.mime.name,\n contentSize: ikBody.size,\n previewImageUrl: ikBody.thumbnailUrl,\n altText: file.name,\n handle: backfillBody.asset.code,\n },\n };\n }\n\n private externalizeBinaryFile(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n pending: boolean | undefined,\n ): void {\n // Persist before unlinking. If persistence fails, retain the local bytes so\n // the developer can retry instead of losing the only provenance record.\n this.assetManifest.reload();\n this.assetManifest.set(file.relativePath, {\n sourceThemeId: this.themeId,\n ...resourceLink(resource),\n ...uploadedAssetMetadata(file, resource),\n ...(pending ? { pending: true } : {}),\n ...(typeof resource.damAssetCode === \"string\"\n ? { damAssetCode: resource.damAssetCode }\n : {}),\n });\n this.assetManifest.write();\n unlinkSync(file.absolutePath);\n }\n\n private canonicalPathToImageKitFolder(canonicalPath: string): string {\n const parts = canonicalPath.split(\".\");\n const companyId = parts[0] ?? \"unknown\";\n const category = parts[1] ?? \"files\";\n const assetCode = parts[2] ?? \"unknown\";\n const folderMap: Record<string, string> = {\n images: \"images\",\n videos: \"videos\",\n audio: \"audio\",\n documents: \"documents\",\n files: \"files\",\n };\n return `${companyId}/${folderMap[category] ?? \"files\"}/${assetCode}`;\n }\n\n // ─── Delete ───────────────────────────────────────────────────────────────\n\n async deleteRemoteFile(\n relativePath: string,\n baseSha?: string | null,\n ): Promise<void> {\n assertMemberConfigurationRetained([relativePath]);\n // Chokidar observes the intentional unlink after a binary has been\n // externalized. That unlink must never delete the server-side DAM link.\n this.assetManifest.reload();\n if (this.assetManifest.has(relativePath)) return;\n\n const body: Record<string, unknown> = {\n application_theme_resource: { key: relativePath },\n };\n if (baseSha) body[\"base_sha\"] = baseSha;\n\n try {\n const response = (await themes.deleteThemeResource(\n this.api,\n this.themeId,\n body as never,\n )) as unknown as { content_version_sha?: string };\n if (response.content_version_sha) {\n this.lastKnownRemoteSha = response.content_version_sha;\n }\n } catch (e) {\n if (!isNotFoundError(e)) throw this.rethrowIfConflict(e);\n }\n this.removeRemoteResource(relativePath);\n }\n\n // ─── Download ─────────────────────────────────────────────────────────────\n\n async downloadAll(): Promise<RemoteResource[]> {\n // Same cast rationale as `fetchChecksums`. The additional\n // `content_version_sha` field is stashed on the syncer so callers\n // (pull command) can persist it as the new `baseSha`.\n const body = (await themes.listThemeResources(\n this.api,\n this.themeId,\n )) as unknown as {\n application_theme_resources?: RemoteResource[];\n content_version_sha?: string;\n };\n const resources = body.application_theme_resources ?? [];\n this.updateChecksums(resources);\n this.lastKnownRemoteSha = body.content_version_sha ?? null;\n this.remoteResourcesLoaded = true;\n return resources;\n }\n\n async downloadBinaryAsset(url: string): Promise<Buffer> {\n const resp = await fetch(url);\n if (!resp.ok) throw new Error(`Failed to download asset: ${resp.status}`);\n return Buffer.from(await resp.arrayBuffer());\n }\n\n /**\n * Move directly-addressable binary `assets/*` resources into the local\n * manifest before merge-pull sees them. This prevents a byte download and\n * keeps those paths out of the shadow repository; their canonical state is\n * the ImageKit URL, not a local file.\n */\n async externalizePulledAssets(\n resources: RemoteResource[],\n opts: { delete: boolean; backgroundGuard?: BackgroundPullGuard },\n ): Promise<{ managedKeys: Set<string>; linked: number; errors: string[] }> {\n this.assetManifest.reload();\n\n const managedKeys = new Set<string>();\n const changedManifestKeys = new Set<string>();\n const remoteManagedKeys = new Set<string>();\n const preservedManifestKeys = new Set<string>();\n const filesToRemove = new Map<string, ThemeFile>();\n const errors: string[] = [];\n let manifestChanged = false;\n\n // ApplicationThemeResource deliberately exposes only the URL/checksum.\n // Capture FileResource metadata during the pull too, so a later push can\n // recreate the reference even if this source/development theme has since\n // been deleted. This is one small JSON request, never a binary download.\n const resourcesNeedingMetadata = resources.filter((resource) => {\n const key = resource.key;\n if (!key) return false;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) return false;\n if (!isLinkableBinaryResource(resource, key, file)) return false;\n\n const existing = this.assetManifest.get(key);\n if (this.themeRoot.ignore.ignore(key) || existing?.pending) return false;\n return (\n !existing ||\n existing.url !== resource.url ||\n !assetMetadataFromLink(existing)\n );\n });\n let assetMetadata = new Map<string, RemoteAssetMetadata>();\n const unresolvedMetadataKeys = new Set<string>();\n if (resourcesNeedingMetadata.length > 0) {\n try {\n assetMetadata = await this.fetchThemeAssetMetadata(this.themeId);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Read remote asset metadata: ${formatError(error)}`);\n for (const resource of resourcesNeedingMetadata) {\n if (resource.key) unresolvedMetadataKeys.add(resource.key);\n }\n }\n\n if (unresolvedMetadataKeys.size === 0) {\n for (const resource of resourcesNeedingMetadata) {\n const key = resource.key;\n if (!key || assetMetadata.has(assetFilename(key))) continue;\n unresolvedMetadataKeys.add(key);\n errors.push(\n `Could not find usable metadata for ${key} in theme #${this.themeId}`,\n );\n }\n }\n }\n\n opts.backgroundGuard?.assertUnchanged();\n for (const resource of resources) {\n const key = resource.key;\n if (!key) continue;\n\n const file = this.themeRoot.file(key);\n if (!this.isSafeThemeFile(key, file)) continue;\n if (!isLinkableBinaryResource(resource, key, file)) continue;\n\n remoteManagedKeys.add(key);\n if (this.themeRoot.ignore.ignore(key)) {\n if (this.assetManifest.has(key)) preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // A newly added dev asset has not necessarily been promoted to the\n // pulled source yet. It remains authoritative until a push makes it\n // durable on the selected target.\n if (this.assetManifest.get(key)?.pending) {\n preservedManifestKeys.add(key);\n managedKeys.add(key);\n continue;\n }\n\n // Do not create an incomplete URL-only reference: a later push may need\n // content type and size after this source theme no longer exists. Let\n // merge-pull retain/download just this binary instead.\n if (unresolvedMetadataKeys.has(key)) {\n // The fallback bytes are now the only trustworthy copy. A manifest\n // entry pointing at a URL the remote no longer serves is stale —\n // keeping it would let linkManagedAssets restore the older asset if\n // the local file later disappears.\n const stale = this.assetManifest.get(key);\n if (stale && stale.url !== resource.url) {\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n continue;\n }\n\n try {\n const existing = this.assetManifest.get(key);\n const metadata = assetMetadata.get(assetFilename(key));\n this.assetManifest.set(key, {\n ...existing,\n sourceThemeId: this.themeId,\n ...metadata,\n ...resourceLink(resource),\n });\n manifestChanged = true;\n changedManifestKeys.add(key);\n managedKeys.add(key);\n filesToRemove.set(key, file);\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n }\n }\n\n if (opts.delete) {\n for (const [key, link] of this.assetManifest.entries()) {\n if (\n remoteManagedKeys.has(key) ||\n preservedManifestKeys.has(key) ||\n this.themeRoot.ignore.ignore(key) ||\n link.pending\n ) {\n continue;\n }\n this.assetManifest.delete(key);\n manifestChanged = true;\n }\n }\n\n if (manifestChanged) {\n try {\n // Persist all links first. If this fails, preserve the local bytes and\n // let merge-pull materialize the affected resources normally.\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([\".fluid-assets.json\"], () =>\n this.assetManifest.write(),\n );\n else this.assetManifest.write();\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Persist remote asset manifest: ${formatError(error)}`);\n for (const key of changedManifestKeys) managedKeys.delete(key);\n return { managedKeys, linked: 0, errors };\n }\n }\n\n let linked = 0;\n for (const [key, file] of filesToRemove) {\n try {\n if (file.exists) {\n if (opts.backgroundGuard)\n opts.backgroundGuard.mutate([key], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n }\n linked++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n errors.push(`Externalize ${key}: ${formatError(error)}`);\n // The manifest link is safely on disk; retain the resource outside the\n // shadow even if an operating-system lock delays local cleanup.\n }\n }\n\n return { managedKeys, linked, errors };\n }\n\n private isSafeThemeFile(key: string, file: ThemeFile): boolean {\n return (\n !key.includes(\"\\0\") &&\n !key.split(/[\\\\/]/).includes(\"..\") &&\n (file.absolutePath === this.themeRoot.root ||\n file.absolutePath.startsWith(this.themeRoot.root + sep))\n );\n }\n\n private managedAssetNeedsRefresh(\n resource: RemoteResource,\n link: ThemeAssetLink,\n ): boolean {\n if (!isManagedAssetResource(resource)) return true;\n if (link.url !== undefined) return resource.url !== link.url;\n if (link.checksum !== undefined) return resource.checksum !== link.checksum;\n return true;\n }\n\n private ensureManagedAssetsAreResolved(): void {\n const unresolved = this.assetManifest\n .entries()\n .filter(\n ([key]) =>\n !this.themeRoot.ignore.ignore(key) &&\n !this.themeRoot.file(key).exists,\n )\n .map(([key]) => key)\n .filter((key) => !isManagedAssetResource(this.remoteResources.get(key)));\n if (unresolved.length > 0) {\n throw new Error(\n `Managed asset(s) could not be linked: ${unresolved.join(\", \")}`,\n );\n }\n }\n\n // ─── Full Upload ──────────────────────────────────────────────────────────\n\n async uploadTheme(\n opts: {\n delete?: boolean;\n validate?: boolean;\n linkManagedAssets?: { replace?: boolean };\n pendingBinaryAssets?: boolean;\n onProgress?: (done: number, total: number) => void;\n // The SHA `.fluid-theme.json` stored on the last pull. Sent to the\n // server on every PUT/DELETE so a stale local aborts the push\n // instead of clobbering. Null / undefined skips the check entirely\n // (force-push and pre-Phase-003 servers).\n baseSha?: string | null;\n /** The caller already ran the stale-base preflight. */\n skipPreflight?: boolean;\n /** Last acknowledged dev resource index, validated by `baseSha`. */\n remoteState?: DevRemoteState;\n /** Without a stored baseSha, guard writes with the fetched index's SHA\n * so a concurrent change 409s instead of being overwritten. */\n guardFetchedRemote?: boolean;\n } = {},\n ): Promise<SyncResult> {\n const localFiles = this.themeRoot.files();\n assertNoCaseCollisions(localFiles.map((file) => file.relativePath));\n const result: SyncResult = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: 0,\n errors: [],\n validationFailed: false,\n };\n\n // Schema validation pass\n if (opts.validate) {\n for (const file of localFiles) {\n if (!file.isLiquid) continue;\n const diagnostics = file.validateSchema();\n const errors = diagnostics.filter((d) => d.severity === \"error\");\n for (const d of errors) {\n result.errors.push(`${file.relativePath}: ${d.message}`);\n }\n }\n if (result.errors.length > 0) {\n result.validationFailed = true;\n return result;\n }\n }\n\n if (opts.remoteState) {\n this.useDevRemoteState(opts.remoteState);\n } else {\n await this.fetchChecksums();\n }\n\n // Preflight before any state change so a stale local aborts with\n // zero writes. On 409, `PushConflictError` propagates to the CLI\n // and the per-file loop below never runs.\n if (!opts.skipPreflight) await this.preflightPush(opts.baseSha);\n\n // Roll the base_sha forward as the server bumps its version on\n // each successful write. Start from the caller's stored SHA (from\n // pull) and update from every response — the next PUT carries the\n // freshest SHA the server has given us so a concurrent third-party\n // write mid-loop 409s cleanly instead of the CLI overwriting.\n let baseSha =\n opts.baseSha ??\n (opts.guardFetchedRemote && !opts.remoteState ? this.remoteSha() : null);\n\n const localPaths = new Set(localFiles.map((file) => file.relativePath));\n if (opts.delete) {\n for (const key of this.assetManifest.keys()) localPaths.add(key);\n }\n const toDelete = opts.delete\n ? this.remoteKeys().filter(\n (key) =>\n key !== MEMBER_CONFIGURATION_KEY &&\n this.canDeleteRemoteResource(key) &&\n !localPaths.has(key) &&\n !this.themeRoot.ignore.ignore(key),\n )\n : [];\n\n const plannedUploads = planThemeUploads(\n this.themeRoot,\n localFiles.filter((f) => f.exists && this.hasChanged(f)),\n toDelete,\n );\n\n if (opts.linkManagedAssets) {\n result.linked = await this.linkManagedAssets(opts.linkManagedAssets);\n // FileResource writes can change the server's content version. The list\n // after reference creation gives the next normal resource write a fresh\n // base when the server supports Phase 003a.\n baseSha = this.lastKnownRemoteSha ?? baseSha;\n } else if (this.assetManifestInstance) {\n this.assetManifest.reload();\n this.ensureManagedAssetsAreResolved();\n }\n\n // Linking managed assets can acknowledge or externalize local binaries.\n // Recheck ordinary files after that step before attempting an upload.\n const toUpload = plannedUploads.filter(\n ({ file, memberConfiguration }) =>\n memberConfiguration || (file.exists && this.hasChanged(file)),\n );\n const uploadCount = toUpload.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n );\n let done = 0;\n for (const upload of toUpload) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n result.errors.length > 0\n ) {\n result.errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await this.uploadMemberConfiguration(upload, baseSha);\n } else {\n await this.uploadFile(file, baseSha, {\n pendingAsset: opts.pendingBinaryAssets,\n });\n }\n // `putResource` updates `lastKnownRemoteSha` from the server's\n // response. Thread it forward so the next iteration carries\n // the current SHA — this is how mid-push races surface.\n baseSha = this.lastKnownRemoteSha;\n result.uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n done += 1 + (upload.memberConfiguration?.files.length ?? 0);\n opts.onProgress?.(done, uploadCount);\n }\n\n if (opts.delete && result.errors.length === 0) {\n for (const key of toDelete) {\n try {\n await this.deleteRemoteFile(key, baseSha);\n baseSha = this.lastKnownRemoteSha;\n result.deleted++;\n } catch (e) {\n if (e instanceof PushConflictError) throw e;\n result.errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n }\n }\n\n return result;\n }\n\n // ─── Full Download ────────────────────────────────────────────────────────\n\n async downloadTheme(\n opts: {\n delete?: boolean;\n skip?: Set<string>;\n onProgress?: (done: number, total: number) => void;\n } = {},\n ): Promise<SyncResult & { skipped: number }> {\n const resources = await this.downloadAll();\n const externalizedAssets = await this.externalizePulledAssets(resources, {\n delete: opts.delete ?? false,\n });\n const result: SyncResult & { skipped: number } = {\n uploaded: 0,\n deleted: 0,\n downloaded: 0,\n linked: externalizedAssets.linked,\n skipped: 0,\n errors: [...externalizedAssets.errors],\n validationFailed: false,\n };\n\n let done = 0;\n for (const resource of resources) {\n if (externalizedAssets.managedKeys.has(resource.key)) {\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n if (opts.skip?.has(resource.key)) {\n result.skipped++;\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n const file = this.themeRoot.file(resource.key);\n\n // Guard against path traversal from malicious API responses\n if (!this.isSafeThemeFile(resource.key, file)) {\n result.errors.push(`Download ${resource.key}: path traversal detected`);\n opts.onProgress?.(++done, resources.length);\n continue;\n }\n\n try {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n const buf = await this.downloadBinaryAsset(resource.url);\n file.write(buf);\n } else if (\n resource.content !== undefined &&\n resource.content !== null\n ) {\n const content =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n file.write(content);\n }\n result.downloaded++;\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${formatError(e)}`);\n }\n opts.onProgress?.(++done, resources.length);\n }\n\n if (opts.delete) {\n const remoteKeys = new Set(resources.map((r) => r.key));\n for (const file of this.themeRoot.files()) {\n if (remoteKeys.has(file.relativePath)) continue;\n // Preserve local stylesheets that the backend hides under\n // STYLESHEET_STRICT_INPUT — their absence from the resources\n // index is intentional (theme-level styles.css /\n // global_styles.css and per-template composite\n // {type}/{name}/styles.css), not a signal that the merchant\n // deleted them.\n if (isStylesheetKey(file.relativePath)) continue;\n\n try {\n unlinkSync(file.absolutePath);\n result.deleted++;\n } catch {\n // ignore\n }\n }\n }\n\n return result;\n }\n}\n\nfunction isLinkableBinaryResource(\n resource: RemoteResource,\n key: string,\n file: ThemeFile,\n): boolean {\n return (\n isThemeAssetKey(key) && isManagedAssetResource(resource) && !file.isText\n );\n}\n\nfunction isNestedBinaryThemeAsset(file: ThemeFile): boolean {\n return (\n !file.isText &&\n file.relativePath.startsWith(\"assets/\") &&\n !isThemeAssetKey(file.relativePath)\n );\n}\n\nfunction isManagedAssetResource(\n resource: RemoteResource | undefined,\n): resource is RemoteResource {\n return (\n resource?.resource_type === \"FileResource\" &&\n typeof resource.url === \"string\" &&\n resource.url.length > 0\n );\n}\n\nfunction remoteResourceFromState(state: RemoteResourceState): RemoteResource {\n return {\n key: state.key,\n checksum: state.checksum,\n content: state.contentPresent ? \"\" : null,\n resource_type: state.resourceType,\n resource_id: state.resourceId,\n url: state.url,\n };\n}\n\nfunction remoteResourceState(resource: RemoteResource): RemoteResourceState {\n return {\n key: resource.key,\n checksum: resource.checksum,\n contentPresent: resource.content != null,\n resourceType: resource.resource_type,\n resourceId: resource.resource_id,\n url: resource.url,\n };\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return isApiError(error) && error.status === 404;\n}\n\nfunction resourceLink(\n resource: RemoteResource,\n): Pick<ThemeAssetLink, \"checksum\" | \"url\"> {\n return {\n ...(typeof resource.checksum === \"string\" && resource.checksum.length > 0\n ? { checksum: resource.checksum }\n : {}),\n ...(typeof resource.url === \"string\" && resource.url.length > 0\n ? { url: resource.url }\n : {}),\n };\n}\n\nfunction assetFilename(key: string): string {\n return key.slice(\"assets/\".length);\n}\n\nfunction assetMetadataFromLink(\n link: ThemeAssetLink,\n): RemoteAssetMetadata | undefined {\n if (\n typeof link.url !== \"string\" ||\n link.url.length === 0 ||\n typeof link.contentType !== \"string\" ||\n link.contentType.length === 0 ||\n typeof link.contentSize !== \"number\" ||\n !Number.isInteger(link.contentSize) ||\n link.contentSize <= 0\n ) {\n return undefined;\n }\n\n return {\n url: link.url,\n contentType: link.contentType,\n contentSize: link.contentSize,\n ...(typeof link.previewImageUrl === \"string\"\n ? { previewImageUrl: link.previewImageUrl }\n : {}),\n ...(typeof link.altText === \"string\" ? { altText: link.altText } : {}),\n ...(typeof link.handle === \"string\" ? { handle: link.handle } : {}),\n };\n}\n\nfunction uploadedAssetMetadata(\n file: ThemeFile,\n resource: UploadedBinaryResource,\n): RemoteAssetMetadata {\n if (resource.assetMetadata) return resource.assetMetadata;\n if (!resource.url) {\n throw new Error(`Uploaded asset has no URL: ${file.relativePath}`);\n }\n\n return {\n url: resource.url,\n contentType: file.mime.name,\n contentSize: file.size(),\n altText: file.name,\n ...(typeof resource.damAssetCode === \"string\"\n ? { handle: resource.damAssetCode }\n : {}),\n };\n}\n\nfunction parseThemeAssetMetadata(\n value: unknown,\n): { filename: string; metadata: RemoteAssetMetadata } | undefined {\n if (!isRecord(value)) return undefined;\n\n const filename = nonEmptyString(value[\"filename\"]);\n const url = nonEmptyString(value[\"url\"]);\n const contentType = nonEmptyString(value[\"content_type\"]);\n const contentSize = positiveInteger(value[\"content_size\"]);\n if (!filename || !url || !contentType || !contentSize) return undefined;\n\n const previewImageUrl = nonEmptyString(value[\"preview_image_url\"]);\n const altText = optionalString(value[\"alt_text\"]);\n const handle = nonEmptyString(value[\"handle\"]);\n return {\n filename,\n metadata: {\n url,\n contentType,\n contentSize,\n ...(previewImageUrl ? { previewImageUrl } : {}),\n ...(altText !== undefined ? { altText } : {}),\n ...(handle ? { handle } : {}),\n },\n };\n}\n\nfunction createdFileResourceId(value: unknown): number | undefined {\n if (!isRecord(value) || !isRecord(value[\"file_resource\"])) {\n return undefined;\n }\n return positiveInteger(value[\"file_resource\"][\"id\"]);\n}\n\nfunction positiveInteger(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isInteger(value) && value > 0) {\n return value;\n }\n if (typeof value !== \"string\" || !/^\\d+$/.test(value)) return undefined;\n\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/**\n * Heuristic-only check for obviously unbalanced liquid delimiters\n * (`{% %}` and `{{ }}`). This is NOT a liquid parser — it only tracks\n * opening delimiters until they are closed. It exists to give watch-mode users a signal\n * when a save is liquid-syntax-broken: the server accepts\n * syntax-broken liquid silently on upload, and the storefront\n * renderer then serves stale content for that section with no error\n * anywhere else in the pipeline.\n *\n * Closing-looking tokens without a preceding Liquid opener are intentionally\n * ignored. Liquid files commonly contain CSS such as `width:100%}` or adjacent\n * block braces (`}}`), so treating every close token as Liquid creates noisy\n * false warnings on valid theme files.\n *\n * Known false positive: delimiters written literally inside a\n * `{% raw %}...{% endraw %}` block are still counted and can trip\n * this check even though the liquid is valid. Acceptable for a\n * warn-only heuristic — a real fix requires parsing liquid, which is\n * out of scope here (see the server-side validation note in the PR).\n */\nexport function hasUnbalancedLiquidDelimiters(content: string): boolean {\n let unclosedTags = 0;\n let unclosedOutputs = 0;\n\n for (const token of content.matchAll(/\\{%|%\\}|\\{\\{|\\}\\}/g)) {\n switch (token[0]) {\n case \"{%\":\n unclosedTags += 1;\n break;\n case \"%}\":\n if (unclosedTags > 0) unclosedTags -= 1;\n break;\n case \"{{\":\n unclosedOutputs += 1;\n break;\n case \"}}\":\n if (unclosedOutputs > 0) unclosedOutputs -= 1;\n break;\n }\n }\n\n return unclosedTags > 0 || unclosedOutputs > 0;\n}\n\nexport interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","import net from \"node:net\";\n\n/**\n * The dev command does real work before it ever binds a port: it resolves\n * (or creates) a server-side dev theme and runs a full initial sync, which\n * can take minutes on a large theme. If the requested port is already taken\n * — most commonly by another `fluid theme dev` or the Mist Desktop preview,\n * which both default to 9292 — all of that work is wasted and the process\n * used to die with a raw `EADDRINUSE` stack trace. Call this before any of\n * that work starts so we fail fast with a clear message instead.\n */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(formatPortConflictMessage(host, port));\n this.name = \"PortInUseError\";\n }\n}\n\nexport function formatPortConflictMessage(host: string, port: number): string {\n return (\n `Port ${port} on ${host} is already in use — likely another ` +\n \"`fluid theme dev` or the Mist Desktop preview. Stop the other \" +\n \"server or pass --port <number>.\"\n );\n}\n\n/**\n * Attempt to bind `host:port`, then immediately release it. Resolves if the\n * port is free; rejects with `PortInUseError` on `EADDRINUSE`/`EACCES`, or\n * the raw error for anything else unexpected.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n reject(new PortInUseError(host, port));\n } else {\n reject(err);\n }\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n\n server.listen(port, host);\n });\n}\n","import http from \"node:http\";\nimport { MemberPreview, isLocalPreviewRequest } from \"./member-preview.js\";\nimport { SSEStream } from \"./sse.js\";\nimport { proxyRequest } from \"./proxy.js\";\nimport { createTemplateOverrideResolver } from \"./template-overrides.js\";\nimport { watchTheme } from \"./watcher.js\";\nimport { PushConflictError, Syncer } from \"../syncer.js\";\nimport {\n findLiquidBlockTagDiagnostics,\n hasUnbalancedLiquidDelimiters,\n} from \"../liquid-delimiters.js\";\nimport type { ThemeRoot } from \"../root.js\";\nimport type { ApiClient } from \"../../api.js\";\nimport { formatPortConflictMessage } from \"./port-preflight.js\";\nimport { assertNoCaseCollisions } from \"../case-collisions.js\";\nimport { isStylesheetKey } from \"../stylesheet-keys.js\";\nimport { ThemeAssetManifest } from \"../asset-manifest.js\";\nimport type { DevRemoteState } from \"../dev-remote-baseline.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n type ThemeUpload,\n} from \"../member-configuration.js\";\n\nfunction timestamp(): string {\n return new Date().toLocaleTimeString(\"en-US\", { hour12: false });\n}\n\nexport interface DevServerOptions {\n host: string;\n port: number;\n reloadMode: \"full-page\" | \"off\";\n /** Cached dev resource index paired with its acknowledged remote version. */\n initialSync?: DevRemoteState;\n /** Persist the complete dev resource baseline and its version atomically. */\n onRemoteState?: (state: DevRemoteState) => void;\n /** Discard persisted trust after the server rejects a watched write. */\n onRemoteStateInvalidated?: () => void;\n}\n\nexport interface DevServerTheme {\n id: number;\n name: string;\n company: string;\n editorUrl?: string;\n}\n\nexport async function startDevServer(\n api: ApiClient,\n theme: DevServerTheme,\n themeRoot: ThemeRoot,\n opts: DevServerOptions & { validate?: boolean },\n onReady?: (address: string) => void,\n): Promise<() => void> {\n const sse = new SSEStream();\n const memberPreview = new MemberPreview(api, theme, opts.reloadMode);\n const syncer = new Syncer(api, theme.id, themeRoot);\n const templateOverrides = createTemplateOverrideResolver(api, theme.id);\n let remoteStateSafe = true;\n const invalidateRemoteState = () => {\n if (!remoteStateSafe) return;\n remoteStateSafe = false;\n try {\n opts.onRemoteStateInvalidated?.();\n } catch {\n // The stale version will fail preflight on the next startup.\n }\n };\n const recordRemoteState = () => {\n if (!remoteStateSafe) return;\n if (opts.onRemoteState) {\n const state = syncer.devRemoteState(\n new ThemeAssetManifest(themeRoot.root).fingerprint(),\n );\n if (state) {\n try {\n opts.onRemoteState(state);\n } catch {\n invalidateRemoteState();\n }\n }\n }\n };\n\n const pendingUpdates = new Set<string>();\n\n // ── Initial sync ─────────────────────────────────────────────────────────\n console.log(`\\nSyncing theme ${theme.name} (#${theme.id})…`);\n const progress = (done: number, total: number) => {\n process.stdout.write(`\\r Uploading ${done}/${total} files…`);\n };\n const uploadFromRemoteIndex = () =>\n syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n onProgress: progress,\n });\n let syncResult;\n if (opts.initialSync) {\n let remoteStateIsTrusted = false;\n try {\n syncer.useDevRemoteState(opts.initialSync);\n await syncer.preflightPush(opts.initialSync.remoteSha);\n remoteStateIsTrusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n\n syncResult = remoteStateIsTrusted\n ? await syncer.uploadTheme({\n delete: true,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n baseSha: syncer.remoteSha(),\n remoteState: opts.initialSync,\n skipPreflight: true,\n onProgress: progress,\n })\n : await uploadFromRemoteIndex();\n } else {\n syncResult = await uploadFromRemoteIndex();\n }\n process.stdout.write(\"\\n\");\n if (syncResult.linked > 0) {\n console.log(` Saved ${syncResult.linked} remote asset reference(s).`);\n }\n if (syncResult.validationFailed) {\n console.error(\n `\\nSchema validation failed (${syncResult.errors.length} error(s)). Use --force to skip.\\n`,\n );\n for (const e of syncResult.errors) console.error(` ${e}`);\n process.exit(1);\n } else if (syncResult.errors.length > 0) {\n invalidateRemoteState();\n for (const e of syncResult.errors) console.error(` ${e}`);\n if (syncResult.uploaded + syncResult.deleted === 0) process.exit(1);\n }\n if (syncResult.errors.length === 0) {\n recordRemoteState();\n }\n\n // ── File watcher ─────────────────────────────────────────────────────────\n //\n // Uploading a file no longer commits anything — Fluid only records that the\n // theme changed, and something has to ask for the commit. `theme push` asks\n // once when its loop ends; the watcher has no such moment, since chokidar\n // reports one event per file and nothing in it says whether this is a lone\n // save or the 200th write of a restore.\n //\n // So ask once the writes stop — but decide where \"the writes\" end by when\n // events ARRIVED, not by when their handlers ran. Handlers are serialized\n // behind awaited uploads, so a genuinely separate edit can queue behind a\n // slow one and start immediately after it; timing the handlers would fold\n // the two into a single commit and erase the boundary between them.\n //\n // An event that arrived within the window continues the operation already\n // in flight, so its pending ask is cancelled and rescheduled. One that\n // arrived after a real pause ends that operation, so the ask goes out NOW\n // rather than being cancelled — otherwise the earlier edit would be\n // swallowed into this one's commit.\n //\n // Asks are also chained against the uploads. A fire-and-forget request can\n // overlap the next batch, committing a half-written operation or leaving\n // the rest of it pending; awaiting the outstanding one before uploading\n // keeps each commit to a settled state.\n //\n // If the server stops with an ask still pending, Fluid's sweeper picks the\n // change up; the cost is a delay, never a lost commit.\n const SYNC_IDLE_MS = 2_000;\n let lastArrivedAt = 0;\n let pendingSync: ReturnType<typeof setTimeout> | null = null;\n let syncInFlight: Promise<void> = Promise.resolve();\n\n // Tracks an ask Fluid did not take. Until it lands the previous edit is\n // uncommitted, and uploading the next one over it merges the two into\n // whichever commit eventually arrives — so the boundary is retried before\n // any further writes go out. A retry that also fails has done what a client\n // can: the content is safe on the server and the sweeper commits it, but\n // the two edits will share a version.\n let askOwed = false;\n let remoteWritesBlocked = false;\n const blockRemoteWrites = (error: PushConflictError): void => {\n if (remoteWritesBlocked) return;\n remoteWritesBlocked = true;\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Remote theme changed outside this dev session (${error.message}). Restart theme dev to compare the current remote state before writing again.`,\n );\n };\n const sendSync = (): void => {\n syncInFlight = syncInFlight.then(async () => {\n const accepted = await syncer.requestSync();\n askOwed = !accepted;\n if (accepted) recordRemoteState();\n });\n };\n const flushSyncNow = (): void => {\n if (!pendingSync) return;\n clearTimeout(pendingSync);\n pendingSync = null;\n sendSync();\n };\n const scheduleSync = (): void => {\n if (pendingSync) clearTimeout(pendingSync);\n pendingSync = setTimeout(() => {\n pendingSync = null;\n sendSync();\n }, SYNC_IDLE_MS);\n };\n\n const stopWatcher = watchTheme(\n themeRoot,\n async (modified, added, removed, arrivedAt) => {\n if (arrivedAt - lastArrivedAt > SYNC_IDLE_MS) {\n // A real pause: whatever was pending belongs to the previous edit.\n flushSyncNow();\n } else if (pendingSync) {\n // Same operation still arriving — hold the ask.\n clearTimeout(pendingSync);\n pendingSync = null;\n }\n lastArrivedAt = arrivedAt;\n // Never upload across an outstanding ask, or it commits a half-written\n // operation.\n await syncInFlight;\n // An ask Fluid refused still owes the edit before this one its own\n // commit. Retry before writing over it.\n if (askOwed) {\n sendSync();\n await syncInFlight;\n }\n if (remoteWritesBlocked) return;\n\n let uploads: ThemeUpload[];\n try {\n assertNoCaseCollisions(\n themeRoot.files().map((file) => file.relativePath),\n );\n assertMemberConfigurationRetained(\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n uploads = planThemeUploads(\n themeRoot,\n [...modified, ...added],\n removed\n .filter((file) => !themeRoot.ignore.ignore(file.relativePath))\n .map((file) => file.relativePath),\n );\n } catch (error) {\n console.error(`\\n[Watcher] Sync blocked: ${String(error)}`);\n return;\n }\n\n let wroteRemote = false;\n const memberTransfer = uploads.some(\n (upload) =>\n upload.memberConfiguration ||\n upload.file.relativePath === \"members_sites.json\",\n );\n\n for (const upload of uploads) {\n const { file } = upload;\n const uploadFiles = [\n file,\n ...(upload.memberConfiguration?.files ?? []),\n ];\n // Validate schema on liquid files during dev (warn, don't block)\n if (opts.validate) {\n for (const candidate of uploadFiles) {\n if (!candidate.isLiquid) continue;\n for (const diagnostic of candidate.validateSchema()) {\n const prefix =\n diagnostic.severity === \"error\"\n ? \"Schema error\"\n : \"Schema warning\";\n console.warn(\n `\\n[${prefix}] ${candidate.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n\n for (const pendingFile of uploadFiles)\n pendingUpdates.add(pendingFile.relativePath);\n try {\n // uploadFile() resolves with the exact bytes it sent, so the\n // diagnostic below always describes the uploaded content —\n // never a re-read of a file an editor may have changed,\n // replaced, or deleted while the request was in flight.\n const uploadedFiles = upload.memberConfiguration\n ? await syncer.uploadMemberConfiguration(upload, syncer.remoteSha())\n : [\n {\n file,\n content: await syncer.uploadFile(file, syncer.remoteSha(), {\n pendingAsset: true,\n }),\n },\n ];\n wroteRemote = true;\n for (const {\n file: uploadedFile,\n content: uploadedContent,\n } of uploadedFiles) {\n console.log(\n ` ✓ synced ${uploadedFile.relativePath} (${timestamp()})`,\n );\n // \"synced\" is true of the upload and false of the page. The\n // storefront inlines a stylesheet from a Themes::FileResource that a\n // background job rewrites after the publish commits, so\n // `inline_asset_content` keeps emitting the previous bytes for a\n // moment after the server accepts this write. Say so, or the ✓ reads\n // as \"the rendered CSS is up to date\" (CURRENT-3899).\n if (isStylesheetKey(uploadedFile.relativePath)) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: the storefront inlines this CSS from a published asset, so it serves the previous bytes until the stylesheet asset sync finishes`,\n );\n }\n // Cheap warn-only heuristic: the server accepts syntax-broken\n // liquid silently, and the storefront then serves stale\n // content for the section with no error anywhere. This does\n // not parse liquid — it can false-positive on delimiters\n // written literally inside {% raw %} — but it's the only\n // client-side signal a save is likely broken.\n if (\n uploadedFile.isLiquid &&\n uploadedContent !== null &&\n hasUnbalancedLiquidDelimiters(uploadedContent)\n ) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: unbalanced liquid delimiters — the storefront may silently serve stale content for this section`,\n );\n }\n if (uploadedFile.isLiquid && uploadedContent !== null) {\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n uploadedContent,\n )) {\n console.warn(\n ` ⚠ ${uploadedFile.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n }\n } catch (e) {\n if (e instanceof PushConflictError) {\n blockRemoteWrites(e);\n break;\n }\n invalidateRemoteState();\n console.error(\n `\\n[Watcher] Upload failed: ${file.relativePath}: ${e}`,\n );\n // Keep safe first-write failures retryable, but never finalize a partial batch.\n remoteWritesBlocked = wroteRemote || memberTransfer;\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n } finally {\n for (const pendingFile of uploadFiles)\n pendingUpdates.delete(pendingFile.relativePath);\n }\n }\n\n if (remoteWritesBlocked) return;\n\n for (const file of removed) {\n if (themeRoot.ignore.ignore(file.relativePath)) continue;\n try {\n await syncer.deleteRemoteFile(file.relativePath, syncer.remoteSha());\n wroteRemote = true;\n console.log(` ✓ removed ${file.relativePath}`);\n } catch (error) {\n if (error instanceof PushConflictError) {\n blockRemoteWrites(error);\n break;\n }\n invalidateRemoteState();\n remoteWritesBlocked = wroteRemote || memberTransfer;\n console.error(\n `[Watcher] Delete failed: ${file.relativePath}: ${error}`,\n );\n if (remoteWritesBlocked)\n console.error(\n \"[Watcher] Restart theme dev to retry the incomplete transfer.\",\n );\n return;\n }\n }\n\n if (remoteWritesBlocked) return;\n\n if (wroteRemote) {\n recordRemoteState();\n try {\n await memberPreview.refreshAfterSync();\n } catch {\n console.error(\n \"[Member preview] Could not refresh routes after sync. The next member request will retry.\",\n );\n }\n }\n // Added or removed files can create or delete dev-theme templates.\n if (added.length > 0 || removed.length > 0) templateOverrides.clear();\n\n if (removed.length > 0) {\n sse.broadcast(JSON.stringify({ reload_page: true }));\n } else if (uploads.length > 0) {\n sse.broadcast(\n JSON.stringify({\n modified: uploads.flatMap(({ file, memberConfiguration }) =>\n [file, ...(memberConfiguration?.files ?? [])].map(\n (updatedFile) => updatedFile.relativePath,\n ),\n ),\n }),\n );\n }\n\n scheduleSync();\n },\n );\n\n // ── HTTP server ───────────────────────────────────────────────────────────\n const server = http.createServer(async (req, res) => {\n if (!isLocalPreviewRequest(req, opts.host)) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Local preview requests only.\");\n return;\n }\n if (await memberPreview.handle(req, res)) return;\n if (req.url === \"/hot-reload\") {\n sse.add(res);\n return;\n }\n\n try {\n await proxyRequest(req, res, {\n company: theme.company,\n themeId: theme.id,\n reloadMode: opts.reloadMode,\n templateOverrides,\n pendingFiles: () =>\n [...pendingUpdates]\n .map((p) => themeRoot.file(p))\n .filter((f) => f.isText)\n .map((f) => ({\n relativePath: f.relativePath,\n read: () => f.read(),\n })),\n });\n } catch (e) {\n console.error(`[Proxy] ${req.method} ${req.url} → ${e}`);\n if (!res.headersSent) {\n // Surface the real upstream failure instead of a bare 502.\n // Every local render round-trips through <company>.fluid.app,\n // so this error is usually environmental — a TLS-inspecting\n // security agent whose root CA the browser trusts via the OS\n // keychain but Node does not, DNS, or a dropped connection —\n // and a naked \"Bad Gateway\" sends humans and QA agents off\n // chasing the theme instead of the network. Diagnosed live:\n // four workflow page steps went needs-review over one masked\n // cert error.\n const message = e instanceof Error ? e.message : String(e);\n res.writeHead(502, { \"content-type\": \"text/plain; charset=utf-8\" });\n res.end(\n `Bad Gateway — the local preview could not reach ${theme.company}.fluid.app: ${message}\\n` +\n \"This is the dev machine's network path to Fluid, not the theme. \" +\n \"Common causes: TLS-inspecting security software (its root CA is in the OS keychain, which Node does not read — set NODE_EXTRA_CA_CERTS to its certificate), DNS, or a proxy. \" +\n \"The same error is logged by the theme dev server process.\",\n );\n }\n }\n });\n\n // The preflight check in `dev.ts` runs before the (potentially minutes-long)\n // initial sync above, but the port could still be grabbed by another\n // process in the window between that check and this `listen()` call.\n // Handle that race the same way: a friendly message and a clean exit\n // instead of an unhandled `EADDRINUSE`/`EACCES` stack trace.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n console.error(formatPortConflictMessage(opts.host, opts.port));\n process.exit(1);\n }\n reject(err);\n });\n server.listen(opts.port, opts.host, () => resolve());\n });\n\n const address = `http://${opts.host}:${opts.port}`;\n onReady?.(address);\n\n // ── Teardown ──────────────────────────────────────────────────────────────\n return function stop() {\n sse.close();\n stopWatcher();\n server.close();\n };\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport {\n mkdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\n\nconst BASELINE_VERSION = 1;\nconst BASELINE_FILE = join(\".fluid-theme\", \"dev-baseline.json\");\n\nexport interface RemoteResourceState {\n key: string;\n checksum: string | null;\n contentPresent: boolean;\n resourceType?: string | null;\n resourceId?: number | null;\n url?: string | null;\n}\n\nexport interface DevRemoteState {\n themeId: number;\n remoteSha: string;\n assetManifestSha: string;\n resources: RemoteResourceState[];\n}\n\nexport function readDevRemoteBaseline(\n themeRoot: string,\n themeId: number,\n assetManifestSha: string,\n): DevRemoteState | null {\n try {\n const parsed = JSON.parse(\n readFileSync(join(themeRoot, BASELINE_FILE), \"utf-8\"),\n );\n const state = parseDocument(parsed);\n if (state.themeId !== themeId) return null;\n if (state.assetManifestSha !== assetManifestSha) return null;\n return state;\n } catch {\n return null;\n }\n}\n\nexport function writeDevRemoteBaseline(\n themeRoot: string,\n state: DevRemoteState,\n): void {\n const document = { version: BASELINE_VERSION, ...state };\n parseDocument(document);\n\n const path = join(themeRoot, BASELINE_FILE);\n const tempPath = `${path}.${randomBytes(6).toString(\"hex\")}.tmp`;\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(tempPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n renameSync(tempPath, path);\n } catch (error) {\n rmSync(tempPath, { force: true });\n throw error;\n }\n}\n\nexport function removeDevRemoteBaseline(themeRoot: string): void {\n rmSync(join(themeRoot, BASELINE_FILE), { force: true });\n}\n\nexport async function devRemoteStateFromSourceShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n themeId: number,\n remoteSha: string,\n): Promise<DevRemoteState | null> {\n try {\n if (!(await shadow.hasHead())) return null;\n\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const resources: RemoteResourceState[] = [];\n for (const key of await shadow.headPaths()) {\n const content = await shadow.blobAtHead(key);\n if (!content) return null;\n const asset = assetManifest.get(key);\n if (asset) {\n if (\n asset.pending ||\n !asset.url ||\n !content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL))\n ) {\n return null;\n }\n resources.push({\n key,\n checksum: asset.checksum ?? null,\n contentPresent: false,\n resourceType: \"FileResource\",\n url: asset.url,\n });\n continue;\n }\n\n if (content.equals(Buffer.from(MANAGED_ASSET_SHADOW_SENTINEL)))\n return null;\n resources.push({\n key,\n checksum: createHash(\"sha256\").update(content).digest(\"hex\"),\n contentPresent: true,\n });\n }\n\n return {\n themeId,\n remoteSha,\n assetManifestSha: assetManifest.fingerprint(),\n resources,\n };\n } catch {\n return null;\n }\n}\n\nfunction parseDocument(document: unknown): DevRemoteState {\n if (!isRecord(document) || document[\"version\"] !== BASELINE_VERSION) {\n throw new Error(`expected version ${BASELINE_VERSION}`);\n }\n\n const themeId = document[\"themeId\"];\n const remoteSha = document[\"remoteSha\"];\n const assetManifestSha = document[\"assetManifestSha\"];\n const rawResources = document[\"resources\"];\n if (!isPositiveInteger(themeId)) throw new Error(\"invalid theme id\");\n if (!isNonEmptyString(remoteSha)) throw new Error(\"invalid remote sha\");\n if (!isNonEmptyString(assetManifestSha)) {\n throw new Error(\"invalid asset manifest sha\");\n }\n if (!Array.isArray(rawResources)) throw new Error(\"invalid resources\");\n\n const resources = rawResources.map(parseResource);\n assertNoCaseCollisions(resources.map((resource) => resource.key));\n return { themeId, remoteSha, assetManifestSha, resources };\n}\n\nfunction parseResource(resource: unknown): RemoteResourceState {\n if (!isRecord(resource)) throw new Error(\"invalid resource\");\n\n const key = resource[\"key\"];\n const checksum = resource[\"checksum\"];\n const contentPresent = resource[\"contentPresent\"];\n const resourceType = resource[\"resourceType\"];\n const resourceId = resource[\"resourceId\"];\n const url = resource[\"url\"];\n if (!isNonEmptyString(key) || key.includes(\"\\0\")) {\n throw new Error(\"invalid resource key\");\n }\n if (checksum !== null && !isNonEmptyString(checksum)) {\n throw new Error(\"invalid resource checksum\");\n }\n if (typeof contentPresent !== \"boolean\") {\n throw new Error(\"invalid resource content marker\");\n }\n if (\n resourceType !== undefined &&\n resourceType !== null &&\n !isNonEmptyString(resourceType)\n ) {\n throw new Error(\"invalid resource type\");\n }\n if (\n resourceId !== undefined &&\n resourceId !== null &&\n !isPositiveInteger(resourceId)\n ) {\n throw new Error(\"invalid resource id\");\n }\n if (url !== undefined && url !== null && !isNonEmptyString(url)) {\n throw new Error(\"invalid resource url\");\n }\n\n return {\n key,\n checksum,\n contentPresent,\n resourceType,\n resourceId,\n url,\n };\n}\n\nfunction isRecord(candidate: unknown): candidate is Record<string, unknown> {\n return typeof candidate === \"object\" && candidate !== null;\n}\n\nfunction isNonEmptyString(candidate: unknown): candidate is string {\n return typeof candidate === \"string\" && candidate.length > 0;\n}\n\nfunction isPositiveInteger(candidate: unknown): candidate is number {\n return (\n typeof candidate === \"number\" &&\n Number.isInteger(candidate) &&\n candidate > 0\n );\n}\n","import { spawn } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * A bare git repo hidden under `.fluid-theme/repo` that stands in for\n * git's index+HEAD when talking to the Fluid server. Every pull commits\n * the incoming remote state onto a single branch (`refs/heads/main`);\n * every successful push commits the outgoing local state onto the same\n * branch. HEAD therefore represents \"the last state the CLI and server\n * agreed on\", and its tree is the natural three-way-merge base for the\n * next pull.\n *\n * We stay in git's plumbing layer — no working tree, no index file\n * next to the theme content — so the shadow repo cannot interfere\n * with the user's own git repo (if they have one) around the theme\n * dir. All state lives inside `.fluid-theme/`.\n *\n * The class only wraps the small handful of plumbing commands we\n * actually need: hash-object, cat-file, write-tree (via a temp index),\n * commit-tree, update-ref, and merge-file. Everything else stays out.\n */\nexport class ShadowRepo {\n // Cached \"does HEAD exist\" result. `pull`/`push` call `blobAtHead`\n // per file (potentially hundreds of times per invocation), and\n // spawning a `git rev-parse` subprocess each time is measurable\n // overhead. HEAD presence only flips one way per instance —\n // `commitState` sets it true after a successful `update-ref` — so\n // the cache stays correct without invalidation.\n private headExists: boolean | undefined = undefined;\n\n private constructor(\n private readonly themeRoot: string,\n private readonly gitDir: string,\n ) {}\n\n /**\n * Return a ShadowRepo bound to `themeId` for the given theme root.\n * The shadow is initialized on first use and re-initialized when\n * the caller passes a themeId different from the one previously\n * recorded — the merge base is only meaningful for the theme it\n * was captured against, so a cross-theme operation (pull A → push\n * B, or two pulls of different themes into one dir) starts from a\n * clean slate rather than pretending B's state matches A's HEAD.\n */\n static async open(themeRoot: string, themeId: number): Promise<ShadowRepo> {\n const shadowDir = join(themeRoot, \".fluid-theme\");\n const gitDir = join(shadowDir, \"repo\");\n const themeIdFile = join(shadowDir, \"theme-id\");\n\n // The stored themeId is written on every commit, so a shadow with\n // a HEAD but no theme-id file is one written by an older CLI —\n // treat it as \"unknown\" and wipe rather than trust it against\n // whatever theme the caller is now operating on.\n if (existsSync(gitDir)) {\n const stored = readStoredThemeId(themeIdFile);\n if (stored !== themeId) {\n rmSync(gitDir, { recursive: true, force: true });\n }\n }\n\n const repo = new ShadowRepo(themeRoot, gitDir);\n\n if (!existsSync(gitDir)) {\n mkdirSync(shadowDir, { recursive: true });\n await repo.git([\"init\", \"--bare\", \"-b\", \"main\", gitDir], {\n cwd: themeRoot,\n });\n }\n\n // Rewrite even when the dir already existed — a fresh init above\n // won't have this file yet, and a preserved dir may have missed a\n // previous write if the process crashed mid-way. Idempotent.\n writeFileSync(themeIdFile, `${themeId}\\n`, \"utf-8\");\n\n // Idempotent: keep `.fluid-theme/` invisible to the user's own git\n // repo. The nested `.gitignore` handles `git add .` inside the\n // shadow dir; the root-level entry handles `git status` on the\n // theme root, which otherwise still lists the untracked shadow\n // directory itself (only its *contents* are ignored by the nested\n // file).\n const shadowIgnore = join(shadowDir, \".gitignore\");\n if (!existsSync(shadowIgnore)) {\n writeFileSync(\n shadowIgnore,\n \"# Fluid CLI shadow repo — internal state, not for version control.\\n*\\n\",\n );\n }\n await ensureRootGitignoreHidesShadow(themeRoot);\n\n return repo;\n }\n\n /** True when the repo has at least one commit on `refs/heads/main`. */\n async hasHead(): Promise<boolean> {\n if (this.headExists !== undefined) return this.headExists;\n try {\n await this.git([\"rev-parse\", \"--verify\", \"HEAD\"]);\n this.headExists = true;\n } catch {\n this.headExists = false;\n }\n return this.headExists;\n }\n\n /** Desktop snapshots deliberately use a different author. They are local\n * recovery points, not proof that those bytes were synced to the server. */\n async hasSyncedHead(): Promise<boolean> {\n if (!(await this.hasHead())) return false;\n const { stdout } = await this.git([\"log\", \"-1\", \"--format=%ae\"]);\n return stdout.toString(\"utf8\").trim() === \"cli@fluid.app\";\n }\n\n /**\n * Every path recorded under HEAD's tree, recursively. Callers use\n * this to detect local deletions (paths in HEAD, absent from the\n * working tree). Returns [] when HEAD has never been committed.\n */\n async headPaths(): Promise<string[]> {\n if (!(await this.hasHead())) return [];\n // -z is required, not cosmetic. Without it git honours core.quotePath\n // (default true) and renders any path containing a non-ASCII byte in\n // its C-quoted display form — `\"assets/caf\\303\\251.css\"`, quotes\n // included. That string matches nothing in the working tree, so\n // diffAgainstShadow reports the file as a local deletion and push\n // sends a DELETE for a key the server has never had. The failed\n // delete then blocks commitPushedState, so shadow HEAD never\n // advances and the phantom delete repeats on every later push.\n // -z also covers the rarer newline-in-path case; splitting on \"\\n\"\n // could not.\n const { stdout } = await this.git([\n \"ls-tree\",\n \"-r\",\n \"-z\",\n \"HEAD\",\n \"--name-only\",\n ]);\n return stdout\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter((line) => line.length > 0);\n }\n\n /**\n * The content of `path` in HEAD's tree, or null when the path does\n * not exist there. Callers use this as the merge base for pull\n * conflict resolution.\n */\n async blobAtHead(path: string): Promise<Buffer | null> {\n if (!(await this.hasHead())) return null;\n try {\n const { stdout } = await this.git([\"cat-file\", \"-p\", `HEAD:${path}`]);\n return stdout;\n } catch {\n return null;\n }\n }\n\n /**\n * Write `content` as a blob in the shadow repo and return its sha.\n * Used by `commitState` to stage each file's content before the\n * `write-tree` call.\n */\n async writeBlob(content: string | Buffer): Promise<string> {\n const buf = typeof content === \"string\" ? Buffer.from(content) : content;\n const { stdout } = await this.git([\"hash-object\", \"-w\", \"--stdin\"], {\n input: buf,\n });\n return stdout.toString(\"utf8\").trim();\n }\n\n /**\n * Commit `files` as HEAD's new tree, threaded onto the current HEAD\n * as the parent. Uses a per-call temp index so a partial run can't\n * corrupt anything reachable from HEAD; the previous commit stays\n * intact until `update-ref` at the end.\n *\n * Returns the new commit sha.\n */\n async commitState(\n files: Array<{ path: string; sha: string }>,\n message: string,\n ): Promise<string> {\n const indexPath = mkdtempSync(join(tmpdir(), \"fluid-shadow-\")) + \"/index\";\n\n try {\n if (files.length > 0) {\n // Keep file lists off argv: large themes exceed Windows' process\n // command-line limit. NUL delimiters preserve paths verbatim.\n await this.git([\"update-index\", \"-z\", \"--index-info\"], {\n input: Buffer.from(\n files.map(({ path, sha }) => `100644 ${sha}\\t${path}\\0`).join(\"\"),\n ),\n env: { GIT_INDEX_FILE: indexPath },\n });\n }\n\n const treeSha = (\n await this.git([\"write-tree\"], { env: { GIT_INDEX_FILE: indexPath } })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n const parent = (await this.hasHead())\n ? (await this.git([\"rev-parse\", \"HEAD\"])).stdout.toString(\"utf8\").trim()\n : null;\n\n const commitArgs = [\"commit-tree\", treeSha, \"-m\", message];\n if (parent) commitArgs.push(\"-p\", parent);\n\n const commitSha = (\n await this.git(commitArgs, {\n env: {\n GIT_AUTHOR_NAME: \"Fluid CLI\",\n GIT_AUTHOR_EMAIL: \"cli@fluid.app\",\n GIT_COMMITTER_NAME: \"Fluid CLI\",\n GIT_COMMITTER_EMAIL: \"cli@fluid.app\",\n },\n })\n ).stdout\n .toString(\"utf8\")\n .trim();\n\n await this.git([\"update-ref\", \"refs/heads/main\", commitSha]);\n this.headExists = true;\n return commitSha;\n } finally {\n // Best-effort — a stale index in tmp is harmless.\n try {\n rmSync(indexPath, { force: true });\n rmSync(indexPath.substring(0, indexPath.length - \"/index\".length), {\n recursive: true,\n force: true,\n });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Three-way merge of `local` against `remote` with `base` as the\n * common ancestor. Returns the merged bytes and a flag when\n * `git merge-file` reported unresolved conflicts (i.e. the output\n * contains `<<<<<<<` markers for the reader to resolve).\n *\n * `base` is null when HEAD has never seen this path; we merge\n * against an empty base, which is what git itself does for a new\n * file added on both sides.\n *\n * `favor` maps to `git merge-file`'s `--ours` / `--theirs`: instead\n * of emitting `<<<<<<<` markers, conflicting hunks are resolved to\n * the local (`\"local\"` → `--ours`, local is file1) or remote\n * (`\"remote\"` → `--theirs`) side. The output then never contains\n * markers, so the result is reported conflict-free even when\n * merge-file's exit code still counts the auto-resolved hunks.\n */\n async merge3(\n base: Buffer | null,\n local: Buffer,\n remote: Buffer,\n favor?: \"local\" | \"remote\",\n ): Promise<{ merged: Buffer; hasConflicts: boolean }> {\n const dir = mkdtempSync(join(tmpdir(), \"fluid-merge-\"));\n const localPath = join(dir, \"local\");\n const basePath = join(dir, \"base\");\n const remotePath = join(dir, \"remote\");\n\n try {\n writeFileSync(localPath, local);\n writeFileSync(basePath, base ?? Buffer.alloc(0));\n writeFileSync(remotePath, remote);\n\n // `git merge-file -p` writes the merged result to stdout instead\n // of mutating `localPath` in place, and exits non-zero (= number\n // of unresolved conflicts) when it couldn't reconcile everything.\n // We treat any non-zero exit as \"conflicts present\" as long as\n // stdout is populated — the caller writes it to disk with markers.\n try {\n const { stdout } = await this.git([\n \"merge-file\",\n \"-p\",\n ...(favor === \"local\"\n ? [\"--ours\"]\n : favor === \"remote\"\n ? [\"--theirs\"]\n : []),\n \"-L\",\n \"local\",\n \"-L\",\n \"base\",\n \"-L\",\n \"remote\",\n localPath,\n basePath,\n remotePath,\n ]);\n return { merged: stdout, hasConflicts: false };\n } catch (err) {\n const e = err as {\n code?: number;\n stdout?: Buffer | string;\n stderr?: Buffer | string;\n };\n // `git merge-file` exit codes:\n // 0 clean merge (handled in the try branch above)\n // 1..127 count of unresolved conflict hunks; stdout is the\n // merged content with markers — treat as conflict.\n // >=128 git itself crashed (SIGSEGV, out-of-memory) —\n // stdout is empty, don't clobber the user's file.\n // negative usage error (bad args) — same, don't clobber.\n // Requiring both the conflict-range code AND a non-empty\n // stdout matches the comment above and stops a crash from\n // silently writing 0 bytes to disk.\n const merged =\n e.stdout instanceof Buffer\n ? e.stdout\n : e.stdout != null\n ? Buffer.from(e.stdout)\n : Buffer.alloc(0);\n const inConflictRange =\n typeof e.code === \"number\" && e.code >= 1 && e.code <= 127;\n // With a favor side, merge-file resolved every hunk itself —\n // the exit code still counts them, but the output carries no\n // markers, so it's a finished merge from the caller's view.\n // The stdout-length guard is dropped here: an auto-resolved\n // result can be legitimately empty (favoring a side that\n // deleted all content).\n if (inConflictRange && favor) {\n return { merged, hasConflicts: false };\n }\n if (inConflictRange && merged.length > 0) {\n return { merged, hasConflicts: true };\n }\n throw err;\n }\n } finally {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // ignore\n }\n }\n }\n\n /**\n * Snapshot the working-tree copy of `paths` into HEAD as a single\n * commit. Intended for the migration path: on the first pull with\n * the new CLI (no shadow repo yet, but a `.fluid-theme.json` with\n * checksums exists) we seed HEAD with whatever is on disk before\n * running the merge, so unmodified files fast-forward cleanly and\n * modified files show a diff.\n */\n async seedFromWorkingTree(\n files: Array<{ path: string; content: string | Buffer }>,\n message: string,\n ): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n for (const { path, content } of files) {\n entries.push({ path, sha: await this.writeBlob(content) });\n }\n await this.commitState(entries, message);\n }\n\n private async git(\n args: string[],\n opts: {\n cwd?: string;\n input?: Buffer;\n env?: Record<string, string>;\n } = {},\n ): Promise<{ stdout: Buffer; stderr: Buffer }> {\n const fullArgs =\n args[0] === \"init\" ? args : [\"--git-dir\", this.gitDir, ...args];\n const child = spawn(\"git\", fullArgs, {\n cwd: opts.cwd ?? this.themeRoot,\n env: { ...process.env, ...opts.env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n return new Promise((resolve, reject) => {\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.on(\"error\", reject);\n child.stdin.on(\"error\", reject);\n child.on(\"close\", (code) => {\n const out = Buffer.concat(stdout);\n const err = Buffer.concat(stderr);\n if (code === 0) {\n resolve({ stdout: out, stderr: err });\n } else {\n const e = new Error(\n `git ${args.join(\" \")} exited with ${code}: ${err.toString(\"utf8\")}`,\n ) as Error & { code: number; stdout: Buffer; stderr: Buffer };\n e.code = code ?? -1;\n e.stdout = out;\n e.stderr = err;\n reject(e);\n }\n });\n child.stdin.end(opts.input);\n });\n }\n}\n\n/**\n * Content-type check the pull command uses to decide whether a file\n * is safe to run through `merge3` (line-based) or must fall back to\n * whole-file \"either/or\" resolution (binary).\n */\nexport function looksBinary(content: Buffer): boolean {\n // Same heuristic git uses for `core.autocrlf` detection: any NUL in\n // the first 8000 bytes counts as binary.\n const scan = content.subarray(0, Math.min(content.length, 8000));\n return scan.includes(0);\n}\n\n/** Best-effort readFile that returns null when the file does not exist. */\nexport function readIfExists(path: string): Buffer | null {\n try {\n return readFileSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the numeric theme id from `.fluid-theme/theme-id`. Returns\n * null when the file is missing or the contents don't parse cleanly;\n * `open` treats that as \"unknown theme\" and rebuilds the shadow.\n */\nfunction readStoredThemeId(themeIdFile: string): number | null {\n try {\n const stored = parseInt(readFileSync(themeIdFile, \"utf-8\").trim(), 10);\n return Number.isFinite(stored) ? stored : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Append `.fluid-theme/` to the theme root's `.gitignore` when the\n * theme dir sits inside a git working tree and the entry isn't\n * already there. Skipped when the user isn't in a git repo — no\n * point manufacturing a `.gitignore` for someone who doesn't use\n * git. Idempotent — a second call is a no-op.\n */\nasync function ensureRootGitignoreHidesShadow(\n themeRoot: string,\n): Promise<void> {\n const insideGit = await new Promise<boolean>((resolve) => {\n const child = spawn(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd: themeRoot,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.on(\"close\", (code) => resolve(code === 0));\n child.on(\"error\", () => resolve(false));\n });\n if (!insideGit) return;\n\n const gitignorePath = join(themeRoot, \".gitignore\");\n let existing = \"\";\n try {\n existing = readFileSync(gitignorePath, \"utf-8\");\n } catch {\n existing = \"\";\n }\n\n const lines = existing.split(\"\\n\").map((line) => line.trim());\n // Match both `.fluid-theme/` and `.fluid-theme` — either shape hides\n // the dir from `git status`.\n if (lines.includes(\".fluid-theme/\") || lines.includes(\".fluid-theme\")) {\n return;\n }\n\n const separator =\n existing.length === 0 || existing.endsWith(\"\\n\") ? \"\" : \"\\n\";\n writeFileSync(\n gitignorePath,\n `${existing}${separator}.fluid-theme/\\n`,\n \"utf-8\",\n );\n}\n","import chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport type { createApiClient } from \"./api.js\";\nimport { themes, type components } from \"@fluid-app/themes-api-client\";\n\nexport type ApplicationTheme = components[\"schemas\"][\"ApplicationTheme\"];\n\nconst PAGE_SIZE = 50;\nconst LOAD_MORE_VALUE = -1;\n\nfunction themeLabel(t: ApplicationTheme): string {\n const active = t.status === \"active\" ? ` ${chalk.green(\"[active]\")}` : \"\";\n return `${t.name} (#${t.id})${active}`;\n}\n\nfunction themeChoices(\n themeList: ApplicationTheme[],\n hasMore: boolean,\n): prompts.Choice[] {\n const choices: prompts.Choice[] = themeList.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n if (hasMore) {\n choices.push({\n title: chalk.dim(`── Load more themes ──`),\n value: LOAD_MORE_VALUE,\n });\n }\n return choices;\n}\n\nasync function fetchThemesPage(\n api: ReturnType<typeof createApiClient>,\n page: number,\n searchQuery?: string,\n): Promise<{\n themes: ApplicationTheme[];\n hasMore: boolean;\n}> {\n const body = await themes.listApplicationThemes(api, {\n per_page: PAGE_SIZE,\n page,\n ...(searchQuery ? { search_query: searchQuery } : {}),\n });\n const list = body.application_themes ?? [];\n const totalPages = body.meta?.total_pages ?? 1;\n return { themes: list, hasMore: page < totalPages };\n}\n\nexport async function selectTheme(\n api: ReturnType<typeof createApiClient>,\n message: string,\n): Promise<ApplicationTheme> {\n const allThemes: ApplicationTheme[] = [];\n let page = 1;\n let hasMore = true;\n let initialIndex = 0;\n\n // Search cache — persists across suggest calls\n let searchQuery = \"\";\n let searchResults: ApplicationTheme[] = [];\n\n while (true) {\n if (hasMore && allThemes.length < page * PAGE_SIZE) {\n const result = await fetchThemesPage(api, page);\n allThemes.push(...result.themes);\n hasMore = result.hasMore;\n }\n\n if (!allThemes.length) {\n console.error(\"No themes found.\");\n process.exit(1);\n }\n\n const choices = themeChoices(allThemes, hasMore);\n\n const { id } = await prompts(\n {\n type: \"autocomplete\",\n name: \"id\",\n message,\n initial: initialIndex,\n choices,\n suggest: async (input: string, choices: prompts.Choice[]) => {\n if (!input) {\n searchQuery = \"\";\n searchResults = [];\n return choices;\n }\n\n if (input !== searchQuery) {\n searchQuery = input;\n try {\n const result = await fetchThemesPage(api, 1, input);\n searchResults = result.themes;\n } catch {\n searchResults = [];\n }\n }\n\n return searchResults.map((t) => ({\n title: themeLabel(t),\n value: t.id,\n }));\n },\n },\n { onCancel: () => process.exit(130) },\n );\n\n if (id === LOAD_MORE_VALUE) {\n initialIndex = allThemes.length;\n page++;\n continue;\n }\n\n if (!id) {\n console.error(\"No theme selected.\");\n process.exit(1);\n }\n\n // Check loaded themes first, then search results\n const found =\n allThemes.find((t) => t.id === id) ??\n searchResults.find((t) => t.id === id);\n if (found) return found;\n\n // Fetch directly by ID as fallback\n const body = await themes.getApplicationTheme(api, id);\n return body.application_theme;\n }\n}\n\nexport async function findTheme(\n api: ReturnType<typeof createApiClient>,\n identifier: string,\n): Promise<ApplicationTheme> {\n // Try ID lookup first\n const idNum = Number(identifier);\n if (Number.isInteger(idNum) && idNum > 0) {\n try {\n const body = await themes.getApplicationTheme(api, idNum);\n if (body.application_theme) return body.application_theme;\n } catch {\n // Not found by ID, fall through to search\n }\n }\n\n // Search by name via API with pagination\n let page = 1;\n let hasMore = true;\n while (hasMore) {\n const result = await fetchThemesPage(api, page, identifier);\n const found = result.themes.find(\n (t) => t.name.toLowerCase() === identifier.toLowerCase(),\n );\n if (found) return found;\n hasMore = result.hasMore;\n page++;\n }\n\n console.error(`No theme found with identifier: ${identifier}`);\n process.exit(1);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\n\nexport interface FluidWorkspace {\n /** Absolute path to the workspace root (where .fluid-workspace.json lives) */\n root: string;\n /** Parsed workspace config */\n config: WorkspaceConfig;\n}\n\ninterface WorkspaceConfig {\n type: string;\n version: number;\n}\n\nconst WORKSPACE_FILE = \".fluid-workspace.json\";\n\n/**\n * Walk up from `startDir` looking for `.fluid-workspace.json`.\n * Returns the workspace info if found, or `null` if not in a workspace.\n */\nexport function findWorkspace(startDir?: string): FluidWorkspace | null {\n let dir = resolve(startDir ?? process.cwd());\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const candidate = join(dir, WORKSPACE_FILE);\n if (existsSync(candidate)) {\n try {\n const raw = readFileSync(candidate, \"utf-8\");\n const config = JSON.parse(raw) as WorkspaceConfig;\n return { root: dir, config };\n } catch {\n return null;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n return null;\n}\n\n/**\n * If cwd is already inside `{workspace}/local/{company}/...`, return that\n * theme root directory. Otherwise return null.\n *\n * Examples (workspace root = /code/fluid-theme-dev):\n * cwd = /code/fluid-theme-dev/local/acme-co → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev/local/acme-co/templates → /code/fluid-theme-dev/local/acme-co\n * cwd = /code/fluid-theme-dev → null\n * cwd = /code/fluid-theme-dev/local → null\n */\nexport function resolveThemeRootFromCwd(\n workspace: FluidWorkspace,\n): string | null {\n const cwd = resolve(process.cwd());\n const localDir = join(workspace.root, \"local\");\n const rel = relative(localDir, cwd);\n\n // Not under local/ at all, or exactly at local/\n if (rel.startsWith(\"..\") || rel === \".\") return null;\n\n // rel is like \"acme-co\" or \"acme-co/templates/subfolder\"\n // The theme root is the first segment: local/{company}\n const firstSegment = rel.split(sep)[0];\n if (!firstSegment) return null;\n\n return join(localDir, firstSegment);\n}\n","import { Command } from \"commander\";\nimport { navigateTheme } from \"./navigate.js\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport {\n devThemeKey,\n getDevTheme,\n setDevTheme,\n setLastDevThemeId,\n clearDevTheme,\n type DevThemeRef,\n} from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { startDevServer } from \"../theme/dev-server/index.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport {\n devRemoteStateFromSourceShadow,\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"../theme/dev-remote-baseline.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n checkPortAvailable,\n PortInUseError,\n} from \"../theme/dev-server/port-preflight.js\";\nimport { isApiError, themes } from \"@fluid-app/themes-api-client\";\nimport { findTheme, type ApplicationTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\n/** Whether this invocation may read and persist the dev remote baseline. */\nexport function devRemoteBaselineEnabled(explicitTheme: boolean): boolean {\n return (\n !explicitTheme && process.env[\"FLUID_THEME_DEV_DISABLE_SHADOW_SYNC\"] !== \"1\"\n );\n}\n\n/**\n * Create the isolated theme used by `theme dev`.\n *\n * A checkout from `theme pull` has a source theme id. New servers clone that\n * source by reference, preserving its DAM/ImageKit assets without moving\n * bytes. A 404/405 keeps older deployments compatible with the established\n * empty-theme flow; other failures must remain visible to the developer.\n */\nexport async function createDevelopmentTheme(\n api: ReturnType<typeof createApiClient>,\n sourceThemeId: number | undefined,\n name: string,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (sourceThemeId !== undefined) {\n try {\n const body = await themes.cloneApplicationThemeForDevelopment(\n api,\n sourceThemeId,\n { application_theme: { name } },\n );\n return { theme: body.application_theme, referenceCloned: true };\n } catch (error) {\n if (\n !isApiError(error) ||\n (error.status !== 404 && error.status !== 405)\n ) {\n throw error;\n }\n\n console.warn(\n \"Server-side theme cloning is unavailable; falling back to an empty development theme. The first sync may take longer.\",\n );\n }\n }\n\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"development\" },\n });\n return { theme: body.application_theme, referenceCloned: false };\n}\n\nasync function ensureDevTheme(\n api: ReturnType<typeof createApiClient>,\n projectKey: string,\n identifier?: string,\n sourceThemeId?: number,\n): Promise<{ theme: ApplicationTheme; referenceCloned: boolean }> {\n if (identifier) {\n const theme = await findTheme(api, identifier);\n // Keep `navigate` pointed at whatever the dev server is actually serving.\n setLastDevThemeId(theme.id);\n return { theme, referenceCloned: false };\n }\n\n // Reuse this project's stored dev theme if it still exists and is still a\n // development theme (a published/promoted theme must not be edited in place).\n const stored = getDevTheme(projectKey);\n // A checkout pulled from a different source needs an isolated sandbox of\n // its own. Older stored entries have no source id, so they are retained only\n // for worktrees that were not pulled from a remote theme.\n if (stored && stored.sourceThemeId === sourceThemeId) {\n try {\n const body = await themes.getApplicationTheme(api, stored.id);\n const existing = body.application_theme;\n if (existing && existing.status === \"development\") {\n console.log(`Using existing dev theme #${existing.id}`);\n // Refresh the stored name and mark it most-recent for `navigate`.\n setDevTheme(projectKey, {\n ...stored,\n id: existing.id,\n name: existing.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n });\n return { theme: existing, referenceCloned: false };\n }\n } catch {\n // Theme no longer exists — fall through to create a new one.\n }\n // Stored theme is gone or no longer a dev theme; forget it.\n clearDevTheme(projectKey);\n }\n\n // Create a new development theme\n const { hostname } = await import(\"node:os\");\n const host = hostname().split(\".\")[0] ?? \"dev\";\n const name =\n `Development (${host}-${Math.random().toString(36).slice(2, 8)})`.slice(\n 0,\n 50,\n );\n\n const creation = await createDevelopmentTheme(api, sourceThemeId, name);\n const { theme } = creation;\n const devTheme: DevThemeRef = {\n id: theme.id,\n name: theme.name,\n ...(sourceThemeId === undefined ? {} : { sourceThemeId }),\n };\n setDevTheme(projectKey, devTheme);\n console.log(`Created dev theme: ${theme.name} (#${theme.id})`);\n return creation;\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\"Start the theme dev server with hot reload\")\n .option(\"--host <host>\", \"Local server host\", \"127.0.0.1\")\n .option(\"--port <port>\", \"Local server port\", \"9292\")\n .option(\n \"-t, --theme <name-or-id>\",\n \"Use an existing theme instead of dev theme\",\n )\n .option(\"-f, --force\", \"Skip schema validation on upload\")\n .option(\"--live-reload <mode>\", \"Reload mode: full-page | off\", \"full-page\")\n .option(\"--navigate\", \"Open browser navigator after server starts\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n host: string;\n port: string;\n theme?: string;\n force?: boolean;\n liveReload: string;\n navigate?: boolean;\n root: string;\n }) => {\n requireToken();\n\n // If no explicit --root and we're inside a workspace, resolve to the theme root\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const port = Number(opts.port);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error(\n `Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`,\n );\n process.exit(1);\n }\n\n // Fail fast if the port is already taken — everything below this\n // point (resolving/creating the dev theme, the initial sync) is\n // expensive and would otherwise run to completion only to crash\n // with a raw EADDRINUSE stack trace when the server finally binds.\n try {\n await checkPortAvailable(opts.host, port);\n } catch (e) {\n if (e instanceof PortInUseError) {\n console.error(e.message);\n } else {\n console.error(`Failed to check port availability: ${e}`);\n }\n process.exit(1);\n }\n\n const reloadMode = opts.liveReload === \"off\" ? \"off\" : \"full-page\";\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n\n // Use company from .fluid-theme.json if available, otherwise fetch\n let company: string;\n if (config?.company) {\n company = config.company;\n } else {\n const companyRes = await api.get<CompanyMe>(\n \"/api/company/v1/companies/me\",\n );\n company = companyRes.data?.company?.subdomain ?? \"\";\n if (!company) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n }\n\n // Always iterate on an isolated dev theme: reuse the stored one or\n // create a fresh `development` theme. `--theme` is the explicit\n // escape hatch for targeting an existing theme. A pulled theme id is\n // never a sync target: it only seeds a new isolated reference clone.\n const projectKey = devThemeKey(company, themeRoot.root);\n const devTarget = opts.theme\n ? await ensureDevTheme(api, projectKey, opts.theme)\n : await ensureDevTheme(api, projectKey, undefined, config?.themeId);\n const { theme } = devTarget;\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const baselineEnabled = devRemoteBaselineEnabled(Boolean(opts.theme));\n let initialRemoteState = baselineEnabled\n ? readDevRemoteBaseline(\n themeRoot.root,\n theme.id,\n assetManifest.fingerprint(),\n )\n : null;\n if (\n !initialRemoteState &&\n baselineEnabled &&\n devTarget.referenceCloned &&\n config?.themeId &&\n config.baseSha\n ) {\n try {\n const sourceShadow = await ShadowRepo.open(\n themeRoot.root,\n config.themeId,\n );\n initialRemoteState = await devRemoteStateFromSourceShadow(\n themeRoot,\n sourceShadow,\n theme.id,\n config.baseSha,\n );\n if (initialRemoteState) {\n writeDevRemoteBaseline(themeRoot.root, initialRemoteState);\n }\n } catch {\n initialRemoteState = null;\n }\n }\n const editorUrl = `https://admin.fluid.app/themes/${theme.id}/editor`;\n\n let stop: (() => void) | undefined;\n\n const cleanup = () => {\n stop?.();\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n stop = await startDevServer(\n api,\n {\n id: theme.id,\n name: theme.name,\n company,\n editorUrl,\n },\n themeRoot,\n {\n host: opts.host,\n port,\n reloadMode,\n validate: !opts.force,\n ...(initialRemoteState ? { initialSync: initialRemoteState } : {}),\n ...(baselineEnabled\n ? {\n onRemoteState: (state) =>\n writeDevRemoteBaseline(themeRoot.root, state),\n onRemoteStateInvalidated: () =>\n removeDevRemoteBaseline(themeRoot.root),\n }\n : {}),\n },\n (address) => {\n console.log(`\\n Dev server: ${address}`);\n console.log(` Web editor: ${editorUrl}`);\n console.log(\"\\n Watching for file changes…\\n\");\n\n if (opts.navigate) {\n void navigateTheme(api, theme.id, address).catch(\n (error: unknown) => {\n console.error(\n error instanceof Error\n ? error.message\n : \"Could not open the preview navigator.\",\n );\n },\n );\n }\n },\n );\n\n // Keep process alive\n await new Promise(() => {});\n },\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport {\n readDevRemoteBaseline,\n removeDevRemoteBaseline,\n writeDevRemoteBaseline,\n} from \"./dev-remote-baseline.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { PushConflictError, Syncer } from \"./syncer.js\";\n\nexport interface DevelopmentPushResult {\n uploaded: number;\n deleted: number;\n linked: number;\n errors: string[];\n validationFailed: boolean;\n /** The remote changed during the push; nothing further was written. */\n conflict: boolean;\n}\n\n/**\n * Push a project to a development theme without touching the project's own\n * sync state.\n *\n * A project directory keeps one shadow repo and one `.fluid-theme.json`, both\n * for the theme it is bound to. Opening the shadow repo for another theme\n * wipes it, so an ordinary push here would cost the bound theme its baseline:\n * the next Publish could no longer detect deletions or stale remotes. This\n * syncs the way `fluid theme dev` does instead: against the development\n * theme's own remote index and `.fluid-theme/dev-baseline.json`, which it\n * keeps current for the next dev session.\n */\nexport async function pushToDevelopmentTheme(\n api: ApiClient,\n themeId: number,\n themeRoot: ThemeRoot,\n opts: {\n validate: boolean;\n /** Remove remote files that no longer exist locally (`--nodelete` off). */\n delete: boolean;\n onProgress?: (done: number, total: number) => void;\n },\n): Promise<DevelopmentPushResult> {\n const syncer = new Syncer(api, themeId, themeRoot);\n const fingerprint = () =>\n new ThemeAssetManifest(themeRoot.root).fingerprint();\n const common = {\n delete: opts.delete,\n validate: opts.validate,\n linkManagedAssets: { replace: true },\n pendingBinaryAssets: true,\n ...(opts.onProgress ? { onProgress: opts.onProgress } : {}),\n } as const;\n\n // Trust the saved dev baseline only while the remote still matches it;\n // otherwise compare against the remote index, as `theme dev` does.\n const saved = readDevRemoteBaseline(themeRoot.root, themeId, fingerprint());\n let trusted = false;\n if (saved) {\n try {\n syncer.useDevRemoteState(saved);\n await syncer.preflightPush(saved.remoteSha);\n trusted = true;\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n }\n }\n let result;\n try {\n result =\n trusted && saved\n ? await syncer.uploadTheme({\n ...common,\n baseSha: syncer.remoteSha(),\n remoteState: saved,\n skipPreflight: true,\n })\n : await syncer.uploadTheme({ ...common, guardFetchedRemote: true });\n } catch (error) {\n if (!(error instanceof PushConflictError)) throw error;\n removeDevRemoteBaseline(themeRoot.root);\n return {\n uploaded: 0,\n deleted: 0,\n linked: 0,\n errors: [\n \"The development theme changed during this push. Run the push again to compare against its current state.\",\n ],\n validationFailed: false,\n conflict: true,\n };\n }\n\n const errors = [...result.errors];\n if (result.validationFailed || errors.length > 0) {\n removeDevRemoteBaseline(themeRoot.root);\n } else if (!(await syncer.requestSync())) {\n // Always ask, even with nothing uploaded: a retry after a rejected\n // finalization finds the files already remote, and some writes (asset\n // reference cleanup) never show in the counts.\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push to commit it.\",\n );\n } else {\n const state = syncer.devRemoteState(fingerprint());\n if (state) writeDevRemoteBaseline(themeRoot.root, state);\n }\n\n return {\n uploaded: result.uploaded,\n deleted: result.deleted,\n linked: result.linked,\n errors,\n validationFailed: result.validationFailed,\n conflict: false,\n };\n}\n","import type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, readIfExists } from \"./shadow-repo.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { assertNoCaseCollisions } from \"./case-collisions.js\";\n\nexport interface DiffSet {\n /** Files whose local content differs from shadow HEAD — the ones to send. */\n changed: ThemeFile[];\n /** Paths present in shadow HEAD but no longer on disk — deletions to send. */\n deleted: string[];\n}\n\n/**\n * Compute what changed locally since the last time the shadow repo\n * committed a state. Replaces the sha256 `checksums` map: shadow HEAD\n * is the source of truth for \"what the CLI last saw the server have\".\n *\n * Files whose local bytes are byte-identical to their HEAD blob are\n * skipped; anything else — new, modified, or a locally-deleted path\n * that HEAD still has — is included.\n */\nexport async function diffAgainstShadow(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<DiffSet> {\n const changed: ThemeFile[] = [];\n const deleted: string[] = [];\n\n const localFiles = themeRoot.files();\n const localByKey = new Map<string, ThemeFile>();\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let headPaths: string[];\n try {\n headPaths = await shadow.headPaths();\n } catch (error) {\n throw new Error(\"Could not read the local theme shadow\", { cause: error });\n }\n\n assertNoCaseCollisions([\n ...localFiles.map((file) => file.relativePath),\n ...headPaths,\n ]);\n const headPathSet = new Set(headPaths);\n\n for (const file of localFiles) {\n if (!file.exists) continue;\n localByKey.set(file.relativePath, file);\n\n const headBlob = await shadow.blobAtHead(file.relativePath);\n if (!headBlob && headPathSet.has(file.relativePath)) {\n throw new Error(\n `Could not read the local theme shadow: ${file.relativePath}`,\n );\n }\n const localBuf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n if (headBlob && headBlob.equals(localBuf)) continue;\n\n changed.push(file);\n }\n\n for (const key of headPaths) {\n if (localByKey.has(key)) continue;\n if (themeRoot.ignore.ignore(key)) continue;\n // A manifest-backed asset is deliberately absent from disk. Its URL is\n // still authoritative and must never be emitted as a remote deletion.\n if (assetManifest.has(key)) continue;\n if (isStylesheetKey(key)) continue; // hidden from the API surface\n deleted.push(key);\n }\n\n return { changed, deleted };\n}\n\n/**\n * Refuse a push when any working file still contains a conflict marker\n * from a previous pull. Mirrors git's \"you have unresolved conflicts;\n * fix them and re-run\" behavior — the whole point of writing markers\n * on pull was to hand resolution to the user, so we can't send them\n * upstream.\n */\nexport function findUnresolvedConflicts(files: ThemeFile[]): string[] {\n const flagged: string[] = [];\n for (const file of files) {\n if (!file.isText) continue;\n const buf = readIfExists(file.absolutePath);\n if (!buf) continue;\n if (containsConflictMarker(buf)) flagged.push(file.relativePath);\n }\n return flagged;\n}\n\n/**\n * Stable, machine-readable one-liner for non-interactive callers\n * (Mist Desktop's publish flow parses push output). Uploading marker-\n * bearing files to a live theme is never acceptable, so `--auto-\n * baseline` pushes still refuse — but they emit this line so the\n * desktop can surface WHICH files block the publish instead of a\n * dead-end wall of prose. Format:\n *\n * FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=a.liquid,b.json\n */\nexport function conflictMarkerBlockLine(files: string[]): string {\n return `FLUID_THEME_PUSH_BLOCKED code=conflict_markers files=${files.join(\",\")}`;\n}\n\nconst CONFLICT_START = Buffer.from(\"<<<<<<<\");\nconst CONFLICT_MID = Buffer.from(\"=======\");\nconst CONFLICT_END = Buffer.from(\">>>>>>>\");\n\n/**\n * A file counts as unresolved when it contains all three marker\n * shapes: `<<<<<<<`, `=======`, and `>>>>>>>`. Requiring all three\n * avoids false positives — a line of equals signs alone (e.g. inside\n * an ASCII table in a template comment) doesn't trip the guard.\n */\nfunction containsConflictMarker(buf: Buffer): boolean {\n return (\n buf.includes(CONFLICT_START) &&\n buf.includes(CONFLICT_MID) &&\n buf.includes(CONFLICT_END)\n );\n}\n\n/**\n * Commit the pushed state to shadow HEAD. Server-acknowledged contents\n * override local bytes for normalized member files, including files edited\n * or deleted locally during upload. Local files remain untouched so those\n * later changes still differ from the next pull's merge base.\n */\nexport async function commitPushedState(\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n message: string,\n acknowledgedContents?: ReadonlyMap<string, string>,\n): Promise<void> {\n const entries: Array<{ path: string; sha: string }> = [];\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n const recordedKeys = new Set<string>();\n for (const [path, content] of new Map(acknowledgedContents)) {\n entries.push({ path, sha: await shadow.writeBlob(content) });\n recordedKeys.add(path);\n }\n for (const file of themeRoot.files()) {\n if (!file.exists || recordedKeys.has(file.relativePath)) continue;\n recordedKeys.add(file.relativePath);\n // A manifest entry normally has no file at all. If bytes are present (for\n // example after a metadata fallback), they remain authoritative until a\n // successful upload can externalize them again.\n const buf = file.isText ? Buffer.from(file.read()) : file.readBinary();\n entries.push({ path: file.relativePath, sha: await shadow.writeBlob(buf) });\n }\n let managedAssetSentinelSha: string | undefined;\n for (const key of assetManifest.keys()) {\n if (recordedKeys.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n entries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // Commit an empty tree too when HEAD exists: a successful final deletion\n // must clear its old path/sentinel so later pushes do not repeat it.\n if (entries.length > 0 || (await shadow.hasHead())) {\n await shadow.commitState(entries, message);\n }\n}\n\n/**\n * A target can already have every URL-backed FileResource (for example from a\n * reference clone), while this checkout's manifest still names its old source\n * theme. Adopt the target and clear any legacy binary from shadow even though\n * no remote write was necessary.\n */\nexport async function finalizeManifestOnlyPush(\n syncer: { repointManagedAssetsToCurrentTheme(): void },\n themeRoot: ThemeRoot,\n shadow: ShadowRepo,\n): Promise<void> {\n syncer.repointManagedAssetsToCurrentTheme();\n await commitPushedState(\n themeRoot,\n shadow,\n `push @ ${new Date().toISOString()}`,\n );\n}\n","import { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { FetchBinary } from \"./merge-pull.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\nexport interface SeedBaselineResult {\n /** False when a baseline already existed (HEAD present) — e.g. the\n * legacy checksum-era migration already seeded it. The migration\n * always wins over server seeding: a migrated HEAD carries the\n * local \"unmodified since last sync\" blobs, which is a better\n * merge base than the server's current state. */\n seeded: boolean;\n /** Paths recorded into the baseline commit. */\n recorded: string[];\n /** Server paths with no local counterpart, deliberately left OUT of\n * the baseline (see invariant below). */\n serverOnly: string[];\n /** Non-fatal per-file problems (e.g. a binary download failed). */\n errors: string[];\n}\n\n/**\n * `fluid theme push --auto-baseline`: when a theme directory has no\n * shadow baseline (scaffold that never pulled, or a dir last synced\n * by the checksum-era CLI whose migration had nothing to seed),\n * record the server's CURRENT state as the baseline commit so the\n * normal push diff (local vs baseline) can run.\n *\n * Invariants:\n *\n * 1. **The working tree is never touched.** Baseline recording writes\n * blobs into the bare shadow repo only; not a single byte on disk\n * changes. Local files identical to the server simply won't diff;\n * files that differ (or exist only locally) will push.\n *\n * 2. **Server-only files are never deleted.** Paths that exist on the\n * server but not locally are deliberately excluded from the\n * baseline commit. `diffAgainstShadow` reports deletions as \"in\n * HEAD but not on disk\", so putting server-only paths into HEAD\n * would mark them for remote deletion — on THIS push (or worse, a\n * later one) — for files the user never had. Excluding them makes\n * the diff structurally unable to delete them; the next `pull`\n * materializes them locally and records them for real.\n *\n * Bandwidth note: a server file whose sha256 checksum matches the\n * local file's is recorded from the LOCAL bytes (identical by\n * definition), so binary assets that are already in sync are never\n * downloaded just to seed the baseline.\n *\n * No-op (`seeded: false`) when HEAD already exists.\n */\nexport async function seedBaselineFromServer(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n message: string;\n}): Promise<SeedBaselineResult> {\n const { shadow, themeRoot, remote, fetchBinary, message } = input;\n const result: SeedBaselineResult = {\n seeded: false,\n recorded: [],\n serverOnly: [],\n errors: [],\n };\n\n if (await shadow.hasHead()) return result;\n\n const entries: Array<{ path: string; sha: string }> = [];\n\n for (const resource of remote) {\n const file = themeRoot.file(resource.key);\n\n // Same traversal guard as mergePull — never trust remote keys.\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Baseline ${resource.key}: path traversal detected`);\n continue;\n }\n\n if (!file.exists) {\n result.serverOnly.push(resource.key);\n continue;\n }\n\n let content: Buffer | null;\n if (resource.checksum && file.checksum() === resource.checksum) {\n // Identical to the server — local bytes ARE the server bytes.\n content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n } else {\n try {\n content = await materializeRemote(resource, fetchBinary);\n } catch (e) {\n result.errors.push(\n `Baseline ${resource.key}: ${e instanceof Error ? e.message : String(e)}`,\n );\n continue;\n }\n }\n if (content == null) {\n // Nothing to record (empty resource) — the local file will be\n // treated as new and pushed, which is the honest outcome.\n continue;\n }\n\n entries.push({ path: resource.key, sha: await shadow.writeBlob(content) });\n result.recorded.push(resource.key);\n }\n\n if (entries.length > 0) {\n await shadow.commitState(entries, message);\n result.seeded = true;\n }\n\n return result;\n}\n\nasync function materializeRemote(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n","import { readLegacyThemeConfig } from \"../theme-config.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport type { ShadowRepo } from \"./shadow-repo.js\";\n\n/**\n * On first pull after upgrading from a checksum-era CLI, the shadow\n * repo starts with no HEAD. `mergePull` would then run every diverged\n * file through a null-base merge — even files the user never touched\n * locally — producing spurious `<<<<<<<` markers for every file the\n * server updated since the last pull.\n *\n * Recover a real merge base by trusting the legacy sha256 checksums:\n * any local file whose content still matches its stored checksum is\n * \"unmodified since last pull\" and can be committed as HEAD. Files\n * whose local sha256 diverges from the stored checksum stay\n * unseeded — we don't have their pre-modification content, so a\n * null-base merge (marker-first UX) is the honest fallback for them.\n *\n * Idempotent: no-op when HEAD already exists, when there is no\n * legacy config, when the config is for a different theme, or when\n * the checksums map is empty. Runs before `mergePull` so its base\n * lookups see the seeded tree.\n */\nexport async function migrateLegacyChecksumsIntoShadow(input: {\n shadow: ShadowRepo;\n themeRoot: ThemeRoot;\n absoluteRoot: string;\n themeId: number;\n}): Promise<void> {\n const { shadow, themeRoot, absoluteRoot, themeId } = input;\n\n if (await shadow.hasHead()) return;\n\n const legacy = readLegacyThemeConfig(absoluteRoot);\n if (!legacy) return;\n if (legacy.themeId !== themeId) return;\n if (!legacy.checksums || Object.keys(legacy.checksums).length === 0) return;\n\n const seed: Array<{ path: string; content: Buffer }> = [];\n for (const file of themeRoot.files()) {\n if (!file.exists) continue;\n const stored = legacy.checksums[file.relativePath];\n if (!stored) continue;\n if (file.checksum() !== stored) continue;\n\n const content = file.isText ? Buffer.from(file.read()) : file.readBinary();\n seed.push({ path: file.relativePath, content });\n }\n\n if (seed.length === 0) return;\n\n await shadow.seedFromWorkingTree(\n seed,\n `migrate from checksum-era CLI @ ${new Date().toISOString()}`,\n );\n}\n","import type { ApiClient } from \"../api.js\";\nimport type { ChangeEntry, GitSyncActor } from \"@fluid-app/fluid-cli\";\nimport {\n gitSyncActorFromMe,\n gitSyncCommitSubject,\n resolveGitSyncActor,\n summarizeChanges,\n} from \"@fluid-app/fluid-cli\";\n\n/**\n * User-stamped shadow-repo commit subjects, in the canonical GitSync\n * format shared with the mist / portal / widget CLIs and the Ruby\n * adapters (see `@fluid-app/fluid-cli`'s git-sync/commit-subject.ts):\n *\n * Mike Tingey: Push theme Aurora · 2026-07-22 05:10:33 (0182d21e-…)\n *\n * When the actor is unknown (offline, /api/me down, token weirdness) the\n * commit is attributed to `Fluid` in the same shape, so consumers need\n * one grammar rather than two:\n *\n * Fluid: Push theme Aurora · 2026-07-22 05:10:33\n *\n * Shadow repos written by older CLIs still hold the pre-format machine\n * wording (`push @ <iso>`). We no longer emit it, but Mist Desktop's\n * History popover still parses it so those repos keep rendering.\n */\n\nexport type SyncVerb =\n | \"Push\"\n | \"Pull\"\n | \"Baseline from server\"\n | \"Snapshot before pull\";\n\n/** Human action per verb, used as the canonical subject's action. */\nconst BASE_MESSAGE: Record<SyncVerb, string> = {\n Push: \"Push from Fluid CLI\",\n Pull: \"Pull from Fluid CLI\",\n \"Baseline from server\": \"Baseline from server\",\n \"Snapshot before pull\": \"Snapshot before pull\",\n};\n\n/** Re-exported under the local name the theme CLI has always used. */\nexport type SyncActor = GitSyncActor;\n\n/**\n * Build a shadow-repo commit subject.\n *\n * `changes` (when supplied) replaces the per-verb constant with a\n * description of what the sync actually carried, so the shadow log reads\n * `Update 3 files in templates` rather than `Push from Fluid CLI` on\n * every entry. Falls back to the constant for an empty change set — a\n * baseline or a no-op sync still deserves a sensible subject.\n */\nexport function syncCommitSubject(\n verb: SyncVerb,\n actor: SyncActor | null,\n when: Date = new Date(),\n changes: readonly ChangeEntry[] = [],\n): string {\n const action = summarizeChanges(changes) ?? BASE_MESSAGE[verb];\n return gitSyncCommitSubject(action, actor, when);\n}\n\n/** Resolve a `/api/me` body to a commit actor. Exported for tests.\n * Delegates to the shared resolver so the name fallback chain\n * (full_name → first+last → email → `user-<id>`) can't drift between\n * CLIs. Null when the body carries no usable identity — the caller then\n * writes a `Fluid:` system commit. */\nexport function actorFromMe(\n raw: Parameters<typeof gitSyncActorFromMe>[0],\n): SyncActor | null {\n return gitSyncActorFromMe(raw);\n}\n\n/**\n * Best-effort fetch of the signed-in user for commit stamping. Races\n * `/api/me` against a short timeout and NEVER rejects — a sync must\n * not block or fail because the identity lookup did. Callers kick\n * this off early and await it only at commit time.\n */\nexport async function fetchSyncActor(\n api: ApiClient,\n timeoutMs = 5_000,\n): Promise<SyncActor | null> {\n // Served from the profile cache after the first sync; /api/me only\n // fires on a miss.\n return resolveGitSyncActor(() => requestSyncActor(api, timeoutMs));\n}\n\nasync function requestSyncActor(\n api: ApiClient,\n timeoutMs: number,\n): Promise<SyncActor | null> {\n try {\n const raw = await Promise.race([\n api.get<Record<string, unknown>>(\"/api/me\"),\n new Promise<never>((_, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n // Don't keep the process alive just for the stamp timeout.\n timer.unref?.();\n }),\n ]);\n return actorFromMe(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(\n ` (skipping user-stamp on sync commit — couldn't fetch /api/me: ${reason})`,\n );\n return null;\n }\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { pushToDevelopmentTheme } from \"../theme/development-push.js\";\nimport { devThemeKey, getDevTheme } from \"../plugin-state.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { PushConflictError, Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport {\n commitPushedState,\n conflictMarkerBlockLine,\n diffAgainstShadow,\n finalizeManifestOnlyPush,\n findUnresolvedConflicts,\n} from \"../theme/merge-push.js\";\nimport { seedBaselineFromServer } from \"../theme/auto-baseline.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport {\n fetchSyncActor,\n syncCommitSubject,\n type SyncActor,\n} from \"../theme/sync-identity.js\";\nimport type { ChangeEntry } from \"@fluid-app/fluid-cli\";\nimport { themes } from \"@fluid-app/themes-api-client\";\nimport {\n selectTheme,\n findTheme,\n type ApplicationTheme,\n} from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { formatError } from \"../theme/format-error.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport {\n assertMemberConfigurationRetained,\n planThemeUploads,\n MEMBER_CONFIGURATION_KEY,\n} from \"../theme/member-configuration.js\";\n\nexport function createPushCommand(): Command {\n return new Command(\"push\")\n .description(\"Push local theme files to a remote theme\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to push to\")\n .option(\"-n, --nodelete\", \"Do not delete remote files missing locally\")\n .option(\n \"-f, --force\",\n \"Skip local Liquid validation and the server-side merge check\",\n )\n .option(\"-p, --publish\", \"Publish the theme after pushing\")\n .option(\n \"-u, --unpublished\",\n \"Create a new unpublished theme and push to it\",\n )\n .option(\n \"--auto-baseline\",\n \"When no local baseline exists, record the server's current state \" +\n \"as the baseline and push only what differs locally (never \" +\n \"modifies local files, never deletes server-only files)\",\n )\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n force?: boolean;\n publish?: boolean;\n unpublished?: boolean;\n autoBaseline?: boolean;\n root: string;\n }) => {\n requireToken();\n\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n console.error(`'${rootPath}' does not look like a theme directory.`);\n process.exit(1);\n }\n\n const api = createApiClient();\n const config = readThemeConfig(themeRoot.root);\n let theme: ApplicationTheme;\n\n if (opts.unpublished) {\n const { name } = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Name for the new theme\",\n },\n { onCancel: () => process.exit(130) },\n );\n if (!name) {\n console.error(\"Theme name is required.\");\n process.exit(1);\n }\n const body = await themes.createApplicationTheme(api, {\n application_theme: { name, status: \"draft\" },\n });\n theme = body.application_theme;\n console.log(\n `Created unpublished theme: ${theme.name} (#${theme.id})`,\n );\n } else if (opts.theme) {\n theme = await findTheme(api, opts.theme);\n } else if (config) {\n console.log(\n ` Using theme from .fluid-theme.json: ${chalk.bold(config.themeName)} (#${config.themeId})`,\n );\n const body = await themes.getApplicationTheme(api, config.themeId);\n theme = body.application_theme;\n if (theme.status === \"development\") {\n console.warn(\n chalk.yellow(\n ` ${theme.name} (#${theme.id}) is a development theme: a scratch copy made by \\`fluid theme dev\\`, not a theme your storefront serves. ` +\n `This push updates only that copy. To update the real theme, run \\`fluid theme pull -t <theme-id>\\` here to bind the project to it, or pass --theme.`,\n ),\n );\n }\n } else {\n theme = await selectTheme(api, \"Select a theme to push to\");\n }\n\n if (config && !mayBindTheme(config, theme)) {\n // Mirroring overwrites the target, so only the development theme\n // `fluid theme dev` made for this directory may be pushed, never a\n // teammate's or another project's scratch copy.\n const own = getDevTheme(devThemeKey(config.company, themeRoot.root));\n if (own?.id !== theme.id) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme, but not this project's` +\n (own ? ` (that is #${own.id})` : \"\") +\n `. Pushing would overwrite it with this directory. Run \\`fluid theme dev\\` here to sync this project's own development theme, or \\`fluid theme dev --theme ${theme.id}\\` to sync this one deliberately.`,\n );\n process.exit(1);\n }\n const unresolvedDev = findUnresolvedConflicts(themeRoot.files());\n if (unresolvedDev.length > 0) {\n console.error(\n `✗ ${unresolvedDev.length} file(s) still contain unresolved conflict markers: ${unresolvedDev.join(\", \")}. Resolve them before pushing.`,\n );\n process.exit(1);\n }\n if (opts.publish) {\n console.error(\n `${theme.name} (#${theme.id}) is a development theme; --publish would make that scratch copy the live theme. Push without --publish, or publish ${config.themeName} (#${config.themeId}).`,\n );\n process.exit(1);\n }\n const devSpinner = ora(\n `Pushing to development theme ${theme.name} (#${theme.id})…`,\n ).start();\n const result = await pushToDevelopmentTheme(\n api,\n theme.id,\n themeRoot,\n {\n validate: !opts.force,\n // --auto-baseline promises to leave server-only files alone.\n delete: !opts.nodelete && !opts.autoBaseline,\n onProgress: (done, total) => {\n devSpinner.text = `Pushing ${done}/${total} files to ${theme.name} (#${theme.id})…`;\n },\n },\n );\n if (result.validationFailed) {\n devSpinner.fail(\n `Schema validation failed (${result.errors.length} error(s)). Use --force to skip.`,\n );\n } else if (result.errors.length > 0) {\n devSpinner.warn(`Pushed with ${result.errors.length} error(s).`);\n } else {\n devSpinner.succeed(\n `Pushed ${result.uploaded} file(s), removed ${result.deleted}, to development theme #${theme.id}.`,\n );\n }\n for (const e of result.errors) console.error(` ${e}`);\n console.log(\n ` This project stays bound to ${config.themeName} (#${config.themeId}); Publish still pushes there.`,\n );\n if (result.errors.length > 0) process.exitCode = 1;\n return;\n }\n\n const shadow = await ShadowRepo.open(themeRoot.root, theme.id);\n\n // Legacy checksum-era dirs (pre-shadow CLI) migrate here — on\n // push as well as pull, so a dir that never pulled with the\n // new CLI still recovers its merge base instead of dead-ending\n // on the baseline check below. Idempotent; the migration wins\n // over `--auto-baseline` server seeding because a migrated\n // HEAD holds the local \"unmodified since last sync\" blobs.\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot: themeRoot.root,\n themeId: theme.id,\n });\n\n const localFiles = themeRoot.files().filter((f) => f.exists);\n\n // Lazy, memoized identity lookup for commit stamping — only\n // fired when we actually commit, never blocks or fails a push.\n let actorPromise: Promise<SyncActor | null> | null = null;\n const getActor = (): Promise<SyncActor | null> =>\n (actorPromise ??= fetchSyncActor(api));\n\n // Refuse to push files that still contain `<<<<<<< / >>>>>>>`\n // markers from a previous merge — the whole point of writing\n // them was to hand resolution to the user; sending them\n // upstream would ship broken content. This guard is absolute:\n // `--auto-baseline` (non-interactive) refuses too, but emits a\n // machine-readable line the desktop can surface well.\n const unresolved = findUnresolvedConflicts(localFiles);\n if (unresolved.length > 0) {\n console.log();\n console.log(\n chalk.red(\n `✗ ${unresolved.length} file(s) still contain unresolved conflict markers:`,\n ),\n );\n for (const key of unresolved) console.log(` ${key}`);\n console.log();\n console.log(\n ` Edit each file to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} sections,`,\n );\n console.log(` then re-run ${chalk.cyan(\"fluid theme push\")}.`);\n console.log();\n if (opts.autoBaseline) {\n console.error(conflictMarkerBlockLine(unresolved));\n }\n process.exit(1);\n }\n\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // baseSha — server-side merge check on every write. Skipped\n // when `--force`, when the config was written by an older CLI\n // that didn't yet capture the pull's `content_version_sha`,\n // or when we're pushing to a different theme than the one the\n // config was written for (a `--unpublished` create, or\n // `--theme` pointing somewhere else). `baseSha` is per-theme —\n // reusing one theme's sha as the base of another theme's push\n // always 409s.\n const configMatchesTheme = config?.themeId === theme.id;\n let baseSha =\n opts.force || !configMatchesTheme ? null : (config?.baseSha ?? null);\n\n // A baseline exists when either half is present: shadow HEAD\n // (the local diff base — written by pull, a legacy migration,\n // or a previous auto-baseline) or a stored `baseSha` (the\n // server-side race check). HEAD alone is enough to diff\n // honestly; a missing baseSha only skips the server preflight.\n const hasBaseline = baseSha != null || (await shadow.hasHead());\n\n // No baseline at all: without `--auto-baseline`, refuse —\n // otherwise the CLI silently clobbers whatever the server\n // currently has, with no way for the user to see the\n // divergence first. `--force` opts out (explicit clobber);\n // `--unpublished` skips because the theme was created empty\n // in this same command and has nothing to protect.\n if (!opts.force && !opts.unpublished && !hasBaseline) {\n if (!opts.autoBaseline) {\n console.error();\n console.error(\n chalk.red(\n `No local baseline for theme \"${theme.name}\" (#${theme.id}).`,\n ),\n );\n console.error();\n console.error(\n ` Run ${chalk.cyan(`fluid theme pull -t ${theme.id}`)} first to sync down the current server state,`,\n );\n console.error(\n ` then push — this way you see what would change before it goes live.`,\n );\n console.error();\n console.error(\n ` Or re-run with ${chalk.cyan(\"--auto-baseline\")} to record the server's current state`,\n );\n console.error(\n ` as the baseline and push only what differs locally (local files are never modified).`,\n );\n console.error();\n console.error(\n ` Or, if you know what you're doing and want to overwrite the server's current`,\n );\n console.error(\n ` contents wholesale, re-run with ${chalk.cyan(\"--force\")}.`,\n );\n console.error();\n process.exit(1);\n }\n\n // `--auto-baseline`: record the server's current state as\n // the baseline WITHOUT touching a single local file, then\n // fall through to the normal diff. Server-only files are\n // deliberately kept out of the baseline so they can never\n // be reported as local deletions (see auto-baseline.ts).\n const baselineSpinner = ora(\n `No local baseline — recording current server state for ${theme.name} (#${theme.id})…`,\n ).start();\n try {\n const resources = await syncer.downloadAll();\n const seedResult = await seedBaselineFromServer({\n shadow,\n themeRoot,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n message: syncCommitSubject(\n \"Baseline from server\",\n await getActor(),\n ),\n });\n baseSha = syncer.remoteSha() ?? null;\n const parts = [`recorded ${seedResult.recorded.length} file(s)`];\n if (seedResult.serverOnly.length > 0) {\n parts.push(\n `left ${seedResult.serverOnly.length} server-only file(s) untouched`,\n );\n }\n baselineSpinner.succeed(`Baseline recorded — ${parts.join(\", \")}.`);\n for (const err of seedResult.errors) {\n console.warn(` ${chalk.yellow(\"warn\")} ${err}`);\n }\n } catch (e) {\n baselineSpinner.fail(\n `Could not record a baseline from the server: ${formatError(e)}`,\n );\n process.exit(1);\n }\n }\n\n // Diff against shadow HEAD is the source of truth for \"what\n // changed since last sync\" — replaces the sha256 checksum map.\n const { changed, deleted } = await diffAgainstShadow(themeRoot, shadow);\n if (!opts.nodelete) assertMemberConfigurationRetained(deleted);\n const managedAssetCount = new ThemeAssetManifest(themeRoot.root).keys()\n .length;\n\n if (\n changed.length === 0 &&\n deleted.length === 0 &&\n managedAssetCount === 0\n ) {\n console.log(\"Nothing to push — local matches the last synced state.\");\n await persistConfig();\n return;\n }\n\n const spinner = ora(`Pushing to ${theme.name} (#${theme.id})…`).start();\n\n // Validate before linking URL references so a schema failure cannot\n // mutate the target theme at all.\n if (!opts.force) {\n const validationErrors: string[] = [];\n for (const file of changed) {\n if (!file.isLiquid) continue;\n for (const diagnostic of file.validateSchema()) {\n if (diagnostic.severity === \"error\") {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(\n file.read(),\n )) {\n validationErrors.push(\n `${file.relativePath}: ${diagnostic.message}`,\n );\n }\n }\n if (validationErrors.length > 0) {\n spinner.fail(\n `Liquid validation failed (${validationErrors.length} error(s)). Use --force to skip.`,\n );\n for (const error of validationErrors) console.error(` ${error}`);\n process.exit(1);\n }\n }\n try {\n await syncer.preflightPush(baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (e) {\n if (e instanceof PushConflictError) {\n renderPullFirst(spinner);\n process.exit(1);\n }\n throw e;\n }\n\n const uploads = planThemeUploads(\n themeRoot,\n changed,\n opts.nodelete ? [] : deleted,\n );\n let linked = 0;\n try {\n linked = await syncer.linkManagedAssets({ replace: true });\n // Reference writes are outside the ordinary resource PUT path. The\n // syncer refreshes its remote SHA afterwards so following PUTs keep\n // the existing Phase-003a conflict chain intact.\n baseSha = syncer.remoteSha() ?? baseSha;\n } catch (error) {\n spinner.fail(\n `Could not save remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n\n if (changed.length === 0 && deleted.length === 0 && linked === 0) {\n try {\n await finalizeManifestOnlyPush(syncer, themeRoot, shadow);\n } catch (error) {\n spinner.fail(\n `Could not finalize remote asset references: ${formatError(error)}`,\n );\n process.exit(1);\n }\n spinner.succeed(\"Nothing to push — local matches the remote theme.\");\n await persistConfig();\n return;\n }\n\n let uploaded = 0;\n let deletedCount = 0;\n const errors: string[] = [];\n let progress = 0;\n const total =\n uploads.reduce(\n (count, upload) =>\n count + 1 + (upload.memberConfiguration?.files.length ?? 0),\n 0,\n ) + (opts.nodelete ? 0 : deleted.length);\n\n for (const upload of uploads) {\n const { file } = upload;\n if (\n file.relativePath === MEMBER_CONFIGURATION_KEY &&\n errors.length > 0\n ) {\n errors.push(\n \"Skipped members_sites.json because a dependency upload failed.\",\n );\n break;\n }\n try {\n if (upload.memberConfiguration) {\n await syncer.uploadMemberConfiguration(upload, baseSha);\n } else {\n await syncer.uploadFile(file, baseSha);\n }\n baseSha = syncer.remoteSha() ?? baseSha;\n uploaded += 1 + (upload.memberConfiguration?.files.length ?? 0);\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Upload ${file.relativePath}: ${formatError(e)}`);\n }\n progress += 1 + (upload.memberConfiguration?.files.length ?? 0);\n spinner.text = `Pushing ${progress}/${total} files…`;\n }\n\n if (!opts.nodelete && errors.length === 0) {\n for (const key of deleted) {\n try {\n await syncer.deleteRemoteFile(key, baseSha);\n baseSha = syncer.remoteSha() ?? baseSha;\n deletedCount++;\n } catch (e) {\n if (e instanceof PushConflictError) {\n spinner.stop();\n renderPullFirst(ora());\n process.exit(1);\n }\n errors.push(`Delete ${key}: ${formatError(e)}`);\n }\n spinner.text = `Pushing ${++progress}/${total} files…`;\n }\n }\n\n if (errors.length === 0) {\n try {\n // The selected target now owns every manifest reference, so a\n // later deleted dev sandbox cannot force a byte upload again.\n syncer.repointManagedAssetsToCurrentTheme();\n } catch (error) {\n errors.push(`Save asset provenance: ${formatError(error)}`);\n }\n }\n\n // Every write is on the server; ask Fluid to commit them as one\n // version. The writes themselves only mark the theme changed — this\n // one call is what makes a 120-file push a single commit instead of\n // 120, and it is the reason the server no longer has to guess where\n // an operation ended.\n //\n // A partial transfer may contain unresolved member dependencies.\n // Managed-asset-only pushes still finalize once every write succeeds.\n if (\n errors.length === 0 &&\n (uploaded > 0 || deletedCount > 0 || linked > 0)\n ) {\n if (!(await syncer.requestSync())) {\n errors.push(\n \"Fluid did not accept sync finalization. Retry the push before publishing.\",\n );\n }\n }\n\n if (errors.length) {\n spinner.warn(`Pushed with ${errors.length} error(s).`);\n for (const err of errors) console.error(` ${err}`);\n // The live theme is missing every rejected file, so this is a\n // failure. Mist's lifecycle retry runs `fluid theme push\n // --auto-baseline` and keys off the exit code, and every other\n // failure in this command already exits 1 — exiting 0 here\n // reported a publish that did not happen.\n //\n // exitCode rather than process.exit(): the command still has\n // output to flush, and an immediate exit can truncate it.\n process.exitCode = 1;\n } else {\n spinner.succeed(\n `Pushed ${uploaded} file(s), saved ${linked} remote asset reference(s)` +\n (deletedCount > 0\n ? `, deleted ${deletedCount} remote file(s).`\n : \".\"),\n );\n }\n\n // Roll shadow HEAD forward only when every write succeeded —\n // committing the working tree on a partial push would make\n // `diffAgainstShadow` treat the failed files as \"already\n // synced\" on the next push, silently swallowing the retry.\n // Leave HEAD stale on failure and let the next push re-diff\n // and re-attempt.\n if (errors.length === 0) {\n // Describe what the push carried rather than stamping every\n // entry \"Push from Fluid CLI\". `changed` is the uploaded set\n // and `deleted` the removed keys — exactly what landed, since\n // we only get here when no write failed.\n const pushedChanges: ChangeEntry[] = [\n ...changed.map((file) => ({\n status: \"modified\" as const,\n path: file.relativePath,\n })),\n ...(opts.nodelete\n ? []\n : deleted.map((key) => ({\n status: \"deleted\" as const,\n path: key,\n }))),\n ];\n try {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\n \"Push\",\n await getActor(),\n new Date(),\n pushedChanges,\n ),\n syncer.acknowledgedMemberContents(),\n );\n } catch (error) {\n console.error(\n `Theme content was uploaded to the server, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Do not repeat the push with --force. Review the remote theme and run a normal pull to recover the baseline.`,\n );\n if (opts.publish) console.error(\"Publishing was not attempted.\");\n process.exitCode = 1;\n return;\n }\n }\n\n await persistConfig();\n\n /**\n * Persist `.fluid-theme.json`. When a config already exists,\n * this is the pre-existing baseSha refresh. When it doesn't\n * (a scaffold's first `--auto-baseline` push), bind the dir\n * to the theme now — best-effort, since the company subdomain\n * needs one more API call — so the next push/pull needs no\n * interactive picker.\n */\n async function persistConfig(): Promise<void> {\n if (config) {\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: config.company,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n return;\n }\n if (!opts.autoBaseline) return;\n try {\n const res = await api.get<{\n data?: { company?: { subdomain?: string } };\n }>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) return;\n writeThemeConfig(themeRoot.root, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: baseSha ?? undefined,\n assetManifestSha: new ThemeAssetManifest(\n themeRoot.root,\n ).fingerprint({ excludePending: true }),\n });\n } catch {\n // Best-effort — the shadow baseline alone is enough for\n // the next push to diff correctly.\n }\n }\n\n if (opts.publish && errors.length === 0) {\n const pubSpinner = ora(\"Publishing theme…\").start();\n try {\n await themes.publishApplicationTheme(api, theme.id);\n pubSpinner.succeed(\"Theme published.\");\n } catch (e) {\n pubSpinner.fail(`Publish failed: ${e}`);\n // Content uploaded but the theme is still a draft. Without\n // this the caller is told the publish succeeded.\n process.exitCode = 1;\n }\n }\n },\n );\n}\n\nfunction renderPullFirst(spinner: ReturnType<typeof ora>): void {\n spinner.fail(\"Server has changed since your last pull. Push aborted.\");\n console.log();\n console.log(\n ` ${chalk.cyan(\"Run `fluid theme pull` first\")} to merge the remote changes,`,\n );\n console.log(\n ` then re-run push. Use ${chalk.cyan(\"fluid theme push --force\")} to overwrite anyway.`,\n );\n console.log();\n}\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readThemeConfig } from \"../theme-config.js\";\nimport { ThemeAssetManifest } from \"./asset-manifest.js\";\nimport { diffAgainstShadow } from \"./merge-push.js\";\nimport { ThemeRoot } from \"./root.js\";\nimport { ShadowRepo } from \"./shadow-repo.js\";\n\nfunction containsSymlink(directory: string): boolean {\n return readdirSync(directory, { withFileTypes: true }).some((entry) => {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\")\n return false;\n return (\n entry.isSymbolicLink() ||\n (entry.isDirectory() && containsSymlink(join(directory, entry.name)))\n );\n });\n}\n\n/** Fail closed for unattended updates. Desktop snapshots can advance shadow\n * HEAD with local work, so only a CLI sync baseline is eligible. */\nexport async function canPullThemeInBackground(\n root: string,\n themeId: number,\n): Promise<boolean> {\n try {\n if (containsSymlink(root)) return false;\n const config = readThemeConfig(root);\n if (\n !config ||\n config.themeId !== themeId ||\n !existsSync(join(root, \".fluid-theme\", \"repo\", \"HEAD\"))\n )\n return false;\n const storedId = readFileSync(\n join(root, \".fluid-theme\", \"theme-id\"),\n \"utf8\",\n ).trim();\n if (storedId !== String(themeId)) return false;\n const manifestPath = join(root, \".fluid-assets.json\");\n if (existsSync(manifestPath)) {\n const raw: unknown = JSON.parse(readFileSync(manifestPath, \"utf8\"));\n if (\n !raw ||\n typeof raw !== \"object\" ||\n !(\"version\" in raw) ||\n raw.version !== 1 ||\n !(\"assets\" in raw) ||\n !raw.assets ||\n typeof raw.assets !== \"object\" ||\n Array.isArray(raw.assets)\n )\n return false;\n const manifest = new ThemeAssetManifest(root);\n if (manifest.entries().some(([, link]) => link.pending)) return false;\n if (\n !config.assetManifestSha ||\n config.assetManifestSha !== manifest.fingerprint()\n )\n return false;\n } else if (\n config.assetManifestSha &&\n config.assetManifestSha !== new ThemeAssetManifest(root).fingerprint()\n )\n return false;\n const shadow = await ShadowRepo.open(root, themeId);\n if (!(await shadow.hasSyncedHead())) return false;\n const diff = await diffAgainstShadow(new ThemeRoot(root), shadow);\n return diff.changed.length === 0 && diff.deleted.length === 0;\n } catch {\n return false;\n }\n}\n","import { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { memberDependencyPaths } from \"./member-dependencies.js\";\nimport { isThemeResourceKey } from \"./resource-key.js\";\n\nexport interface MemberOmissionInput {\n /** Both accepted and edited documents: omission must not erase either graph. */\n previousDocuments: readonly (Buffer | null)[];\n availablePaths: ReadonlySet<string>;\n incomingPaths: ReadonlySet<string>;\n ignored: (path: string) => boolean;\n}\n\n/** Missing/ignored is not empty. Return paths to retain without acknowledging\n * their local bytes as remotely accepted. Used before pull writes or pruning. */\nexport function planOmittedMemberFiles(\n input: MemberOmissionInput,\n): Set<string> {\n if (\n input.incomingPaths.has(MEMBER_CONFIGURATION_KEY) &&\n !input.ignored(MEMBER_CONFIGURATION_KEY)\n )\n return new Set();\n\n const protectedPaths = new Set<string>();\n for (const content of input.previousDocuments) {\n if (content === null) continue;\n protectedPaths.add(MEMBER_CONFIGURATION_KEY);\n if (content.byteLength > 2 * 1024 * 1024) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json exceeds 2 MB.\",\n );\n }\n const document: unknown = JSON.parse(content.toString(\"utf8\"));\n if (!isRecord(document) || document.version !== 2) {\n throw new Error(\n \"Cannot preserve member files: members_sites.json must be a version 2 document.\",\n );\n }\n for (const field of [\"sites\", \"menus\", \"menu_references\"]) {\n if (!Array.isArray(document[field])) {\n throw new Error(\n `Cannot preserve member files: members_sites.json ${field} must be an array.`,\n );\n }\n }\n assertDependencyShape(document);\n // An omitted manifest cannot authorize pruning shared composition files.\n // Their dependency relationships live in the template contents, not the manifest.\n if ((document.sites as unknown[]).length > 0) {\n for (const path of input.availablePaths) {\n if (/^(sections|blocks|components|snippets|navbar|footer)\\//.test(path))\n protectedPaths.add(path);\n }\n }\n for (const path of memberDependencyPaths(document, input.availablePaths)) {\n if (\n !isThemeResourceKey(path) ||\n path.includes(\"\\\\\") ||\n [...path].some((character) => character.charCodeAt(0) < 32) ||\n path === \"config/members_sites.json\"\n ) {\n throw new Error(\n `Cannot preserve member files: invalid dependency path ${path}.`,\n );\n }\n protectedPaths.add(path);\n }\n }\n return new Set(\n [...protectedPaths].filter(\n (path) => !input.incomingPaths.has(path) || input.ignored(path),\n ),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Reject incomplete dependency declarations rather than silently pruning files. */\nfunction assertDependencyShape(document: Record<string, unknown>): void {\n const entries = (value: unknown): Record<string, unknown>[] => {\n if (!Array.isArray(value) || !value.every(isRecord)) {\n throw new Error(\n \"Cannot preserve member files: malformed dependency declaration.\",\n );\n }\n return value;\n };\n for (const entry of [\n ...entries(document.templates ?? []),\n ...entries(document.menu_references),\n ]) {\n if (typeof entry.path !== \"string\" || !entry.path) {\n throw new Error(\n \"Cannot preserve member files: dependency path is missing.\",\n );\n }\n }\n for (const site of entries(document.sites)) {\n if (site.layout_name !== null && typeof site.layout_name !== \"string\") {\n throw new Error(\"Cannot preserve member files: invalid layout name.\");\n }\n for (const page of entries(site.pages)) {\n if (\n page.template_path !== null &&\n typeof page.template_path !== \"string\"\n ) {\n throw new Error(\n \"Cannot preserve member files: invalid page template path.\",\n );\n }\n }\n }\n}\n","import {\n BackgroundPullChangedError,\n type BackgroundPullGuard,\n} from \"./background-pull-guard.js\";\nimport { unlinkSync } from \"node:fs\";\nimport { sep } from \"node:path\";\nimport type { components } from \"@fluid-app/themes-api-client\";\nimport {\n MANAGED_ASSET_SHADOW_SENTINEL,\n ThemeAssetManifest,\n} from \"./asset-manifest.js\";\nimport { ShadowRepo, looksBinary, readIfExists } from \"./shadow-repo.js\";\nimport { MEMBER_CONFIGURATION_KEY } from \"./member-configuration.js\";\nimport { planOmittedMemberFiles } from \"./member-transfer.js\";\nimport type { ThemeFile } from \"./file.js\";\nimport type { ThemeRoot } from \"./root.js\";\nimport { isStylesheetKey } from \"./stylesheet-keys.js\";\nimport { commitPushedState } from \"./merge-push.js\";\nimport { syncCommitSubject, type SyncActor } from \"./sync-identity.js\";\n\ntype RemoteResource = components[\"schemas\"][\"ApplicationThemeResource\"];\n\n/** `write(..., RESOLVES_CONFLICT)`: this write is the chosen side of a conflict. */\nconst RESOLVES_CONFLICT = true;\n\n/**\n * Appended to every per-file write failure. Resource keys are verbatim\n * relative paths, so a template named `Sale 11/14/2025, 12:30 PM` asks\n * the filesystem for directories that Windows (and any path already\n * occupied by a file) refuses. Renaming the template in the admin is\n * the only fix the user owns.\n */\nconst RENAME_REMEDY =\n 'Rename the template in the admin visual builder to remove / \\\\ : * ? \" < > | from its name, then pull again.';\n\nexport interface MergePullResult {\n /** Files written from the remote without any local content to merge. */\n written: number;\n /** Files that went through a clean three-way merge with no markers. */\n merged: number;\n /** Files where markers remain — the caller must surface these. */\n conflicts: string[];\n /** Conflicting files auto-resolved to one side via `resolve`. */\n autoResolved: string[];\n /** Files removed locally because remote no longer emitted them. */\n deleted: number;\n /** Files that needed no change (already in sync). */\n skipped: number;\n /** Non-fatal per-file errors. */\n errors: string[];\n /** The remote revision must not be accepted as the new sync baseline. */\n baselinePreserved?: boolean;\n}\n\nexport type FetchBinary = (url: string) => Promise<Buffer>;\n\nexport interface MergePullInput {\n themeRoot: ThemeRoot;\n backgroundGuard?: BackgroundPullGuard;\n shadow: ShadowRepo;\n remote: RemoteResource[];\n fetchBinary: FetchBinary;\n delete: boolean;\n /**\n * When true, skip the merge and take remote wholesale for every\n * file. Used by `fluid theme pull --force` when the user wants a\n * clean slate. HEAD still advances to reflect the state we wrote.\n */\n force?: boolean;\n /**\n * Remote resources already represented by `.fluid-assets.json`. They are\n * intentionally not materialized; ShadowRepo retains only a tiny path\n * sentinel, never their binary bytes.\n */\n skipRemoteKeys?: ReadonlySet<string>;\n /**\n * Non-interactive conflict resolution (`fluid theme pull --resolve\n * <local|remote>`): conflicting hunks are resolved to the chosen\n * side via `git merge-file --ours/--theirs` instead of writing\n * `<<<<<<<` markers; binary conflicts keep the whole chosen side.\n * Because \"remote\" resolution discards local hunks that live\n * NOWHERE else (shadow HEAD only holds the last-synced base, not\n * the user's edits), the pre-merge working tree is committed to the\n * shadow repo first — so the discarded state stays restorable.\n * Undefined = current behavior (markers).\n */\n resolve?: \"local\" | \"remote\";\n /**\n * Signed-in user for commit stamping (best-effort — null falls back\n * to the legacy machine-flavored subjects).\n */\n actor?: SyncActor | null;\n onProgress?: (done: number, total: number) => void;\n}\n\n/**\n * Reconcile the just-downloaded remote tree against the working tree,\n * using the shadow repo's HEAD as the merge base. Text files that\n * diverge on both sides get run through `git merge-file`; the result\n * — clean or with `<<<<<<<` markers — is written to disk. Binary\n * files fall back to \"take remote\" because there is no principled\n * three-way merge for them.\n *\n * HEAD advances to reflect the remote state we just materialized,\n * even when unresolved conflict markers remain in the working tree.\n * The user resolves markers in their editor and re-runs push; push\n * refuses to send files whose content still starts with a marker,\n * so a half-resolved push cannot silently ship broken content.\n */\nexport async function mergePull(\n input: MergePullInput,\n): Promise<MergePullResult> {\n const { themeRoot, shadow, remote, fetchBinary, onProgress } = input;\n const doDelete = input.delete;\n const result: MergePullResult = {\n written: 0,\n merged: 0,\n conflicts: [],\n autoResolved: [],\n deleted: 0,\n skipped: 0,\n errors: [],\n };\n\n // In `resolve` mode, working-tree writes are DEFERRED until after\n // the merge loop: if any conflict got auto-resolved, the pre-merge\n // working tree (still untouched at that point) is committed to the\n // shadow repo first, so the side we discard remains restorable.\n const pendingWrites: Array<{\n key: string;\n file: ThemeFile;\n content: Buffer;\n record: () => void;\n resolvesConflict: boolean;\n }> = [];\n // Keys whose bytes never reached disk — refused by the traversal\n // guard, or failed on the write. They must be kept out of the shadow\n // HEAD commit below, exactly like a failed download: HEAD is what\n // push diffs against, so recording content we did not write would\n // turn a tolerated failure into a remote DELETE. Refused keys have a\n // second reason — git rejects paths like `../x` outright, and the\n // commit takes the whole pull down with it.\n const unwrittenKeys = new Set<string>();\n const tryWrite = (key: string, file: ThemeFile, content: Buffer): boolean => {\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([key], () => file.write(content));\n else file.write(content);\n return true;\n } catch (e) {\n if (e instanceof BackgroundPullChangedError) throw e;\n unwrittenKeys.add(key);\n result.errors.push(`Reconcile ${key}: ${errMsg(e)}. ${RENAME_REMEDY}`);\n return false;\n }\n };\n /**\n * Write `content`, then apply `record` — the bookkeeping that says\n * which bucket of the summary this file counted as. The two travel\n * together, including into the deferred flush: bookkeeping applied\n * when the write was merely QUEUED would report a file the flush\n * never managed to write as both written and errored.\n */\n const write = (\n key: string,\n file: ThemeFile,\n content: Buffer,\n record: () => void,\n resolvesConflict = false,\n ): void => {\n if (input.resolve) {\n pendingWrites.push({ key, file, content, record, resolvesConflict });\n return;\n }\n if (tryWrite(key, file, content)) record();\n };\n\n const remoteContent = new Map<string, Buffer>();\n const remoteKeys = new Set<string>();\n let done = 0;\n for (const resource of remote) {\n remoteKeys.add(resource.key);\n if (input.skipRemoteKeys?.has(resource.key)) {\n onProgress?.(++done, remote.length);\n continue;\n }\n try {\n const buf = await materialize(resource, fetchBinary);\n if (buf) remoteContent.set(resource.key, buf);\n } catch (e) {\n result.errors.push(`Download ${resource.key}: ${errMsg(e)}`);\n }\n onProgress?.(++done, remote.length);\n }\n\n input.backgroundGuard?.assertUnchanged();\n let retainedMemberPaths: Set<string>;\n try {\n if (\n remoteKeys.has(MEMBER_CONFIGURATION_KEY) &&\n !themeRoot.ignore.ignore(MEMBER_CONFIGURATION_KEY) &&\n !remoteContent.has(MEMBER_CONFIGURATION_KEY)\n ) {\n throw new Error(\n \"members_sites.json was listed but could not be downloaded; previous member state retained.\",\n );\n }\n retainedMemberPaths = planOmittedMemberFiles({\n previousDocuments: [\n readIfExists(themeRoot.file(MEMBER_CONFIGURATION_KEY).absolutePath),\n await shadow.blobAtHead(MEMBER_CONFIGURATION_KEY),\n ],\n availablePaths: new Set([\n ...themeRoot.files().map((file) => file.relativePath),\n ...(await shadow.headPaths()),\n ]),\n incomingPaths: remoteKeys,\n ignored: (path) => themeRoot.ignore.ignore(path),\n });\n } catch (error) {\n result.errors.push(errMsg(error));\n result.baselinePreserved = true;\n return result;\n }\n for (const path of retainedMemberPaths) remoteContent.delete(path);\n\n for (const [key, remoteBuf] of remoteContent) {\n const file = themeRoot.file(key);\n\n if (!file.absolutePath.startsWith(themeRoot.root + sep)) {\n result.errors.push(`Reconcile ${key}: path traversal detected`);\n unwrittenKeys.add(key);\n continue;\n }\n\n if (input.force) {\n // `--force`: clobber everything with the remote content. HEAD\n // still advances below so the next pull's merge base is right.\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n const localBuf = readIfExists(file.absolutePath);\n const baseBuf = await shadow.blobAtHead(key);\n\n if (localBuf == null) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (localBuf.equals(remoteBuf)) {\n result.skipped++;\n continue;\n }\n\n if (baseBuf && localBuf.equals(baseBuf)) {\n write(key, file, remoteBuf, () => result.written++);\n continue;\n }\n\n if (baseBuf && remoteBuf.equals(baseBuf)) {\n // A prior unresolved pull advances shadow HEAD to the clean remote\n // state while leaving markers in the working tree. A later\n // `--resolve remote` must be able to finish that interrupted clone;\n // treating the marker-filled file as an ordinary local edit traps\n // every retry in the same broken state.\n if (\n input.resolve === \"remote\" &&\n !looksBinary(localBuf) &&\n hasGeneratedConflictMarkers(localBuf)\n ) {\n write(\n key,\n file,\n remoteBuf,\n () => result.autoResolved.push(`${key} (cleared conflict markers)`),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n // Server didn't move; keep the user's local edits.\n result.skipped++;\n continue;\n }\n\n const isBinary =\n looksBinary(localBuf) ||\n looksBinary(remoteBuf) ||\n (baseBuf ? looksBinary(baseBuf) : false);\n\n if (isBinary) {\n // No textual merge for binary files: whole-file either/or.\n if (input.resolve === \"local\") {\n // Keep the local side untouched — nothing to write.\n result.autoResolved.push(`${key} (binary — kept local)`);\n continue;\n }\n // Take remote. In `--resolve remote` that's the chosen side;\n // otherwise flag it so the user can compare against the shadow\n // repo (`git show HEAD:...`) and reapply local intent by hand.\n write(\n key,\n file,\n remoteBuf,\n () => {\n if (input.resolve === \"remote\") {\n result.autoResolved.push(`${key} (binary — kept remote)`);\n } else {\n result.conflicts.push(`${key} (binary — kept remote)`);\n }\n },\n input.resolve === \"remote\",\n );\n continue;\n }\n\n // First merge WITHOUT a favor side so we know whether this file\n // actually conflicts — that drives honest bookkeeping (merged vs\n // autoResolved) and the pre-merge snapshot decision.\n const { merged, hasConflicts } = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n );\n if (hasConflicts && input.resolve) {\n const favored = await shadow.merge3(\n baseBuf,\n localBuf,\n remoteBuf,\n input.resolve,\n );\n write(\n key,\n file,\n favored.merged,\n () => result.autoResolved.push(key),\n RESOLVES_CONFLICT,\n );\n continue;\n }\n write(key, file, merged, () => {\n if (hasConflicts) result.conflicts.push(key);\n else result.merged++;\n });\n }\n\n // Flush deferred writes (resolve mode only). When any conflict was\n // auto-resolved, snapshot the pre-merge working tree first — it is\n // the ONLY place the losing side of each resolution still exists.\n if (input.resolve) {\n if (pendingWrites.some((w) => w.resolvesConflict)) {\n await commitPushedState(\n themeRoot,\n shadow,\n syncCommitSubject(\"Snapshot before pull\", input.actor ?? null),\n );\n }\n input.backgroundGuard?.assertUnchanged();\n for (const { key, file, content, record } of pendingWrites) {\n if (tryWrite(key, file, content)) record();\n }\n }\n\n if (doDelete && (await shadow.hasHead())) {\n for (const file of themeRoot.files()) {\n if (\n remoteKeys.has(file.relativePath) ||\n retainedMemberPaths.has(file.relativePath)\n )\n continue;\n // Stylesheet-hidden files are intentionally absent from the\n // resources index — deleting them locally would fight the\n // STYLESHEET_STRICT_INPUT feature, not honor a real deletion.\n if (isStylesheetKey(file.relativePath)) continue;\n\n const baseBuf = await shadow.blobAtHead(file.relativePath);\n if (!baseBuf) continue; // never seen by shadow — leave alone\n const localBuf = readIfExists(file.absolutePath);\n if (!localBuf) continue;\n if (!localBuf.equals(baseBuf)) continue; // user has local edits\n\n try {\n if (input.backgroundGuard)\n input.backgroundGuard.mutate([file.relativePath], () =>\n unlinkSync(file.absolutePath),\n );\n else unlinkSync(file.absolutePath);\n result.deleted++;\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n // ignore best-effort deletion\n }\n }\n }\n\n // Advance HEAD to the remote state we just downloaded. Even when\n // some files carry unresolved conflict markers, HEAD reflects the\n // server's clean bytes — the next push's diff-against-HEAD is what\n // surfaces the markers as \"unresolved\".\n //\n // Preserve failed-download paths from the previous HEAD so a\n // transient network error doesn't quietly drop them from the merge\n // base. If a file was in HEAD before the pull and its download\n // just failed, the next pull needs the previous blob as the base\n // for its merge — otherwise `blobAtHead` returns null next time and\n // a server-side change fires a spurious null-base conflict on a\n // file the user never touched.\n //\n // Keys whose write failed take the same route: their bytes are on\n // the server but not on disk, so recording the remote content here\n // would claim a state this checkout never had.\n try {\n const commitEntries: Array<{ path: string; sha: string }> = [];\n for (const [key, buf] of remoteContent) {\n if (unwrittenKeys.has(key)) continue;\n commitEntries.push({ path: key, sha: await shadow.writeBlob(buf) });\n }\n for (const key of new Set([...remoteKeys, ...retainedMemberPaths])) {\n if (input.skipRemoteKeys?.has(key) && !retainedMemberPaths.has(key))\n continue;\n // Downloaded AND written — already added above.\n if (remoteContent.has(key) && !unwrittenKeys.has(key)) continue;\n const prevBlob = await shadow.blobAtHead(key);\n if (prevBlob == null) continue; // wasn't in HEAD to begin with\n commitEntries.push({ path: key, sha: await shadow.writeBlob(prevBlob) });\n }\n const assetManifest = new ThemeAssetManifest(themeRoot.root);\n let managedAssetSentinelSha: string | undefined;\n for (const key of input.skipRemoteKeys ?? []) {\n // Ignored resources without a manifest link remain outside the shadow\n // entirely. Otherwise removing an ignore rule could make a skipped file\n // look like a local deletion on the next push.\n if (!remoteKeys.has(key) || !assetManifest.has(key)) continue;\n managedAssetSentinelSha ??= await shadow.writeBlob(\n MANAGED_ASSET_SHADOW_SENTINEL,\n );\n commitEntries.push({\n path: key,\n sha: managedAssetSentinelSha,\n });\n }\n // A pull containing only manifest-backed assets still needs a shadow commit:\n // sentinel entries replace any legacy binary blobs while keeping a baseline\n // for an intentional manifest-entry removal on a later push.\n // When the server becomes empty, record an empty tree rather than retaining\n // stale paths from HEAD as a future merge/delete baseline — but only when\n // the server really is empty. Empty entries with a non-empty remote mean\n // every write failed, and HEAD must survive as the next pull's merge base.\n if (\n commitEntries.length > 0 ||\n (remoteKeys.size === 0 && (await shadow.hasHead()))\n ) {\n input.backgroundGuard?.assertUnchanged();\n await shadow.commitState(\n commitEntries,\n syncCommitSubject(\"Pull\", input.actor ?? null),\n );\n }\n } catch (error) {\n if (error instanceof BackgroundPullChangedError) throw error;\n result.baselinePreserved = true;\n result.errors.push(\n `Downloaded changes have already been applied locally, but the local restore point and sync baseline could not be saved: ${error instanceof Error ? error.message : String(error)}. Review the files and retry a normal pull before pushing; do not use --force to bypass the missing baseline.`,\n );\n }\n\n return result;\n}\n\nfunction hasGeneratedConflictMarkers(content: Buffer): boolean {\n const text = content.toString(\"utf8\");\n return (\n /^<<<<<<< local\\r?$/m.test(text) &&\n /^=======\\r?$/m.test(text) &&\n /^>>>>>>> remote\\r?$/m.test(text)\n );\n}\n\nasync function materialize(\n resource: RemoteResource,\n fetchBinary: FetchBinary,\n): Promise<Buffer | null> {\n if (resource.resource_type === \"FileResource\" && resource.url) {\n return fetchBinary(resource.url);\n }\n if (resource.content == null) return null;\n const text =\n typeof resource.content === \"string\"\n ? resource.content\n : JSON.stringify(resource.content);\n return Buffer.from(text);\n}\n\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","import {\n BackgroundPullChangedError,\n BackgroundPullGuard,\n} from \"../theme/background-pull-guard.js\";\nimport { canPullThemeInBackground } from \"../theme/background-pull.js\";\nimport { join, resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport { requireToken, createApiClient } from \"../api.js\";\nimport {\n developmentThemeRefusal,\n mayBindTheme,\n readThemeConfig,\n writeThemeConfig,\n} from \"../theme-config.js\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { Syncer } from \"../theme/syncer.js\";\nimport { ShadowRepo } from \"../theme/shadow-repo.js\";\nimport { mergePull } from \"../theme/merge-pull.js\";\nimport { migrateLegacyChecksumsIntoShadow } from \"../theme/legacy-migration.js\";\nimport { fetchSyncActor } from \"../theme/sync-identity.js\";\nimport { selectTheme, findTheme } from \"../theme-picker.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\nimport { ThemeAssetManifest } from \"../theme/asset-manifest.js\";\nimport { CaseCollisionError } from \"../theme/case-collisions.js\";\n\ninterface CompanyMe {\n data: { company: { subdomain?: string; name?: string } };\n}\n\nasync function fetchCompanySubdomain(\n api: ReturnType<typeof createApiClient>,\n): Promise<string> {\n const res = await api.get<CompanyMe>(\"/api/company/v1/companies/me\");\n const subdomain = res.data?.company?.subdomain;\n if (!subdomain) {\n console.error(\n \"Could not determine company subdomain. Make sure your token is valid.\",\n );\n process.exit(1);\n }\n return subdomain;\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\"Pull a remote theme to your local directory\")\n .option(\"-t, --theme <name-or-id>\", \"Theme name or ID to pull\")\n .option(\"-n, --nodelete\", \"Do not delete local files missing on remote\")\n .option(\"--root <path>\", \"Theme root directory\")\n .option(\"-y, --yes\", \"Skip confirmation prompt\")\n .option(\n \"--only-if-clean\",\n \"Skip with exit code 3 when local work cannot be proven synced\",\n )\n .option(\n \"-f, --force\",\n \"Overwrite local without merging (skip conflict markers)\",\n )\n .option(\n \"--resolve <side>\",\n \"Auto-resolve merge conflicts to one side instead of writing \" +\n \"conflict markers: 'local' keeps your files' hunks, 'remote' \" +\n \"takes the server's (for non-interactive use)\",\n )\n .action(\n async (opts: {\n theme?: string;\n nodelete?: boolean;\n root?: string;\n yes?: boolean;\n force?: boolean;\n resolve?: string;\n onlyIfClean?: boolean;\n }) => {\n let spinner: ReturnType<typeof ora> | undefined;\n try {\n requireToken();\n\n if (\n opts.resolve !== undefined &&\n opts.resolve !== \"local\" &&\n opts.resolve !== \"remote\"\n ) {\n console.error(\n `Invalid --resolve value \"${opts.resolve}\" — use \"local\" or \"remote\".`,\n );\n process.exit(1);\n }\n const resolveSide = opts.resolve as \"local\" | \"remote\" | undefined;\n\n const api = createApiClient();\n const workspace = findWorkspace();\n\n const theme = opts.theme\n ? await findTheme(api, opts.theme)\n : await selectTheme(api, \"Select a theme to pull\");\n\n const subdomain = await fetchCompanySubdomain(api);\n let root: string;\n if (opts.root) {\n root = opts.root;\n } else if (workspace) {\n root =\n resolveThemeRootFromCwd(workspace) ??\n join(workspace.root, \"local\", subdomain);\n } else {\n root = `.`;\n }\n\n const absoluteRoot = resolve(root);\n const existingConfig = readThemeConfig(absoluteRoot);\n if (existingConfig && !mayBindTheme(existingConfig, theme)) {\n // Background preparation treats 3 as a safe skip; 1 would pause\n // its queue and retry a directory that belongs to another theme.\n if (opts.onlyIfClean) {\n console.log(\n `Skipped: this directory is bound to ${existingConfig.themeName} (#${existingConfig.themeId}), not development theme #${theme.id}.`,\n );\n process.exit(3);\n }\n console.error(developmentThemeRefusal(existingConfig, theme));\n process.exit(1);\n }\n const backgroundGuard = opts.onlyIfClean\n ? new BackgroundPullGuard(absoluteRoot)\n : undefined;\n\n console.log();\n console.log(` Theme: ${chalk.bold(theme.name)} (#${theme.id})`);\n console.log(` Company: ${chalk.bold(subdomain)}`);\n console.log(` Target: ${chalk.bold(absoluteRoot)}`);\n console.log();\n\n if (!opts.yes) {\n const { confirmed } = await prompts(\n {\n type: \"confirm\",\n name: \"confirmed\",\n message: \"Pull theme to this directory?\",\n initial: true,\n },\n { onCancel: () => process.exit(130) },\n );\n if (!confirmed) {\n console.log(\"Aborted.\");\n process.exit(0);\n }\n }\n\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n console.log(\"Skipped: local changes or an unknown sync baseline.\");\n process.exit(3);\n }\n backgroundGuard?.assertUnchanged();\n const themeRoot = new ThemeRoot(root);\n const shadow = await ShadowRepo.open(absoluteRoot, theme.id);\n await migrateLegacyChecksumsIntoShadow({\n shadow,\n themeRoot,\n absoluteRoot,\n themeId: theme.id,\n });\n const syncer = new Syncer(api, theme.id, themeRoot);\n\n // Fire the identity lookup alongside the download — it never\n // rejects, and mergePull only awaits it at commit time.\n const actorPromise = fetchSyncActor(api);\n\n spinner = ora(`Pulling ${theme.name} (#${theme.id})…`).start();\n const resources = await syncer\n .downloadAll()\n .catch((error: unknown) => {\n if (!(error instanceof CaseCollisionError)) throw error;\n if (spinner) spinner.fail(error.message);\n process.exit(1);\n });\n // Recheck after network I/O and before any asset or content writes.\n if (\n opts.onlyIfClean &&\n !(await canPullThemeInBackground(absoluteRoot, theme.id))\n ) {\n spinner.stop();\n console.log(\"Skipped: local files changed while downloading.\");\n process.exit(3);\n }\n const externalizedAssets = await syncer.externalizePulledAssets(\n resources,\n {\n delete: !opts.nodelete,\n backgroundGuard,\n },\n );\n\n const result = await mergePull({\n themeRoot,\n shadow,\n backgroundGuard,\n remote: resources,\n fetchBinary: (url) => syncer.downloadBinaryAsset(url),\n delete: !opts.nodelete,\n force: opts.force ?? false,\n skipRemoteKeys: externalizedAssets.managedKeys,\n resolve: resolveSide,\n actor: await actorPromise,\n onProgress: (done, total) => {\n if (spinner) spinner.text = `Downloading ${done}/${total} files…`;\n },\n });\n result.errors.push(...externalizedAssets.errors);\n\n const parts: string[] = [];\n if (result.written > 0) parts.push(`wrote ${result.written} file(s)`);\n if (result.merged > 0)\n parts.push(`merged ${result.merged} file(s) cleanly`);\n if (externalizedAssets.linked > 0) {\n parts.push(\n `kept ${externalizedAssets.linked} binary asset(s) remote`,\n );\n }\n if (result.autoResolved.length > 0)\n parts.push(\n `auto-resolved ${result.autoResolved.length} conflict(s) (kept ${resolveSide})`,\n );\n if (result.deleted > 0)\n parts.push(`deleted ${result.deleted} local file(s)`);\n if (result.skipped > 0)\n parts.push(`${result.skipped} already in sync`);\n\n if (result.errors.length) {\n spinner.warn(\n `Pulled with ${result.errors.length} error(s): ${parts.join(\", \")}.`,\n );\n for (const e of result.errors) console.error(` ${e}`);\n // A failed file or restore point leaves the checkout incomplete\n // or without a safe sync baseline. Conflicts already exit 1\n // below; this branch used to fall through at 0.\n //\n // Mist spawns `fluid theme pull -t <id> --root <path> --yes` to\n // clone a theme, and only records a lifecycle failure when the\n // exit code is non-zero — so a partial clone never reached the\n // agent's context and it went on to edit a checkout that was\n // missing files.\n //\n // exitCode rather than an immediate exit: the per-file error\n // list is printed right here and `.fluid-theme.json` is written\n // below, both of which still need to happen.\n process.exitCode = 1;\n }\n // Conflicts still need to be reported when snapshot saving failed;\n // Mist parses these paths to guide the user through resolution.\n if (result.conflicts.length > 0) {\n spinner.warn(\n `${result.conflicts.length} conflict(s) — resolve markers before pushing: ${parts.join(\", \")}.`,\n );\n console.log();\n for (const c of result.conflicts) {\n console.log(` ${chalk.yellow(\"CONFLICT\")} ${c}`);\n }\n console.log();\n console.log(\n ` Edit each file above to reconcile the ${chalk.cyan(\"<<<<<<<\")} / ${chalk.cyan(\">>>>>>>\")} markers,`,\n );\n console.log(\n ` then run ${chalk.cyan(\"fluid theme push\")} once your resolution is in place.`,\n );\n console.log();\n } else if (result.errors.length === 0) {\n spinner.succeed(parts.join(\", \") || \"Already up to date.\");\n }\n\n // A rejected transfer or failed snapshot did not accept the revision.\n if (result.baselinePreserved) return;\n\n // Update .fluid-theme.json — baseSha is the server-side handle\n // the push preflight uses; the merge base itself lives in the\n // shadow repo.\n const remoteSha = syncer.remoteSha();\n backgroundGuard?.assertUnchanged();\n const updateConfig = () =>\n writeThemeConfig(absoluteRoot, {\n themeId: theme.id,\n themeName: theme.name,\n company: subdomain,\n baseSha: remoteSha ?? existingConfig?.baseSha,\n assetManifestSha: new ThemeAssetManifest(\n absoluteRoot,\n ).fingerprint({\n excludePending: true,\n }),\n });\n\n if (backgroundGuard)\n backgroundGuard.mutate([\".fluid-theme.json\"], updateConfig);\n else updateConfig();\n if (result.conflicts.length > 0) process.exit(1);\n } catch (error) {\n if (!(error instanceof BackgroundPullChangedError)) throw error;\n spinner?.stop();\n console.log(error.message);\n process.exitCode = 3;\n }\n },\n );\n}\n","import chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport {\n findMissingSectionReferences,\n validateSchemaText,\n VALID_SETTING_TYPES,\n type BlocksSchemaType,\n type Diagnostic,\n type TemplateInput,\n} from \"@fluid-app/theme-schema\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { findLiquidBlockTagDiagnostics } from \"../theme/liquid-delimiters.js\";\nimport { findWorkspace, resolveThemeRootFromCwd } from \"../workspace.js\";\n\ninterface FileDiagnostics {\n path: string;\n diagnostics: Diagnostic[];\n}\n\n// A theme section is defined by a liquid file under the top-level `sections/`\n// directory. Returns the section name a `{% section %}` tag would reference, or\n// null if the file is not a section definition. Handles both the flat layout\n// (`sections/hero.liquid`) and the nested one (`sections/hero/index.liquid`).\nfunction sectionNameOf(relativePath: string): string | null {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts[0] === \"sections\" && parts.length >= 2) {\n return parts[1]!.replace(/\\.liquid$/, \"\");\n }\n return null;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\"Validate theme files locally (read-only — no upload)\")\n .option(\"--root <path>\", \"Theme root directory\", \".\")\n .option(\"--json\", \"Output results as compact JSON\")\n .action(async (opts: { root: string; json?: boolean }) => {\n // Resolve the theme root the same way push/dev do: when left at the\n // default, prefer the workspace's theme root if we're inside one.\n let rootPath = opts.root;\n if (rootPath === \".\") {\n const workspace = findWorkspace();\n if (workspace) {\n rootPath = resolveThemeRootFromCwd(workspace) ?? rootPath;\n }\n }\n\n const themeRoot = new ThemeRoot(rootPath);\n if (!themeRoot.isValid()) {\n const message = `'${rootPath}' does not look like a theme directory.`;\n if (opts.json) {\n console.log(JSON.stringify({ ok: false, error: message }));\n } else {\n console.error(message);\n }\n process.exit(1);\n }\n\n const files = themeRoot.files();\n // Read each liquid file once and reuse the content for both passes\n // (validateSchemaText and the section scan) to avoid a double disk read.\n const liquidFiles = files\n .filter((f) => f.isLiquid)\n .map((f) => ({ file: f, content: f.read() }));\n\n const byFile = new Map<string, Diagnostic[]>();\n const record = (path: string, diagnostic: Diagnostic): void => {\n const existing = byFile.get(path);\n if (existing) existing.push(diagnostic);\n else byFile.set(path, [diagnostic]);\n };\n\n // ── Schema pass — the same {% schema %} validation `fluid theme push`\n // runs. blocksSchemaType mirrors ThemeFile.validateSchema: page/layout\n // templates use object blocks, sections use array blocks.\n for (const { file, content } of liquidFiles) {\n const blocksSchemaType: BlocksSchemaType = file.isTemplate\n ? \"object\"\n : \"array\";\n for (const diagnostic of validateSchemaText(content, {\n blocksSchemaType,\n })) {\n record(file.relativePath, diagnostic);\n }\n for (const diagnostic of findLiquidBlockTagDiagnostics(content)) {\n record(file.relativePath, diagnostic);\n }\n }\n\n // ── Section pass — flag `{% section 'x' %}` references to a section\n // that has no definition on disk. Section definitions and assets are\n // not themselves referrers, so they are excluded from the scan.\n const existingSectionNames = new Set<string>();\n for (const { file } of liquidFiles) {\n const name = sectionNameOf(file.relativePath);\n if (name) existingSectionNames.add(name);\n }\n const referrers: TemplateInput[] = liquidFiles\n .filter(({ file }) => sectionNameOf(file.relativePath) === null)\n .map(({ file, content }) => ({ path: file.relativePath, content }));\n for (const missing of findMissingSectionReferences(\n referrers,\n existingSectionNames,\n )) {\n record(missing.templatePath, missing.diagnostic);\n }\n\n const results: FileDiagnostics[] = [...byFile.entries()]\n .map(([path, diagnostics]) => ({ path, diagnostics }))\n .sort((a, b) => a.path.localeCompare(b.path));\n\n let errors = 0;\n let warnings = 0;\n for (const { diagnostics } of results) {\n for (const d of diagnostics) {\n if (d.severity === \"error\") errors++;\n else warnings++;\n }\n }\n\n // Surface the canonical setting types once (not in every diagnostic) so a\n // consumer fixing an \"Invalid settings type\" error has the valid set to\n // hand without it bloating each message.\n const hasInvalidSettingType = results.some(({ diagnostics }) =>\n diagnostics.some(\n (d) =>\n d.target?.kind === \"setting\" &&\n d.target.field === \"type\" &&\n d.target.settingType !== undefined,\n ),\n );\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n ok: errors === 0,\n errors,\n warnings,\n filesChecked: liquidFiles.length,\n ...(hasInvalidSettingType\n ? { validSettingTypes: VALID_SETTING_TYPES }\n : {}),\n files: results,\n }),\n );\n } else {\n printText(results, errors, warnings, liquidFiles.length);\n }\n\n process.exit(errors > 0 ? 1 : 0);\n });\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\nfunction printText(\n results: FileDiagnostics[],\n errors: number,\n warnings: number,\n filesChecked: number,\n): void {\n for (const { path, diagnostics } of results) {\n console.log(chalk.bold(path));\n for (const d of diagnostics) {\n const label =\n d.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n // Only the first line — a few messages (e.g. the `Invalid JSON:` parse\n // error) carry a multi-line body that `--json` preserves in full.\n const message = d.message.split(\"\\n\")[0];\n console.log(` ${label} ${message}`);\n }\n }\n\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n if (errors > 0) {\n console.log(\n `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else if (warnings > 0) {\n console.log(\n `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n } else {\n console.log(`${chalk.green(\"✓ No problems found\")} ${suffix}`);\n }\n}\n","import { Command } from \"commander\";\nimport { execFileSync } from \"node:child_process\";\nimport { rmSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport prompts from \"prompts\";\n\nconst DEFAULT_CLONE_URL = \"git@github.com:fluid-commerce/base-theme.git\";\n\nconst SAFE_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9._-]*$/;\n\nexport function createInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Initialize a new theme by cloning the base theme\")\n .argument(\"[name]\", \"Directory name for the new theme\")\n .option(\"-u, --clone-url <url>\", \"Git URL to clone from\", DEFAULT_CLONE_URL)\n .action(async (name: string | undefined, opts: { cloneUrl: string }) => {\n if (!name) {\n const res = await prompts(\n {\n type: \"text\",\n name: \"name\",\n message: \"Theme name\",\n },\n { onCancel: () => process.exit(130) },\n );\n name = res.name as string;\n if (!name) {\n console.error(\"No name provided.\");\n process.exit(1);\n }\n }\n\n if (!SAFE_NAME_RE.test(name)) {\n console.error(\n `Invalid theme name: '${name}'. Use only letters, numbers, hyphens, underscores, and dots.`,\n );\n process.exit(1);\n }\n\n console.log(`Cloning theme from ${opts.cloneUrl} into ${name}…`);\n execFileSync(\"git\", [\"clone\", opts.cloneUrl, name], { stdio: \"inherit\" });\n\n for (const dir of [\".git\", \".github\"]) {\n const path = join(name, dir);\n if (existsSync(path)) rmSync(path, { recursive: true, force: true });\n }\n\n console.log(`\\nTheme initialized in ./${name}`);\n console.log(`Next steps:\\n cd ${name}\\n fluid theme push`);\n });\n}\n","import {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\n\n// A skill is a directory containing a SKILL.md. The bundled skills directory\n// holds one such directory per skill (e.g. `themes-review/`).\nexport function listSkillNames(skillsDir: string): string[] {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .filter((name) => existsSync(join(skillsDir, name, \"SKILL.md\")))\n .sort();\n}\n\nexport interface InstallSkillsOptions {\n /** Directory holding the bundled skills (one sub-directory per skill). */\n sourceDir: string;\n /** Directory the skills are copied into (one sub-directory per skill). */\n targetRoot: string;\n /** Overwrite existing skills without asking. */\n force: boolean;\n /**\n * Asked once per skill that already exists when not forcing. Return true to\n * overwrite, false to leave the existing copy untouched.\n */\n confirmOverwrite: (name: string) => Promise<boolean>;\n /**\n * Called immediately for each install that left a temporary backup directory\n * behind (it could not be removed). Reported as it happens — not via the\n * return value — so the warning isn't lost if a later skill throws mid-loop.\n */\n onLeftover: (path: string) => void;\n}\n\nexport interface InstallSkillsResult {\n readonly installed: readonly string[];\n readonly skipped: readonly string[];\n}\n\n// Copy each bundled skill into `targetRoot/<name>`. Existing skills are only\n// replaced with `force` or an affirmative `confirmOverwrite`; everything else is\n// reported as skipped so the caller can summarize what happened.\nexport async function installSkills(\n options: InstallSkillsOptions,\n): Promise<InstallSkillsResult> {\n const { sourceDir, targetRoot, force, confirmOverwrite, onLeftover } =\n options;\n\n const installed: string[] = [];\n const skipped: string[] = [];\n\n mkdirSync(targetRoot, { recursive: true });\n\n for (const name of listSkillNames(sourceDir)) {\n const from = join(sourceDir, name);\n const to = join(targetRoot, name);\n const exists = existsSync(to);\n\n if (exists && !force && !(await confirmOverwrite(name))) {\n skipped.push(name);\n continue;\n }\n\n // Atomic copy + swap: a failed copy never destroys an existing install, and\n // the whole-directory replace prunes files removed from the bundled skill.\n // Report any leftover backup immediately so the warning isn't lost if a\n // later skill throws mid-loop.\n const leftover = replaceDirectory(from, to);\n if (leftover !== null) onLeftover(leftover);\n installed.push(name);\n }\n\n return { installed, skipped };\n}\n\n/**\n * Replace `target` with a fresh copy of `source` without ever leaving `target`\n * missing or partially written.\n *\n * Filesystem copies are not atomic, so a naive \"delete then copy\" loses the\n * original if the copy fails (permissions, no disk space, an interrupted\n * process). This stages the copy in a sibling directory and only swaps it into\n * place once it has fully succeeded; an existing `target` is moved aside to a\n * sibling backup first and restored if the swap fails. Because the whole\n * directory is replaced, files removed or renamed in `source` do not linger.\n *\n * Staging and backup directories live beside `target`, so its parent must\n * already exist and be on the same filesystem — that keeps the swap a cheap,\n * atomic rename rather than a cross-device copy.\n *\n * Not safe against a second process racing on the same `target`; intended for\n * single-process CLI use.\n *\n * @returns the path of a leftover backup directory that could not be removed\n * after an otherwise-successful replace (the previous contents are retained\n * there for manual cleanup), or `null` when nothing was left behind. The caller\n * should surface a non-null result so the leftover isn't silently hidden.\n */\nexport function replaceDirectory(\n source: string,\n target: string,\n): string | null {\n const staging = reserveSiblingPath(target, \"staging\");\n try {\n cpSync(source, staging, { recursive: true });\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n\n // No existing target: a single rename moves the staged copy into place.\n if (!existsSync(target)) {\n return swapIntoPlace(staging, target, null);\n }\n\n // Existing target: move it aside first so the swap stays reversible.\n const backup = reserveSiblingPath(target, \"backup\");\n try {\n renameSync(target, backup);\n } catch (error) {\n removeQuietly(staging);\n throw error;\n }\n return swapIntoPlace(staging, target, backup);\n}\n\n// Move the staged copy into `target`. If the move fails, restore the original\n// from `backup` (when there was one) *before* any cleanup, so a failed staging\n// removal can never leave the caller without a directory at `target`; then\n// best-effort discard the staged copy and rethrow. On success, returns the\n// backup path if it could not be removed (a leftover the caller should report),\n// or `null`.\nfunction swapIntoPlace(\n staging: string,\n target: string,\n backup: string | null,\n): string | null {\n try {\n renameSync(staging, target);\n } catch (error) {\n if (backup !== null) restoreBackup(backup, target, error);\n removeQuietly(staging);\n throw error;\n }\n return removeQuietly(backup);\n}\n\n// Best-effort restore of the original directory. If even this fails, surface an\n// error pointing at the backup so the user can recover their data by hand.\nfunction restoreBackup(backup: string, target: string, cause: unknown): void {\n try {\n renameSync(backup, target);\n } catch {\n throw new Error(\n `Failed to replace ${target}; its previous contents are preserved at ${backup}.`,\n { cause },\n );\n }\n}\n\n// Best-effort removal of a temporary staging/backup directory. Cleanup must\n// never throw: a failure here should not mask the real outcome or strand the\n// caller. Returns the path when removal failed (so the caller can surface the\n// leftover), or `null` when the directory was removed or there was none.\nfunction removeQuietly(path: string | null): string | null {\n if (path === null) return null;\n try {\n rmSync(path, { recursive: true, force: true });\n return null;\n } catch {\n return path;\n }\n}\n\n// Pick a sibling path of `basePath` that does not exist yet, so renaming onto it\n// is a clean create on every platform (Windows rejects a rename onto an existing\n// directory). Deterministic — no randomness.\nfunction reserveSiblingPath(basePath: string, label: string): string {\n let candidate = `${basePath}.${label}`;\n for (let n = 1; existsSync(candidate); n += 1) {\n candidate = `${basePath}.${label}.${n}`;\n }\n return candidate;\n}\n","import { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport { ThemeRoot } from \"../theme/root.js\";\nimport { installSkills, listSkillNames } from \"../skills/install.js\";\n\n// Where skills land by default — `.agents/skills/` is the tool-neutral\n// convention for agent skills.\nconst DEFAULT_TARGET_DIR = \".agents/skills\";\n\n// The bundled skills ship beside the compiled CLI at `dist/skills/` and live at\n// the package root `skills/` in source. Resolve relative to this module so the\n// lookup works whether the code runs from the published bundle or from source.\nfunction resolveBundledSkillsDir(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n join(here, \"skills\"), // dist/skills (bundled build output)\n join(here, \"..\", \"skills\"), // package root from dist/index.mjs\n join(here, \"..\", \"..\", \"skills\"), // package root from src/commands/\n ];\n for (const dir of candidates) {\n if (listSkillNames(dir).length > 0) return dir;\n }\n // Last resort: walk up looking for a skills/ dir that holds a skill.\n let dir = here;\n for (let depth = 0; depth < 6; depth++) {\n const candidate = join(dir, \"skills\");\n if (listSkillNames(candidate).length > 0) return candidate;\n dir = dirname(dir);\n }\n throw new Error(\n \"Could not locate the bundled theme skills — this is a packaging bug.\",\n );\n}\n\n// Skills stay out of `fluid theme push/pull/dev/lint` only when some path\n// segment is a dot-directory — that is the sole thing ThemeRoot.glob() skips.\n// Returns true when installing into a *visible* dir inside a theme, where the\n// files would be uploaded as theme content instead of kept local.\nfunction skillsWouldShipWithTheme(cwd: string, targetRoot: string): boolean {\n if (!new ThemeRoot(cwd).isValid()) return false; // not a theme dir — irrelevant\n const rel = relative(cwd, targetRoot);\n if (rel.startsWith(\"..\")) return false; // outside the theme root — never scanned\n return !rel.split(sep).some((segment) => segment.startsWith(\".\"));\n}\n\nexport function createSkillsCommand(): Command {\n const skills = new Command(\"skills\").description(\n \"Manage the bundled Fluid theme AI skills\",\n );\n\n skills\n .command(\"install\")\n .description(\n \"Copy the bundled theme skills into the current directory (default: .agents/skills/)\",\n )\n .option(\"-d, --dir <path>\", \"Directory to install into\", DEFAULT_TARGET_DIR)\n .option(\"-f, --force\", \"Overwrite existing skills without prompting\")\n .action(async (opts: { dir: string; force?: boolean }) => {\n const sourceDir = resolveBundledSkillsDir();\n if (listSkillNames(sourceDir).length === 0) {\n console.error(\"No bundled skills found to install.\");\n process.exit(1);\n }\n\n const targetRoot = resolve(process.cwd(), opts.dir);\n if (skillsWouldShipWithTheme(process.cwd(), targetRoot)) {\n console.log(\n `${chalk.yellow(\"⚠\")} ${chalk.bold(opts.dir)} is not a hidden directory — ` +\n `'fluid theme push' will upload these skills as theme files. ` +\n `Install into a dot-directory like ${chalk.cyan(DEFAULT_TARGET_DIR)} to keep them local.`,\n );\n }\n\n const { installed, skipped } = await installSkills({\n sourceDir,\n targetRoot,\n force: Boolean(opts.force),\n confirmOverwrite: async (name) => {\n const res = await prompts(\n {\n type: \"confirm\",\n name: \"overwrite\",\n message: `${chalk.yellow(name)} already exists in ${opts.dir}. Overwrite?`,\n initial: false,\n },\n { onCancel: () => process.exit(130) },\n );\n return Boolean(res.overwrite);\n },\n // Warn as soon as a leftover is found, so it's reported even if a later\n // skill fails before the install finishes.\n onLeftover: (path) => {\n console.log(\n `${chalk.yellow(\"⚠\")} kept the previous copy at ${path} (couldn't remove it — delete it manually)`,\n );\n },\n });\n\n for (const name of installed) {\n console.log(`${chalk.green(\"✓\")} ${name} → ${join(opts.dir, name)}`);\n }\n for (const name of skipped) {\n console.log(`${chalk.dim(`· skipped ${name} (kept existing)`)}`);\n }\n\n const parts = [\n installed.length > 0 ? `${installed.length} installed` : null,\n skipped.length > 0 ? `${skipped.length} skipped` : null,\n ].filter(Boolean);\n console.log(\n `\\n${chalk.bold(parts.join(\", \") || \"Nothing to do\")} in ${targetRoot}`,\n );\n if (installed.length > 0) {\n console.log(\n chalk.dim(\"Restart your agent session to pick up the new skills.\"),\n );\n }\n });\n\n return skills;\n}\n","import type { LookupFunction } from \"node:net\";\nimport {\n Agent,\n fetch as undiciFetch,\n type Response as UndiciResponse,\n} from \"undici\";\nimport type { ResolvedAddress } from \"./public-url-policy\";\n\nexport function createPinnedLookup(\n addresses: readonly ResolvedAddress[],\n): LookupFunction {\n return ((_hostname, options, callback) => {\n const requestedFamily =\n typeof options === \"number\" ? options : (options.family ?? 0);\n const eligible =\n requestedFamily === 4 || requestedFamily === 6\n ? addresses.filter(({ family }) => family === requestedFamily)\n : addresses;\n const selected = eligible[0];\n if (!selected) {\n (\n callback as unknown as (\n error: NodeJS.ErrnoException,\n address?: string,\n family?: number,\n ) => void\n )(\n Object.assign(new Error(\"No validated address matches DNS family\"), {\n code: \"ENOTFOUND\",\n }),\n );\n return;\n }\n if (typeof options === \"object\" && options.all) {\n (\n callback as unknown as (\n error: null,\n results: readonly ResolvedAddress[],\n ) => void\n )(null, eligible);\n return;\n }\n (\n callback as unknown as (\n error: null,\n address: string,\n family: number,\n ) => void\n )(null, selected.address, selected.family);\n }) as LookupFunction;\n}\n\n/**\n * Fetch a public URL through only the DNS addresses that passed validation.\n * Keeping the original hostname preserves Host/SNI and certificate checks;\n * overriding lookup closes the DNS-rebinding gap between validation and I/O.\n */\nexport async function pinnedPublicFetch(\n url: URL,\n addresses: readonly ResolvedAddress[],\n options: {\n signal: AbortSignal;\n headers: Record<string, string>;\n method?: \"GET\" | \"HEAD\";\n },\n): Promise<{ response: UndiciResponse; close: () => Promise<void> }> {\n const dispatcher = new Agent({\n connect: {\n lookup: createPinnedLookup(addresses),\n },\n });\n try {\n const response = await undiciFetch(url, {\n dispatcher,\n headers: options.headers,\n method: options.method ?? \"GET\",\n redirect: \"manual\",\n signal: options.signal,\n });\n return {\n response,\n close: async () => {\n await dispatcher.close();\n },\n };\n } catch (error) {\n await dispatcher.close();\n throw error;\n }\n}\n","import { lookup as dnsLookup } from \"node:dns/promises\";\nimport { isIP } from \"node:net\";\n\nconst BLOCKED_HOSTS = new Set([\n \"localhost\",\n \"metadata\",\n \"metadata.goog\",\n \"metadata.google.internal\",\n \"instance-data\",\n]);\n\nconst BLOCKED_HOST_SUFFIXES = [\n \".localhost\",\n \".local\",\n \".internal\",\n \".home.arpa\",\n];\n\nexport interface ResolvedAddress {\n address: string;\n family: number;\n}\n\nexport type ResolveHostname = (\n hostname: string,\n) => Promise<readonly ResolvedAddress[]>;\n\nexport type PublicUrlValidation =\n | { ok: true; url: URL; addresses: readonly ResolvedAddress[] }\n | { ok: false; reason: string };\n\nfunction normalizeHostname(hostname: string): string {\n return hostname\n .toLowerCase()\n .replace(/^\\[|\\]$/g, \"\")\n .replace(/\\.$/, \"\");\n}\n\nfunction ipv4Octets(value: string): number[] | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const octets = parts.map((part) => Number(part));\n if (\n octets.some(\n (octet, index) =>\n !Number.isInteger(octet) ||\n octet < 0 ||\n octet > 255 ||\n String(octet) !== parts[index],\n )\n ) {\n return null;\n }\n return octets;\n}\n\nfunction isBlockedIpv4(value: string): boolean {\n const octets = ipv4Octets(value);\n if (!octets) return true;\n const [a = 0, b = 0, c = 0, d = 0] = octets;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 0 && c === 0 && d !== 9 && d !== 10) ||\n (a === 192 && b === 0 && c === 2) ||\n (a === 192 && b === 88 && c === 99) ||\n (a === 192 && b === 168) ||\n (a === 198 && (b === 18 || b === 19)) ||\n (a === 198 && b === 51 && c === 100) ||\n (a === 203 && b === 0 && c === 113) ||\n a >= 224\n );\n}\n\nfunction isBlockedIpv6(value: string): boolean {\n const normalized = value.toLowerCase().split(\"%\", 1)[0] ?? \"\";\n const firstHextet = Number.parseInt(normalized.split(\":\")[0] ?? \"\", 16);\n\n // Routable public IPv6 currently lives in 2000::/3. Keeping the\n // client-side preflight to that range blocks loopback, unspecified,\n // ULA, link-local, multicast, IPv4-mapped, and NAT64 destinations.\n if (\n !Number.isFinite(firstHextet) ||\n firstHextet < 0x2000 ||\n firstHextet > 0x3fff\n ) {\n return true;\n }\n\n // Special/documentation transition ranges inside 2000::/3.\n return (\n normalized.startsWith(\"2001::\") ||\n /^2001:0{1,4}:/.test(normalized) ||\n normalized.startsWith(\"2001:db8:\") ||\n normalized === \"2001:db8::\" ||\n normalized.startsWith(\"2002:\") ||\n normalized.startsWith(\"3fff:\")\n );\n}\n\nexport function isBlockedPublicHost(hostname: string): boolean {\n const normalized = normalizeHostname(hostname);\n if (\n !normalized ||\n BLOCKED_HOSTS.has(normalized) ||\n BLOCKED_HOST_SUFFIXES.some((suffix) => normalized.endsWith(suffix))\n ) {\n return true;\n }\n const family = isIP(normalized);\n if (family !== 0) return isBlockedPublicAddress(normalized);\n return false;\n}\n\nexport function isBlockedPublicAddress(address: string): boolean {\n const normalized = normalizeHostname(address);\n const family = isIP(normalized);\n if (family === 4) return isBlockedIpv4(normalized);\n if (family === 6) return isBlockedIpv6(normalized);\n return true;\n}\n\nconst defaultResolveHostname: ResolveHostname = async (hostname) =>\n dnsLookup(hostname, { all: true, verbatim: true });\n\nexport async function validatePublicHttpUrl(\n rawUrl: string | URL,\n resolveHostname: ResolveHostname = defaultResolveHostname,\n): Promise<PublicUrlValidation> {\n let url: URL;\n try {\n url = rawUrl instanceof URL ? rawUrl : new URL(rawUrl);\n } catch {\n return { ok: false, reason: \"not a valid URL\" };\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return {\n ok: false,\n reason: `only http(s) URLs are allowed (got ${url.protocol})`,\n };\n }\n if (url.username || url.password) {\n return {\n ok: false,\n reason: \"embedded URL credentials are not allowed\",\n };\n }\n\n const hostname = normalizeHostname(url.hostname);\n if (isBlockedPublicHost(hostname)) {\n return {\n ok: false,\n reason: `hostname ${hostname} is not a public destination`,\n };\n }\n\n let addresses: readonly ResolvedAddress[];\n const literalFamily = isIP(hostname);\n if (literalFamily === 0) {\n let resolved: readonly ResolvedAddress[];\n try {\n resolved = await resolveHostname(hostname);\n } catch (error) {\n return {\n ok: false,\n reason: `hostname ${hostname} did not resolve (${\n error instanceof Error ? error.message : String(error)\n })`,\n };\n }\n if (resolved.length === 0) {\n return {\n ok: false,\n reason: `hostname ${hostname} returned no addresses`,\n };\n }\n const blocked = resolved.find(({ address }) =>\n isBlockedPublicAddress(address),\n );\n if (blocked) {\n return {\n ok: false,\n reason: `hostname ${hostname} resolves to a non-public address (${blocked.address})`,\n };\n }\n addresses = resolved;\n } else {\n addresses = [{ address: hostname, family: literalFamily }];\n }\n\n return { ok: true, url, addresses };\n}\n","/**\n * Collect the CSS a source page actually renders with.\n *\n * The crawl provider returns processed HTML with `<style>` blocks and\n * `<link rel=\"stylesheet\">` stripped. Measured on one real storefront: the raw\n * page was 465KB with 7 external stylesheets and 29 inline `<style>` blocks;\n * the retained crawl HTML was 234KB with none of them. So a page-clone agent\n * sees `class=\"hero-banner\"` and has no way to know what that class does — it\n * has to infer every spacing, breakpoint, and animation value from a\n * screenshot. That is the single largest source of layout error in a clone:\n * sections land in the right order with the right copy and the wrong height.\n *\n * The 165KB of CSS recovered from that same page contained 220 padding/margin\n * declarations, 83 media queries, the real font stacks, the design tokens, and\n * the `@keyframes` driving its animations — i.e. precisely the values the\n * agent was guessing.\n *\n * This module refetches the canonical page, extracts inline and linked CSS,\n * and concatenates it with provenance comments so the agent can read real\n * declarations instead of eyeballing pixels.\n */\nimport { pinnedPublicFetch } from \"./pinned-public-fetch\";\nimport { validatePublicHttpUrl } from \"./public-url-policy\";\n\n/** Bounds. A clone needs the page's own CSS, not an unbounded asset crawl.\n *\n * The byte caps are the real protection; the count is a runaway guard only.\n * A real storefront measured 36 references (29 inline blocks + 7 links) for\n * 120KB total, so a count of 16 silently dropped 20 sources while the byte\n * budget sat at 4% used. Cap on size, not on how many files a bundler emitted. */\nexport const MAX_STYLESHEETS = 64;\n/** Ceiling on sheets ATTEMPTED, not just retained. A rejected sheet — failed\n * fetch, oversized, truncated — previously consumed no budget, so a page\n * advertising thousands of links could drive thousands of requests while the\n * retained count stayed at zero. */\nexport const MAX_STYLESHEET_ATTEMPTS = 96;\nexport const MAX_TOTAL_CSS_BYTES = 3 * 1024 * 1024;\nexport const MAX_SINGLE_CSS_BYTES = 1024 * 1024;\n/** Ceiling for the HTML document itself. Larger than a single sheet because a\n * server-rendered storefront page is routinely several hundred KB. */\nexport const MAX_SOURCE_DOCUMENT_BYTES = 8 * 1024 * 1024;\nconst FETCH_TIMEOUT_MS = 15_000;\n/** Matches the identifier the rest of Mist's outbound fetches use. Sending no\n * User-Agent is itself a rate-limit trigger: one real storefront answered 429\n * to every header-less request while accepting identified ones. */\nconst USER_AGENT = \"MistDesktop/0.1 (+https://fluid.app)\";\n/** Transient statuses worth one retry. A 429 on a first-party page we are\n * mid-crawl of is usually burst pressure, not a refusal. */\nexport const RETRY_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);\nconst RETRY_DELAY_MS = 1_500;\n\nexport interface StylesheetRef {\n kind: \"inline\" | \"link\";\n /** Absolute URL for `link`; `inline#<n>` for an inline block. */\n href: string;\n /** The source's `media` attribute, when it narrows applicability. A\n * `media=\"print\"` sheet does not style the screen capture we compare\n * against, so persisting it as unconditional CSS would misreport the\n * effective cascade. Absent when the source applies unconditionally. */\n media?: string;\n}\n\nexport interface CollectedStylesheet extends StylesheetRef {\n byteLength: number;\n /** Which document contributed this source. */\n from: DocumentAcquisition;\n origin: StylesheetOrigin;\n}\n\n/**\n * What a stylesheet actually describes.\n *\n * The hydrated DOM is a superset of the served document, but not all of the\n * extra is the brand's design. Measured on one storefront, the post-JS document\n * carried 397KB of inline CSS against the server's 52KB — and 96KB of that was\n * Chrome's own network-error page stylesheet, repeated four times by failed\n * iframes. An agent reading it would find `@media` rules and spacing values\n * belonging to the browser's error screen.\n */\nexport type StylesheetOrigin =\n /** The site's own CSS. */\n | \"site\"\n /** A runtime widget: consent banner, accessibility overlay, review embed.\n * It renders on the page, so it can affect layout, but it does not describe\n * the brand's design system. */\n | \"third-party-widget\"\n /** Not the page at all — browser UI injected into the serialized DOM. */\n | \"browser-artifact\";\n\nconst BROWSER_ARTIFACT_MARKERS = [\n \"main-frame-error\",\n \"sub-frame-error\",\n \"neterror\",\n \"chrome-error\",\n \"icon-offline\",\n];\n\nconst WIDGET_MARKERS = [\n \"consent-banner\",\n \"cookieyes\",\n \"onetrust\",\n \"cookie-consent\",\n \"userway\",\n \"klaviyo\",\n \"gorgias\",\n \"intercom\",\n \"zendesk\",\n \"drift-\",\n \"attentive\",\n \"privy-\",\n \"yotpo\",\n \"trustpilot\",\n];\n\n/** Classify a source from its href and body. */\nexport function classifyStylesheet(\n href: string,\n css: string,\n pageHost: string,\n): StylesheetOrigin {\n const head = css.slice(0, 4000).toLowerCase();\n if (BROWSER_ARTIFACT_MARKERS.some((m) => head.includes(m))) {\n return \"browser-artifact\";\n }\n const hrefLower = href.toLowerCase();\n if (WIDGET_MARKERS.some((m) => hrefLower.includes(m) || head.includes(m))) {\n return \"third-party-widget\";\n }\n if (hrefLower.startsWith(\"http\")) {\n try {\n const host = new URL(href).hostname.toLowerCase();\n const bare = (h: string) => h.replace(/^www\\./, \"\");\n // A different registrable-ish host that is not the page's own CDN.\n if (\n !bare(host).endsWith(bare(pageHost)) &&\n !bare(pageHost).endsWith(bare(host))\n ) {\n return \"third-party-widget\";\n }\n } catch {\n // fall through\n }\n }\n return \"site\";\n}\n\n/** How the unstripped document was obtained. */\nexport type DocumentAcquisition =\n | \"direct\"\n | \"provider-raw-html\"\n | \"unavailable\";\n\nexport interface CollectedStylesheets {\n css: string;\n sources: CollectedStylesheet[];\n warnings: string[];\n /** Which documents contributed, in the order they were consulted. */\n documentsUsed?: DocumentAcquisition[];\n /** Source counts by origin, so a reader can weight brand CSS above runtime\n * widget CSS and disregard browser artifacts. */\n byOrigin?: {\n site: number;\n thirdPartyWidget: number;\n browserArtifact: number;\n };\n /** Declarations present in the collected CSS — a cheap signal to the agent\n * (and to QA) that the payload is substantive rather than a stub. */\n stats: {\n spacingDeclarations: number;\n mediaQueries: number;\n keyframes: number;\n fontFaces: number;\n customProperties: number;\n };\n}\n\n/**\n * Pull inline `<style>` bodies and `<link rel=\"stylesheet\">` hrefs out of raw\n * HTML, in document order.\n *\n * Deliberately a scanner rather than a DOM parse: this runs on untrusted\n * third-party markup in the main process, and we only need two tag shapes.\n */\n/**\n * The document base for resolving relative URLs.\n *\n * Per the HTML spec the first `<base>` element with an `href` sets it, and that\n * href may itself be relative to the document URL. A page using `<base>` would\n * otherwise have every relative stylesheet href resolved from the wrong\n * location.\n */\nexport function resolveDocumentBase(html: string, documentUrl: string): string {\n // The spec selects the first `<base>` element THAT HAS AN HREF, not simply\n // the first `<base>`. A leading `<base target=\"_blank\">` must not suppress a\n // later `<base href>`.\n for (const match of html.matchAll(/<base\\b[^>]*>/gi)) {\n const href = readAttribute(match[0], \"href\");\n if (href === undefined || href.trim().length === 0) continue;\n try {\n return new URL(href, documentUrl).toString();\n } catch {\n return documentUrl;\n }\n }\n return documentUrl;\n}\n\n/**\n * Read one attribute from a tag, accepting double-quoted, single-quoted, and\n * unquoted values.\n *\n * `<link rel=stylesheet media=print href=/p.css>` is valid HTML. A\n * quoted-only pattern silently misses those, which for `media` means a\n * print-only sheet is recorded as unconditional CSS.\n */\nexport function readAttribute(tag: string, name: string): string | undefined {\n const pattern = new RegExp(\n `${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'=<>\\`]+))`,\n \"i\",\n );\n const match = pattern.exec(tag);\n if (!match) return undefined;\n return match[1] ?? match[2] ?? match[3];\n}\n\n/** `media` values that do not narrow anything — treat as unconditional. */\nfunction normalizeMedia(raw: string | undefined): string | undefined {\n const value = raw?.trim();\n if (!value) return undefined;\n return /^all$/i.test(value) ? undefined : value;\n}\n\nexport function extractStylesheetRefs(\n html: string,\n documentUrl: string,\n): { refs: StylesheetRef[]; inlineBlocks: string[] } {\n const baseUrl = resolveDocumentBase(html, documentUrl);\n const inlineBlocks: string[] = [];\n const refs: StylesheetRef[] = [];\n\n // ONE pass over both tag shapes so refs come back in document order.\n // Order is the cascade: for equal specificity the later rule wins, so\n // scanning `<style>` and `<link>` separately and concatenating would report a\n // different winning declaration than the browser applies.\n const pattern = /<style\\b[^>]*>([\\s\\S]*?)<\\/style>|<link\\b[^>]*>/gi;\n for (const match of html.matchAll(pattern)) {\n const [tag, inlineBody] = match;\n if (inlineBody !== undefined) {\n if (inlineBody.trim().length === 0) continue;\n const media = normalizeMedia(readAttribute(tag, \"media\"));\n refs.push({\n kind: \"inline\",\n href: `inline#${inlineBlocks.length}`,\n ...(media ? { media } : {}),\n });\n inlineBlocks.push(inlineBody);\n continue;\n }\n const rel = readAttribute(tag, \"rel\");\n if (!rel || !/(^|\\s)stylesheet(\\s|$)/i.test(rel)) continue;\n const href = readAttribute(tag, \"href\");\n if (!href || href.trim().length === 0) continue;\n let absolute: string;\n try {\n absolute = new URL(href, baseUrl).toString();\n } catch {\n continue;\n }\n if (!/^https?:$/i.test(new URL(absolute).protocol)) continue;\n // Repeated references are NOT deduplicated. A browser applies a linked\n // sheet again at each occurrence, so a sheet that reappears after an\n // inline block wins ties against it. Dropping the later occurrence would\n // report the intervening declarations as effective when they are not.\n // The body is fetched once and reused (see collectSourceStylesheets); it is\n // only the cascade POSITION that must be preserved.\n const linkMedia = normalizeMedia(readAttribute(tag, \"media\"));\n refs.push({\n kind: \"link\",\n href: absolute,\n ...(linkMedia ? { media: linkMedia } : {}),\n });\n }\n\n return { refs, inlineBlocks };\n}\n\nfunction countMatches(source: string, pattern: RegExp): number {\n return source.match(pattern)?.length ?? 0;\n}\n\nexport function summarizeCss(css: string): CollectedStylesheets[\"stats\"] {\n return {\n spacingDeclarations: countMatches(\n css,\n /(?:^|[;{\\s])(?:padding|margin|gap|row-gap|column-gap)[a-z-]*\\s*:/gi,\n ),\n mediaQueries: countMatches(css, /@media\\b/gi),\n keyframes: countMatches(css, /@keyframes\\b/gi),\n fontFaces: countMatches(css, /@font-face\\b/gi),\n customProperties: countMatches(css, /(?:^|[;{\\s])--[a-z0-9-]+\\s*:/gi),\n };\n}\n\n/** Result of one guarded text fetch. */\nexport interface TextFetchResult {\n ok: boolean;\n status: number;\n body: string;\n /** Why the fetch failed, for surfacing rather than swallowing. */\n error?: string;\n /** URL the body actually came from, after redirects. Relative references in\n * the body MUST resolve against this, not the requested URL: a page that\n * redirects to another directory or origin would otherwise have every\n * relative stylesheet href resolved against the wrong base. */\n finalUrl: string;\n /** True when the response exceeded `maxBytes` and reading stopped early. */\n truncated: boolean;\n}\n\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\nconst MAX_REDIRECTS = 3;\n\n/**\n * Read a response body with a hard byte ceiling, streaming so an oversized\n * response is abandoned mid-flight.\n *\n * `response.text()` materializes the whole body first, which lets any public\n * URL we crawl allocate unbounded memory in the Electron main process before a\n * size check can run. Checking the length afterwards is not a limit.\n *\n * `content-length` is only a hint here — it is absent on chunked responses and\n * can lie — so the ceiling is enforced against bytes actually received.\n */\nexport async function readTextCapped(\n response: { body: unknown; headers: { get: (n: string) => string | null } },\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const declared = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(declared) && declared > maxBytes) {\n return { text: \"\", truncated: true };\n }\n const stream = response.body as AsyncIterable<Uint8Array> | null | undefined;\n if (\n !stream ||\n typeof (stream as AsyncIterable<Uint8Array>)[Symbol.asyncIterator] !==\n \"function\"\n ) {\n return { text: \"\", truncated: false };\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n for await (const chunk of stream) {\n total += chunk.byteLength;\n if (total > maxBytes) return { text: \"\", truncated: true };\n chunks.push(chunk);\n }\n return { text: Buffer.concat(chunks).toString(\"utf8\"), truncated: false };\n}\n\n/**\n * SSRF-guarded GET returning text. Validation is re-run on every hop because a\n * redirect can point somewhere the first check would have refused, and the\n * fetch is pinned to the addresses that passed so DNS cannot be rebound\n * between validation and connection.\n */\nexport async function fetchPublicText(\n rawUrl: string,\n timeoutMs = FETCH_TIMEOUT_MS,\n maxBytes = MAX_SINGLE_CSS_BYTES,\n attempt = 0,\n): Promise<TextFetchResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let currentUrl = rawUrl;\n try {\n for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {\n const validation = await validatePublicHttpUrl(currentUrl);\n if (!validation.ok) {\n return {\n ok: false,\n status: 0,\n body: \"\",\n finalUrl: currentUrl,\n truncated: false,\n error: `refused by URL policy: ${validation.reason}`,\n };\n }\n const pinned = await pinnedPublicFetch(\n validation.url,\n validation.addresses,\n {\n signal: controller.signal,\n headers: {\n Accept: \"text/css,text/html;q=0.9,*/*;q=0.1\",\n \"User-Agent\": USER_AGENT,\n },\n },\n );\n try {\n const { response } = pinned;\n if (REDIRECT_STATUSES.has(response.status)) {\n const location = response.headers.get(\"location\");\n if (!location) {\n return {\n ok: false,\n status: response.status,\n body: \"\",\n finalUrl: currentUrl,\n truncated: false,\n };\n }\n currentUrl = new URL(location, currentUrl).toString();\n continue;\n }\n if (!response.ok) {\n // One retry on a transient status. Five instances crawling the same\n // page drew a 429 on roughly one request in five.\n if (RETRY_STATUSES.has(response.status) && attempt === 0) {\n await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));\n return fetchPublicText(rawUrl, timeoutMs, maxBytes, 1);\n }\n return {\n ok: false,\n status: response.status,\n body: \"\",\n finalUrl: currentUrl,\n truncated: false,\n error: `HTTP ${response.status}`,\n };\n }\n const read = await readTextCapped(response, maxBytes);\n return {\n ok: !read.truncated,\n status: response.status,\n body: read.text,\n finalUrl: currentUrl,\n truncated: read.truncated,\n };\n } finally {\n await pinned.close();\n }\n }\n return {\n ok: false,\n status: 0,\n body: \"\",\n finalUrl: currentUrl,\n truncated: false,\n };\n } catch (error) {\n return {\n ok: false,\n status: 0,\n body: \"\",\n finalUrl: currentUrl,\n truncated: false,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n/** One document we managed to obtain, and how. */\nexport interface SourceSnapshot {\n html: string;\n baseUrl: string;\n via: DocumentAcquisition;\n}\n\n/**\n * Build the fullest picture of a page's CSS available.\n *\n * Both documents are used, not one or the other:\n *\n * - The SERVED document defines the authoritative cascade. Its sources come\n * first, in document order.\n * - The HYDRATED document (post-JavaScript) contributes anything the server\n * never sent — CSS-in-JS blocks and stylesheets fetched by scripts. Proven\n * on one storefront: a widget's stylesheet appears only after hydration\n * while its loader script is in the served HTML.\n *\n * Sources already present in the served document are not repeated. Everything\n * is labelled with its origin so a reader can weight brand CSS above runtime\n * widget CSS and ignore browser artifacts entirely.\n */\n/**\n * The one line that tells a clone agent whether its layout values are sourced.\n *\n * Recovery and retention are separate outcomes and the note must not conflate\n * them. CSS is collected in memory here but only reaches the agent as a file\n * written by the screenshot save; if that save throws, is skipped, or drops\n * the stylesheet document, there is nothing on disk to read. Saying \"retained\"\n * in that case sends the agent after a file that does not exist and invites it\n * to present guesses as source-backed.\n */\n/** read_file returns at most this many bytes per call (mirrors the\n * cap documented on the tool itself). The retention note only warns\n * about whole-file truncation when the stylesheet actually exceeds\n * it. */\nconst READ_FILE_RESULT_CAP_BYTES = 200_000;\n\n/** search_files silently SKIPS files above this size (mirrors\n * SEARCH_MAX_FILE_BYTES in tools/index.ts), and retained CSS can run\n * to MAX_TOTAL_CSS_BYTES = 3MB. Past this ceiling the note must stop\n * recommending search — a zero-match result on a skipped file reads\n * as \"the selector does not exist\", which is worse than no guidance. */\nconst SEARCH_FILE_CAP_BYTES = 2 * 1024 * 1024;\n\nexport function buildSourceCssNote(input: {\n collected: CollectedStylesheets | undefined;\n failure: string | undefined;\n retained: boolean;\n}): string {\n const { collected, failure, retained } = input;\n if (!collected) {\n return failure\n ? ` SOURCE CSS UNAVAILABLE: ${failure}. Layout values for this capture are NOT sourced — every spacing, breakpoint, and animation value you use is an estimate from pixels. Record that explicitly rather than implying the CSS was consulted.`\n : \"\";\n }\n const stats = ` ${collected.sources.length} source(s) (${collected.byOrigin?.site ?? 0} site, ${collected.byOrigin?.thirdPartyWidget ?? 0} third-party widget, ${collected.byOrigin?.browserArtifact ?? 0} browser artifact — weight site CSS first and ignore browser artifacts), ${collected.stats.spacingDeclarations} spacing declarations, ${collected.stats.mediaQueries} media queries, ${collected.stats.keyframes} keyframes, ${collected.stats.fontFaces} font-faces, ${collected.stats.customProperties} custom properties.`;\n const warnings =\n collected.warnings.length > 0\n ? ` Stylesheet warnings: ${collected.warnings.join(\"; \")}.`\n : \"\";\n if (!retained) {\n return ` SOURCE CSS RECOVERED BUT NOT RETAINED: Mist read${stats} The durable copy could not be written, so there is no stylesheet file to open. Treat every spacing, breakpoint, and animation value as an estimate from pixels and say so, rather than implying the CSS was consulted.${warnings}`;\n }\n // Only claim the file exceeds one read when it actually does — the\n // note vouches for evidence, so it must not overstate a 40KB sheet.\n // Byte length, not String.length: read_file caps by BYTES, and CSS\n // with enough multibyte UTF-8 exceeds the cap while staying under it\n // in UTF-16 code units — exactly the case where the missing warning\n // lets a silently truncated read pass as complete.\n const cssBytes = Buffer.byteLength(collected.css, \"utf8\");\n const exceedsOneRead = cssBytes > READ_FILE_RESULT_CAP_BYTES;\n const searchable = cssBytes <= SEARCH_FILE_CAP_BYTES;\n const sizeCaution = exceedsOneRead\n ? ` At ${Math.round(cssBytes / 1024)}KB it is larger than one read_file call returns (${Math.round(READ_FILE_RESULT_CAP_BYTES / 1024)}KB cap), so a whole-file read silently truncates while looking complete — never read it whole.`\n : \"\";\n // Past search_files' per-file ceiling the tool SKIPS the sheet and a\n // zero-match answer would read as \"selector not found\" — send agents\n // straight to sliced reads instead of vouching for a dead end.\n const consultHow = searchable\n ? ` SEARCH it before inferring layout from pixels — search_files reaches .mist-desktop/source-baselines, so query the selector, custom property, or @media block you need, or read_file it in offset/limit slices.`\n : ` CONSULT it before inferring layout from pixels — but at ${Math.round(cssBytes / 1024)}KB it exceeds search_files' ${Math.round(SEARCH_FILE_CAP_BYTES / (1024 * 1024))}MB per-file ceiling, so searches will silently skip it and report no matches. Read it with read_file offset/limit slices (or run_cli grep) instead.`;\n return ` Source CSS retained from ${(collected.documentsUsed ?? []).join(\" + \") || \"direct\"}:${stats}${consultHow}${sizeCaution}${warnings}`;\n}\n\nexport async function collectSourceStylesheets(\n snapshots: readonly SourceSnapshot[],\n fetchImpl: (url: string) => Promise<TextFetchResult> = (url) =>\n fetchPublicText(url),\n): Promise<CollectedStylesheets> {\n const warnings: string[] = [];\n const sources: CollectedStylesheet[] = [];\n const chunks: string[] = [];\n let total = 0;\n let attempts = 0;\n const fetched = new Map<string, string>();\n /** Inline bodies already emitted. A serialized DOM repeats identical inline\n * blocks — one real capture carried the same 24KB browser error stylesheet\n * four times — and those are duplication, not cascade. */\n const emittedInlineBodies = new Set<string>();\n /** Link hrefs emitted by an EARLIER snapshot. The hydrated document restates\n * the served document's sheets; those are the same sheet seen twice, not a\n * re-application. A link repeated WITHIN one document is genuine cascade\n * re-application and is kept at each position. */\n const hrefsFromEarlierSnapshots = new Set<string>();\n\n // Served document first when we have it: it defines the real cascade.\n const ordered = [...snapshots].sort((a, b) =>\n a.via === \"direct\" ? -1 : b.via === \"direct\" ? 1 : 0,\n );\n\n let pageHost = \"\";\n try {\n pageHost = new URL(ordered[0]?.baseUrl ?? \"https://invalid.invalid\")\n .hostname;\n } catch {\n pageHost = \"\";\n }\n\n for (const snapshot of ordered) {\n const { refs, inlineBlocks } = extractStylesheetRefs(\n snapshot.html,\n snapshot.baseUrl,\n );\n let inlineIndex = 0;\n const hrefsThisSnapshot = new Set<string>();\n\n for (const ref of refs) {\n if (ref.kind === \"link\" && hrefsFromEarlierSnapshots.has(ref.href)) {\n continue;\n }\n const needsRequest = ref.kind === \"link\" && !fetched.has(ref.href);\n if (needsRequest && attempts >= MAX_STYLESHEET_ATTEMPTS) {\n warnings.push(\n `stylesheet attempt cap reached (${MAX_STYLESHEET_ATTEMPTS}); remaining references not fetched`,\n );\n break;\n }\n if (needsRequest) attempts += 1;\n if (sources.length >= MAX_STYLESHEETS) {\n warnings.push(\n `stylesheet cap reached (${MAX_STYLESHEETS}); later references not retained`,\n );\n break;\n }\n\n let body: string;\n if (ref.kind === \"inline\") {\n body = inlineBlocks[inlineIndex] ?? \"\";\n inlineIndex += 1;\n } else if (fetched.has(ref.href)) {\n body = fetched.get(ref.href)!;\n } else {\n const res = await fetchImpl(ref.href);\n if (!res.ok) {\n warnings.push(\n res.truncated\n ? `${ref.href}: exceeded the ${MAX_SINGLE_CSS_BYTES}-byte single-sheet cap mid-stream; skipped`\n : `${ref.href}: ${res.error ?? `HTTP ${res.status || \"fetch failed\"}`}`,\n );\n continue;\n }\n body = res.body;\n fetched.set(ref.href, body);\n }\n\n if (ref.kind === \"inline\") {\n const fingerprint = `${ref.media ?? \"\"}\\u0000${body}`;\n if (emittedInlineBodies.has(fingerprint)) continue;\n emittedInlineBodies.add(fingerprint);\n }\n\n const byteLength = Buffer.byteLength(body, \"utf8\");\n if (byteLength > MAX_SINGLE_CSS_BYTES) {\n warnings.push(\n `${ref.href}: ${byteLength} bytes exceeds the ${MAX_SINGLE_CSS_BYTES}-byte single-sheet cap; skipped`,\n );\n continue;\n }\n if (total + byteLength > MAX_TOTAL_CSS_BYTES) {\n warnings.push(\n `total CSS cap reached (${MAX_TOTAL_CSS_BYTES} bytes); ${ref.href} and any later sheets not retained`,\n );\n break;\n }\n\n const origin = classifyStylesheet(ref.href, body, pageHost);\n const priorOccurrences = sources.filter(\n (existing) => existing.kind === \"link\" && existing.href === ref.href,\n ).length;\n\n if (ref.kind === \"link\") hrefsThisSnapshot.add(ref.href);\n total += byteLength;\n sources.push({ ...ref, byteLength, from: snapshot.via, origin });\n\n const provenance = `/* ===== ${ref.kind}: ${ref.href}${\n priorOccurrences > 0\n ? ` (reapplied, occurrence ${priorOccurrences + 1})`\n : \"\"\n }${ref.media ? ` [media: ${ref.media}]` : \"\"} [${origin}, from ${snapshot.via}] ===== */`;\n chunks.push(\n ref.media\n ? `${provenance}\\n@media ${ref.media} {\\n${body}\\n}`\n : `${provenance}\\n${body}`,\n );\n }\n for (const href of hrefsThisSnapshot) hrefsFromEarlierSnapshots.add(href);\n }\n\n const css = chunks.join(\"\\n\\n\");\n const byOrigin = {\n site: sources.filter((x) => x.origin === \"site\").length,\n thirdPartyWidget: sources.filter((x) => x.origin === \"third-party-widget\")\n .length,\n browserArtifact: sources.filter((x) => x.origin === \"browser-artifact\")\n .length,\n };\n return {\n css,\n sources,\n warnings,\n stats: summarizeCss(css),\n byOrigin,\n documentsUsed: ordered.map((x) => x.via),\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n/**\n * Intern a computed style into a shared table and return its index.\n *\n * A page has far more text elements than distinct text styles: every item in a\n * list, every card title, resolves identically. Emitting a style object per\n * landmark would repeat the same handful of shapes hundreds of times, so\n * landmarks carry an index into this table instead.\n *\n * Returns null once `limit` distinct shapes exist, which the caller records as\n * truncation rather than silently attributing the wrong style to an element.\n *\n * Interpolated into the browser evidence script, so it must stay dependency\n * free and side-effect free apart from the table it is handed.\n */\nexport function internLandmarkStyle(\n table: Record<string, string>[],\n index: Map<string, number>,\n style: Record<string, string>,\n limit: number,\n): number | null {\n const key = JSON.stringify(style);\n const existing = index.get(key);\n if (existing !== undefined) return existing;\n if (table.length >= limit) return null;\n const next = table.length;\n table.push(style);\n index.set(key, next);\n return next;\n}\n\nexport function buildSourcePageAnchor(\n parts: readonly string[],\n fallback: string,\n): string {\n const fullAnchor = parts.join(\" > \") || fallback;\n if (fullAnchor.length <= 2_000) return fullAnchor;\n\n let hash = 2_166_136_261;\n for (let index = 0; index < fullAnchor.length; index++) {\n hash ^= fullAnchor.charCodeAt(index);\n hash = Math.imul(hash, 16_777_619);\n }\n const marker = `[truncated:${(hash >>> 0).toString(16).padStart(8, \"0\")}] > `;\n return marker + fullAnchor.slice(-(2_000 - marker.length));\n}\n\ninterface RenderedTextFragment {\n text: string;\n leadingWhitespace: boolean;\n trailingWhitespace: boolean;\n firstRect: { x: number; y: number; width: number; height: number };\n lastRect: { x: number; y: number; width: number; height: number };\n}\n\nexport function joinRenderedTextFragments(\n fragments: RenderedTextFragment[],\n): string {\n let output = \"\";\n let previous: RenderedTextFragment | undefined;\n for (const fragment of fragments) {\n if (previous) {\n const previousRight = previous.lastRect.x + previous.lastRect.width;\n const previousBottom = previous.lastRect.y + previous.lastRect.height;\n const currentRight = fragment.firstRect.x + fragment.firstRect.width;\n const currentBottom = fragment.firstRect.y + fragment.firstRect.height;\n const verticallySeparated =\n fragment.firstRect.y >= previousBottom - 1 ||\n previous.lastRect.y >= currentBottom - 1;\n const horizontallySeparated =\n !verticallySeparated &&\n (fragment.firstRect.x - previousRight > 1 ||\n previous.lastRect.x - currentRight > 1);\n if (\n previous.trailingWhitespace ||\n fragment.leadingWhitespace ||\n verticallySeparated ||\n horizontallySeparated\n ) {\n output += \"\\n\";\n }\n }\n output += fragment.text;\n previous = fragment;\n }\n return output;\n}\n\nexport const SOURCE_PAGE_EVIDENCE_VERSION = 1;\nexport const SOURCE_PAGE_EVIDENCE_MAX_TEXT_CHARS = 250_000;\nexport const SOURCE_PAGE_EVIDENCE_MAX_LANDMARKS = 1_000;\nexport const SOURCE_PAGE_EVIDENCE_MAX_SECTIONS = 200;\nexport const SOURCE_PAGE_EVIDENCE_MAX_MEDIA = 500;\n\nconst RectSchema = z.object({\n x: z.number().finite(),\n y: z.number().finite(),\n width: z.number().finite().nonnegative(),\n height: z.number().finite().nonnegative(),\n});\n\nconst HttpUrlSchema = z\n .string()\n .url()\n .max(16_000)\n .refine((value) => /^https?:\\/\\//i.test(value), {\n message: \"Rendered source evidence URL must use HTTP(S).\",\n });\n\nconst SourcePageLandmarkSchema = z.object({\n tag: z.string().min(1).max(40),\n role: z.string().max(100).nullable(),\n open: z.boolean().nullable().optional(),\n text: z.string().max(2_000),\n href: z.string().max(8_000).nullable(),\n ariaLabel: z.string().max(2_000).nullable(),\n rect: RectSchema,\n /** Index into `landmarkStyles`; null when the style table filled up. */\n styleRef: z.number().int().nonnegative().nullable().optional(),\n});\n\nconst ComputedStyleValueSchema = z.string().max(1_000);\n\nconst SourcePageCaptureNormalizationSchema = z.object({\n kind: z.literal(\"inactive-dialog\"),\n anchor: z.string().min(1).max(2_000),\n role: z.enum([\"dialog\", \"alertdialog\"]),\n ariaHidden: z.literal(true),\n rect: RectSchema,\n style: z.object({\n display: ComputedStyleValueSchema,\n visibility: ComputedStyleValueSchema,\n position: ComputedStyleValueSchema,\n opacity: ComputedStyleValueSchema,\n transform: z.string().max(8_000),\n }),\n});\n\nconst SourcePagePostActionDismissalSchema = z.object({\n kind: z.literal(\"post-action-dismissal\"),\n anchor: z.string().min(1).max(2_000),\n selector: z.string().min(1).max(400),\n scopeSelector: z.string().min(1).max(400).nullable().default(null),\n reason: z.enum([\"unrendered\", \"outside-viewport\", \"explicit-dismissal\"]),\n rect: RectSchema,\n style: z.object({\n display: ComputedStyleValueSchema,\n visibility: ComputedStyleValueSchema,\n position: ComputedStyleValueSchema,\n opacity: ComputedStyleValueSchema,\n transform: z.string().max(8_000),\n }),\n});\nconst SourcePageStitchNormalizationSchema = z.object({\n kind: z.literal(\"stitch-chrome\"),\n anchor: z.string().min(1).max(2_000),\n originalPosition: z.enum([\"fixed\", \"sticky\"]),\n normalizedPosition: z.enum([\"absolute\", \"relative\"]),\n rect: RectSchema,\n style: z.object({\n display: ComputedStyleValueSchema,\n visibility: ComputedStyleValueSchema,\n opacity: ComputedStyleValueSchema,\n top: ComputedStyleValueSchema,\n right: ComputedStyleValueSchema,\n bottom: ComputedStyleValueSchema,\n left: ComputedStyleValueSchema,\n width: ComputedStyleValueSchema,\n height: ComputedStyleValueSchema,\n zIndex: ComputedStyleValueSchema,\n transform: z.string().max(8_000),\n }),\n});\n\nconst SourcePageSectionStyleSchema = z.object({\n display: ComputedStyleValueSchema,\n position: ComputedStyleValueSchema,\n backgroundColor: ComputedStyleValueSchema,\n backgroundImage: z.string().max(8_000),\n color: ComputedStyleValueSchema,\n padding: ComputedStyleValueSchema,\n margin: ComputedStyleValueSchema,\n gap: ComputedStyleValueSchema,\n gridTemplateColumns: z.string().max(8_000),\n flexDirection: ComputedStyleValueSchema,\n justifyContent: ComputedStyleValueSchema,\n alignItems: ComputedStyleValueSchema,\n borderRadius: ComputedStyleValueSchema,\n});\n\nconst SourcePageTextStyleSchema = z.object({\n color: ComputedStyleValueSchema,\n fontFamily: z.string().max(4_000),\n fontSize: ComputedStyleValueSchema,\n fontWeight: ComputedStyleValueSchema,\n lineHeight: ComputedStyleValueSchema,\n letterSpacing: ComputedStyleValueSchema,\n textAlign: ComputedStyleValueSchema,\n textTransform: ComputedStyleValueSchema,\n});\n\nconst SourcePageStyledTextSchema = z.object({\n tag: z.string().min(1).max(40),\n text: z.string().max(2_000),\n rect: RectSchema,\n style: SourcePageTextStyleSchema,\n});\n\nconst SourcePageSectionSchema = z.object({\n tag: z.string().min(1).max(40),\n anchor: z.string().min(1).max(2_000),\n role: z.string().max(100).nullable(),\n text: z.string().max(4_000),\n rect: RectSchema,\n style: SourcePageSectionStyleSchema,\n stitchHazard: z.boolean().default(false),\n heading: SourcePageStyledTextSchema.nullable(),\n body: SourcePageStyledTextSchema.nullable(),\n action: SourcePageStyledTextSchema.nullable(),\n});\n\nconst SourcePageMediaSchema = z.object({\n kind: z.enum([\"image\", \"video\", \"background\"]),\n tag: z.string().min(1).max(40),\n anchor: z.string().min(1).max(2_000).nullable().default(null),\n linkHref: HttpUrlSchema.nullable().default(null),\n contextText: z.string().max(4_000).nullable().default(null),\n currentSrc: z.string().max(16_000).nullable(),\n src: z.string().max(16_000).nullable(),\n poster: z.string().max(16_000).nullable(),\n alt: z.string().max(4_000).nullable(),\n sourceCandidates: z\n .array(\n z.object({\n src: z.string().max(16_000),\n srcset: z.string().max(32_000),\n media: z.string().max(4_000),\n type: z.string().max(1_000),\n }),\n )\n .max(100),\n readyState: z.number().int().min(0).max(4).nullable(),\n naturalWidth: z.number().int().nonnegative().nullable(),\n naturalHeight: z.number().int().nonnegative().nullable(),\n videoWidth: z.number().int().nonnegative().nullable(),\n videoHeight: z.number().int().nonnegative().nullable(),\n autoplay: z.boolean().nullable().default(null),\n loop: z.boolean().nullable().default(null),\n muted: z.boolean().nullable().default(null),\n playsInline: z.boolean().nullable().default(null),\n controls: z.boolean().nullable().default(null),\n rect: RectSchema,\n});\n\nexport const RenderedSourcePageEvidenceSchema = z.object({\n version: z.literal(SOURCE_PAGE_EVIDENCE_VERSION),\n url: HttpUrlSchema,\n title: z.string().max(8_000),\n viewport: z.object({\n width: z.number().int().positive().max(8_000),\n height: z.number().int().positive().max(8_000),\n }),\n document: z.object({\n width: z.number().int().nonnegative().max(100_000),\n height: z.number().int().nonnegative().max(1_000_000),\n }),\n visibleText: z.string().max(SOURCE_PAGE_EVIDENCE_MAX_TEXT_CHARS),\n visibleTextTruncated: z.boolean(),\n // New captures preserve one normalized line per painted text-node fragment\n // so copy mismatches can be localized. Optional fields keep existing\n // version-one evidence readable and comparable through visibleText.\n orderedText: z.string().max(SOURCE_PAGE_EVIDENCE_MAX_TEXT_CHARS).optional(),\n orderedTextTruncated: z.boolean().optional(),\n landmarks: z\n .array(SourcePageLandmarkSchema)\n .max(SOURCE_PAGE_EVIDENCE_MAX_LANDMARKS),\n landmarksTruncated: z.boolean(),\n /**\n * Distinct computed styles for the text-bearing landmarks, referenced by\n * `landmarks[].styleRef`.\n *\n * Before this, computed styles existed only for the ~20 structural sections\n * a page exposes — about 1.3% of its elements. Everything below that level\n * had to be inferred from the stylesheet by resolving the cascade by hand,\n * which is the step models get wrong. Styles are deduplicated because a page\n * has far more text elements than distinct text styles.\n *\n * Optional so evidence from older Mist builds stays verifiable.\n */\n landmarkStyles: z\n .array(\n z.object({\n color: z.string(),\n backgroundColor: z.string(),\n fontFamily: z.string(),\n fontSize: z.string(),\n fontWeight: z.string(),\n lineHeight: z.string(),\n letterSpacing: z.string(),\n textAlign: z.string(),\n textTransform: z.string(),\n margin: z.string(),\n padding: z.string(),\n display: z.string(),\n }),\n )\n .max(250)\n .optional(),\n landmarkStylesTruncated: z.boolean().optional(),\n sections: z\n .array(SourcePageSectionSchema)\n .max(SOURCE_PAGE_EVIDENCE_MAX_SECTIONS)\n .default([]),\n sectionsTruncated: z.boolean().default(false),\n stitchHazardsTruncated: z.boolean().default(false),\n captureNormalizations: z\n .array(SourcePageCaptureNormalizationSchema)\n .max(100)\n .default([]),\n captureNormalizationsTruncated: z.boolean().default(false),\n postActionDismissals: z\n .array(SourcePagePostActionDismissalSchema)\n .max(8)\n .default([]),\n stitchNormalizations: z\n .array(SourcePageStitchNormalizationSchema)\n .max(200)\n .default([]),\n stitchNormalizationsTruncated: z.boolean().default(false),\n // Older version-one sidecars predate open-shadow-root traversal. Preserve\n // their readability, but leave the field absent so comparison can treat\n // traversal completeness as unknown instead of silently assuming success.\n shadowRootsTruncated: z.boolean().optional(),\n media: z.array(SourcePageMediaSchema).max(SOURCE_PAGE_EVIDENCE_MAX_MEDIA),\n mediaTruncated: z.boolean(),\n});\n\nexport type RenderedSourcePageEvidence = z.infer<\n typeof RenderedSourcePageEvidenceSchema\n>;\n\n/**\n * Firecrawl's full-page image is assembled from viewport-height tiles. Fixed\n * and sticky chrome can therefore appear at multiple tile boundaries even\n * though the rendered DOM contains only one structural section. Surface those\n * signed section anchors so agents do not recreate a stitching artifact.\n */\nexport function renderedPageStitchHazardAnchors(\n evidence: RenderedSourcePageEvidence,\n): string[] {\n return evidence.sections\n .filter(\n ({ stitchHazard, style }) =>\n stitchHazard ||\n [\"fixed\", \"sticky\"].includes(style.position.toLowerCase()),\n )\n .map(({ anchor }) => anchor);\n}\n\nconst DurableSourceDocumentReceiptSchema = z.object({\n path: z\n .string()\n .min(1)\n .max(16_000)\n .refine((value) =>\n value.replaceAll(\"\\\\\", \"/\").startsWith(\".mist-desktop/source-baselines/\"),\n ),\n sha256: z.string().regex(/^[a-f0-9]{64}$/),\n byteLength: z.number().int().positive(),\n});\n\nconst DurableSourceCaptureGeometrySchema = z.object({\n mode: z.enum([\"viewport\", \"full-page\"]),\n status: z.enum([\"exact\", \"dynamic-height\"]),\n renderedDocument: z.object({\n width: z.number().int().nonnegative(),\n height: z.number().int().nonnegative(),\n }),\n screenshot: z.object({\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n }),\n heightDelta: z.number().int(),\n});\n\nconst MAX_FULL_PAGE_SCREENSHOT_SHORTFALL_RATIO = 0.01;\nconst MAX_FULL_PAGE_SCREENSHOT_SHORTFALL_VIEWPORT_RATIO = 0.125;\n\n/**\n * Dynamic storefronts can grow slightly after Firecrawl stitches the\n * authoritative screenshot but before Mist recollects the signed DOM.\n * Preserve that bounded drift instead of forcing identical recapture loops.\n * The viewport cap still rejects a missing lazy-loaded section on tall pages.\n */\nexport function isFullPageScreenshotGeometryComplete({\n screenshotHeight,\n renderedDocumentHeight,\n viewportHeight,\n}: {\n screenshotHeight: number;\n renderedDocumentHeight: number;\n viewportHeight: number;\n}): boolean {\n const shortfall = renderedDocumentHeight - screenshotHeight;\n if (shortfall <= 0) return true;\n const maxShortfall = Math.min(\n Math.ceil(\n renderedDocumentHeight * MAX_FULL_PAGE_SCREENSHOT_SHORTFALL_RATIO,\n ),\n Math.ceil(\n viewportHeight * MAX_FULL_PAGE_SCREENSHOT_SHORTFALL_VIEWPORT_RATIO,\n ),\n );\n return shortfall <= maxShortfall;\n}\n\nexport const DurableSourcePageEvidenceSchema = z.object({\n version: z.literal(SOURCE_PAGE_EVIDENCE_VERSION),\n capturedAt: z.string().datetime(),\n requestedUrl: HttpUrlSchema,\n finalUrl: HttpUrlSchema,\n statusCode: z.number().int().min(100).max(599),\n requestedViewport: z.object({\n width: z.number().int().positive().max(8_000),\n height: z.number().int().positive().max(8_000),\n }),\n screenshot: z.object({\n path: z\n .string()\n .min(1)\n .max(16_000)\n .refine((value) =>\n value\n .replaceAll(\"\\\\\", \"/\")\n .startsWith(\".mist-desktop/source-baselines/\"),\n ),\n sha256: z.string().regex(/^[a-f0-9]{64}$/),\n byteLength: z.number().int().positive(),\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n }),\n documents: z\n .object({\n html: DurableSourceDocumentReceiptSchema.optional(),\n markdown: DurableSourceDocumentReceiptSchema.optional(),\n /**\n * The page's own CSS — inline `<style>` bodies plus fetched\n * `<link rel=\"stylesheet\">` sheets, concatenated with provenance\n * comments. The crawl provider strips both from the HTML it returns, so\n * without this a clone agent has the DOM and class names but none of the\n * declarations that position them, and must infer spacing, breakpoints,\n * and animation from a screenshot. Optional so evidence from older Mist\n * builds stays verifiable.\n */\n stylesheet: DurableSourceDocumentReceiptSchema.optional(),\n /**\n * The UNPROCESSED source document. The provider's `html` has `<style>`\n * bodies and `<link rel=stylesheet>` stripped, so an agent auditing the\n * DOM through it is reading a reduced copy. Retained when a document was\n * obtained either directly or as the provider's rawHtml. Optional so\n * evidence from older Mist builds stays verifiable.\n */\n rawHtml: DurableSourceDocumentReceiptSchema.optional(),\n })\n .default({}),\n /**\n * A full-page browser capture can materialize lazy content while the\n * provider stitches viewport tiles. Keep both measured geometries signed\n * instead of rejecting otherwise complete evidence and forcing the agent\n * into an identical recapture loop.\n */\n captureGeometry: DurableSourceCaptureGeometrySchema.optional(),\n rendered: RenderedSourcePageEvidenceSchema,\n visibleTextSha256: z.string().regex(/^[a-f0-9]{64}$/),\n});\n\nexport type DurableSourcePageEvidence = z.infer<\n typeof DurableSourcePageEvidenceSchema\n>;\n\nexport interface RenderedPageCopyComparison {\n exact: boolean;\n sourceTextSha256: string;\n previewTextSha256: string;\n sourceCharacters: number;\n previewCharacters: number;\n sourceLines: number;\n previewLines: number;\n firstMismatchLine: number | null;\n sourceMismatch: string | null;\n previewMismatch: string | null;\n}\n\nexport interface RenderedPageCopySelection {\n sourceText: string;\n previewText: string;\n granularity: \"ordered-fragments\" | \"legacy-collapsed\";\n sourceTruncated: boolean;\n previewTruncated: boolean;\n}\n\nexport type RenderedPageVideoOrientation = \"landscape\" | \"portrait\" | \"square\";\n\nexport interface RenderedPageVideoMismatch {\n index: number;\n reasons: string[];\n}\n\nexport interface RenderedPageVideoComparison {\n exact: boolean;\n sourceCount: number;\n previewCount: number;\n sourceIdentified: number;\n previewIdentified: number;\n sourceTruncated: boolean;\n previewTruncated: boolean;\n mismatches: RenderedPageVideoMismatch[];\n}\n\nexport interface RenderedPageImageComparison {\n exact: boolean;\n sourceCount: number;\n previewCount: number;\n sourceIdentified: number;\n previewIdentified: number;\n sourceTruncated: boolean;\n previewTruncated: boolean;\n mismatches: RenderedPageVideoMismatch[];\n}\n\nfunction renderedImageSourceIdentified(\n media: RenderedSourcePageEvidence[\"media\"][number],\n): boolean {\n return Boolean(\n media.currentSrc ||\n media.src ||\n media.sourceCandidates.some(\n (candidate) => candidate.src || candidate.srcset,\n ),\n );\n}\n\nfunction splitCssImageLayers(value: string): string[] {\n const layers: string[] = [];\n let start = 0;\n let depth = 0;\n let quote: \"'\" | '\"' | null = null;\n let escaped = false;\n\n for (let index = 0; index < value.length; index += 1) {\n const character = value[index]!;\n if (escaped) {\n escaped = false;\n continue;\n }\n if (character === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (quote) {\n if (character === quote) quote = null;\n continue;\n }\n if (character === \"'\" || character === '\"') {\n quote = character;\n continue;\n }\n if (character === \"(\") {\n depth += 1;\n continue;\n }\n if (character === \")\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (character === \",\" && depth === 0) {\n layers.push(value.slice(start, index).trim());\n start = index + 1;\n }\n }\n layers.push(value.slice(start).trim());\n return layers.filter(Boolean);\n}\n\nfunction cssImageLayerIdentified(layer: string): boolean {\n const urls = Array.from(\n layer.matchAll(/url\\(\\s*(?:\"([^\"]*)\"|'([^']*)'|([^)]*))\\s*\\)/gi),\n );\n if (urls.length > 0) {\n return urls.some((match) =>\n Boolean((match[1] ?? match[2] ?? match[3] ?? \"\").trim()),\n );\n }\n const imageSet = layer.match(/(?:-webkit-)?image-set\\s*\\((.*)\\)\\s*$/i);\n return Boolean(imageSet?.[1]?.trim());\n}\n\ninterface RenderedImageOccurrence {\n identified: boolean;\n semanticLabel: string | null;\n assetKeys: string[];\n}\n\nfunction normalizedImageSemanticLabel(value: string | null): string | null {\n if (!value) return null;\n const normalized = normalizeRenderedPageText(value)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .trim();\n return normalized || null;\n}\n\nfunction imageAssetKey(value: string): string | null {\n const trimmed = value.trim().replace(/^[\"']|[\"']$/g, \"\");\n if (!trimmed) return null;\n try {\n const path = new URL(trimmed).pathname;\n const basename = decodeURIComponent(path.split(\"/\").at(-1) ?? \"\")\n .replace(/\\.[a-z0-9]+$/i, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return basename || null;\n } catch {\n return null;\n }\n}\n\nfunction cssImageLayerUrls(layer: string): string[] {\n return Array.from(\n layer.matchAll(/url\\(\\s*(?:\"([^\"]*)\"|'([^']*)'|([^)]*))\\s*\\)/gi),\n )\n .map((match) => (match[1] ?? match[2] ?? match[3] ?? \"\").trim())\n .filter(Boolean);\n}\n\nfunction imageSourceUrls(\n media: RenderedSourcePageEvidence[\"media\"][number],\n): string[] {\n const candidates = [\n media.currentSrc,\n media.src,\n ...media.sourceCandidates.flatMap(({ src, srcset }) => [\n src,\n ...srcset\n .split(\",\")\n .map((candidate) => candidate.trim().split(/\\s+/)[0] ?? \"\"),\n ]),\n ];\n return candidates.filter((candidate): candidate is string =>\n Boolean(candidate),\n );\n}\n\nfunction renderedImageOccurrences(\n media: RenderedSourcePageEvidence[\"media\"],\n): RenderedImageOccurrence[] {\n return media.flatMap((item) => {\n const semanticLabel =\n normalizedImageSemanticLabel(item.alt) ??\n normalizedImageSemanticLabel(item.contextText);\n if (item.kind === \"image\") {\n return [\n {\n identified: renderedImageSourceIdentified(item),\n semanticLabel,\n assetKeys: imageSourceUrls(item)\n .map(imageAssetKey)\n .filter((key): key is string => Boolean(key)),\n },\n ];\n }\n if (item.kind !== \"background\" || !item.currentSrc) return [];\n\n return splitCssImageLayers(item.currentSrc)\n .filter((layer) => /(?:url|(?:-webkit-)?image-set)\\s*\\(/i.test(layer))\n .map((layer) => ({\n identified: cssImageLayerIdentified(layer),\n semanticLabel,\n assetKeys: cssImageLayerUrls(layer)\n .map(imageAssetKey)\n .filter((key): key is string => Boolean(key)),\n }));\n });\n}\n\nfunction imageAssetKeysCompatible(\n sourceKeys: readonly string[],\n previewKeys: readonly string[],\n): boolean {\n return sourceKeys.some((sourceKey) =>\n previewKeys.some(\n (previewKey) =>\n sourceKey === previewKey ||\n sourceKey.startsWith(`${previewKey}-`) ||\n previewKey.startsWith(`${sourceKey}-`),\n ),\n );\n}\n\nfunction imageOccurrencesCompatible(\n source: RenderedImageOccurrence,\n preview: RenderedImageOccurrence,\n): boolean {\n return Boolean(\n (source.semanticLabel &&\n preview.semanticLabel &&\n source.semanticLabel === preview.semanticLabel) ||\n imageAssetKeysCompatible(source.assetKeys, preview.assetKeys),\n );\n}\n\nfunction describeImageOccurrence(occurrence: RenderedImageOccurrence): string {\n return (\n occurrence.semanticLabel ??\n occurrence.assetKeys[0] ??\n \"unlabeled source image\"\n );\n}\n\n/**\n * Verify that a clone did not silently omit visible source imagery. Source\n * image URLs are expected to change when assets move to Fluid DAM, and a\n * source <img> may reasonably become a CSS background, so parity matches a\n * normalized alt/context label or a filename lineage preserved by DAM upload.\n * Totals and identifiable sources remain hard gates. Gradient-only backgrounds\n * do not count as imagery, while multiple CSS image layers count individually.\n */\nexport function compareRenderedPageImages(\n source: RenderedSourcePageEvidence,\n preview: RenderedSourcePageEvidence,\n): RenderedPageImageComparison {\n const sourceImages = renderedImageOccurrences(source.media);\n const previewImages = renderedImageOccurrences(preview.media);\n const sourceIdentified = sourceImages.filter(\n ({ identified }) => identified,\n ).length;\n const previewIdentified = previewImages.filter(\n ({ identified }) => identified,\n ).length;\n const mismatches: RenderedPageVideoMismatch[] = [];\n\n if (sourceImages.length !== previewImages.length) {\n mismatches.push({\n index: Math.min(sourceImages.length, previewImages.length) + 1,\n reasons: [\n `visible image count expected ${sourceImages.length}, received ${previewImages.length}`,\n ],\n });\n }\n if (sourceImages.length === previewImages.length) {\n const unmatchedPreviewIndexes = new Set(previewImages.keys());\n sourceImages.forEach((sourceImage, sourceIndex) => {\n const previewIndex = Array.from(unmatchedPreviewIndexes).find((index) =>\n imageOccurrencesCompatible(sourceImage, previewImages[index]!),\n );\n if (previewIndex === undefined) {\n mismatches.push({\n index: sourceIndex + 1,\n reasons: [\n `no preview image matches source identity \"${describeImageOccurrence(sourceImage)}\"`,\n ],\n });\n return;\n }\n unmatchedPreviewIndexes.delete(previewIndex);\n });\n }\n if (sourceIdentified !== sourceImages.length) {\n mismatches.push({\n index: sourceIdentified + 1,\n reasons: [\n `${sourceImages.length - sourceIdentified} source image occurrence(s) have no identifiable URL or source candidate`,\n ],\n });\n }\n if (previewIdentified !== previewImages.length) {\n mismatches.push({\n index: previewIdentified + 1,\n reasons: [\n `${previewImages.length - previewIdentified} preview image occurrence(s) have no identifiable URL or source candidate`,\n ],\n });\n }\n\n return {\n exact:\n !source.mediaTruncated &&\n !preview.mediaTruncated &&\n sourceIdentified === sourceImages.length &&\n previewIdentified === previewImages.length &&\n mismatches.length === 0,\n sourceCount: sourceImages.length,\n previewCount: previewImages.length,\n sourceIdentified,\n previewIdentified,\n sourceTruncated: source.mediaTruncated,\n previewTruncated: preview.mediaTruncated,\n mismatches,\n };\n}\n\nfunction renderedVideoSourceIdentified(\n media: RenderedSourcePageEvidence[\"media\"][number],\n): boolean {\n return Boolean(\n media.currentSrc ||\n media.src ||\n media.sourceCandidates.some(\n (candidate) => candidate.src || candidate.srcset,\n ),\n );\n}\n\nfunction renderedVideoOrientation(\n media: RenderedSourcePageEvidence[\"media\"][number],\n): RenderedPageVideoOrientation {\n const width =\n media.videoWidth && media.videoWidth > 0\n ? media.videoWidth\n : media.rect.width;\n const height =\n media.videoHeight && media.videoHeight > 0\n ? media.videoHeight\n : media.rect.height;\n const ratio = height > 0 ? width / height : 1;\n if (ratio > 1.1) return \"landscape\";\n if (ratio < 0.9) return \"portrait\";\n return \"square\";\n}\n\n/**\n * Verify that a clone did not silently replace or omit source video. URLs are\n * expected to change when assets move to Fluid DAM, so parity is based on\n * ordered video presence, identifiable sources, rendered orientation, and\n * source playback behavior rather than URL equality.\n */\nexport function compareRenderedPageVideos(\n source: RenderedSourcePageEvidence,\n preview: RenderedSourcePageEvidence,\n): RenderedPageVideoComparison {\n const sourceVideos = source.media.filter((media) => media.kind === \"video\");\n const previewVideos = preview.media.filter((media) => media.kind === \"video\");\n const sourceIdentified = sourceVideos.filter(\n renderedVideoSourceIdentified,\n ).length;\n const previewIdentified = previewVideos.filter(\n renderedVideoSourceIdentified,\n ).length;\n const mismatches: RenderedPageVideoMismatch[] = [];\n const pairCount = Math.min(sourceVideos.length, previewVideos.length);\n const playbackKeys = [\n \"autoplay\",\n \"loop\",\n \"muted\",\n \"playsInline\",\n \"controls\",\n ] as const;\n\n for (let index = 0; index < pairCount; index += 1) {\n const sourceVideo = sourceVideos[index]!;\n const previewVideo = previewVideos[index]!;\n const reasons: string[] = [];\n const sourceOrientation = renderedVideoOrientation(sourceVideo);\n const previewOrientation = renderedVideoOrientation(previewVideo);\n if (sourceOrientation !== previewOrientation) {\n reasons.push(\n `orientation ${sourceOrientation} became ${previewOrientation}`,\n );\n }\n if (!renderedVideoSourceIdentified(sourceVideo)) {\n reasons.push(\"source video has no identifiable URL or source candidate\");\n }\n if (!renderedVideoSourceIdentified(previewVideo)) {\n reasons.push(\"preview video has no identifiable URL or media id\");\n }\n for (const key of playbackKeys) {\n if (sourceVideo[key] !== null && sourceVideo[key] !== previewVideo[key]) {\n reasons.push(\n `${key} expected ${String(sourceVideo[key])}, received ${String(previewVideo[key])}`,\n );\n }\n }\n if (reasons.length > 0) mismatches.push({ index: index + 1, reasons });\n }\n\n if (sourceVideos.length !== previewVideos.length) {\n mismatches.push({\n index: pairCount + 1,\n reasons: [\n `video count expected ${sourceVideos.length}, received ${previewVideos.length}`,\n ],\n });\n }\n\n return {\n exact:\n !source.mediaTruncated &&\n !preview.mediaTruncated &&\n sourceIdentified === sourceVideos.length &&\n previewIdentified === previewVideos.length &&\n mismatches.length === 0,\n sourceCount: sourceVideos.length,\n previewCount: previewVideos.length,\n sourceIdentified,\n previewIdentified,\n sourceTruncated: source.mediaTruncated,\n previewTruncated: preview.mediaTruncated,\n mismatches,\n };\n}\n\nexport function normalizeRenderedPageText(value: string): string {\n return value\n .replaceAll(\"\\u00a0\", \" \")\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => line.replace(/\\s+/g, \" \").trim())\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nexport function renderedPageTextSha256(value: string): string {\n return createHash(\"sha256\")\n .update(normalizeRenderedPageText(value))\n .digest(\"hex\");\n}\n\nexport function selectRenderedPageCopy(\n source: RenderedSourcePageEvidence,\n preview: RenderedSourcePageEvidence,\n): RenderedPageCopySelection {\n if (\n source.orderedText !== undefined &&\n source.orderedTextTruncated !== undefined &&\n preview.orderedText !== undefined &&\n preview.orderedTextTruncated !== undefined\n ) {\n return {\n sourceText: source.orderedText,\n previewText: preview.orderedText,\n granularity: \"ordered-fragments\",\n sourceTruncated: source.orderedTextTruncated,\n previewTruncated: preview.orderedTextTruncated,\n };\n }\n return {\n sourceText: source.visibleText,\n previewText: preview.visibleText,\n granularity: \"legacy-collapsed\",\n sourceTruncated: source.visibleTextTruncated,\n previewTruncated: preview.visibleTextTruncated,\n };\n}\n\nexport function compareRenderedPageCopy(\n sourceValue: string,\n previewValue: string,\n): RenderedPageCopyComparison {\n const source = normalizeRenderedPageText(sourceValue);\n const preview = normalizeRenderedPageText(previewValue);\n const sourceLines = source ? source.split(\"\\n\") : [];\n const previewLines = preview ? preview.split(\"\\n\") : [];\n const tokensWithLines = (lines: string[]) =>\n lines.flatMap((line, lineIndex) =>\n line.split(\" \").map((value) => ({ lineIndex, value })),\n );\n const sourceTokens = tokensWithLines(sourceLines);\n const previewTokens = tokensWithLines(previewLines);\n const sourceComparable = sourceTokens.map(({ value }) => value).join(\" \");\n const previewComparable = previewTokens.map(({ value }) => value).join(\" \");\n const tokenCount = Math.max(sourceTokens.length, previewTokens.length);\n let firstMismatchTokenIndex: number | null = null;\n for (let index = 0; index < tokenCount; index += 1) {\n if (sourceTokens[index]?.value !== previewTokens[index]?.value) {\n firstMismatchTokenIndex = index;\n break;\n }\n }\n const sourceMismatchLine =\n firstMismatchTokenIndex === null\n ? null\n : (sourceTokens[firstMismatchTokenIndex]?.lineIndex ??\n Math.max(sourceLines.length - 1, 0));\n const previewMismatchLine =\n firstMismatchTokenIndex === null\n ? null\n : (previewTokens[firstMismatchTokenIndex]?.lineIndex ??\n Math.max(previewLines.length - 1, 0));\n const comparableSha256 = (value: string) =>\n createHash(\"sha256\").update(value).digest(\"hex\");\n return {\n // DOM structure is not copy. Compare the ordered token stream so the same\n // rendered wording remains exact when source and preview split it across\n // different text-node boundaries; retain fragment lines for diagnostics.\n exact: sourceComparable === previewComparable,\n sourceTextSha256: comparableSha256(sourceComparable),\n previewTextSha256: comparableSha256(previewComparable),\n sourceCharacters: source.length,\n previewCharacters: preview.length,\n sourceLines: sourceLines.length,\n previewLines: previewLines.length,\n firstMismatchLine:\n sourceMismatchLine === null ? null : sourceMismatchLine + 1,\n sourceMismatch:\n sourceMismatchLine === null\n ? null\n : (sourceLines[sourceMismatchLine] ?? null),\n previewMismatch:\n previewMismatchLine === null\n ? null\n : (previewLines[previewMismatchLine] ?? null),\n };\n}\n\nexport function parseRenderedSourcePageEvidence(\n value: unknown,\n): RenderedSourcePageEvidence {\n const candidate =\n typeof value === \"string\" ? (JSON.parse(value) as unknown) : value;\n const parsed = RenderedSourcePageEvidenceSchema.parse(candidate);\n return {\n ...parsed,\n visibleText: normalizeRenderedPageText(parsed.visibleText),\n ...(parsed.orderedText === undefined\n ? {}\n : { orderedText: normalizeRenderedPageText(parsed.orderedText) }),\n };\n}\n\n/**\n * Firecrawl runs JavaScript actions in a vendor-default viewport unless the\n * request first primes the browser with a viewport-specific screenshot action.\n * Refuse evidence unless that priming action established the exact responsive\n * viewport used for the returned screenshot.\n */\nexport function parseFirecrawlRenderedSourcePageEvidence(\n value: unknown,\n requestedViewport: { width: number; height: number },\n): RenderedSourcePageEvidence {\n const requested =\n RenderedSourcePageEvidenceSchema.shape.viewport.parse(requestedViewport);\n const candidate =\n typeof value === \"string\" ? (JSON.parse(value) as unknown) : value;\n const firecrawlEnvelope = z\n .object({\n viewport: z.object({\n width: z.number().int().positive().max(100_000),\n height: z.number().int().positive().max(1_000_000),\n }),\n })\n .passthrough()\n .parse(candidate);\n\n if (\n firecrawlEnvelope.viewport.width !== requested.width ||\n firecrawlEnvelope.viewport.height !== requested.height\n ) {\n throw new Error(\n `Firecrawl rendered source viewport ${firecrawlEnvelope.viewport.width}x${firecrawlEnvelope.viewport.height} does not match the requested ${requested.width}x${requested.height} viewport.`,\n );\n }\n\n return parseRenderedSourcePageEvidence(firecrawlEnvelope);\n}\n\nexport function parseDurableSourcePageEvidence(\n value: unknown,\n): DurableSourcePageEvidence {\n const candidate =\n typeof value === \"string\" ? (JSON.parse(value) as unknown) : value;\n const parsed = DurableSourcePageEvidenceSchema.parse(candidate);\n const rendered = parseRenderedSourcePageEvidence(parsed.rendered);\n const visibleTextSha256 = renderedPageTextSha256(rendered.visibleText);\n if (visibleTextSha256 !== parsed.visibleTextSha256) {\n throw new Error(\n \"Rendered source evidence visible-text hash does not match its payload.\",\n );\n }\n if (\n rendered.url !== parsed.finalUrl ||\n rendered.viewport.width !== parsed.requestedViewport.width ||\n rendered.viewport.height !== parsed.requestedViewport.height\n ) {\n throw new Error(\n \"Rendered source evidence route or viewport does not match its durable capture envelope.\",\n );\n }\n if (parsed.captureGeometry) {\n const geometry = parsed.captureGeometry;\n const expectedScreenshotHeight =\n geometry.mode === \"viewport\"\n ? parsed.requestedViewport.height\n : geometry.renderedDocument.height;\n const expectedStatus =\n geometry.mode === \"viewport\" ||\n geometry.screenshot.height === geometry.renderedDocument.height\n ? \"exact\"\n : \"dynamic-height\";\n if (\n geometry.screenshot.width !== parsed.screenshot.width ||\n geometry.screenshot.height !== parsed.screenshot.height ||\n geometry.screenshot.width !== parsed.requestedViewport.width ||\n (geometry.mode === \"viewport\" &&\n geometry.screenshot.height !== parsed.requestedViewport.height) ||\n (geometry.mode === \"full-page\" &&\n !isFullPageScreenshotGeometryComplete({\n screenshotHeight: geometry.screenshot.height,\n renderedDocumentHeight: geometry.renderedDocument.height,\n viewportHeight: parsed.requestedViewport.height,\n })) ||\n geometry.renderedDocument.width !== rendered.document.width ||\n geometry.renderedDocument.height !== rendered.document.height ||\n geometry.heightDelta !==\n geometry.screenshot.height - expectedScreenshotHeight ||\n geometry.status !== expectedStatus\n ) {\n throw new Error(\n \"Rendered source evidence capture geometry does not match its signed screenshot and document measurements.\",\n );\n }\n }\n return { ...parsed, rendered, visibleTextSha256 };\n}\n\n/**\n * Fixed, non-model-authored browser action used to collect source and local\n * evidence after their capture mode has established the page's lazy-load\n * state. It intentionally returns bounded JSON rather than arbitrary HTML.\n */\nexport const RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT = String.raw`(() => {\n const MAX_TEXT_CHARS = 250000;\n const MAX_LANDMARKS = 1000;\n const MAX_SECTIONS = 200;\n const MAX_MEDIA = 500;\n // Distinct style shapes, not elements. One real storefront resolved ~1,600\n // elements down to a few dozen shapes, so this ceiling is generous.\n const MAX_LANDMARK_STYLES = 250;\n const MAX_OPEN_SHADOW_ROOTS = 200;\n const MIN_PAINTED_SIZE = 1;\n const normalize = (value) => String(value || \"\")\n .replace(/\\u00a0/g, \" \")\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => line.replace(/\\s+/g, \" \").trim())\n .filter(Boolean)\n .join(\"\\n\");\n const rectFor = (element) => {\n const rect = element.getBoundingClientRect();\n return {\n x: Number(rect.x.toFixed(3)),\n y: Number(rect.y.toFixed(3)),\n width: Number(rect.width.toFixed(3)),\n height: Number(rect.height.toFixed(3)),\n };\n };\n const parentElementOrHost = (element) => {\n if (element.parentElement) return element.parentElement;\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root.host : null;\n };\n let shadowRootsTruncated = false;\n const collectEvidenceRoots = () => {\n const roots = [document];\n let openShadowRoots = 0;\n for (let rootIndex = 0; rootIndex < roots.length; rootIndex += 1) {\n const root = roots[rootIndex];\n for (const element of Array.from(root.querySelectorAll(\"*\"))) {\n const shadowRoot = element.shadowRoot;\n if (!shadowRoot) continue;\n if (openShadowRoots >= MAX_OPEN_SHADOW_ROOTS) {\n shadowRootsTruncated = true;\n return roots;\n }\n roots.push(shadowRoot);\n openShadowRoots += 1;\n }\n }\n return roots;\n };\n const evidenceRoots = collectEvidenceRoots();\n const queryAllEvidenceRoots = (selector) =>\n evidenceRoots.flatMap((root) => Array.from(root.querySelectorAll(selector)));\n const hasPaintedAncestors = (element) => {\n let current = element;\n while (current instanceof Element) {\n if (\n current.matches(\n '[hidden],template',\n )\n ) return false;\n const style = getComputedStyle(current);\n if (\n style.display === \"none\" ||\n style.visibility === \"hidden\" ||\n style.visibility === \"collapse\" ||\n style.contentVisibility === \"hidden\" ||\n Number(style.opacity) === 0\n ) return false;\n current = parentElementOrHost(current);\n }\n return true;\n };\n const isPaintedRect = (rect) =>\n rect.width > MIN_PAINTED_SIZE &&\n rect.height > MIN_PAINTED_SIZE &&\n rect.right > 0 &&\n rect.left < window.innerWidth &&\n rect.bottom > 0 &&\n rect.top <\n Math.max(\n document.documentElement?.scrollHeight || 0,\n document.body?.scrollHeight || 0,\n window.innerHeight,\n );\n const isVisible = (element) => {\n if (!(element instanceof Element)) return false;\n return (\n hasPaintedAncestors(element) &&\n isPaintedRect(element.getBoundingClientRect())\n );\n };\n const paintedTextByElement = new Map();\n const joinOrderedFragments = ${joinRenderedTextFragments.toString()};\n const body = document.body;\n const textRoots = [\n ...(body ? [body] : []),\n ...evidenceRoots.filter((root) => root instanceof ShadowRoot),\n ];\n for (const textRoot of textRoots) {\n const walker = document.createTreeWalker(textRoot, NodeFilter.SHOW_TEXT);\n let node = walker.nextNode();\n while (node) {\n const rawText = String(node.nodeValue || \"\").replace(/\\u00a0/g, \" \");\n const text = normalize(rawText);\n const parent = node.parentElement;\n if (text && parent && hasPaintedAncestors(parent)) {\n const range = document.createRange();\n range.selectNodeContents(node);\n const paintedRects = Array.from(range.getClientRects()).filter(\n isPaintedRect,\n );\n range.detach();\n const firstRect = paintedRects[0];\n const lastRect = paintedRects.at(-1);\n if (firstRect && lastRect) {\n const fragment = {\n text,\n leadingWhitespace: /^\\s/.test(rawText),\n trailingWhitespace: /\\s$/.test(rawText),\n firstRect: rectFor({ getBoundingClientRect: () => firstRect }),\n lastRect: rectFor({ getBoundingClientRect: () => lastRect }),\n };\n let current = parent;\n while (current instanceof Element) {\n const fragments = paintedTextByElement.get(current) || [];\n fragments.push(fragment);\n paintedTextByElement.set(current, fragments);\n if (current === body) break;\n current = parentElementOrHost(current);\n }\n }\n }\n node = walker.nextNode();\n }\n }\n const visibleTextFor = (element, maxChars) =>\n normalize(\n (paintedTextByElement.get(element) || [])\n .map(({ text }) => text)\n .join(\" \"),\n ).slice(0, maxChars);\n const absoluteAttribute = (element, name) => {\n const value = element.getAttribute(name);\n if (!value) return null;\n try {\n return new URL(value, document.baseURI).href;\n } catch {\n return value;\n }\n };\n const sectionStyleFor = (element) => {\n const style = getComputedStyle(element);\n return {\n display: style.display,\n position: style.position,\n backgroundColor: style.backgroundColor,\n backgroundImage: style.backgroundImage,\n color: style.color,\n padding: style.padding,\n margin: style.margin,\n gap: style.gap,\n gridTemplateColumns: style.gridTemplateColumns,\n flexDirection: style.flexDirection,\n justifyContent: style.justifyContent,\n alignItems: style.alignItems,\n borderRadius: style.borderRadius,\n };\n };\n const textStyleFor = (element) => {\n const style = getComputedStyle(element);\n return {\n color: style.color,\n fontFamily: style.fontFamily,\n fontSize: style.fontSize,\n fontWeight: style.fontWeight,\n lineHeight: style.lineHeight,\n letterSpacing: style.letterSpacing,\n textAlign: style.textAlign,\n textTransform: style.textTransform,\n };\n };\n /**\n * Computed style for a text-bearing landmark: typography plus the box\n * spacing around it. textStyleFor omits margin/padding, but a heading's\n * spacing is exactly what a clone gets wrong.\n */\n const landmarkStyleFor = (element) => {\n const style = getComputedStyle(element);\n return {\n color: style.color,\n backgroundColor: style.backgroundColor,\n fontFamily: style.fontFamily,\n fontSize: style.fontSize,\n fontWeight: style.fontWeight,\n lineHeight: style.lineHeight,\n letterSpacing: style.letterSpacing,\n textAlign: style.textAlign,\n textTransform: style.textTransform,\n margin: style.margin,\n padding: style.padding,\n display: style.display,\n };\n };\n /**\n * Distinct landmark styles, with each landmark referencing one by index.\n *\n * A page has far more text elements than distinct text styles — every item\n * in a list resolves identically — so emitting a style object per landmark\n * would repeat the same dozen shapes hundreds of times. The table keeps full\n * per-element coverage at a fraction of the payload.\n */\n const landmarkStyleTable = [];\n const landmarkStyleIndex = new Map();\n const internStyle = ${internLandmarkStyle.toString()};\n const styleRefFor = (element) =>\n internStyle(\n landmarkStyleTable,\n landmarkStyleIndex,\n landmarkStyleFor(element),\n MAX_LANDMARK_STYLES,\n );\n const buildAnchor = ${buildSourcePageAnchor.toString()};\n const anchorFor = (element) => {\n if (element.id) {\n return buildAnchor(\n [\n /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test(element.id)\n ? \"#\" + element.id\n : \"[id=\" + JSON.stringify(element.id) + \"]\",\n ],\n element.tagName.toLowerCase(),\n );\n }\n const parts = [];\n let current = element;\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(\n /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test(current.id)\n ? \"#\" + current.id\n : \"[id=\" + JSON.stringify(current.id) + \"]\",\n );\n break;\n }\n const tag = current.tagName.toLowerCase();\n const siblings = current.parentElement\n ? Array.from(current.parentElement.children).filter(\n (sibling) => sibling.tagName === current.tagName,\n )\n : [];\n const position = siblings.indexOf(current);\n parts.unshift(\n tag +\n (siblings.length > 1 && position >= 0\n ? \":nth-of-type(\" + (position + 1) + \")\"\n : \"\"),\n );\n current = parentElementOrHost(current);\n }\n return buildAnchor(parts, element.tagName.toLowerCase());\n };\n const firstVisible = (root, selector) => {\n for (const element of root.querySelectorAll(selector)) {\n if (isVisible(element)) return element;\n }\n return null;\n };\n const styledTextFor = (element) =>\n element\n ? {\n tag: element.tagName.toLowerCase(),\n text: normalize(\n (element.innerText || element.textContent || \"\").slice(0, 4000),\n ).slice(0, 2000),\n rect: rectFor(element),\n style: textStyleFor(element),\n }\n : null;\n const mediaContextFor = (element) => {\n const link = element.closest(\"a[href]\");\n const rawLinkHref = link ? absoluteAttribute(link, \"href\") : null;\n const linkHref =\n rawLinkHref && /^https?:\\/\\//i.test(rawLinkHref) ? rawLinkHref : null;\n const contextRoot =\n element.closest(\n \"[data-product-id],[data-product-handle],[data-product],[data-item-id],[data-grid-item],product-grid-item,article,li,figure\",\n ) ||\n link ||\n element.parentElement;\n const contextText = normalize(\n contextRoot?.innerText ||\n contextRoot?.textContent ||\n link?.getAttribute(\"aria-label\") ||\n \"\",\n ).slice(0, 4000);\n return {\n anchor: anchorFor(element),\n linkHref,\n contextText: contextText || null,\n };\n };\n\n const rawVisibleText = body\n ? normalize(\n (paintedTextByElement.get(body) || [])\n .map(({ text }) => text)\n .join(\" \"),\n )\n : \"\";\n const visibleTextTruncated = rawVisibleText.length > MAX_TEXT_CHARS;\n const visibleText = rawVisibleText.slice(0, MAX_TEXT_CHARS);\n const rawOrderedText = body\n ? normalize(joinOrderedFragments(paintedTextByElement.get(body) || []))\n : \"\";\n const orderedTextTruncated = rawOrderedText.length > MAX_TEXT_CHARS;\n const orderedText = rawOrderedText.slice(0, MAX_TEXT_CHARS);\n\n const landmarkSelector = [\n \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n \"p\", \"li\", \"dt\", \"dd\", \"blockquote\",\n \"a\", \"button\", \"label\", \"legend\", \"summary\",\n \"dialog\", \"[role=dialog]\", \"[role=alertdialog]\",\n \"[role=button]\", \"[role=link]\", \"[role=heading]\",\n \"[aria-label]\"\n ].join(\",\");\n const allLandmarks = queryAllEvidenceRoots(landmarkSelector)\n .filter(\n (element) =>\n element.tagName.toLowerCase() !== \"dialog\" ||\n element.hasAttribute(\"open\"),\n )\n .filter(isVisible);\n const landmarks = allLandmarks.slice(0, MAX_LANDMARKS).map((element) => ({\n tag: element.tagName.toLowerCase(),\n role: element.getAttribute(\"role\"),\n open:\n element.tagName.toLowerCase() === \"dialog\"\n ? element.hasAttribute(\"open\")\n : null,\n text: visibleTextFor(element, 2000),\n href: element instanceof HTMLAnchorElement ? element.href : null,\n ariaLabel: element.getAttribute(\"aria-label\"),\n rect: rectFor(element),\n styleRef: styleRefFor(element),\n }));\n\n const sectionSelector = [\n '[id^=\"shopify-section-\"]',\n \"header\", \"nav\", \"main\", \"section\", \"article\", \"aside\", \"footer\",\n \"[role=banner]\", \"[role=navigation]\", \"[role=main]\", \"[role=contentinfo]\",\n \"main > div\", \"[role=main] > div\"\n ].join(\",\");\n const allSections = queryAllEvidenceRoots(sectionSelector).filter(isVisible);\n const sections = allSections.slice(0, MAX_SECTIONS).map((section) => ({\n tag: section.tagName.toLowerCase(),\n anchor: anchorFor(section),\n role: section.getAttribute(\"role\"),\n text: visibleTextFor(section, 4000),\n rect: rectFor(section),\n style: sectionStyleFor(section),\n stitchHazard:\n typeof mistSourceStitchHazardSections !== \"undefined\" &&\n mistSourceStitchHazardSections.has(section),\n heading: styledTextFor(\n firstVisible(section, \"h1,h2,h3,h4,h5,h6,[role=heading]\"),\n ),\n body: styledTextFor(\n firstVisible(section, \"p,li,dt,dd,blockquote\"),\n ),\n action: styledTextFor(\n firstVisible(section, \"a,button,[role=button],[role=link]\"),\n ),\n }));\n\n const media = [];\n const recordedVideoNodes = new Set();\n for (const element of queryAllEvidenceRoots(\"img,video\")) {\n if (media.length >= MAX_MEDIA) break;\n if (!isVisible(element)) continue;\n const sourceContainer =\n element instanceof HTMLImageElement\n ? element.closest(\"picture\") || element\n : element;\n const sourceCandidates = Array.from(sourceContainer.querySelectorAll(\"source\")).map((source) => ({\n src: absoluteAttribute(source, \"src\") || \"\",\n srcset: source.getAttribute(\"srcset\") || source.getAttribute(\"data-srcset\") || \"\",\n media: source.getAttribute(\"media\") || \"\",\n type: source.getAttribute(\"type\") || \"\",\n }));\n if (element instanceof HTMLImageElement) {\n media.push({\n kind: \"image\",\n tag: \"img\",\n ...mediaContextFor(element),\n currentSrc: element.currentSrc || null,\n src: element.src || null,\n poster: null,\n alt: element.alt || null,\n sourceCandidates,\n readyState: null,\n naturalWidth: element.naturalWidth,\n naturalHeight: element.naturalHeight,\n videoWidth: null,\n videoHeight: null,\n autoplay: null,\n loop: null,\n muted: null,\n playsInline: null,\n controls: null,\n rect: rectFor(element),\n });\n } else if (element instanceof HTMLVideoElement) {\n media.push({\n kind: \"video\",\n tag: \"video\",\n ...mediaContextFor(element),\n currentSrc: element.currentSrc || null,\n src: element.src || null,\n poster: element.poster || null,\n alt: element.getAttribute(\"aria-label\"),\n sourceCandidates,\n readyState: element.readyState,\n naturalWidth: null,\n naturalHeight: null,\n videoWidth: element.videoWidth,\n videoHeight: element.videoHeight,\n autoplay: element.autoplay,\n loop: element.loop,\n muted: element.muted,\n playsInline: element.playsInline,\n controls: element.controls,\n rect: rectFor(element),\n });\n recordedVideoNodes.add(element);\n }\n }\n if (media.length < MAX_MEDIA) {\n const videoEmbedPattern =\n /(?:youtube(?:-nocookie)?\\.com|youtu\\.be|player\\.vimeo\\.com|fast\\.wistia\\.(?:net|com)|stream\\.mux\\.com|tiktok\\.com)/i;\n for (const element of queryAllEvidenceRoots(\"iframe[src]\")) {\n if (media.length >= MAX_MEDIA) break;\n if (!isVisible(element)) continue;\n const src = absoluteAttribute(element, \"src\");\n if (!src || !videoEmbedPattern.test(src)) continue;\n media.push({\n kind: \"video\",\n tag: \"iframe\",\n ...mediaContextFor(element),\n currentSrc: src,\n src,\n poster: null,\n alt: element.getAttribute(\"title\") || element.getAttribute(\"aria-label\"),\n sourceCandidates: [],\n readyState: null,\n naturalWidth: null,\n naturalHeight: null,\n videoWidth: null,\n videoHeight: null,\n autoplay: null,\n loop: null,\n muted: null,\n playsInline: null,\n controls: null,\n rect: rectFor(element),\n });\n recordedVideoNodes.add(element);\n }\n }\n if (media.length < MAX_MEDIA) {\n const customVideoSelector =\n \"mux-player,wistia-player,fluid-media-widget\";\n for (const element of queryAllEvidenceRoots(customVideoSelector)) {\n if (media.length >= MAX_MEDIA) break;\n if (!isVisible(element)) continue;\n // Skip only children that were actually inventoried. A blanket\n // light-DOM iframe skip creates a blind spot for providers outside the\n // fixed embed allowlist.\n const lightVideo = element.querySelector(\"video\");\n const lightIframe = element.querySelector(\"iframe\");\n const shadowVideo = element.shadowRoot?.querySelector(\"video\");\n const shadowIframe = element.shadowRoot?.querySelector(\"iframe\");\n const renderedChild =\n [lightVideo, lightIframe, shadowVideo, shadowIframe].find(\n (candidate) => candidate && !recordedVideoNodes.has(candidate),\n ) || null;\n if (\n !renderedChild &&\n (lightVideo || lightIframe || shadowVideo || shadowIframe)\n ) {\n continue;\n }\n const renderedVideo =\n renderedChild instanceof HTMLVideoElement ? renderedChild : null;\n const source =\n (renderedVideo\n ? renderedVideo.currentSrc || absoluteAttribute(renderedVideo, \"src\")\n : renderedChild\n ? absoluteAttribute(renderedChild, \"src\")\n : null) ||\n absoluteAttribute(element, \"src\") ||\n element.getAttribute(\"media-id\") ||\n element.getAttribute(\"video-id\");\n const sourceCandidates =\n renderedVideo\n ? Array.from(renderedVideo.querySelectorAll(\"source\")).map((candidate) => ({\n src: absoluteAttribute(candidate, \"src\") || \"\",\n srcset: candidate.getAttribute(\"srcset\") || candidate.getAttribute(\"data-srcset\") || \"\",\n media: candidate.getAttribute(\"media\") || \"\",\n type: candidate.getAttribute(\"type\") || \"\",\n }))\n : [];\n media.push({\n kind: \"video\",\n tag: element.tagName.toLowerCase(),\n ...mediaContextFor(element),\n currentSrc: source,\n src: source,\n poster:\n renderedVideo\n ? absoluteAttribute(renderedVideo, \"poster\")\n : absoluteAttribute(element, \"poster\"),\n alt: element.getAttribute(\"title\") || element.getAttribute(\"aria-label\"),\n sourceCandidates,\n readyState: renderedVideo ? renderedVideo.readyState : null,\n naturalWidth: null,\n naturalHeight: null,\n videoWidth: renderedVideo ? renderedVideo.videoWidth : null,\n videoHeight: renderedVideo ? renderedVideo.videoHeight : null,\n autoplay:\n renderedVideo\n ? renderedVideo.autoplay\n : element.hasAttribute(\"autoplay\"),\n loop:\n renderedVideo\n ? renderedVideo.loop\n : element.hasAttribute(\"loop\"),\n muted:\n renderedVideo\n ? renderedVideo.muted\n : element.hasAttribute(\"muted\"),\n playsInline:\n renderedVideo\n ? renderedVideo.playsInline\n : element.hasAttribute(\"playsinline\"),\n controls:\n renderedVideo\n ? renderedVideo.controls\n : element.hasAttribute(\"controls\"),\n rect: rectFor(renderedChild || element),\n });\n if (renderedChild) recordedVideoNodes.add(renderedChild);\n }\n }\n if (media.length < MAX_MEDIA) {\n for (const element of queryAllEvidenceRoots(\"*\")) {\n if (media.length >= MAX_MEDIA) break;\n if (!isVisible(element)) continue;\n const background = getComputedStyle(element).backgroundImage;\n if (!background || background === \"none\") continue;\n media.push({\n kind: \"background\",\n tag: element.tagName.toLowerCase(),\n ...mediaContextFor(element),\n currentSrc: background,\n src: null,\n poster: null,\n alt: element.getAttribute(\"aria-label\"),\n sourceCandidates: [],\n readyState: null,\n naturalWidth: null,\n naturalHeight: null,\n videoWidth: null,\n videoHeight: null,\n autoplay: null,\n loop: null,\n muted: null,\n playsInline: null,\n controls: null,\n rect: rectFor(element),\n });\n }\n }\n\n const captureNormalizations =\n typeof mistSourceCaptureNormalizations === \"undefined\"\n ? []\n : mistSourceCaptureNormalizations.map(\n ({ element, role, rect, style }) => ({\n kind: \"inactive-dialog\",\n anchor: anchorFor(element),\n role,\n ariaHidden: true,\n rect,\n style,\n }),\n );\n const postActionDismissals =\n typeof mistSourcePostActionDismissalReceipts === \"undefined\"\n ? []\n : mistSourcePostActionDismissalReceipts.map(\n ({\n element,\n anchor,\n selector,\n scopeSelector,\n reason,\n rect,\n style,\n }) => ({\n kind: \"post-action-dismissal\",\n anchor: anchor || anchorFor(element),\n selector,\n scopeSelector,\n reason,\n rect,\n style,\n }),\n );\n const stitchNormalizations =\n typeof mistSourceStitchNormalizations === \"undefined\"\n ? []\n : mistSourceStitchNormalizations.map(\n ({\n element,\n originalPosition,\n normalizedPosition,\n rect,\n style,\n }) => ({\n kind: \"stitch-chrome\",\n anchor: anchorFor(element),\n originalPosition,\n normalizedPosition,\n rect,\n style,\n }),\n );\n\n return JSON.stringify({\n version: 1,\n url: location.href,\n title: document.title || \"\",\n viewport: {\n width: window.innerWidth,\n height: window.innerHeight,\n },\n document: {\n width: Math.max(document.documentElement?.scrollWidth || 0, document.body?.scrollWidth || 0),\n height: Math.max(document.documentElement?.scrollHeight || 0, document.body?.scrollHeight || 0),\n },\n visibleText,\n visibleTextTruncated,\n orderedText,\n orderedTextTruncated,\n landmarks,\n landmarksTruncated: allLandmarks.length > MAX_LANDMARKS,\n landmarkStyles: landmarkStyleTable,\n // A full table is not a truncated one. Filling the last slot exactly and\n // dropping nothing is complete evidence; report truncation only when some\n // landmark actually went without a style.\n landmarkStylesTruncated: landmarks.some(({ styleRef }) => styleRef === null),\n sections,\n sectionsTruncated: allSections.length > MAX_SECTIONS,\n stitchHazardsTruncated:\n typeof mistSourceStitchHazardsTruncated !== \"undefined\" &&\n mistSourceStitchHazardsTruncated === true,\n captureNormalizations,\n captureNormalizationsTruncated:\n typeof mistSourceCaptureNormalizationsTruncated !== \"undefined\" &&\n mistSourceCaptureNormalizationsTruncated === true,\n postActionDismissals,\n stitchNormalizations,\n stitchNormalizationsTruncated:\n typeof mistSourceStitchNormalizationsTruncated !== \"undefined\" &&\n mistSourceStitchNormalizationsTruncated === true,\n shadowRootsTruncated,\n media,\n mediaTruncated: media.length >= MAX_MEDIA,\n });\n})()`;\n\n/**\n * Firecrawl full-page capture does not expose Mist's local capture walk, so\n * perform the same bounded bottom-touch + viewport walk before collecting\n * evidence. Viewport-only source captures use the collector above directly\n * and therefore never move the source page.\n */\nexport const RENDERED_SOURCE_PAGE_EVIDENCE_SCRIPT = String.raw`(async () => {\n const MAX_FULL_PAGE_SECTIONS = 12;\n const LAZY_LOAD_SETTLE_MS = 450;\n const POST_ACTION_SETTLE_POLL_MS = 100;\n const POST_ACTION_SETTLE_QUIET_MS = 200;\n const POST_ACTION_SETTLE_MAX_MS = 2_000;\n const MEDIA_STABILITY_MIN_MS = 1500;\n const MEDIA_STABILITY_MAX_MS = 5000;\n const MEDIA_STABILITY_QUIET_MS = 500;\n const MEDIA_STABILITY_POLL_MS = 100;\n const STITCH_SECTION_SELECTOR = [\n '[id^=\"shopify-section-\"]',\n \"header\", \"nav\", \"main\", \"section\", \"article\", \"aside\", \"footer\",\n \"[role=banner]\", \"[role=navigation]\", \"[role=main]\", \"[role=contentinfo]\"\n ].join(\",\");\n const MAX_DYNAMIC_STITCH_CANDIDATES_PER_SETTLE = 1_000;\n const MAX_DYNAMIC_STITCH_CANDIDATES_PER_CAPTURE = 10_000;\n const MAX_STATIC_STITCH_CANDIDATES = 10_000;\n const MAX_CAPTURE_NORMALIZATIONS = 100;\n const MAX_CAPTURE_NORMALIZATION_CANDIDATES = 1_000;\n const MAX_CAPTURE_NORMALIZATION_OPEN_SHADOW_ROOTS = 200;\n const MAX_STITCH_NORMALIZATIONS = 200;\n const MAX_STITCH_NORMALIZATION_CANDIDATES = 10_000;\n const mistSourceStitchHazardSections = new WeakSet();\n const mistSourceStitchHazardSectionObservations = new WeakMap();\n const mistSourceStitchHazardElements = new Map();\n let mistSourceStitchHazardsTruncated = false;\n const mistSourceCaptureNormalizations = [];\n let mistSourceCaptureNormalizationsTruncated = false;\n const mistSourceStitchNormalizations = [];\n const mistSourceStitchNormalizationByElement = new WeakMap();\n let mistSourceStitchNormalizationsTruncated = false;\n const mistSourcePostActionStitchCandidates = new Map();\n const mistSourcePostActionDismissals = new WeakSet();\n const mistSourcePostActionDismissalReceipts = [];\n const mistSourceDefersStitchNormalization =\n window.__MIST_DEFER_SOURCE_STITCH_NORMALIZATION_V1__ === true;\n const observedCaptureNormalizationRoots = new WeakSet();\n const observedStitchNormalizationRoots = new WeakSet();\n const attachedCaptureNormalizationRoots = [];\n const queuedDynamicStitchCandidates = new Set();\n const dynamicStitchCandidateQueue = [];\n let dynamicStitchCandidateHead = 0;\n let dynamicStitchCandidatesRecorded = 0;\n let reconcileStitchChromeAfterMutation = () => {};\n const root = document.documentElement;\n const sleep = (milliseconds) =>\n milliseconds > 0\n ? new Promise((resolve) => setTimeout(resolve, milliseconds))\n : Promise.resolve();\n const nextPaint = () =>\n new Promise((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(resolve)),\n );\n const isViewportPainted = (element, style, rect) =>\n !element.closest(\"[hidden],template\") &&\n style.display !== \"none\" &&\n style.visibility !== \"hidden\" &&\n style.visibility !== \"collapse\" &&\n style.contentVisibility !== \"hidden\" &&\n Number(style.opacity) !== 0 &&\n rect.width > 0 &&\n rect.height > 0 &&\n rect.right > 0 &&\n rect.left < window.innerWidth &&\n rect.bottom > 0 &&\n rect.top < window.innerHeight;\n // Full-page providers scroll and stitch the document after page actions.\n // Some storefronts paint a closed off-canvas dialog during that walk even\n // though its settled state is aria-hidden and outside the viewport. Keep\n // currently painted dialogs untouched, but pin inactive ones closed and\n // sign their pre-normalization state so the pixels remain auditable.\n const collectBoundedCaptureRoots = (markTruncated) => {\n const roots = [document];\n let openShadowRoots = 0;\n for (const captureRoot of attachedCaptureNormalizationRoots) {\n if (roots.includes(captureRoot)) continue;\n if (openShadowRoots >= MAX_CAPTURE_NORMALIZATION_OPEN_SHADOW_ROOTS) {\n markTruncated();\n return roots;\n }\n roots.push(captureRoot);\n openShadowRoots += 1;\n }\n for (let rootIndex = 0; rootIndex < roots.length; rootIndex += 1) {\n const captureRoot = roots[rootIndex];\n for (const element of Array.from(captureRoot.querySelectorAll(\"*\"))) {\n const shadowRoot = element.shadowRoot;\n if (!shadowRoot || roots.includes(shadowRoot)) continue;\n if (\n openShadowRoots >= MAX_CAPTURE_NORMALIZATION_OPEN_SHADOW_ROOTS\n ) {\n markTruncated();\n return roots;\n }\n roots.push(shadowRoot);\n openShadowRoots += 1;\n }\n }\n return roots;\n };\n const collectCaptureNormalizationRoots = () =>\n collectBoundedCaptureRoots(() => {\n mistSourceCaptureNormalizationsTruncated = true;\n });\n const collectStitchNormalizationRoots = () =>\n collectBoundedCaptureRoots(() => {\n mistSourceStitchNormalizationsTruncated = true;\n });\n const restoreActivatedDialogs = () => {\n for (\n let index = mistSourceCaptureNormalizations.length - 1;\n index >= 0;\n index -= 1\n ) {\n const normalization = mistSourceCaptureNormalizations[index];\n const ariaHidden = String(\n normalization.element.getAttribute(\"aria-hidden\") || \"\",\n ).toLowerCase();\n if (normalization.element.isConnected && ariaHidden === \"true\") continue;\n if (normalization.inlineDisplay) {\n normalization.element.style.setProperty(\n \"display\",\n normalization.inlineDisplay,\n normalization.inlineDisplayPriority,\n );\n } else {\n normalization.element.style.removeProperty(\"display\");\n }\n normalization.element.removeAttribute(\"data-mist-capture-normalized\");\n mistSourceCaptureNormalizations.splice(index, 1);\n }\n };\n const normalizeInactiveDialogs = (captureRoots) => {\n let scanned = 0;\n for (const captureRoot of captureRoots) {\n const candidates = captureRoot.querySelectorAll(\n '[role=\"dialog\"],[role=\"alertdialog\"]',\n );\n for (const element of candidates) {\n if (scanned >= MAX_CAPTURE_NORMALIZATION_CANDIDATES) {\n mistSourceCaptureNormalizationsTruncated = true;\n return;\n }\n scanned += 1;\n if (\n mistSourceCaptureNormalizations.some(\n (normalization) => normalization.element === element,\n )\n ) {\n continue;\n }\n if (\n String(element.getAttribute(\"aria-hidden\") || \"\").toLowerCase() !==\n \"true\"\n ) {\n continue;\n }\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n if (isViewportPainted(element, style, rect)) continue;\n if (\n mistSourceCaptureNormalizations.length >= MAX_CAPTURE_NORMALIZATIONS\n ) {\n mistSourceCaptureNormalizationsTruncated = true;\n return;\n }\n mistSourceCaptureNormalizations.push({\n element,\n role:\n String(element.getAttribute(\"role\") || \"\").toLowerCase() ===\n \"alertdialog\"\n ? \"alertdialog\"\n : \"dialog\",\n rect: {\n x: Number(rect.x.toFixed(3)),\n y: Number(rect.y.toFixed(3)),\n width: Number(rect.width.toFixed(3)),\n height: Number(rect.height.toFixed(3)),\n },\n style: {\n display: style.display,\n visibility: style.visibility,\n position: style.position,\n opacity: style.opacity,\n transform: style.transform,\n },\n inlineDisplay: element.style.getPropertyValue(\"display\"),\n inlineDisplayPriority: element.style.getPropertyPriority(\"display\"),\n });\n element.setAttribute(\n \"data-mist-capture-normalized\",\n \"inactive-dialog\",\n );\n element.style.setProperty(\"display\", \"none\", \"important\");\n }\n }\n };\n let reconcilingInactiveDialogs = false;\n const reconcileInactiveDialogs = () => {\n if (reconcilingInactiveDialogs) return;\n reconcilingInactiveDialogs = true;\n try {\n restoreActivatedDialogs();\n const captureRoots = collectCaptureNormalizationRoots();\n for (const captureRoot of captureRoots) {\n if (observedCaptureNormalizationRoots.has(captureRoot)) continue;\n captureNormalizationObserver.observe(captureRoot, {\n attributes: true,\n attributeFilter: [\"aria-hidden\"],\n childList: true,\n subtree: true,\n });\n observedCaptureNormalizationRoots.add(captureRoot);\n }\n normalizeInactiveDialogs(captureRoots);\n } finally {\n reconcilingInactiveDialogs = false;\n }\n };\n // Firecrawl takes its screenshot after this action returns. Keep watching\n // through that handoff so a timer- or scroll-activated dialog is restored\n // before it can be incorrectly omitted from the final pixels.\n const captureNormalizationObserver = new MutationObserver(() => {\n reconcileInactiveDialogs();\n });\n const observeCaptureNormalizationRoot = (captureRoot) => {\n if (observedCaptureNormalizationRoots.has(captureRoot)) return;\n captureNormalizationObserver.observe(captureRoot, {\n attributes: true,\n attributeFilter: [\"aria-hidden\"],\n childList: true,\n subtree: true,\n });\n observedCaptureNormalizationRoots.add(captureRoot);\n };\n const originalAttachShadow = Element.prototype.attachShadow;\n if (typeof originalAttachShadow === \"function\") {\n // MutationObserver cannot see attachShadow() itself. Retain each root\n // created through the action-to-screenshot handoff (including a closed\n // root), observe its children immediately, and reconcile once the\n // synchronous custom-element setup has populated it.\n Element.prototype.attachShadow = function (...parameters) {\n const captureRoot = Reflect.apply(\n originalAttachShadow,\n this,\n parameters,\n );\n if (\n !attachedCaptureNormalizationRoots.includes(captureRoot) &&\n attachedCaptureNormalizationRoots.length <\n MAX_CAPTURE_NORMALIZATION_OPEN_SHADOW_ROOTS\n ) {\n attachedCaptureNormalizationRoots.push(captureRoot);\n observeCaptureNormalizationRoot(captureRoot);\n void Promise.resolve().then(() => {\n reconcileInactiveDialogs();\n reconcileStitchChromeAfterMutation();\n });\n } else if (\n !attachedCaptureNormalizationRoots.includes(captureRoot)\n ) {\n mistSourceCaptureNormalizationsTruncated = true;\n mistSourceStitchNormalizationsTruncated = true;\n }\n return captureRoot;\n };\n }\n const captureParentElementOrHost = (element) => {\n if (element.parentElement) return element.parentElement;\n const elementRoot = element.getRootNode();\n return elementRoot instanceof ShadowRoot ? elementRoot.host : null;\n };\n const isRendered = (element) => {\n if (!(element instanceof Element)) return false;\n let current = element;\n while (current instanceof Element) {\n if (current.matches('[hidden],template')) return false;\n const currentStyle = getComputedStyle(current);\n if (\n currentStyle.display === \"none\" ||\n currentStyle.visibility === \"hidden\" ||\n currentStyle.visibility === \"collapse\" ||\n currentStyle.contentVisibility === \"hidden\" ||\n Number(currentStyle.opacity) === 0\n ) return false;\n current = captureParentElementOrHost(current);\n }\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const pendingRenderedMedia = () =>\n Array.from(document.querySelectorAll(\"img,video\")).filter((element) => {\n if (!isRendered(element)) return false;\n if (element instanceof HTMLImageElement) {\n return Boolean(element.currentSrc || element.src) && !element.complete;\n }\n return (\n element instanceof HTMLVideoElement &&\n Boolean(element.currentSrc || element.poster) &&\n element.readyState < HTMLMediaElement.HAVE_CURRENT_DATA\n );\n }).length;\n const settleRenderedMedia = async () => {\n const startedAt = Date.now();\n let lastMutationAt = startedAt;\n const observedRoot = document.body || document.documentElement;\n const observer = new MutationObserver(() => {\n lastMutationAt = Date.now();\n });\n if (observedRoot) {\n observer.observe(observedRoot, {\n attributes: true,\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n try {\n if (document.fonts?.ready) {\n await Promise.race([document.fonts.ready, sleep(1000)]);\n }\n const deadline = startedAt + MEDIA_STABILITY_MAX_MS;\n while (Date.now() < deadline) {\n const waitMilliseconds = Math.min(\n MEDIA_STABILITY_POLL_MS,\n Math.max(0, deadline - Date.now()),\n );\n await Promise.all([\n sleep(waitMilliseconds),\n Promise.race([nextPaint(), sleep(waitMilliseconds)]),\n ]);\n const now = Date.now();\n if (\n now - startedAt >= MEDIA_STABILITY_MIN_MS &&\n now - lastMutationAt >= MEDIA_STABILITY_QUIET_MS &&\n pendingRenderedMedia() === 0\n ) {\n break;\n }\n }\n } finally {\n observer.disconnect();\n }\n };\n const markStitchHazardOverflow = () => {\n mistSourceStitchHazardsTruncated = true;\n };\n const addDynamicCandidate = (candidate) => {\n // The first walk already queued this root and its descendants. Rewalking\n // an already queued subtree on every attribute mutation bypasses the\n // candidate budget and can monopolize the captured page's renderer.\n if (queuedDynamicStitchCandidates.has(candidate)) return false;\n if (\n dynamicStitchCandidatesRecorded >=\n MAX_DYNAMIC_STITCH_CANDIDATES_PER_CAPTURE\n ) {\n markStitchHazardOverflow();\n return false;\n }\n queuedDynamicStitchCandidates.add(candidate);\n dynamicStitchCandidateQueue.push(candidate);\n dynamicStitchCandidatesRecorded += 1;\n return true;\n };\n const addDynamicCandidateTree = (node) => {\n if (!(node instanceof Element)) return;\n if (!addDynamicCandidate(node)) return;\n const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n let candidate = walker.nextNode();\n while (candidate) {\n if (!addDynamicCandidate(candidate)) return;\n candidate = walker.nextNode();\n }\n };\n const observer = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === \"attributes\") {\n addDynamicCandidateTree(record.target);\n }\n for (const node of record.addedNodes) addDynamicCandidateTree(node);\n }\n });\n const captureStitchObservation = (element, style, rect, position) => ({\n position,\n rect: {\n x: Number(rect.x.toFixed(3)),\n y: Number(rect.y.toFixed(3)),\n width: Number(rect.width.toFixed(3)),\n height: Number(rect.height.toFixed(3)),\n },\n pageLeft: rect.left + (window.scrollX || 0),\n pageTop: rect.top + (window.scrollY || 0),\n marginLeft: Number.parseFloat(style.marginLeft) || 0,\n marginTop: Number.parseFloat(style.marginTop) || 0,\n style: {\n display: style.display,\n visibility: style.visibility,\n opacity: style.opacity,\n top: style.top,\n right: style.right,\n bottom: style.bottom,\n left: style.left,\n width: style.width,\n height: style.height,\n zIndex: style.zIndex,\n transform: style.transform,\n },\n });\n const markStitchHazard = (element) => {\n const style = getComputedStyle(element);\n const position = style.position.toLowerCase();\n if (![\"fixed\", \"sticky\"].includes(position)) return;\n const rect = element.getBoundingClientRect();\n // Off-canvas drawers and hidden fixed widgets do not paint into a tile.\n // Moving them into document flow can create overflow that was not present\n // in the source page, so only remember controls that actually intersected\n // the viewport during the bounded scroll walk.\n if (!isViewportPainted(element, style, rect)) return;\n const observation = captureStitchObservation(\n element,\n style,\n rect,\n position,\n );\n const section = element.matches(STITCH_SECTION_SELECTOR)\n ? element\n : element.closest(STITCH_SECTION_SELECTOR);\n const existingHazard = mistSourceStitchHazardElements.get(element);\n if (!existingHazard) {\n const pathFromSection = [];\n let current = element;\n while (section && current !== section) {\n const parent = captureParentElementOrHost(current);\n if (!parent) {\n pathFromSection.length = 0;\n break;\n }\n const tag = current.tagName;\n const siblings = Array.from(parent.children || []).filter(\n (sibling) => sibling.tagName === tag,\n );\n const index = siblings.indexOf(current);\n if (index < 0) {\n pathFromSection.length = 0;\n break;\n }\n pathFromSection.unshift({ tag, index });\n current = parent;\n }\n mistSourceStitchHazardElements.set(element, {\n observation,\n section,\n pathFromSection:\n section && current === section ? pathFromSection : null,\n });\n } else if (\n observation.pageTop < existingHazard.observation.pageTop\n ) {\n existingHazard.observation = observation;\n }\n if (!section) return;\n const sectionStyle = getComputedStyle(section);\n const sectionRect = section.getBoundingClientRect();\n const sectionObservation = captureStitchObservation(\n section,\n sectionStyle,\n sectionRect,\n position,\n );\n const existingSectionObservation =\n mistSourceStitchHazardSectionObservations.get(section);\n if (\n !existingSectionObservation ||\n sectionObservation.pageTop < existingSectionObservation.pageTop\n ) {\n mistSourceStitchHazardSectionObservations.set(\n section,\n sectionObservation,\n );\n }\n mistSourceStitchHazardSections.add(section);\n };\n const markStaticStitchHazards = () => {\n if (!document.body) return;\n const walker = document.createTreeWalker(\n document.body,\n NodeFilter.SHOW_ELEMENT,\n );\n let scanned = 0;\n let element = walker.nextNode();\n while (element && scanned < MAX_STATIC_STITCH_CANDIDATES) {\n markStitchHazard(element);\n scanned += 1;\n element = walker.nextNode();\n }\n if (element) markStitchHazardOverflow();\n };\n const markStitchHazards = () => {\n const stopAt = Math.min(\n dynamicStitchCandidateQueue.length,\n dynamicStitchCandidateHead +\n MAX_DYNAMIC_STITCH_CANDIDATES_PER_SETTLE,\n );\n while (dynamicStitchCandidateHead < stopAt) {\n const element =\n dynamicStitchCandidateQueue[dynamicStitchCandidateHead++];\n queuedDynamicStitchCandidates.delete(element);\n if (element.isConnected) markStitchHazard(element);\n }\n if (\n dynamicStitchCandidateHead > 2_000 &&\n dynamicStitchCandidateHead * 2 > dynamicStitchCandidateQueue.length\n ) {\n dynamicStitchCandidateQueue.splice(0, dynamicStitchCandidateHead);\n dynamicStitchCandidateHead = 0;\n }\n };\n const transformMetrics = (transform) => {\n const match = String(transform || \"\").match(\n /^matrix(?:3d)?\\(([^)]+)\\)$/,\n );\n if (!match) return { preservesSize: true, x: 0, y: 0 };\n const values = match[1]\n .split(\",\")\n .map((value) => Number(value.trim()));\n if (values.some((value) => !Number.isFinite(value))) {\n return { preservesSize: false, x: 0, y: 0 };\n }\n return values.length === 16\n ? {\n preservesSize:\n values[0] === 1 &&\n values[1] === 0 &&\n values[4] === 0 &&\n values[5] === 1,\n x: values[12] || 0,\n y: values[13] || 0,\n }\n : {\n preservesSize:\n values[0] === 1 &&\n values[1] === 0 &&\n values[2] === 0 &&\n values[3] === 1,\n x: values[4] || 0,\n y: values[5] || 0,\n };\n };\n const resolveStitchHazardElement = (originalElement, hazard) => {\n if (originalElement.isConnected) return originalElement;\n if (\n !hazard.section?.isConnected ||\n !Array.isArray(hazard.pathFromSection)\n ) {\n return null;\n }\n let current = hazard.section;\n for (const step of hazard.pathFromSection) {\n const siblings = Array.from(current.children || []).filter(\n (sibling) => sibling.tagName === step.tag,\n );\n current = siblings[step.index];\n if (!current) return null;\n }\n return current;\n };\n const buildPostActionAnchor = ${buildSourcePageAnchor.toString()};\n const postActionAnchorFor = (element) => {\n if (element.id) {\n return buildPostActionAnchor(\n [\n /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test(element.id)\n ? \"#\" + element.id\n : \"[id=\" + JSON.stringify(element.id) + \"]\",\n ],\n element.tagName.toLowerCase(),\n );\n }\n const parts = [];\n let current = element;\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(\n /^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/.test(current.id)\n ? \"#\" + current.id\n : \"[id=\" + JSON.stringify(current.id) + \"]\",\n );\n break;\n }\n const tag = current.tagName.toLowerCase();\n const siblings = current.parentElement\n ? Array.from(current.parentElement.children).filter(\n (sibling) => sibling.tagName === current.tagName,\n )\n : [];\n const position = siblings.indexOf(current);\n parts.unshift(\n tag +\n (siblings.length > 1 && position >= 0\n ? \":nth-of-type(\" + (position + 1) + \")\"\n : \"\"),\n );\n current = captureParentElementOrHost(current);\n }\n return buildPostActionAnchor(parts, element.tagName.toLowerCase());\n };\n const postActionStateFor = (element) => {\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n return {\n anchor: postActionAnchorFor(element),\n rect: {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n },\n style: {\n display: style.display,\n visibility: style.visibility,\n position: style.position,\n opacity: style.opacity,\n transform: style.transform,\n },\n };\n };\n const preparePostEvidenceActions = (targets) => {\n mistSourcePostActionStitchCandidates.clear();\n if (!Array.isArray(targets)) return 0;\n for (const targetInput of targets.slice(0, 8)) {\n const selector = targetInput?.selector;\n if (typeof selector !== \"string\") continue;\n const enforceDismissal = targetInput?.enforceDismissal === true;\n const scopeSelector =\n typeof targetInput?.scopeSelector === \"string\"\n ? targetInput.scopeSelector\n : null;\n let target = null;\n try {\n target = document.querySelector(selector);\n } catch {\n continue;\n }\n if (!(target instanceof Element)) {\n if (enforceDismissal) {\n throw new Error(\n \"Mist could not find the inspected post-evidence dismissal control: \" +\n selector,\n );\n }\n continue;\n }\n if (enforceDismissal) {\n if (!scopeSelector) {\n throw new Error(\n \"Mist requires an inspected scope selector for explicit post-evidence dismissal.\",\n );\n }\n let scope = null;\n try {\n scope = document.querySelector(scopeSelector);\n } catch {\n scope = null;\n }\n const scopePosition =\n scope instanceof Element\n ? getComputedStyle(scope).position.toLowerCase()\n : \"\";\n if (\n !(scope instanceof Element) ||\n !scope.contains(target) ||\n (![\"fixed\", \"sticky\"].includes(scopePosition) &&\n !mistSourceStitchHazardElements.has(scope))\n ) {\n throw new Error(\n \"Mist could not verify a fixed/sticky dismissal scope for selector: \" +\n scopeSelector,\n );\n }\n mistSourcePostActionStitchCandidates.set(scope, {\n selector,\n scopeSelector,\n enforceDismissal: true,\n preparedState: postActionStateFor(scope),\n });\n continue;\n }\n let current = target;\n while (current instanceof Element) {\n const position = getComputedStyle(current).position.toLowerCase();\n if (\n [\"fixed\", \"sticky\"].includes(position) ||\n mistSourceStitchHazardElements.has(current)\n ) {\n if (!mistSourcePostActionStitchCandidates.has(current)) {\n mistSourcePostActionStitchCandidates.set(current, {\n selector,\n scopeSelector: null,\n enforceDismissal: false,\n preparedState: postActionStateFor(current),\n });\n }\n break;\n }\n // Section observations help pin repeated chrome once, but are too\n // broad to authorize a post-action dismissal. A replaced drawer may\n // make its section the only retained observation; hiding that section\n // would also erase legitimate closed-state triggers and navigation.\n current = captureParentElementOrHost(current);\n }\n }\n return mistSourcePostActionStitchCandidates.size;\n };\n const reconcilePostEvidenceActions = () => {\n for (const [\n candidate,\n { selector, scopeSelector, enforceDismissal, preparedState },\n ] of mistSourcePostActionStitchCandidates) {\n if (!candidate.isConnected) {\n mistSourcePostActionDismissals.add(candidate);\n mistSourcePostActionDismissalReceipts.push({\n element: candidate,\n anchor: preparedState.anchor,\n selector,\n scopeSelector,\n reason: \"unrendered\",\n rect: preparedState.rect,\n style: preparedState.style,\n });\n continue;\n }\n const style = getComputedStyle(candidate);\n const rect = candidate.getBoundingClientRect();\n const rendered = isRendered(candidate);\n const viewportPainted =\n rendered && isViewportPainted(candidate, style, rect);\n if (!viewportPainted || enforceDismissal) {\n // Firecrawl's full-page stitch can paint a closed off-canvas subtree\n // after it scrolls. An explicit dismiss action additionally lets the\n // agent adjudicate a flaky source close handler without arbitrary\n // JavaScript. Preserve the post-click state in a signed receipt, then\n // pin only this verified fixed/sticky subtree closed for the pixels.\n mistSourcePostActionDismissals.add(candidate);\n mistSourcePostActionDismissalReceipts.push({\n element: candidate,\n anchor: postActionAnchorFor(candidate),\n selector,\n scopeSelector,\n reason: viewportPainted\n ? \"explicit-dismissal\"\n : rendered\n ? \"outside-viewport\"\n : \"unrendered\",\n rect: {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height,\n },\n style: {\n display: style.display,\n visibility: style.visibility,\n position: style.position,\n opacity: style.opacity,\n transform: style.transform,\n },\n });\n candidate.style.setProperty(\"display\", \"none\", \"important\");\n candidate.setAttribute(\n \"data-mist-capture-normalized\",\n \"post-action-dismissal\",\n );\n }\n }\n mistSourcePostActionStitchCandidates.clear();\n };\n const settlePostEvidenceActions = async () => {\n const ordinaryClickCandidates = Array.from(\n mistSourcePostActionStitchCandidates.entries(),\n ).filter(([, { enforceDismissal }]) => !enforceDismissal);\n if (ordinaryClickCandidates.length === 0) return;\n // Native close handlers commonly schedule state updates on a timer or\n // animate fixed drawers across several frames. Reconciliation must observe\n // their settled state rather than the still-painted click frame. Poll for\n // a quiet closed interval, but fail closed at a bounded deadline instead\n // of signing pixels while an ordinary-click candidate is still painted.\n let waitedMilliseconds = 0;\n let quietMilliseconds = 0;\n while (waitedMilliseconds < POST_ACTION_SETTLE_MAX_MS) {\n await nextPaint();\n const hasPaintedCandidate = ordinaryClickCandidates.some(\n ([candidate]) => {\n if (!candidate.isConnected) return false;\n const style = getComputedStyle(candidate);\n const rect = candidate.getBoundingClientRect();\n return (\n isRendered(candidate) &&\n isViewportPainted(candidate, style, rect)\n );\n },\n );\n if (hasPaintedCandidate) {\n quietMilliseconds = 0;\n } else if (quietMilliseconds >= POST_ACTION_SETTLE_QUIET_MS) {\n return;\n }\n await sleep(POST_ACTION_SETTLE_POLL_MS);\n waitedMilliseconds += POST_ACTION_SETTLE_POLL_MS;\n if (!hasPaintedCandidate) {\n quietMilliseconds += POST_ACTION_SETTLE_POLL_MS;\n }\n }\n throw new Error(\n \"Mist post-evidence click did not reach a stable closed state before the bounded deadline: \" +\n ordinaryClickCandidates\n .map(([, { selector }]) => selector)\n .join(\", \") +\n \". Inspect the retained source DOM and use dismiss with an exact scope_selector only if this source control is known to be flaky.\",\n );\n };\n const isPostActionDismissed = (element) => {\n let current = element;\n while (current instanceof Element) {\n if (mistSourcePostActionDismissals.has(current)) return true;\n current = captureParentElementOrHost(current);\n }\n return false;\n };\n const stitchNormalizationFor = (element, observedObservation) => {\n // A control observed pinning during the scroll walk can return to an\n // unpainted/static top state before normalization. Keep that observation\n // authoritative: it is exactly the control that will reappear when the\n // provider scrolls again to stitch pixels.\n if (!element.isConnected) return null;\n const style = getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n if (\n !observedObservation &&\n !isViewportPainted(element, style, rect)\n ) {\n return null;\n }\n const currentPosition = style.position.toLowerCase();\n const currentObservation = captureStitchObservation(\n element,\n style,\n rect,\n currentPosition,\n );\n const authoritativeObservation =\n observedObservation || currentObservation;\n const originalPosition = authoritativeObservation.position;\n if (![\"fixed\", \"sticky\"].includes(originalPosition)) return null;\n return {\n element,\n originalPosition,\n currentPosition,\n normalizedPosition: currentPosition === \"fixed\" ? \"absolute\" : \"relative\",\n rect: authoritativeObservation.rect,\n pageLeft: authoritativeObservation.pageLeft,\n pageTop: authoritativeObservation.pageTop,\n marginLeft: authoritativeObservation.marginLeft,\n marginTop: authoritativeObservation.marginTop,\n transformMetrics: transformMetrics(\n authoritativeObservation.style.transform,\n ),\n style: authoritativeObservation.style,\n appliedInlineStyle: null,\n };\n };\n const stitchInlineStyleSnapshot = (element) =>\n [\n \"position\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"width\",\n \"height\",\n \"box-sizing\",\n ].map((property) => [\n property,\n element.style.getPropertyValue(property),\n element.style.getPropertyPriority(property),\n ]);\n const stitchNormalizationIsApplied = (normalization) =>\n normalization.appliedInlineStyle &&\n JSON.stringify(stitchInlineStyleSnapshot(normalization.element)) ===\n normalization.appliedInlineStyle;\n const applyStitchNormalization = (normalization) => {\n const { element } = normalization;\n element.setAttribute(\n \"data-mist-capture-normalized\",\n \"stitch-chrome\",\n );\n element.style.setProperty(\n \"position\",\n normalization.normalizedPosition,\n \"important\",\n );\n if (normalization.normalizedPosition === \"relative\") {\n for (const inset of [\"top\", \"right\", \"bottom\", \"left\"]) {\n element.style.setProperty(inset, \"auto\", \"important\");\n }\n // Sticky elements still occupy a flow slot. Preserve that slot while\n // offsetting a currently pinned element back to its captured rectangle;\n // nested scrollers can otherwise jump when sticky becomes relative.\n const naturalRect = element.getBoundingClientRect();\n element.style.setProperty(\n \"left\",\n normalization.pageLeft -\n (naturalRect.left + (window.scrollX || 0)) +\n \"px\",\n \"important\",\n );\n element.style.setProperty(\n \"top\",\n normalization.pageTop -\n (naturalRect.top + (window.scrollY || 0)) +\n \"px\",\n \"important\",\n );\n normalization.appliedInlineStyle = JSON.stringify(\n stitchInlineStyleSnapshot(element),\n );\n return;\n }\n const offsetParent =\n element.offsetParent instanceof Element ? element.offsetParent : null;\n const offsetParentRect = offsetParent?.getBoundingClientRect();\n const parentPageLeft = offsetParentRect\n ? offsetParentRect.left +\n (window.scrollX || 0) +\n (offsetParent.clientLeft || 0) -\n (offsetParent.scrollLeft || 0)\n : 0;\n const parentPageTop = offsetParentRect\n ? offsetParentRect.top +\n (window.scrollY || 0) +\n (offsetParent.clientTop || 0) -\n (offsetParent.scrollTop || 0)\n : 0;\n element.style.setProperty(\n \"top\",\n normalization.pageTop -\n parentPageTop -\n normalization.marginTop -\n normalization.transformMetrics.y +\n \"px\",\n \"important\",\n );\n element.style.setProperty(\n \"left\",\n normalization.pageLeft -\n parentPageLeft -\n normalization.marginLeft -\n normalization.transformMetrics.x +\n \"px\",\n \"important\",\n );\n element.style.setProperty(\"right\", \"auto\", \"important\");\n element.style.setProperty(\"bottom\", \"auto\", \"important\");\n if (normalization.transformMetrics.preservesSize) {\n element.style.setProperty(\n \"width\",\n Math.max(0, normalization.rect.width) + \"px\",\n \"important\",\n );\n element.style.setProperty(\n \"height\",\n Math.max(0, normalization.rect.height) + \"px\",\n \"important\",\n );\n element.style.setProperty(\"box-sizing\", \"border-box\", \"important\");\n }\n normalization.appliedInlineStyle = JSON.stringify(\n stitchInlineStyleSnapshot(element),\n );\n };\n const normalizeStitchCandidate = (element, observedObservation) => {\n if (isPostActionDismissed(element)) return;\n const existing = mistSourceStitchNormalizationByElement.get(element);\n if (existing) {\n if (!stitchNormalizationIsApplied(existing)) {\n applyStitchNormalization(existing);\n }\n return;\n }\n if (\n mistSourceStitchNormalizations.length >= MAX_STITCH_NORMALIZATIONS\n ) {\n mistSourceStitchNormalizationsTruncated = true;\n return;\n }\n const normalization = stitchNormalizationFor(element, observedObservation);\n if (!normalization) return;\n mistSourceStitchNormalizations.push(normalization);\n mistSourceStitchNormalizationByElement.set(element, normalization);\n applyStitchNormalization(normalization);\n };\n const normalizeStitchChrome = () => {\n const captureRoots = collectStitchNormalizationRoots();\n let scanned = 0;\n for (const captureRoot of captureRoots) {\n for (const element of captureRoot.querySelectorAll(\"*\")) {\n if (scanned >= MAX_STITCH_NORMALIZATION_CANDIDATES) {\n mistSourceStitchNormalizationsTruncated = true;\n break;\n }\n scanned += 1;\n normalizeStitchCandidate(\n element,\n mistSourceStitchHazardElements.get(element)?.observation ||\n mistSourceStitchHazardSectionObservations.get(element),\n );\n }\n if (mistSourceStitchNormalizationsTruncated) break;\n }\n for (const [originalElement, hazard] of mistSourceStitchHazardElements) {\n const currentElement = resolveStitchHazardElement(\n originalElement,\n hazard,\n );\n if (currentElement) {\n normalizeStitchCandidate(currentElement, hazard.observation);\n }\n }\n };\n const pendingStitchMutationRoots = new Set();\n let reconcilingStitchChrome = false;\n let stitchReconcileScheduled = false;\n const stitchNormalizationObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.target instanceof Element) {\n pendingStitchMutationRoots.add(record.target);\n }\n for (const node of record.addedNodes) {\n if (node instanceof Element) pendingStitchMutationRoots.add(node);\n }\n }\n if (stitchReconcileScheduled) return;\n stitchReconcileScheduled = true;\n void Promise.resolve().then(() => {\n stitchReconcileScheduled = false;\n reconcileStitchChromeAfterMutation();\n });\n });\n reconcileStitchChromeAfterMutation = () => {\n if (reconcilingStitchChrome) return;\n reconcilingStitchChrome = true;\n try {\n const captureRoots = collectStitchNormalizationRoots();\n for (const captureRoot of captureRoots) {\n if (observedStitchNormalizationRoots.has(captureRoot)) continue;\n stitchNormalizationObserver.observe(captureRoot, {\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n childList: true,\n subtree: true,\n });\n observedStitchNormalizationRoots.add(captureRoot);\n }\n for (const normalization of mistSourceStitchNormalizations) {\n if (\n normalization.element.isConnected &&\n !stitchNormalizationIsApplied(normalization)\n ) {\n applyStitchNormalization(normalization);\n }\n }\n let scanned = 0;\n for (const rootCandidate of pendingStitchMutationRoots) {\n const candidates = [\n rootCandidate,\n ...rootCandidate.querySelectorAll(\"*\"),\n ];\n for (const element of candidates) {\n if (scanned >= MAX_STITCH_NORMALIZATION_CANDIDATES) {\n mistSourceStitchNormalizationsTruncated = true;\n break;\n }\n scanned += 1;\n normalizeStitchCandidate(\n element,\n mistSourceStitchHazardElements.get(element)?.observation ||\n mistSourceStitchHazardSectionObservations.get(element),\n );\n }\n if (mistSourceStitchNormalizationsTruncated) break;\n }\n pendingStitchMutationRoots.clear();\n for (const [originalElement, hazard] of mistSourceStitchHazardElements) {\n const currentElement = resolveStitchHazardElement(\n originalElement,\n hazard,\n );\n if (currentElement) {\n normalizeStitchCandidate(currentElement, hazard.observation);\n }\n }\n } finally {\n reconcilingStitchChrome = false;\n }\n };\n const previousScrollBehavior = root?.style.scrollBehavior || \"\";\n try {\n reconcileInactiveDialogs();\n if (document.body) {\n observer.observe(document.body, {\n attributes: true,\n attributeFilter: [\"class\", \"style\", \"hidden\"],\n childList: true,\n subtree: true,\n });\n }\n markStaticStitchHazards();\n if (root) root.style.scrollBehavior = \"auto\";\n window.scrollTo(\n 0,\n Math.max(\n document.documentElement?.scrollHeight || 0,\n document.body?.scrollHeight || 0,\n ),\n );\n await sleep(LAZY_LOAD_SETTLE_MS);\n reconcileInactiveDialogs();\n markStitchHazards();\n for (let index = 0; index < MAX_FULL_PAGE_SECTIONS; index += 1) {\n const viewportHeight = Math.max(window.innerHeight, 1);\n const totalHeight = Math.max(\n document.documentElement?.scrollHeight || 0,\n document.body?.scrollHeight || 0,\n viewportHeight,\n );\n const offset = Math.min(\n index * viewportHeight,\n Math.max(0, totalHeight - viewportHeight),\n );\n window.scrollTo(0, offset);\n await nextPaint();\n await sleep(Math.min(LAZY_LOAD_SETTLE_MS, 150));\n reconcileInactiveDialogs();\n markStitchHazards();\n const capturableBottom = Math.min(\n Math.max(\n document.documentElement?.scrollHeight || 0,\n document.body?.scrollHeight || 0,\n viewportHeight,\n ),\n viewportHeight * MAX_FULL_PAGE_SECTIONS,\n );\n if (offset + viewportHeight >= capturableBottom) break;\n }\n window.scrollTo(0, 0);\n await sleep(Math.min(LAZY_LOAD_SETTLE_MS, 300));\n await settleRenderedMedia();\n reconcileInactiveDialogs();\n markStitchHazards();\n if (dynamicStitchCandidateHead < dynamicStitchCandidateQueue.length) {\n markStitchHazardOverflow();\n }\n const finalizeSourceEvidence = async () => {\n window.scrollTo(0, 0);\n await nextPaint();\n await settlePostEvidenceActions();\n reconcilePostEvidenceActions();\n // Firecrawl captures the full page after this script resolves. Convert\n // rendered fixed/sticky controls into one equivalent document position\n // so each is painted once, then sign the pre-normalization state in\n // evidence. A deferred post-action capture reaches this point only after\n // its constrained native interactions have settled.\n normalizeStitchChrome();\n reconcileStitchChromeAfterMutation();\n await nextPaint();\n return ${RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT};\n };\n Object.defineProperty(\n window,\n \"__MIST_PREPARE_POST_EVIDENCE_ACTIONS_V1__\",\n {\n value: preparePostEvidenceActions,\n configurable: true,\n },\n );\n Object.defineProperty(\n window,\n \"__MIST_FINALIZE_SOURCE_EVIDENCE_V1__\",\n {\n value: finalizeSourceEvidence,\n configurable: true,\n },\n );\n if (mistSourceDefersStitchNormalization) {\n return ${RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT};\n }\n return await finalizeSourceEvidence();\n } finally {\n observer.disconnect();\n if (root) root.style.scrollBehavior = previousScrollBehavior;\n // Firecrawl takes the requested full-page screenshot after actions finish.\n // Its mobile browser can enter this action one viewport down after the\n // viewport-priming screenshot. Restoring that inherited offset makes the\n // final image exactly one viewport shorter than the signed document.\n window.scrollTo(0, 0);\n }\n})()`;\n","import { createHash } from \"node:crypto\";\nimport type { CrawlFormat, CrawlScreenshotOptions } from \"./firecrawl-request\";\nimport {\n isFullPageScreenshotGeometryComplete,\n parseDurableSourcePageEvidence,\n renderedPageTextSha256,\n type DurableSourcePageEvidence,\n type RenderedSourcePageEvidence,\n} from \"./source-page-evidence\";\n\n/**\n * The crawl half of the page-clone evidence contract, shared by Mist's `crawl`\n * tool and the Fluid CLI's `fluid theme crawl`. Both write the same bundle\n * layout and sidecar, so a bundle captured by either one can be compared by\n * either one.\n */\n\nexport const FIRECRAWL_API_URL = \"https://api.firecrawl.dev/v2/scrape\";\n/** Wall-clock bound for one crawl, so a runaway page cannot stall a turn. */\nexport const CRAWL_TIMEOUT_MS = 60_000;\n/** Inline output cap for an ordinary crawl. */\nexport const CRAWL_MAX_OUTPUT = 200 * 1024;\n/** Inline output cap when the complete documents are retained on disk. */\nexport const CRAWL_DURABLE_CHAT_OUTPUT = 16 * 1024;\n/** Largest single Markdown/HTML/CSS document kept in an evidence bundle. */\nexport const CRAWL_DOCUMENT_MAX_BYTES = 10 * 1024 * 1024;\n/** Project-relative directory every durable source bundle lives in. The\n * durable sidecar schema requires its screenshot path to start here. */\nexport const CRAWL_BASELINE_DIR = \".mist-desktop/source-baselines\";\n\nexport type CrawlDocumentFormat = \"html\" | \"markdown\" | \"stylesheet\" | \"rawHtml\";\n\n/**\n * Keep ordinary crawls useful as a general-purpose reader while avoiding a\n * second, very large copy of source documents in the model context when the\n * complete documents are already retained with durable page evidence.\n */\nexport function formatCrawlDocumentsForChat(\n requestedFormats: readonly CrawlFormat[],\n data: Partial<Record<CrawlFormat, string>>,\n durablePageEvidence: boolean,\n): string[] {\n let remaining = durablePageEvidence\n ? CRAWL_DURABLE_CHAT_OUTPUT\n : CRAWL_MAX_OUTPUT;\n const parts: string[] = [];\n const prioritizedFormats =\n durablePageEvidence && requestedFormats.includes(\"markdown\")\n ? [\n \"markdown\" as const,\n ...requestedFormats.filter((format) => format !== \"markdown\"),\n ]\n : requestedFormats;\n\n for (const format of prioritizedFormats) {\n const content = data[format];\n if (typeof content !== \"string\" || content.length === 0) continue;\n\n if (content.length <= remaining) {\n parts.push(`<!-- ${format} -->\\n${content}`);\n remaining -= content.length;\n if (remaining <= 0) break;\n continue;\n }\n\n const suffix = durablePageEvidence\n ? `\\n…[chat excerpt truncated; complete ${format} retained with durable source evidence; original ${content.length} characters]`\n : `\\n…[truncated; original ${content.length} characters]`;\n const excerptLength = Math.max(0, remaining - suffix.length);\n parts.push(\n `<!-- ${format} -->\\n${content.slice(0, excerptLength)}${suffix}`,\n );\n break;\n }\n\n return parts;\n}\n\nexport function hasCompleteDurableCrawlDocuments(\n requestedFormats: readonly CrawlFormat[],\n data: Partial<Record<CrawlFormat, string>>,\n documents: Partial<Record<\"html\" | \"markdown\", unknown>> | undefined,\n): boolean {\n const requestedDocuments = requestedFormats.filter(\n (format): format is \"html\" | \"markdown\" =>\n format === \"html\" || format === \"markdown\",\n );\n return requestedDocuments.every((format) => {\n const content = data[format];\n return (\n typeof content === \"string\" &&\n content.length > 0 &&\n documents?.[format] !== undefined\n );\n });\n}\n\nexport function summarizeCrawlMetadata({\n requestedUrl,\n metadata,\n renderedPageEvidence,\n renderedPageStatusCode,\n}: {\n requestedUrl: string;\n metadata: Record<string, unknown>;\n renderedPageEvidence?: RenderedSourcePageEvidence;\n renderedPageStatusCode?: number;\n}) {\n const providerFinalUrl =\n typeof metadata[\"sourceURL\"] === \"string\"\n ? metadata[\"sourceURL\"]\n : typeof metadata[\"url\"] === \"string\"\n ? metadata[\"url\"]\n : requestedUrl;\n const renderedFinalUrl = renderedPageEvidence?.url;\n\n return {\n requested_url: requestedUrl,\n final_url: renderedFinalUrl ?? providerFinalUrl,\n ...(renderedFinalUrl && renderedFinalUrl !== providerFinalUrl\n ? { provider_reported_final_url: providerFinalUrl }\n : {}),\n status_code: renderedPageStatusCode ?? metadata[\"statusCode\"] ?? null,\n title: metadata[\"title\"] ?? null,\n description: metadata[\"description\"] ?? null,\n };\n}\n\nexport function crawlScreenshotExtension(mediaType: string): string {\n switch (mediaType.toLowerCase()) {\n case \"image/jpeg\":\n return \"jpg\";\n case \"image/webp\":\n return \"webp\";\n case \"image/gif\":\n return \"gif\";\n default:\n return \"png\";\n }\n}\n\n/**\n * Bundle file name for one capture. The query string is folded into a short\n * fingerprint rather than spelled out, because it can carry session or\n * preview tokens that must not leak into filenames, logs, or chat history.\n * The UUID keeps parallel captures of the same route and viewport inside the\n * same millisecond distinct.\n */\nexport function crawlScreenshotFileName({\n sourceUrl,\n screenshotOptions,\n mediaType,\n capturedAt,\n uuid,\n}: {\n sourceUrl: string;\n screenshotOptions?: CrawlScreenshotOptions;\n mediaType: string;\n capturedAt: string;\n uuid: string;\n}): string {\n const source = new URL(sourceUrl);\n const routeName = `${source.hostname}${source.pathname}`\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 80);\n const sourceFingerprint = createHash(\"sha256\")\n .update(source.href)\n .digest(\"hex\")\n .slice(0, 10);\n const viewport = screenshotOptions?.viewport;\n const viewportName = viewport\n ? `${viewport.width}x${viewport.height}`\n : \"auto\";\n const pageName = screenshotOptions?.fullPage\n ? `${viewportName}-full`\n : viewportName;\n const timestamp = capturedAt\n .replace(/[:.]/g, \"-\")\n .replace(\"T\", \"_\")\n .replace(\"Z\", \"\");\n return `source-${routeName || \"page\"}-${sourceFingerprint}-${pageName}-${timestamp}-${uuid}.${crawlScreenshotExtension(mediaType)}`;\n}\n\n/** Sibling path for one bundle document: same basename, format extension. */\nexport function crawlDocumentRelativePath(\n screenshotRelativePath: string,\n format: CrawlDocumentFormat | \"page-evidence\",\n): string {\n const extension =\n format === \"markdown\"\n ? \"md\"\n : format === \"stylesheet\"\n ? \"css\"\n : format === \"rawHtml\"\n ? \"raw.html\"\n : format === \"page-evidence\"\n ? \"page-evidence.json\"\n : \"html\";\n return `${screenshotRelativePath.replace(/\\.[^.]+$/, \"\")}.${extension}`;\n}\n\nexport interface DurableCrawlEvidenceInput {\n capturedAt: string;\n sourceUrl: string;\n screenshotOptions?: CrawlScreenshotOptions;\n screenshot: {\n relativePath: string;\n sha256: string;\n byteLength: number;\n width: number;\n height: number;\n };\n documents: ReadonlyArray<{\n format: CrawlDocumentFormat;\n relativePath: string;\n sha256: string;\n byteLength: number;\n }>;\n rendered: RenderedSourcePageEvidence;\n statusCode: number;\n}\n\n/**\n * Bind rendered DOM evidence to the screenshot pixels and documents it was\n * captured with, producing the `.page-evidence.json` sidecar. Refuses pixels\n * whose geometry cannot belong to the signed viewport/document.\n */\nexport function buildDurableCrawlEvidence(\n input: DurableCrawlEvidenceInput,\n): DurableSourcePageEvidence {\n const viewport = input.screenshotOptions?.viewport;\n if (!viewport) {\n throw new Error(\n \"Rendered page evidence requires an exact requested viewport and a decodable source screenshot.\",\n );\n }\n const dimensions = input.screenshot;\n const fullPage = input.screenshotOptions?.fullPage === true;\n const expectedPixelHeight = fullPage\n ? input.rendered.document.height\n : viewport.height;\n if (\n dimensions.width !== viewport.width ||\n (!fullPage && dimensions.height !== expectedPixelHeight) ||\n (fullPage &&\n !isFullPageScreenshotGeometryComplete({\n screenshotHeight: dimensions.height,\n renderedDocumentHeight: expectedPixelHeight,\n viewportHeight: viewport.height,\n }))\n ) {\n const expectedGeometry = fullPage\n ? `the requested ${viewport.width}px viewport width and signed ${expectedPixelHeight}px full-page document height`\n : `the signed ${viewport.width}x${expectedPixelHeight} viewport`;\n throw new Error(\n `Rendered page evidence cannot bind ${dimensions.width}x${dimensions.height} source pixels to ${expectedGeometry}. The provider or page changed capture geometry; recapture before visual QA.`,\n );\n }\n const captureGeometry: NonNullable<\n DurableSourcePageEvidence[\"captureGeometry\"]\n > = {\n mode: fullPage ? \"full-page\" : \"viewport\",\n status:\n !fullPage || dimensions.height === input.rendered.document.height\n ? \"exact\"\n : \"dynamic-height\",\n renderedDocument: input.rendered.document,\n screenshot: { width: dimensions.width, height: dimensions.height },\n heightDelta: dimensions.height - expectedPixelHeight,\n };\n return parseDurableSourcePageEvidence({\n version: 1,\n capturedAt: input.capturedAt,\n requestedUrl: input.sourceUrl,\n finalUrl: input.rendered.url,\n statusCode: input.statusCode,\n requestedViewport: viewport,\n screenshot: {\n path: dimensions.relativePath,\n sha256: dimensions.sha256,\n byteLength: dimensions.byteLength,\n width: dimensions.width,\n height: dimensions.height,\n },\n documents: Object.fromEntries(\n input.documents.map((document) => [\n document.format,\n {\n path: document.relativePath,\n sha256: document.sha256,\n byteLength: document.byteLength,\n },\n ]),\n ),\n captureGeometry,\n rendered: input.rendered,\n visibleTextSha256: renderedPageTextSha256(input.rendered.visibleText),\n });\n}\n","import { FIRECRAWL_API_URL } from \"@fluid-app/mist-core/crawl-evidence\";\nimport type { FirecrawlScrapeRequest } from \"@fluid-app/mist-core/firecrawl-request\";\n\n/**\n * Where a crawl runs. Fluid's hosted endpoint is the default: it holds the\n * Firecrawl key server-side, so a published CLI never has to carry one. A\n * caller's own `FIRECRAWL_API_KEY` is the fallback for when that endpoint is\n * not deployed (404) or not configured (503), and `--direct` forces it.\n */\nexport const FLUID_CRAWL_ENDPOINT = \"/api/v202604/company/crawls\";\nexport const FIRECRAWL_KEY_HELP_URL = \"https://www.firecrawl.dev/app/api-keys\";\n\n/** Firecrawl's v2 scrape `data` object, which Fluid's endpoint returns unchanged. */\nexport interface FirecrawlScrapeData {\n markdown?: string;\n html?: string;\n /** Unprocessed document. The provider's `html` has `<style>` bodies and\n * `<link rel=stylesheet>` stripped; this one does not. */\n rawHtml?: string;\n screenshot?: string;\n actions?: {\n javascriptReturns?: Array<{ type?: string; value?: unknown }>;\n screenshots?: string[];\n };\n metadata?: Record<string, unknown>;\n}\n\nexport type CrawlVia = \"fluid\" | \"firecrawl\";\n\nexport interface ScrapeResult {\n data: FirecrawlScrapeData;\n via: CrawlVia;\n /** Why the Fluid endpoint was not used, when it was skipped. */\n fallbackReason?: string;\n}\n\nexport class CrawlTransportError extends Error {\n constructor(\n readonly code: string,\n message: string,\n readonly details: Record<string, unknown> = {},\n ) {\n super(message);\n this.name = \"CrawlTransportError\";\n }\n}\n\n/** A non-2xx answer from the Fluid crawl endpoint. */\nexport interface FluidCrawlHttpError {\n status: number;\n body: unknown;\n retryAfter: string | null;\n}\n\nexport interface CrawlTransportDeps {\n /**\n * POST the scrape request to Fluid's crawl endpoint. Resolves with the\n * parsed 2xx body or rejects with a {@link FluidCrawlHttpError}. Null when\n * no Fluid token is available, so the endpoint cannot be called at all.\n */\n postFluidCrawl:\n | ((request: FirecrawlScrapeRequest, signal: AbortSignal) => Promise<unknown>)\n | null;\n /** The caller's own Firecrawl key, if any. Never printed. */\n firecrawlApiKey: string | undefined;\n fetch: typeof fetch;\n}\n\nfunction isFluidCrawlHttpError(error: unknown): error is FluidCrawlHttpError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"status\" in error &&\n typeof (error as { status: unknown }).status === \"number\" &&\n \"body\" in error\n );\n}\n\nfunction bodyField(body: unknown, field: string): unknown {\n return typeof body === \"object\" && body !== null\n ? (body as Record<string, unknown>)[field]\n : undefined;\n}\n\nfunction bodyMessage(body: unknown): string | undefined {\n const error = bodyField(body, \"error\");\n if (typeof error === \"string\" && error.length > 0) return error;\n const message = bodyField(body, \"error_message\") ?? bodyField(body, \"message\");\n return typeof message === \"string\" && message.length > 0\n ? message\n : undefined;\n}\n\nconst KEY_FALLBACK_HINT = `Set FIRECRAWL_API_KEY (create one at ${FIRECRAWL_KEY_HELP_URL}) to crawl with your own Firecrawl key until it is.`;\n\n/** Map a Fluid endpoint failure to a stable error code and actionable text. */\nexport function fluidCrawlError(error: FluidCrawlHttpError): CrawlTransportError {\n const detail = bodyMessage(error.body);\n const suffix = detail ? `: ${detail}` : \"\";\n switch (error.status) {\n case 401:\n case 403:\n return new CrawlTransportError(\n \"fluid_auth_failed\",\n `Fluid refused the crawl (HTTP ${error.status})${suffix}. Run \\`fluid login\\` again, or pass --direct with FIRECRAWL_API_KEY set.`,\n { status: error.status },\n );\n case 404:\n return new CrawlTransportError(\n \"crawl_endpoint_unavailable\",\n `Fluid's crawl endpoint is not available on this API host yet (HTTP 404). ${KEY_FALLBACK_HINT}`,\n { status: 404 },\n );\n case 422:\n return new CrawlTransportError(\n \"invalid_crawl_request\",\n `Fluid rejected the crawl request (HTTP 422)${suffix}.`,\n {\n status: 422,\n ...(bodyField(error.body, \"errors\") === undefined\n ? {}\n : { errors: bodyField(error.body, \"errors\") }),\n },\n );\n case 429:\n return new CrawlTransportError(\n \"crawl_rate_limited\",\n `Fluid's crawl endpoint is rate limited (HTTP 429)${error.retryAfter ? `; retry after ${error.retryAfter}${/^\\d+$/.test(error.retryAfter) ? \"s\" : \"\"}` : \"; retry later\"}.`,\n { status: 429, retry_after: error.retryAfter },\n );\n case 502:\n return new CrawlTransportError(\n \"crawl_upstream_failed\",\n `The crawl provider failed behind Fluid's endpoint (HTTP 502)${suffix}.`,\n {\n status: 502,\n upstream_status: bodyField(error.body, \"upstream_status\") ?? null,\n },\n );\n case 503:\n return new CrawlTransportError(\n \"crawl_not_configured\",\n `Fluid's crawl endpoint is not configured on this API host (HTTP 503). ${KEY_FALLBACK_HINT}`,\n { status: 503 },\n );\n case 504:\n return new CrawlTransportError(\n \"crawl_timeout\",\n \"Fluid's crawl endpoint timed out (HTTP 504). Retry, or crawl a lighter page.\",\n { status: 504 },\n );\n default:\n return new CrawlTransportError(\n \"crawl_failed\",\n `Fluid's crawl endpoint failed (HTTP ${error.status})${suffix}.`,\n { status: error.status },\n );\n }\n}\n\nfunction asScrapeData(value: unknown, origin: string): FirecrawlScrapeData {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CrawlTransportError(\n \"crawl_invalid_response\",\n `${origin} returned no crawl data object.`,\n );\n }\n return value as FirecrawlScrapeData;\n}\n\nasync function scrapeWithFirecrawlKey(\n request: FirecrawlScrapeRequest,\n apiKey: string,\n signal: AbortSignal,\n fetchImpl: typeof fetch,\n): Promise<FirecrawlScrapeData> {\n const response = await fetchImpl(FIRECRAWL_API_URL, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(request),\n signal,\n });\n if (!response.ok) {\n let detail = \"\";\n try {\n detail = await response.text();\n } catch {\n // unreadable body — generic message\n }\n throw new CrawlTransportError(\n \"firecrawl_failed\",\n `Firecrawl failed (HTTP ${response.status})${detail ? `: ${detail.slice(0, 400)}` : \"\"}`,\n { status: response.status },\n );\n }\n const body = (await response.json()) as {\n success?: boolean;\n data?: unknown;\n error?: string;\n };\n if (!body.success || !body.data) {\n throw new CrawlTransportError(\n \"firecrawl_failed\",\n `Crawl returned an error: ${body.error ?? \"unknown — Firecrawl reported success=false with no error message\"}`,\n );\n }\n return asScrapeData(body.data, \"Firecrawl\");\n}\n\n/**\n * Run one scrape: Fluid's endpoint first, the caller's Firecrawl key only on\n * the documented fallbacks (or `--direct`).\n */\nexport async function requestScrape(\n request: FirecrawlScrapeRequest,\n options: { direct: boolean; signal: AbortSignal },\n deps: CrawlTransportDeps,\n): Promise<ScrapeResult> {\n const apiKey = deps.firecrawlApiKey?.trim() || undefined;\n const viaKey = async (fallbackReason?: string): Promise<ScrapeResult> => {\n if (!apiKey) {\n throw new CrawlTransportError(\n \"firecrawl_key_missing\",\n `--direct needs FIRECRAWL_API_KEY in the environment. Create a key at ${FIRECRAWL_KEY_HELP_URL}, or drop --direct to crawl through Fluid.`,\n );\n }\n const data = await scrapeWithFirecrawlKey(\n request,\n apiKey,\n options.signal,\n deps.fetch,\n );\n return {\n data,\n via: \"firecrawl\",\n ...(fallbackReason ? { fallbackReason } : {}),\n };\n };\n\n if (options.direct) return viaKey();\n\n if (!deps.postFluidCrawl) {\n if (apiKey) return viaKey(\"not logged in to Fluid\");\n throw new CrawlTransportError(\n \"not_logged_in\",\n `Not logged in. Run \\`fluid login\\` to crawl through Fluid, or set FIRECRAWL_API_KEY (create one at ${FIRECRAWL_KEY_HELP_URL}) to crawl with your own key.`,\n );\n }\n\n let body: unknown;\n try {\n body = await deps.postFluidCrawl(request, options.signal);\n } catch (error) {\n if (!isFluidCrawlHttpError(error)) throw error;\n if ((error.status === 404 || error.status === 503) && apiKey) {\n return viaKey(\n error.status === 404\n ? \"Fluid crawl endpoint not deployed (HTTP 404)\"\n : \"Fluid crawl endpoint not configured (HTTP 503)\",\n );\n }\n throw fluidCrawlError(error);\n }\n return { data: asScrapeData(bodyField(body, \"data\"), \"Fluid\"), via: \"fluid\" };\n}\n\n/**\n * POST to Fluid with the CLI's stored token. A raw fetch rather than the\n * shared API client because the client's ApiError drops response headers,\n * and a 429 must surface its Retry-After.\n */\nexport function createFluidCrawlPoster(\n apiBase: string,\n token: string | null,\n fetchImpl: typeof fetch = fetch,\n): CrawlTransportDeps[\"postFluidCrawl\"] {\n if (!token) return null;\n return async (request, signal) => {\n const response = await fetchImpl(\n `${apiBase.replace(/\\/+$/, \"\")}${FLUID_CRAWL_ENDPOINT}`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(request),\n signal,\n },\n );\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n // Non-JSON body: keep null; the status still decides.\n }\n if (!response.ok) {\n const failure: FluidCrawlHttpError = {\n status: response.status,\n body,\n retryAfter: response.headers.get(\"retry-after\"),\n };\n throw failure;\n }\n return body;\n };\n}\n","import {\n RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT,\n RENDERED_SOURCE_PAGE_EVIDENCE_SCRIPT,\n} from \"./source-page-evidence\";\n\nexport type CrawlFormat = \"markdown\" | \"html\" | \"screenshot\" | \"rawHtml\";\n\nexport interface CrawlScreenshotOptions {\n fullPage?: boolean;\n quality?: number;\n viewport?: {\n width: number;\n height: number;\n };\n}\n\nexport type CrawlAction =\n | {\n type: \"wait\";\n milliseconds: number;\n }\n | {\n type: \"click\";\n selector: string;\n }\n | {\n type: \"scroll\";\n direction: \"up\" | \"down\";\n selector?: string;\n };\n\nexport type CrawlPostEvidenceAction =\n | CrawlAction\n | {\n type: \"dismiss\";\n selector: string;\n scope_selector: string;\n };\n\ninterface BuildFirecrawlScrapeRequestArgs {\n url: string;\n formats?: CrawlFormat[];\n onlyMainContent?: boolean;\n screenshotOptions?: CrawlScreenshotOptions;\n actions?: CrawlAction[];\n postEvidenceActions?: CrawlPostEvidenceAction[];\n capturePageEvidence?: boolean;\n}\n\nexport type FirecrawlAction =\n | CrawlAction\n | FirecrawlScreenshotFormat\n | {\n type: \"executeJavascript\";\n script: string;\n };\n\nexport interface FirecrawlScreenshotFormat {\n type: \"screenshot\";\n fullPage?: boolean;\n quality?: number;\n viewport?: {\n width: number;\n height: number;\n };\n}\n\nexport interface FirecrawlScrapeRequest {\n url: string;\n formats: Array<\n Exclude<CrawlFormat, \"screenshot\"> | FirecrawlScreenshotFormat\n >;\n onlyMainContent: boolean;\n mobile?: boolean;\n actions?: FirecrawlAction[];\n}\n\nexport function shouldUseFirecrawlEvidenceScreenshotAction({\n capturePageEvidence,\n screenshotOptions,\n requestedFormats,\n}: {\n capturePageEvidence?: boolean;\n screenshotOptions?: CrawlScreenshotOptions;\n requestedFormats: CrawlFormat[];\n}): boolean {\n return (\n capturePageEvidence === true &&\n screenshotOptions?.fullPage === true &&\n requestedFormats.some((format) => format !== \"screenshot\") &&\n (screenshotOptions.viewport?.width ?? Number.POSITIVE_INFINITY) > 768\n );\n}\n\ninterface FirecrawlScreenshotResponse {\n screenshot?: string;\n actions?: {\n screenshots?: string[];\n };\n}\n\n/**\n * Exact source evidence uses the final screenshot action because Firecrawl\n * executes actions sequentially. That binds the pixels to the immediately\n * preceding fixed evidence script instead of relying on a later format\n * screenshot. Older responses remain readable through the top-level fallback.\n */\nexport function selectFirecrawlEvidenceScreenshotUrl(\n data: FirecrawlScreenshotResponse,\n preferActionScreenshot: boolean,\n): string | undefined {\n if (preferActionScreenshot) {\n const actionScreenshots = data.actions?.screenshots ?? [];\n for (let index = actionScreenshots.length - 1; index >= 0; index -= 1) {\n const candidate = actionScreenshots[index];\n if (typeof candidate === \"string\" && candidate.length > 0) {\n return candidate;\n }\n }\n }\n return typeof data.screenshot === \"string\" && data.screenshot.length > 0\n ? data.screenshot\n : undefined;\n}\n\nconst FIRECRAWL_VIEWPORT_SETTLE_SCRIPT = String.raw`(async () => {\n const VIEWPORT_SETTLE_MS = 300;\n const nextPaint = () =>\n new Promise((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(resolve)),\n );\n window.dispatchEvent(new Event(\"resize\"));\n await nextPaint();\n await new Promise((resolve) => setTimeout(resolve, VIEWPORT_SETTLE_MS));\n return JSON.stringify({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n})()`;\n\n/**\n * Firecrawl's desktop browser reserves a classic scrollbar gutter even when\n * the requested viewport is mobile-sized. Hide that browser chrome before a\n * full-page evidence capture so screenshot pixels and signed DOM geometry use\n * the same content box. The style intentionally remains until Firecrawl takes\n * the post-action screenshot.\n */\nconst FIRECRAWL_HIDE_SCROLLBARS_SCRIPT = String.raw`(() => {\n const style = document.createElement(\"style\");\n style.setAttribute(\"data-mist-hide-scrollbars\", \"true\");\n style.textContent = \"html{scrollbar-width:none!important}::-webkit-scrollbar{display:none!important;width:0!important;height:0!important}\";\n document.head.appendChild(style);\n return true;\n})()`;\n\nconst FIRECRAWL_DEFER_SOURCE_STITCH_NORMALIZATION_SCRIPT = String.raw`(() => {\n window.__MIST_DEFER_SOURCE_STITCH_NORMALIZATION_V1__ = true;\n return true;\n})()`;\n\nfunction firecrawlPreparePostEvidenceActionsScript(\n actions: readonly CrawlPostEvidenceAction[],\n): string | undefined {\n const targets = actions.flatMap((action) =>\n action.type === \"click\" || action.type === \"dismiss\"\n ? [\n {\n selector: action.selector,\n enforceDismissal: action.type === \"dismiss\",\n scopeSelector:\n action.type === \"dismiss\" ? action.scope_selector : null,\n },\n ]\n : [],\n );\n if (targets.length === 0) return undefined;\n const serializedTargets = JSON.stringify(targets)\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n return String.raw`(() => {\n const prepare = window.__MIST_PREPARE_POST_EVIDENCE_ACTIONS_V1__;\n if (typeof prepare !== \"function\") {\n throw new Error(\"Mist post-evidence action preparation is unavailable.\");\n }\n return prepare(${serializedTargets});\n})()`;\n}\n\n/**\n * The full-page screenshot action scrolls after the evidence action returns.\n * Re-read the closure-backed evidence after those pixels are complete so any\n * fixed/sticky controls replaced or re-pinned during stitching are included\n * in the durable normalization receipt.\n */\nconst FIRECRAWL_FINALIZE_SOURCE_EVIDENCE_SCRIPT = String.raw`(async () => {\n const finalize = window.__MIST_FINALIZE_SOURCE_EVIDENCE_V1__;\n if (typeof finalize !== \"function\") {\n throw new Error(\"Mist source-evidence finalizer is unavailable.\");\n }\n return await finalize();\n})()`;\n\n/**\n * Build a Firecrawl v2 request without requiring Playwright on the user's\n * computer. A screenshot options object implies the screenshot format so an\n * agent cannot accidentally request a viewport and receive only Markdown.\n */\nexport function buildFirecrawlScrapeRequest({\n url,\n formats,\n onlyMainContent,\n screenshotOptions,\n actions,\n postEvidenceActions,\n capturePageEvidence,\n}: BuildFirecrawlScrapeRequestArgs): FirecrawlScrapeRequest {\n if (\n postEvidenceActions &&\n postEvidenceActions.length > 0 &&\n !(capturePageEvidence && screenshotOptions?.fullPage)\n ) {\n throw new Error(\n \"Post-evidence actions require capturePageEvidence and a full-page screenshot.\",\n );\n }\n const requestedFormats =\n formats && formats.length > 0 ? [...formats] : [\"markdown\" as const];\n if (screenshotOptions && !requestedFormats.includes(\"screenshot\")) {\n requestedFormats.push(\"screenshot\");\n }\n // The provider's `html` is post-processed: `<style>` bodies and\n // `<link rel=stylesheet>` are stripped. `rawHtml` is the untouched document,\n // and it arrives through a real browser that gets past the bot controls\n // which refuse our own document refetch. Ask for it whenever we are already\n // capturing evidence, so the fallback costs no extra round-trip.\n if (\n capturePageEvidence &&\n requestedFormats.includes(\"html\") &&\n !requestedFormats.includes(\"rawHtml\")\n ) {\n requestedFormats.push(\"rawHtml\");\n }\n\n const usesEvidenceScreenshotAction =\n shouldUseFirecrawlEvidenceScreenshotAction({\n capturePageEvidence,\n screenshotOptions,\n requestedFormats,\n });\n const normalizedFormats: FirecrawlScrapeRequest[\"formats\"] = requestedFormats\n .filter(\n (format) => format !== \"screenshot\" || !usesEvidenceScreenshotAction,\n )\n .map((format) => {\n if (format !== \"screenshot\") return format;\n return {\n type: \"screenshot\",\n ...(screenshotOptions?.fullPage === undefined\n ? {}\n : { fullPage: screenshotOptions.fullPage }),\n ...(screenshotOptions?.quality === undefined\n ? {}\n : { quality: screenshotOptions.quality }),\n ...(screenshotOptions?.viewport\n ? { viewport: screenshotOptions.viewport }\n : {}),\n };\n });\n\n const normalizedActions: FirecrawlAction[] = [];\n if (capturePageEvidence && screenshotOptions?.viewport) {\n // Firecrawl applies a screenshot format's viewport to the returned pixels,\n // but its JavaScript actions otherwise keep running in the vendor default\n // browser (1920px desktop or 360px mobile). A cheap viewport-only action\n // first makes the page, caller actions, DOM evidence, and final screenshot\n // share the exact requested responsive viewport. Some storefronts cache\n // viewport-derived CSS variables before Firecrawl applies this action, so\n // explicitly notify resize listeners and let their debounced layout work\n // settle before interacting with the responsive page.\n normalizedActions.push({\n type: \"screenshot\",\n fullPage: false,\n quality: 1,\n viewport: screenshotOptions.viewport,\n });\n normalizedActions.push({\n type: \"executeJavascript\",\n script: FIRECRAWL_VIEWPORT_SETTLE_SCRIPT,\n });\n }\n normalizedActions.push(...(actions ?? []));\n if (capturePageEvidence) {\n if (screenshotOptions?.fullPage) {\n normalizedActions.push({\n type: \"executeJavascript\",\n script: FIRECRAWL_HIDE_SCROLLBARS_SCRIPT,\n });\n if (postEvidenceActions && postEvidenceActions.length > 0) {\n // Keep provider-native clicks/waits/scrolls operating on the source\n // page's own CSS. Normalizing an open drawer first can pin its\n // geometry with !important and prevent the site's close handler from\n // moving it off-canvas.\n normalizedActions.push({\n type: \"executeJavascript\",\n script: FIRECRAWL_DEFER_SOURCE_STITCH_NORMALIZATION_SCRIPT,\n });\n }\n }\n normalizedActions.push({\n type: \"executeJavascript\",\n script: screenshotOptions?.fullPage\n ? RENDERED_SOURCE_PAGE_EVIDENCE_SCRIPT\n : RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT,\n });\n if (postEvidenceActions && postEvidenceActions.length > 0) {\n const prepareScript =\n firecrawlPreparePostEvidenceActionsScript(postEvidenceActions);\n if (prepareScript) {\n normalizedActions.push({\n type: \"executeJavascript\",\n script: prepareScript,\n });\n }\n normalizedActions.push(\n ...postEvidenceActions.map((action) =>\n action.type === \"dismiss\"\n ? { type: \"click\" as const, selector: action.selector }\n : action,\n ),\n );\n // A bounded full-page walk can materialize controls that did not exist\n // when caller actions ran. Let the agent adjudicate those controls with\n // the existing constrained action vocabulary, then recollect the signed\n // DOM at the exact state the screenshot will capture. The finalizer owns\n // a bounded settle window for ordinary close clicks so an asynchronous\n // source transition cannot be reconciled from its still-painted frame.\n normalizedActions.push({\n type: \"executeJavascript\",\n script: FIRECRAWL_FINALIZE_SOURCE_EVIDENCE_SCRIPT,\n });\n }\n if (screenshotOptions?.fullPage && usesEvidenceScreenshotAction) {\n // Keep the authoritative pixels adjacent to their evidence action.\n // Firecrawl returns this URL in data.actions.screenshots.\n normalizedActions.push({\n type: \"screenshot\",\n fullPage: true,\n ...(screenshotOptions.quality === undefined\n ? {}\n : { quality: screenshotOptions.quality }),\n ...(screenshotOptions.viewport\n ? { viewport: screenshotOptions.viewport }\n : {}),\n });\n normalizedActions.push({\n type: \"executeJavascript\",\n script: FIRECRAWL_FINALIZE_SOURCE_EVIDENCE_SCRIPT,\n });\n }\n }\n\n return {\n url,\n formats: normalizedFormats,\n onlyMainContent: onlyMainContent ?? true,\n // Firecrawl's mobile emulation currently truncates full-page screenshots\n // by exactly one requested viewport after browser actions. Exact full-page\n // evidence already primes and verifies the explicit responsive viewport,\n // so omit provider emulation only for that affected path. Non-full-page\n // evidence still needs the mobile user agent and touch/browser semantics.\n ...(screenshotOptions?.viewport?.width !== undefined &&\n screenshotOptions.viewport.width <= 768 &&\n !(capturePageEvidence && screenshotOptions.fullPage)\n ? { mobile: true }\n : {}),\n ...(normalizedActions.length > 0 ? { actions: normalizedActions } : {}),\n };\n}\n","import jpeg from \"jpeg-js\";\nimport { PNG } from \"pngjs\";\n\n/**\n * Pure-JS image helpers for the page-clone commands. Mist decodes with\n * Electron's nativeImage; the CLI has no Electron, and a native decoder would\n * make every theme-dev install compile one. PNG and JPEG cover what Firecrawl\n * and Playwright return.\n */\n\nexport type EvidenceImageMediaType =\n | \"image/png\"\n | \"image/jpeg\"\n | \"image/webp\"\n | \"image/gif\";\n\nexport interface ImageSize {\n width: number;\n height: number;\n}\n\n/** RGBA pixels, four bytes per pixel, row-major. */\nexport interface DecodedImage extends ImageSize {\n data: Uint8Array;\n}\n\n/** Identify an image by its magic bytes rather than a server's header. */\nexport function sniffImageMediaType(\n bytes: Uint8Array,\n): EvidenceImageMediaType | null {\n if (\n bytes.length >= 8 &&\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47\n ) {\n return \"image/png\";\n }\n if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n return \"image/jpeg\";\n }\n if (\n bytes.length >= 12 &&\n ascii(bytes, 0, 4) === \"RIFF\" &&\n ascii(bytes, 8, 4) === \"WEBP\"\n ) {\n return \"image/webp\";\n }\n if (bytes.length >= 6 && ascii(bytes, 0, 3) === \"GIF\") return \"image/gif\";\n return null;\n}\n\nfunction ascii(bytes: Uint8Array, start: number, length: number): string {\n return String.fromCharCode(...bytes.subarray(start, start + length));\n}\n\nfunction readUint16BE(bytes: Uint8Array, offset: number): number {\n return (bytes[offset]! << 8) | bytes[offset + 1]!;\n}\n\nfunction readUint32BE(bytes: Uint8Array, offset: number): number {\n return (\n ((bytes[offset]! << 24) >>> 0) +\n (bytes[offset + 1]! << 16) +\n (bytes[offset + 2]! << 8) +\n bytes[offset + 3]!\n );\n}\n\nfunction readUint24LE(bytes: Uint8Array, offset: number): number {\n return (\n bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16)\n );\n}\n\n/**\n * Read width/height from the image header without decoding its pixels. A\n * crawl only needs the dimensions to bind the screenshot to its sidecar.\n */\nexport function readImageSize(bytes: Uint8Array): ImageSize | null {\n const type = sniffImageMediaType(bytes);\n try {\n if (type === \"image/png\" && bytes.length >= 24) {\n return { width: readUint32BE(bytes, 16), height: readUint32BE(bytes, 20) };\n }\n if (type === \"image/gif\" && bytes.length >= 10) {\n return {\n width: bytes[6]! | (bytes[7]! << 8),\n height: bytes[8]! | (bytes[9]! << 8),\n };\n }\n if (type === \"image/jpeg\") return readJpegSize(bytes);\n if (type === \"image/webp\") return readWebpSize(bytes);\n } catch {\n return null;\n }\n return null;\n}\n\nfunction readJpegSize(bytes: Uint8Array): ImageSize | null {\n let offset = 2;\n while (offset + 9 < bytes.length) {\n if (bytes[offset] !== 0xff) return null;\n const marker = bytes[offset + 1]!;\n // Standalone markers carry no length.\n if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) {\n offset += 2;\n continue;\n }\n const length = readUint16BE(bytes, offset + 2);\n const isStartOfFrame =\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc;\n if (isStartOfFrame) {\n return {\n height: readUint16BE(bytes, offset + 5),\n width: readUint16BE(bytes, offset + 7),\n };\n }\n offset += 2 + length;\n }\n return null;\n}\n\nfunction readWebpSize(bytes: Uint8Array): ImageSize | null {\n const chunk = ascii(bytes, 12, 4);\n if (chunk === \"VP8X\" && bytes.length >= 30) {\n return {\n width: readUint24LE(bytes, 24) + 1,\n height: readUint24LE(bytes, 27) + 1,\n };\n }\n if (chunk === \"VP8 \" && bytes.length >= 30) {\n return {\n width: (bytes[26]! | (bytes[27]! << 8)) & 0x3fff,\n height: (bytes[28]! | (bytes[29]! << 8)) & 0x3fff,\n };\n }\n if (chunk === \"VP8L\" && bytes.length >= 25) {\n const bits =\n bytes[21]! |\n (bytes[22]! << 8) |\n (bytes[23]! << 16) |\n ((bytes[24]! << 24) >>> 0);\n return {\n width: (bits & 0x3fff) + 1,\n height: ((bits >>> 14) & 0x3fff) + 1,\n };\n }\n return null;\n}\n\n/** Decode a PNG or JPEG into RGBA pixels. */\nexport function decodeImage(bytes: Uint8Array, label: string): DecodedImage {\n const type = sniffImageMediaType(bytes);\n if (type === \"image/png\") {\n const png = PNG.sync.read(Buffer.from(bytes));\n return { width: png.width, height: png.height, data: png.data };\n }\n if (type === \"image/jpeg\") {\n const decoded = jpeg.decode(bytes, {\n useTArray: true,\n formatAsRGBA: true,\n // A tall full-page capture can exceed jpeg-js's conservative defaults.\n maxResolutionInMP: 200,\n maxMemoryUsageInMB: 2048,\n });\n return {\n width: decoded.width,\n height: decoded.height,\n data: decoded.data,\n };\n }\n throw new Error(\n `${label} is ${type ?? \"an unrecognised format\"}; the CLI comparison decodes PNG and JPEG only. Recapture the source with \\`fluid theme crawl\\`, which keeps the provider's PNG/JPEG bytes.`,\n );\n}\n\n/** Copy full-width rows `[y, y + height)` out of a decoded image. */\nexport function cropRows(\n image: DecodedImage,\n y: number,\n height: number,\n): Uint8Array {\n const rowBytes = image.width * 4;\n if (y < 0 || height <= 0 || y + height > image.height) {\n throw new Error(\n `Crop rows ${y}-${y + height} fall outside the ${image.width}×${image.height} image.`,\n );\n }\n return image.data.subarray(y * rowBytes, (y + height) * rowBytes);\n}\n\n/**\n * Nearest-neighbour downsample of a full-width tile. Used only to sample\n * repeated-viewport evidence on a bounded budget, never to \"fix\" a capture\n * whose size does not match the requested viewport.\n */\nexport function sampleTile(\n image: DecodedImage,\n tileY: number,\n tileHeight: number,\n sampledWidth: number,\n sampledHeight: number,\n): Uint8Array {\n const out = new Uint8Array(sampledWidth * sampledHeight * 4);\n for (let y = 0; y < sampledHeight; y += 1) {\n const sourceY =\n tileY + Math.min(tileHeight - 1, Math.floor((y * tileHeight) / sampledHeight));\n for (let x = 0; x < sampledWidth; x += 1) {\n const sourceX = Math.min(\n image.width - 1,\n Math.floor((x * image.width) / sampledWidth),\n );\n const from = (sourceY * image.width + sourceX) * 4;\n const to = (y * sampledWidth + x) * 4;\n out[to] = image.data[from]!;\n out[to + 1] = image.data[from + 1]!;\n out[to + 2] = image.data[from + 2]!;\n out[to + 3] = image.data[from + 3]!;\n }\n }\n return out;\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, realpath, unlink, writeFile } from \"node:fs/promises\";\nimport { join, resolve, sep } from \"node:path\";\nimport {\n buildDurableCrawlEvidence,\n CRAWL_BASELINE_DIR,\n CRAWL_DOCUMENT_MAX_BYTES,\n CRAWL_TIMEOUT_MS,\n crawlDocumentRelativePath,\n crawlScreenshotFileName,\n formatCrawlDocumentsForChat,\n hasCompleteDurableCrawlDocuments,\n summarizeCrawlMetadata,\n type CrawlDocumentFormat,\n} from \"@fluid-app/mist-core/crawl-evidence\";\nimport {\n buildFirecrawlScrapeRequest,\n selectFirecrawlEvidenceScreenshotUrl,\n shouldUseFirecrawlEvidenceScreenshotAction,\n type CrawlAction,\n type CrawlFormat,\n type CrawlPostEvidenceAction,\n type CrawlScreenshotOptions,\n} from \"@fluid-app/mist-core/firecrawl-request\";\nimport {\n parseFirecrawlRenderedSourcePageEvidence,\n renderedPageStitchHazardAnchors,\n type DurableSourcePageEvidence,\n type RenderedSourcePageEvidence,\n} from \"@fluid-app/mist-core/source-page-evidence\";\nimport type { CollectedStylesheets } from \"@fluid-app/mist-core/source-stylesheets\";\nimport {\n CrawlTransportError,\n type FirecrawlScrapeData,\n type ScrapeResult,\n} from \"./crawl-transport.js\";\nimport { readImageSize, sniffImageMediaType } from \"./image.js\";\nimport { pinnedPublicFetch } from \"@fluid-app/mist-core/pinned-public-fetch\";\nimport { validatePublicHttpUrl } from \"@fluid-app/mist-core/public-url-policy\";\n\n/** Stylesheet recovery is additive evidence; never let it hold a crawl open\n * far past the scrape's own 60s bound. */\nexport const CRAWL_STYLESHEET_TIMEOUT_MS = 30_000;\n\n/** Largest source screenshot a bundle keeps; matches the compare read cap. */\nexport const CRAWL_SCREENSHOT_MAX_BYTES = 50 * 1024 * 1024;\nconst CRAWL_SCREENSHOT_TIMEOUT_MS = 20_000;\n\nexport type CrawlRequestFormat = \"markdown\" | \"html\" | \"screenshot\";\n\nexport interface CrawlOptions {\n url: string;\n formats?: CrawlRequestFormat[];\n viewport?: { width: number; height: number };\n fullPage?: boolean;\n quality?: number;\n onlyMainContent?: boolean;\n /** Project root that receives `.mist-desktop/source-baselines/…`. */\n evidenceRoot?: string;\n direct?: boolean;\n /** Browser actions before the evidence walk (wait, click, scroll). */\n actions?: CrawlAction[];\n /** Constrained actions after the evidence walk, before final pixels.\n * Need evidence and a full-page screenshot. */\n postEvidenceActions?: CrawlPostEvidenceAction[];\n}\n\nexport interface CrawlDeps {\n scrape: (\n request: ReturnType<typeof buildFirecrawlScrapeRequest>,\n options: { direct: boolean; signal: AbortSignal },\n ) => Promise<ScrapeResult>;\n fetchScreenshot: (\n url: string,\n ) => Promise<{ bytes: Buffer; mediaType: string } | { note: string }>;\n /** Recover the page's own CSS; additive, so it reports rather than throws. */\n collectStylesheets: (\n url: string,\n providerRawHtml: string,\n /** Aborted when the stylesheet deadline passes: start no new requests. */\n signal: AbortSignal,\n ) => Promise<{ collected?: CollectedStylesheets; failure?: string }>;\n now: () => Date;\n uuid: () => string;\n}\n\ninterface SavedDocument {\n path: string;\n sha256: string;\n bytes: number;\n}\n\nexport interface CrawlEvidenceReport {\n root: string;\n path: string;\n captured_at: string;\n sha256: string;\n bytes: number;\n width: number;\n height: number;\n requested_viewport?: { width: number; height: number };\n page_evidence?: {\n path: string;\n sha256: string;\n bytes: number;\n visible_text_sha256: string;\n landmarks: number;\n sections: number;\n media: number;\n capture_geometry?: DurableSourcePageEvidence[\"captureGeometry\"];\n };\n documents?: Partial<Record<CrawlDocumentFormat, SavedDocument>>;\n /** Absolute paths, for agents that do not track the root. */\n files: Record<string, string>;\n}\n\nexport interface CrawlReport {\n ok: boolean;\n via?: ScrapeResult[\"via\"];\n fallback_reason?: string;\n metadata?: ReturnType<typeof summarizeCrawlMetadata>;\n /** Requested documents, capped like Mist's chat output: ~200KB, or a\n * ~16KB excerpt when the complete documents are saved as evidence. */\n content?: string;\n screenshot?: { url: string };\n evidence?: CrawlEvidenceReport;\n css?: {\n retained: boolean;\n sources?: number;\n by_origin?: CollectedStylesheets[\"byOrigin\"];\n stats?: CollectedStylesheets[\"stats\"];\n warnings?: string[];\n failure?: string;\n };\n notes?: string[];\n warnings?: string[];\n error?: { code: string; message: string; [key: string]: unknown };\n}\n\nfunction fail(\n code: string,\n message: string,\n extra: Record<string, unknown> = {},\n): CrawlReport {\n return { ok: false, error: { code, message, ...extra } };\n}\n\nfunction sha256(bytes: Buffer): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\n/** Notes Mist adds about full-page stitching, so an agent reads the same\n * caveats whether it captured with Mist or the CLI. */\nfunction stitchNotes(rendered: RenderedSourcePageEvidence): string[] {\n const notes: string[] = [];\n const list = (anchors: string[]) =>\n `${anchors.slice(0, 20).join(\", \")}${anchors.length > 20 ? \", …\" : \"\"}`;\n const stitchHazards = renderedPageStitchHazardAnchors(rendered);\n if (rendered.stitchNormalizations.length > 0) {\n const anchors = rendered.stitchNormalizations.map(({ anchor }) => anchor);\n notes.push(\n `Pinned ${anchors.length} rendered fixed/sticky element${anchors.length === 1 ? \"\" : \"s\"} into one equivalent document position before the full-page screenshot (${list(anchors)}). The sidecar retains each original position and geometry; treat it as one control, not repeated page content.`,\n );\n }\n if (\n stitchHazards.length > 0 &&\n (rendered.stitchNormalizations.length === 0 ||\n rendered.stitchNormalizationsTruncated)\n ) {\n notes.push(\n `The source DOM contains ${stitchHazards.length} fixed/sticky section${stitchHazards.length === 1 ? \"\" : \"s\"} (${list(stitchHazards)}). The full-page image may repeat that chrome at stitched viewport boundaries. Treat each sidecar anchor as one structural section; do not recreate visual stitch repetitions as page content.`,\n );\n }\n if (rendered.stitchNormalizationsTruncated) {\n notes.push(\n \"The bounded fixed/sticky normalization limit was reached. Recorded normalizations remain valid, but the source needs_adjudication if other chrome repeats at stitched viewport boundaries.\",\n );\n }\n if (rendered.stitchHazardsTruncated) {\n notes.push(\n \"The dynamic fixed/sticky scan was bounded before every candidate could be inspected. Only explicitly listed stitch-hazard anchors are confirmed.\",\n );\n }\n if (rendered.captureNormalizations.length > 0) {\n const anchors = rendered.captureNormalizations.map(({ anchor }) => anchor);\n notes.push(\n `Excluded ${anchors.length} inactive offscreen dialog${anchors.length === 1 ? \"\" : \"s\"} marked \\`aria-hidden=\"true\"\\` (${list(anchors)}) from the stitched pixels. The complete HTML and normalization receipts are retained; do not recreate these closed dialogs as page content.`,\n );\n }\n if (rendered.captureNormalizationsTruncated) {\n notes.push(\n \"The bounded inactive-dialog scan limit was reached. Treat the source as needs_adjudication if another closed dialog appears in the stitched pixels.\",\n );\n }\n if (rendered.postActionDismissals.length > 0) {\n const anchors = rendered.postActionDismissals.map(({ anchor }) => anchor);\n notes.push(\n `Excluded ${anchors.length} fixed/sticky subtree${anchors.length === 1 ? \"\" : \"s\"} after constrained post-evidence dismissal (${list(anchors)}). The sidecar retains each control and scope selector, post-click geometry, computed style, and whether the site settled it closed or the dismissal was enforced; treat the subtree as dismissed UI, not page content.`,\n );\n }\n return notes;\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const normalizedRoot = root.endsWith(sep) ? root : `${root}${sep}`;\n return candidate.startsWith(normalizedRoot);\n}\n\n/**\n * Write one evidence bundle in Mist's layout: every file shares the\n * screenshot's basename under `<root>/.mist-desktop/source-baselines/`, and\n * the sidecar binds them by path, byte length, and SHA-256. A partial write\n * is rolled back so a half-bundle can never be compared.\n */\nasync function saveEvidenceBundle(args: {\n root: string;\n sourceUrl: string;\n screenshotOptions: CrawlScreenshotOptions;\n bytes: Buffer;\n mediaType: string;\n capturedAt: string;\n uuid: string;\n documents: Array<{ format: CrawlDocumentFormat; content: string }>;\n pageEvidence?: { rendered: RenderedSourcePageEvidence; statusCode: number };\n}): Promise<{ report: CrawlEvidenceReport; warnings: string[] }> {\n const root = resolve(args.root);\n if (args.bytes.length > CRAWL_SCREENSHOT_MAX_BYTES) {\n throw new Error(\n `Source screenshot is ${(args.bytes.length / (1024 * 1024)).toFixed(1)}MB, above the ${CRAWL_SCREENSHOT_MAX_BYTES / (1024 * 1024)}MB evidence limit.`,\n );\n }\n const dimensions = readImageSize(args.bytes);\n if (!dimensions) {\n throw new Error(\n \"The source screenshot's dimensions could not be read, so it cannot be bound to rendered page evidence.\",\n );\n }\n const filename = crawlScreenshotFileName({\n sourceUrl: args.sourceUrl,\n screenshotOptions: args.screenshotOptions,\n mediaType: args.mediaType,\n capturedAt: args.capturedAt,\n uuid: args.uuid,\n });\n const relativePath = `${CRAWL_BASELINE_DIR}/${filename}`;\n const absolute = (relative: string) => {\n const full = resolve(root, ...relative.split(\"/\"));\n if (!isInside(root, full)) {\n throw new Error(`Evidence path ${relative} escapes ${root}.`);\n }\n return full;\n };\n const screenshotSha256 = sha256(args.bytes);\n\n const warnings: string[] = [];\n const documents = args.documents.flatMap((document) => {\n const bytes = Buffer.from(document.content, \"utf8\");\n if (bytes.length === 0) {\n warnings.push(\n `Requested ${document.format} source evidence was empty and cannot be retained.`,\n );\n return [];\n }\n if (bytes.length > CRAWL_DOCUMENT_MAX_BYTES) {\n warnings.push(\n `Requested ${document.format} source evidence is ${(bytes.length / (1024 * 1024)).toFixed(1)}MB, above the ${CRAWL_DOCUMENT_MAX_BYTES / (1024 * 1024)}MB per-document evidence limit.`,\n );\n return [];\n }\n const documentRelativePath = crawlDocumentRelativePath(\n relativePath,\n document.format,\n );\n return [\n {\n format: document.format,\n bytes,\n relativePath: documentRelativePath,\n fullPath: absolute(documentRelativePath),\n sha256: sha256(bytes),\n },\n ];\n });\n\n let sidecar:\n | {\n relativePath: string;\n fullPath: string;\n bytes: Buffer;\n document: DurableSourcePageEvidence;\n }\n | undefined;\n if (args.pageEvidence) {\n const document = buildDurableCrawlEvidence({\n capturedAt: args.capturedAt,\n sourceUrl: args.sourceUrl,\n screenshotOptions: args.screenshotOptions,\n screenshot: {\n relativePath,\n sha256: screenshotSha256,\n byteLength: args.bytes.length,\n width: dimensions.width,\n height: dimensions.height,\n },\n documents: documents.map((document) => ({\n format: document.format,\n relativePath: document.relativePath,\n sha256: document.sha256,\n byteLength: document.bytes.length,\n })),\n rendered: args.pageEvidence.rendered,\n statusCode: args.pageEvidence.statusCode,\n });\n const sidecarRelativePath = crawlDocumentRelativePath(\n relativePath,\n \"page-evidence\",\n );\n sidecar = {\n relativePath: sidecarRelativePath,\n fullPath: absolute(sidecarRelativePath),\n bytes: Buffer.from(JSON.stringify(document, null, 2)),\n document,\n };\n }\n\n const screenshotFullPath = absolute(relativePath);\n const baselineDir = join(root, ...CRAWL_BASELINE_DIR.split(\"/\"));\n await mkdir(baselineDir, { recursive: true });\n // A symlinked `.mist-desktop` or `source-baselines` would pass the lexical\n // check above yet write outside the chosen project; compare the real paths.\n const [realRoot, realBaselineDir] = await Promise.all([\n realpath(root),\n realpath(baselineDir),\n ]);\n if (realBaselineDir !== join(realRoot, ...CRAWL_BASELINE_DIR.split(\"/\"))) {\n throw new Error(\n `${baselineDir} resolves to ${realBaselineDir}, outside ${realRoot}. Remove the symlink or choose another --evidence directory.`,\n );\n }\n const written: string[] = [];\n try {\n await writeFile(screenshotFullPath, args.bytes);\n written.push(screenshotFullPath);\n if (sidecar) {\n await writeFile(sidecar.fullPath, sidecar.bytes);\n written.push(sidecar.fullPath);\n }\n for (const document of documents) {\n await writeFile(document.fullPath, document.bytes);\n written.push(document.fullPath);\n }\n } catch (error) {\n await Promise.all(\n written.map((path) => unlink(path).catch(() => undefined)),\n );\n throw error;\n }\n\n const files: Record<string, string> = { screenshot: screenshotFullPath };\n if (sidecar) files[\"page_evidence\"] = sidecar.fullPath;\n for (const document of documents) files[document.format] = document.fullPath;\n\n return {\n warnings,\n report: {\n root,\n path: relativePath,\n captured_at: args.capturedAt,\n sha256: screenshotSha256,\n bytes: args.bytes.length,\n width: dimensions.width,\n height: dimensions.height,\n ...(args.screenshotOptions.viewport\n ? { requested_viewport: args.screenshotOptions.viewport }\n : {}),\n ...(sidecar\n ? {\n page_evidence: {\n path: sidecar.relativePath,\n sha256: sha256(sidecar.bytes),\n bytes: sidecar.bytes.length,\n visible_text_sha256: sidecar.document.visibleTextSha256,\n landmarks: sidecar.document.rendered.landmarks.length,\n sections: sidecar.document.rendered.sections.length,\n media: sidecar.document.rendered.media.length,\n ...(sidecar.document.captureGeometry\n ? { capture_geometry: sidecar.document.captureGeometry }\n : {}),\n },\n }\n : {}),\n ...(documents.length > 0\n ? {\n documents: Object.fromEntries(\n documents.map((document) => [\n document.format,\n {\n path: document.relativePath,\n sha256: document.sha256,\n bytes: document.bytes.length,\n },\n ]),\n ),\n }\n : {}),\n files,\n },\n };\n}\n\nfunction statusCodeFrom(metadata: Record<string, unknown> | undefined): number {\n const raw = metadata?.[\"statusCode\"];\n const statusCode =\n typeof raw === \"number\"\n ? raw\n : typeof raw === \"string\"\n ? Number(raw)\n : Number.NaN;\n if (!Number.isInteger(statusCode) || statusCode < 100 || statusCode > 599) {\n throw new Error(\n \"The crawl did not return a valid source HTTP status code.\",\n );\n }\n return statusCode;\n}\n\nasync function boundedStylesheets(\n collect: (signal: AbortSignal) => ReturnType<CrawlDeps[\"collectStylesheets\"]>,\n): ReturnType<CrawlDeps[\"collectStylesheets\"]> {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<{ failure: string }>((resolve) => {\n timer = setTimeout(() => {\n // Stop the collector from starting further requests; each in-flight\n // request is already bounded by its own fetch timeout.\n controller.abort();\n resolve({\n failure: `stylesheet recovery timed out after ${CRAWL_STYLESHEET_TIMEOUT_MS / 1000}s`,\n });\n }, CRAWL_STYLESHEET_TIMEOUT_MS);\n });\n try {\n return await Promise.race([collect(controller.signal), timeout]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * `fluid theme crawl`: scrape one URL (JS rendered) and, with an evidence\n * root, save the durable bundle Mist's `compare_preview_to_source` and\n * `view_project_image` read. Mist's agent runs this command for every crawl.\n */\nexport async function runCrawl(\n options: CrawlOptions,\n deps: CrawlDeps,\n): Promise<CrawlReport> {\n if (!/^https?:\\/\\//i.test(options.url)) {\n return fail(\n \"invalid_url\",\n `Refusing to crawl ${options.url}: only http(s) URLs are allowed.`,\n );\n }\n const capturePageEvidence = options.evidenceRoot !== undefined;\n const requestedFormats: CrawlFormat[] =\n options.formats && options.formats.length > 0\n ? [...new Set(options.formats)]\n : capturePageEvidence\n ? [\"markdown\", \"html\", \"screenshot\"]\n : [\"markdown\"];\n const screenshotOptions: CrawlScreenshotOptions | undefined =\n options.viewport || options.fullPage || options.quality !== undefined\n ? {\n ...(options.fullPage ? { fullPage: true } : {}),\n ...(options.quality === undefined\n ? {}\n : { quality: options.quality }),\n ...(options.viewport ? { viewport: options.viewport } : {}),\n }\n : undefined;\n if (screenshotOptions && !requestedFormats.includes(\"screenshot\")) {\n requestedFormats.push(\"screenshot\");\n }\n if (\n capturePageEvidence &&\n (!requestedFormats.includes(\"screenshot\") || !screenshotOptions?.viewport)\n ) {\n return fail(\n \"evidence_needs_viewport\",\n \"--evidence needs --viewport WxH (for example 1440x900 or 390x844) so the rendered DOM evidence can be bound to source pixels captured at exactly that width and height.\",\n );\n }\n\n if (\n options.postEvidenceActions &&\n options.postEvidenceActions.length > 0 &&\n !(capturePageEvidence && screenshotOptions?.fullPage)\n ) {\n return fail(\n \"post_evidence_actions_need_evidence\",\n \"--post-evidence-action needs --evidence and --full-page, so the evidence is collected again after the actions run and before the screenshot.\",\n );\n }\n\n const request = buildFirecrawlScrapeRequest({\n url: options.url,\n formats: requestedFormats,\n onlyMainContent: options.onlyMainContent,\n screenshotOptions,\n capturePageEvidence,\n ...(options.actions?.length ? { actions: options.actions } : {}),\n ...(options.postEvidenceActions?.length\n ? { postEvidenceActions: options.postEvidenceActions }\n : {}),\n });\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CRAWL_TIMEOUT_MS);\n let scrape: ScrapeResult;\n try {\n scrape = await deps.scrape(request, {\n direct: options.direct === true,\n signal: controller.signal,\n });\n } catch (error) {\n if (error instanceof CrawlTransportError) {\n return fail(error.code, error.message, error.details);\n }\n if (error instanceof Error && error.name === \"AbortError\") {\n return fail(\n \"crawl_timeout\",\n `Crawl timed out after ${CRAWL_TIMEOUT_MS / 1000}s.`,\n );\n }\n return fail(\n \"crawl_failed\",\n `Crawl threw: ${error instanceof Error ? error.message : String(error)}`,\n );\n } finally {\n clearTimeout(timer);\n }\n\n const data: FirecrawlScrapeData = scrape.data;\n const notes: string[] = [];\n const warnings: string[] = [];\n let evidenceError: string | undefined;\n let rendered: RenderedSourcePageEvidence | undefined;\n let statusCode: number | undefined;\n if (capturePageEvidence) {\n try {\n rendered = parseFirecrawlRenderedSourcePageEvidence(\n data.actions?.javascriptReturns?.at(-1)?.value,\n screenshotOptions!.viewport!,\n );\n statusCode = statusCodeFrom(data.metadata);\n } catch (error) {\n evidenceError = error instanceof Error ? error.message : String(error);\n }\n }\n\n const report: CrawlReport = {\n ok: true,\n via: scrape.via,\n ...(scrape.fallbackReason\n ? { fallback_reason: scrape.fallbackReason }\n : {}),\n };\n if (data.metadata) {\n report.metadata = summarizeCrawlMetadata({\n requestedUrl: options.url,\n metadata: data.metadata,\n ...(rendered ? { renderedPageEvidence: rendered } : {}),\n ...(statusCode === undefined\n ? {}\n : { renderedPageStatusCode: statusCode }),\n });\n }\n if (screenshotOptions?.fullPage && rendered)\n notes.push(...stitchNotes(rendered));\n\n const screenshotUrl = selectFirecrawlEvidenceScreenshotUrl(\n data,\n shouldUseFirecrawlEvidenceScreenshotAction({\n capturePageEvidence,\n screenshotOptions,\n requestedFormats,\n }),\n );\n if (screenshotUrl) report.screenshot = { url: screenshotUrl };\n\n let durableDocumentsRetained = false;\n if (\n capturePageEvidence &&\n requestedFormats.includes(\"screenshot\") &&\n screenshotUrl\n ) {\n // The provider's HTML has `<style>` and `<link rel=\"stylesheet\">`\n // stripped, so the page's own CSS has to be recovered separately.\n const css = await boundedStylesheets((signal) =>\n deps.collectStylesheets(\n options.url,\n typeof data.rawHtml === \"string\" ? data.rawHtml : \"\",\n signal,\n ),\n );\n const fetched = await deps.fetchScreenshot(screenshotUrl);\n let cssRetained = false;\n if (\"bytes\" in fetched) {\n try {\n const documents: Array<{\n format: CrawlDocumentFormat;\n content: string;\n }> = [\n ...requestedFormats.flatMap((format) => {\n if (format !== \"html\" && format !== \"markdown\") return [];\n const content = data[format];\n return typeof content === \"string\" && content.length > 0\n ? [{ format, content }]\n : [];\n }),\n ...(css.collected && css.collected.css.length > 0\n ? [{ format: \"stylesheet\" as const, content: css.collected.css }]\n : []),\n ...(typeof data.rawHtml === \"string\" && data.rawHtml.length > 0\n ? [{ format: \"rawHtml\" as const, content: data.rawHtml }]\n : []),\n ];\n const saved = await saveEvidenceBundle({\n root: options.evidenceRoot!,\n sourceUrl: options.url,\n screenshotOptions: screenshotOptions!,\n bytes: fetched.bytes,\n mediaType: fetched.mediaType,\n capturedAt: deps.now().toISOString(),\n uuid: deps.uuid(),\n documents,\n ...(rendered && statusCode !== undefined && !evidenceError\n ? { pageEvidence: { rendered, statusCode } }\n : {}),\n });\n report.evidence = saved.report;\n warnings.push(...saved.warnings);\n cssRetained = Boolean(saved.report.documents?.stylesheet);\n durableDocumentsRetained = hasCompleteDurableCrawlDocuments(\n requestedFormats,\n data,\n saved.report.documents,\n );\n if (!durableDocumentsRetained && !evidenceError) {\n evidenceError =\n saved.warnings.join(\" \") ||\n \"Not every requested Markdown/HTML source document was retained.\";\n }\n const geometry = saved.report.page_evidence?.capture_geometry;\n if (geometry?.status === \"dynamic-height\") {\n notes.push(\n `The page changed height while the browser stitched the full-page image. Both measurements are kept: rendered DOM ${geometry.renderedDocument.width}x${geometry.renderedDocument.height}, screenshot ${geometry.screenshot.width}x${geometry.screenshot.height} (${geometry.heightDelta >= 0 ? \"+\" : \"\"}${geometry.heightDelta}px). Use the screenshot for complete-page geometry and the sidecar/HTML for measured DOM facts.`,\n );\n }\n } catch (error) {\n evidenceError ??=\n error instanceof Error ? error.message : String(error);\n }\n } else {\n warnings.push(fetched.note);\n }\n report.css = css.collected\n ? {\n retained: cssRetained,\n sources: css.collected.sources.length,\n ...(css.collected.byOrigin\n ? { by_origin: css.collected.byOrigin }\n : {}),\n stats: css.collected.stats,\n ...(css.collected.warnings.length > 0\n ? { warnings: css.collected.warnings }\n : {}),\n }\n : {\n retained: false,\n ...(css.failure ? { failure: css.failure } : {}),\n };\n }\n\n const content = formatCrawlDocumentsForChat(\n requestedFormats,\n data,\n capturePageEvidence || durableDocumentsRetained,\n ).join(\"\\n\\n\");\n if (content.length > 0) report.content = content;\n\n if (\n capturePageEvidence &&\n !report.evidence?.page_evidence &&\n !evidenceError\n ) {\n evidenceError =\n \"The crawl did not return a durable source screenshot to bind to the rendered page evidence.\";\n }\n if (notes.length > 0) report.notes = notes;\n if (warnings.length > 0) report.warnings = warnings;\n if (capturePageEvidence && evidenceError) {\n report.ok = false;\n report.error = {\n code: \"evidence_not_bound\",\n message: `Could not bind rendered source copy/landmarks/media to this screenshot: ${evidenceError}`,\n };\n }\n return report;\n}\n\nexport interface BinaryFetchResult {\n status: number;\n bytes: Buffer;\n}\n\nconst MAX_SCREENSHOT_REDIRECTS = 5;\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\n\n/** Read a response body, refusing as soon as it passes the evidence cap,\n * so a missing or understated Content-Length cannot exhaust memory. */\nexport async function readCappedBody(response: {\n body: {\n getReader(): {\n read(): Promise<{ done: boolean; value?: Uint8Array }>;\n cancel(): Promise<void>;\n };\n } | null;\n}): Promise<Buffer> {\n if (!response.body) return Buffer.alloc(0);\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done || !value) break;\n total += value.byteLength;\n if (total > CRAWL_SCREENSHOT_MAX_BYTES) {\n throw new Error(\n `screenshot exceeds the ${CRAWL_SCREENSHOT_MAX_BYTES / (1024 * 1024)}MB evidence limit`,\n );\n }\n chunks.push(value);\n }\n } finally {\n await reader.cancel().catch(() => undefined);\n }\n return Buffer.concat(chunks);\n}\n\n/**\n * GET a public URL as bytes through the shared SSRF guard: every hop is\n * validated against the public-address policy and the connection is pinned\n * to the addresses that passed, so a crawl response (or a redirect it leads\n * to) cannot make the CLI read a localhost or private-network address.\n */\nexport async function fetchPublicBytes(\n url: string,\n signal: AbortSignal,\n): Promise<BinaryFetchResult> {\n let current = url;\n for (let hop = 0; hop <= MAX_SCREENSHOT_REDIRECTS; hop += 1) {\n const validation = await validatePublicHttpUrl(current);\n if (!validation.ok) {\n throw new Error(`refused by URL policy: ${validation.reason}`);\n }\n const pinned = await pinnedPublicFetch(\n validation.url,\n validation.addresses,\n {\n signal,\n headers: { Accept: \"image/*\" },\n },\n );\n try {\n const { response } = pinned;\n if (REDIRECT_STATUSES.has(response.status)) {\n const location = response.headers.get(\"location\");\n if (!location)\n return { status: response.status, bytes: Buffer.alloc(0) };\n current = new URL(location, current).toString();\n continue;\n }\n const declared = Number(response.headers.get(\"content-length\") ?? \"0\");\n if (declared > CRAWL_SCREENSHOT_MAX_BYTES) {\n throw new Error(\n `screenshot is ${(declared / (1024 * 1024)).toFixed(1)}MB, above the ${CRAWL_SCREENSHOT_MAX_BYTES / (1024 * 1024)}MB evidence limit`,\n );\n }\n return { status: response.status, bytes: await readCappedBody(response) };\n } finally {\n await pinned.close();\n }\n }\n throw new Error(`more than ${MAX_SCREENSHOT_REDIRECTS} redirects`);\n}\n\n/** Fetch the provider-hosted screenshot bytes, bounded in time and size. */\nexport async function fetchCrawlScreenshot(\n url: string,\n fetchBytes: (\n url: string,\n signal: AbortSignal,\n ) => Promise<BinaryFetchResult> = fetchPublicBytes,\n): Promise<{ bytes: Buffer; mediaType: string } | { note: string }> {\n if (!/^https?:\\/\\//i.test(url)) {\n return { note: `screenshot URL isn't fetchable (${url.slice(0, 100)})` };\n }\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n CRAWL_SCREENSHOT_TIMEOUT_MS,\n );\n try {\n const response = await fetchBytes(url, controller.signal);\n if (response.status < 200 || response.status >= 300) {\n return { note: `screenshot fetch failed (HTTP ${response.status})` };\n }\n const bytes = response.bytes;\n if (bytes.length === 0)\n return { note: \"screenshot fetch returned an empty body\" };\n if (bytes.length > CRAWL_SCREENSHOT_MAX_BYTES) {\n return {\n note: `screenshot is ${(bytes.length / (1024 * 1024)).toFixed(1)}MB, above the ${CRAWL_SCREENSHOT_MAX_BYTES / (1024 * 1024)}MB evidence limit`,\n };\n }\n const sniffed = sniffImageMediaType(bytes);\n if (!sniffed) {\n return {\n note: \"screenshot URL did not return a PNG, JPEG, WebP, or GIF image\",\n };\n }\n return { bytes, mediaType: sniffed };\n } catch (error) {\n return {\n note:\n error instanceof Error && error.name === \"AbortError\"\n ? `screenshot fetch timed out after ${CRAWL_SCREENSHOT_TIMEOUT_MS / 1000}s`\n : `screenshot fetch threw: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport { getAuthToken } from \"@fluid-app/fluid-cli\";\nimport {\n collectSourceStylesheets,\n fetchPublicText,\n MAX_SOURCE_DOCUMENT_BYTES,\n type SourceSnapshot,\n} from \"@fluid-app/mist-core/source-stylesheets\";\nimport { getApiBase } from \"../api.js\";\nimport {\n createFluidCrawlPoster,\n requestScrape,\n} from \"../page-clone/crawl-transport.js\";\nimport {\n fetchCrawlScreenshot,\n runCrawl,\n type CrawlDeps,\n type CrawlRequestFormat,\n} from \"../page-clone/run-crawl.js\";\nimport type {\n CrawlAction,\n CrawlPostEvidenceAction,\n} from \"@fluid-app/mist-core/firecrawl-request\";\n\nconst FORMATS: readonly CrawlRequestFormat[] = [\n \"markdown\",\n \"html\",\n \"screenshot\",\n];\n\nexport function parseViewport(value: string): {\n width: number;\n height: number;\n} {\n const match = /^(\\d+)x(\\d+)$/i.exec(value.trim());\n if (!match) {\n throw new InvalidArgumentError(\"Use WxH, for example 1440x900.\");\n }\n const width = Number(match[1]);\n const height = Number(match[2]);\n if (width < 320 || width > 3840 || height < 320 || height > 2160) {\n throw new InvalidArgumentError(\n \"Viewport must be 320–3840 wide and 320–2160 tall.\",\n );\n }\n return { width, height };\n}\n\nfunction collectFormat(\n value: string,\n previous: CrawlRequestFormat[] | undefined,\n): CrawlRequestFormat[] {\n const formats = value.split(\",\").map((part) => part.trim().toLowerCase());\n for (const format of formats) {\n if (!FORMATS.includes(format as CrawlRequestFormat)) {\n throw new InvalidArgumentError(\n `Unknown format \"${format}\". Use markdown, html, or screenshot.`,\n );\n }\n }\n return [...(previous ?? []), ...(formats as CrawlRequestFormat[])];\n}\n\n/** Most actions one crawl takes in each phase, matching Mist's limit. */\nexport const MAX_CRAWL_ACTIONS = 8;\nconst MAX_SELECTOR_LENGTH = 400;\nconst MAX_WAIT_MS = 10_000;\n\nfunction selectorOf(value: unknown, field: string): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0 ||\n value.length > MAX_SELECTOR_LENGTH\n ) {\n throw new InvalidArgumentError(\n `${field} must be a CSS selector of 1-${MAX_SELECTOR_LENGTH} characters.`,\n );\n }\n return value;\n}\n\n/** Parse one `--action` / `--post-evidence-action` JSON object into the\n * shapes Mist's former crawl tool accepted; `dismiss` only after the\n * evidence walk. */\nexport function parseCrawlAction(\n raw: string,\n allowDismiss: boolean,\n): CrawlPostEvidenceAction {\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n throw new InvalidArgumentError(\n `Not JSON: ${raw}. Use for example '{\"type\":\"wait\",\"milliseconds\":1000}'.`,\n );\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new InvalidArgumentError(\"Each action is one JSON object.\");\n }\n const action = value as Record<string, unknown>;\n switch (action[\"type\"]) {\n case \"wait\": {\n const ms = action[\"milliseconds\"];\n if (\n typeof ms !== \"number\" ||\n !Number.isInteger(ms) ||\n ms < 0 ||\n ms > MAX_WAIT_MS\n ) {\n throw new InvalidArgumentError(\n `wait.milliseconds must be an integer from 0 to ${MAX_WAIT_MS}.`,\n );\n }\n return { type: \"wait\", milliseconds: ms };\n }\n case \"click\":\n return {\n type: \"click\",\n selector: selectorOf(action[\"selector\"], \"click.selector\"),\n };\n case \"scroll\": {\n const direction = action[\"direction\"];\n if (direction !== \"up\" && direction !== \"down\") {\n throw new InvalidArgumentError(\n 'scroll.direction must be \"up\" or \"down\".',\n );\n }\n return action[\"selector\"] === undefined\n ? { type: \"scroll\", direction }\n : {\n type: \"scroll\",\n direction,\n selector: selectorOf(action[\"selector\"], \"scroll.selector\"),\n };\n }\n case \"dismiss\":\n if (!allowDismiss) {\n throw new InvalidArgumentError(\n \"dismiss is only accepted by --post-evidence-action.\",\n );\n }\n return {\n type: \"dismiss\",\n selector: selectorOf(action[\"selector\"], \"dismiss.selector\"),\n scope_selector: selectorOf(\n action[\"scope_selector\"],\n \"dismiss.scope_selector\",\n ),\n };\n default:\n throw new InvalidArgumentError(\n `Unknown action type ${JSON.stringify(action[\"type\"])}. Use wait, click, or scroll${allowDismiss ? \", or dismiss\" : \"\"}.`,\n );\n }\n}\n\nfunction collectAction(allowDismiss: boolean) {\n return (\n value: string,\n previous: CrawlPostEvidenceAction[] | undefined,\n ): CrawlPostEvidenceAction[] => {\n const actions = [\n ...(previous ?? []),\n parseCrawlAction(value, allowDismiss),\n ];\n if (actions.length > MAX_CRAWL_ACTIONS) {\n throw new InvalidArgumentError(\n `At most ${MAX_CRAWL_ACTIONS} actions per phase.`,\n );\n }\n return actions;\n };\n}\n\nfunction parseQuality(value: string): number {\n const quality = Number(value);\n if (!Number.isInteger(quality) || quality < 1 || quality > 100) {\n throw new InvalidArgumentError(\"Quality must be an integer from 1 to 100.\");\n }\n return quality;\n}\n\n/** Recover the page's own CSS from the served document and the provider's\n * raw HTML, using the shared SSRF-guarded fetch. Never throws. */\nasync function collectStylesheets(\n url: string,\n providerRawHtml: string,\n signal: AbortSignal,\n): ReturnType<CrawlDeps[\"collectStylesheets\"]> {\n try {\n const snapshots: SourceSnapshot[] = [];\n const direct = await fetchPublicText(\n url,\n 20_000,\n MAX_SOURCE_DOCUMENT_BYTES,\n );\n if (direct.ok && direct.body.length > 0) {\n snapshots.push({\n html: direct.body,\n baseUrl: direct.finalUrl,\n via: \"direct\",\n });\n }\n if (providerRawHtml.length > 0) {\n snapshots.push({\n html: providerRawHtml,\n baseUrl: url,\n via: \"provider-raw-html\",\n });\n }\n if (snapshots.length === 0) {\n return {\n failure: direct.error ?? `HTTP ${direct.status || \"fetch failed\"}`,\n };\n }\n const collected = await collectSourceStylesheets(snapshots, (sheetUrl) =>\n signal.aborted\n ? Promise.resolve({\n ok: false,\n status: 0,\n body: \"\",\n finalUrl: sheetUrl,\n truncated: false,\n error: \"stylesheet recovery deadline passed\",\n })\n : fetchPublicText(sheetUrl),\n );\n if (!direct.ok) {\n collected.warnings.push(\n `served document unavailable (${direct.error ?? `HTTP ${direct.status}`}); cascade order is from the hydrated document only`,\n );\n }\n return { collected };\n } catch (error) {\n return { failure: error instanceof Error ? error.message : String(error) };\n }\n}\n\nexport function defaultCrawlDeps(\n env: NodeJS.ProcessEnv = process.env,\n): CrawlDeps {\n return {\n scrape: (request, options) =>\n requestScrape(request, options, {\n postFluidCrawl: createFluidCrawlPoster(getApiBase(), getAuthToken()),\n firecrawlApiKey: env[\"FIRECRAWL_API_KEY\"],\n fetch,\n }),\n fetchScreenshot: (url) => fetchCrawlScreenshot(url),\n collectStylesheets,\n now: () => new Date(),\n uuid: () => randomUUID(),\n };\n}\n\ninterface CrawlCommandOptions {\n format?: CrawlRequestFormat[];\n viewport?: { width: number; height: number };\n fullPage?: boolean;\n quality?: number;\n mainContentOnly: boolean;\n evidence?: string;\n direct?: boolean;\n action?: CrawlAction[];\n postEvidenceAction?: CrawlPostEvidenceAction[];\n}\n\nexport function createCrawlCommand(\n deps: () => CrawlDeps = () => defaultCrawlDeps(),\n): Command {\n return new Command(\"crawl\")\n .description(\n \"Scrape a page through Fluid's managed browser (JS rendered) and print Markdown/HTML/screenshot as JSON; --evidence saves a durable page-clone bundle\",\n )\n .argument(\"<url>\", \"Absolute http(s) URL to scrape\")\n .option(\n \"--format <format>\",\n \"markdown, html, or screenshot (repeatable or comma-separated; default markdown, or all three with --evidence)\",\n collectFormat,\n )\n .option(\n \"--viewport <WxH>\",\n \"Exact CSS-pixel viewport, e.g. 1440x900 or 390x844 (implies screenshot)\",\n parseViewport,\n )\n .option(\"--full-page\", \"Capture the complete document height\")\n .option(\"--quality <1-100>\", \"Screenshot quality\", parseQuality)\n .option(\n \"--no-main-content-only\",\n \"Keep global chrome (nav, announcement bar, footer) — use for site cloning\",\n )\n .option(\n \"--evidence <dir>\",\n \"Save the evidence bundle under <dir>/.mist-desktop/source-baselines/ (needs --viewport)\",\n )\n .option(\n \"--action <json>\",\n `Browser action before the evidence walk, as JSON: {\"type\":\"wait\",\"milliseconds\":1000}, {\"type\":\"click\",\"selector\":\"…\"}, or {\"type\":\"scroll\",\"direction\":\"down\"} (repeatable, up to ${MAX_CRAWL_ACTIONS})`,\n collectAction(false),\n )\n .option(\n \"--post-evidence-action <json>\",\n `Action after the evidence walk and before the final screenshot (needs --evidence and --full-page); also {\"type\":\"dismiss\",\"selector\":\"…\",\"scope_selector\":\"…\"} (repeatable, up to ${MAX_CRAWL_ACTIONS})`,\n collectAction(true),\n )\n .option(\n \"--direct\",\n \"Skip Fluid's crawl endpoint and call Firecrawl with FIRECRAWL_API_KEY\",\n )\n .action(async (url: string, opts: CrawlCommandOptions) => {\n const report = await runCrawl(\n {\n url,\n ...(opts.format ? { formats: opts.format } : {}),\n ...(opts.viewport ? { viewport: opts.viewport } : {}),\n ...(opts.fullPage ? { fullPage: true } : {}),\n ...(opts.quality === undefined ? {} : { quality: opts.quality }),\n onlyMainContent: opts.mainContentOnly,\n ...(opts.evidence ? { evidenceRoot: opts.evidence } : {}),\n ...(opts.direct ? { direct: true } : {}),\n ...(opts.action ? { actions: opts.action } : {}),\n ...(opts.postEvidenceAction\n ? { postEvidenceActions: opts.postEvidenceAction }\n : {}),\n },\n deps(),\n );\n console.log(JSON.stringify(report, null, 2));\n if (!report.ok) process.exitCode = 1;\n });\n}\n","import { execFileSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\n/**\n * Playwright is an optional peer: most theme developers never compare\n * against a source, and a hard dependency would download browsers on every\n * theme-dev install. It is loaded only when `fluid theme compare` runs.\n */\nexport const PLAYWRIGHT_INSTALL_COMMAND =\n \"npm i -g playwright && npx playwright install chromium\";\n\n/** The slice of Playwright's API the comparison uses. */\nexport interface PlaywrightResponseLike {\n status(): number;\n statusText(): string;\n}\n\nexport interface PlaywrightPageLike {\n goto(\n url: string,\n options: { waitUntil: \"load\"; timeout: number },\n ): Promise<PlaywrightResponseLike | null>;\n waitForLoadState(\n state: \"networkidle\",\n options: { timeout: number },\n ): Promise<void>;\n evaluate(expression: string): Promise<unknown>;\n screenshot(options: {\n type: \"png\";\n fullPage?: boolean;\n clip?: { x: number; y: number; width: number; height: number };\n }): Promise<Buffer>;\n url(): string;\n}\n\nexport interface PlaywrightContextLike {\n newPage(): Promise<PlaywrightPageLike>;\n}\n\nexport interface PlaywrightBrowserLike {\n newContext(options: {\n viewport: { width: number; height: number };\n deviceScaleFactor: number;\n isMobile: boolean;\n hasTouch: boolean;\n }): Promise<PlaywrightContextLike>;\n close(): Promise<void>;\n}\n\nexport interface PlaywrightLike {\n chromium: {\n launch(options: { headless: boolean }): Promise<PlaywrightBrowserLike>;\n };\n}\n\nexport class PlaywrightUnavailableError extends Error {\n readonly installCommand = PLAYWRIGHT_INSTALL_COMMAND;\n constructor(message: string) {\n super(message);\n this.name = \"PlaywrightUnavailableError\";\n }\n}\n\nfunction isPlaywright(value: unknown): value is PlaywrightLike {\n const candidate =\n typeof value === \"object\" && value !== null && \"default\" in value\n ? ((value as { default: unknown }).default ?? value)\n : value;\n return (\n typeof candidate === \"object\" &&\n candidate !== null &&\n \"chromium\" in candidate &&\n typeof (candidate as { chromium?: { launch?: unknown } }).chromium\n ?.launch === \"function\"\n );\n}\n\nfunction unwrap(value: unknown): PlaywrightLike {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"chromium\" in value\n ) {\n return value as PlaywrightLike;\n }\n return (value as { default: PlaywrightLike }).default;\n}\n\nfunction globalNodeModules(): string | null {\n try {\n return execFileSync(\"npm\", [\"root\", \"-g\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 10_000,\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve Playwright from this package, the current project, or the global\n * npm root — the last because the documented install is `npm i -g`.\n */\nexport async function loadPlaywright(\n importer: (specifier: string) => Promise<unknown> = (specifier) =>\n import(specifier),\n): Promise<PlaywrightLike> {\n const attempts: Array<() => string> = [\n () => \"playwright\",\n () => createRequire(join(process.cwd(), \"noop.js\")).resolve(\"playwright\"),\n () => {\n const root = globalNodeModules();\n if (!root) throw new Error(\"no global npm root\");\n return createRequire(join(root, \"noop.js\")).resolve(\"playwright\");\n },\n ];\n for (const attempt of attempts) {\n try {\n const specifier = attempt();\n const loaded = await importer(\n specifier === \"playwright\" ? specifier : pathToFileURL(specifier).href,\n );\n if (isPlaywright(loaded)) return unwrap(loaded);\n } catch {\n // Try the next location.\n }\n }\n throw new PlaywrightUnavailableError(\n `fluid theme compare renders the local preview in a headless browser, which needs Playwright. Install it with: ${PLAYWRIGHT_INSTALL_COMMAND}`,\n );\n}\n\nexport interface PreviewMediaReadiness {\n images: { total: number; ready: number; failed: number; pending: number };\n videos: { total: number; ready: number; failed: number; pending: number };\n timedOut: boolean;\n}\n\nexport interface PreviewCapture {\n httpStatus: number | null;\n httpStatusText: string;\n finalUrl: string;\n png: Buffer;\n /** CSS-pixel viewport width the page laid out at. */\n pageWidth: number;\n /** Widest scrollable extent; wider than pageWidth means overflow. */\n documentWidth: number;\n /** Full document height (or the viewport height in viewport mode). */\n totalHeight: number;\n /** Rows actually captured; less than totalHeight when truncated. */\n capturedHeight: number;\n truncated: boolean;\n mediaReadiness: PreviewMediaReadiness;\n /** Return value of the evidence script. */\n evaluated: unknown;\n}\n\n/** Mist's full-page caps: one image up to 8000px, else up to 12 viewports. */\nexport const MAX_SINGLE_CAPTURE_HEIGHT_PX = 8000;\nexport const MAX_FULL_PAGE_SECTIONS = 12;\nconst NAVIGATION_TIMEOUT_MS = 30_000;\nconst NETWORK_IDLE_TIMEOUT_MS = 10_000;\n\n/** Walk the page one viewport at a time so lazy content materializes, then\n * return to the top — the same priming Mist does before a full capture. */\nconst LAZY_LOAD_WALK_SCRIPT = String.raw`(async () => {\n const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n const root = document.scrollingElement || document.documentElement;\n root.style.scrollBehavior = \"auto\";\n const step = Math.max(200, window.innerHeight);\n for (let y = 0; y < root.scrollHeight && y < 200000; y += step) {\n window.scrollTo(0, y);\n await pause(120);\n }\n window.scrollTo(0, root.scrollHeight);\n await pause(450);\n window.scrollTo(0, 0);\n await new Promise((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(resolve)),\n );\n return true;\n})()`;\n\nfunction mediaReadinessScript(fullPage: boolean): string {\n return String.raw`(async () => {\n const FULL_PAGE = ${fullPage ? \"true\" : \"false\"};\n const deadline = Date.now() + 8000;\n const inScope = (element) => {\n const rect = element.getBoundingClientRect();\n const style = getComputedStyle(element);\n if (rect.width <= 0 || rect.height <= 0) return false;\n if (style.display === \"none\" || style.visibility === \"hidden\") return false;\n return FULL_PAGE || (rect.bottom > 0 && rect.top < window.innerHeight);\n };\n const snapshot = () => {\n const images = [...document.images].filter(\n (image) => (image.currentSrc || image.src) && inScope(image),\n );\n const videos = [...document.querySelectorAll(\"video\")].filter(\n (video) => inScope(video) && (video.autoplay || video.preload !== \"none\"),\n );\n return {\n images: {\n total: images.length,\n ready: images.filter((image) => image.complete && image.naturalWidth > 0).length,\n failed: images.filter((image) => image.complete && image.naturalWidth === 0).length,\n pending: images.filter((image) => !image.complete).length,\n },\n videos: {\n total: videos.length,\n ready: videos.filter((video) => video.readyState >= 2).length,\n failed: videos.filter((video) => video.error !== null).length,\n pending: videos.filter((video) => video.readyState < 2 && video.error === null).length,\n },\n };\n };\n let state = snapshot();\n while ((state.images.pending > 0 || state.videos.pending > 0) && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, 200));\n state = snapshot();\n }\n return { ...state, timedOut: state.images.pending > 0 || state.videos.pending > 0 };\n})()`;\n}\n\nconst MEASURE_SCRIPT = String.raw`(() => {\n const root = document.documentElement;\n const body = document.body;\n return {\n pageWidth: window.innerWidth,\n documentWidth: Math.max(root.scrollWidth, body ? body.scrollWidth : 0),\n documentHeight: Math.max(root.scrollHeight, body ? body.scrollHeight : 0),\n };\n})()`;\n\n/**\n * Render one local route at an exact CSS-pixel viewport (device scale 1, so\n * no resizing is ever needed) and capture it with the evidence script's\n * result. Mobile emulation follows Mist's rule: widths up to 600px.\n */\nexport async function capturePreview(\n playwright: PlaywrightLike,\n args: {\n url: string;\n width: number;\n height: number;\n mode: \"full\" | \"viewport\";\n evidenceScript: string;\n },\n): Promise<PreviewCapture> {\n const browser = await playwright.chromium.launch({ headless: true });\n try {\n const context = await browser.newContext({\n viewport: { width: args.width, height: args.height },\n deviceScaleFactor: 1,\n isMobile: args.width <= 600,\n hasTouch: args.width <= 600,\n });\n const page = await context.newPage();\n const response = await page.goto(args.url, {\n waitUntil: \"load\",\n timeout: NAVIGATION_TIMEOUT_MS,\n });\n await page\n .waitForLoadState(\"networkidle\", { timeout: NETWORK_IDLE_TIMEOUT_MS })\n .catch(() => undefined);\n if (args.mode === \"full\") await page.evaluate(LAZY_LOAD_WALK_SCRIPT);\n const mediaReadiness = (await page.evaluate(\n mediaReadinessScript(args.mode === \"full\"),\n )) as PreviewMediaReadiness;\n const evaluated = await page.evaluate(args.evidenceScript);\n const measured = (await page.evaluate(MEASURE_SCRIPT)) as {\n pageWidth: number;\n documentWidth: number;\n documentHeight: number;\n };\n let png: Buffer;\n let totalHeight: number;\n let capturedHeight: number;\n let truncated = false;\n if (args.mode === \"full\") {\n totalHeight = Math.max(args.height, measured.documentHeight);\n const capHeight = Math.max(\n MAX_SINGLE_CAPTURE_HEIGHT_PX,\n MAX_FULL_PAGE_SECTIONS * args.height,\n );\n truncated = totalHeight > capHeight;\n capturedHeight = Math.min(totalHeight, capHeight);\n png = await page.screenshot({\n type: \"png\",\n fullPage: true,\n ...(truncated\n ? {\n clip: {\n x: 0,\n y: 0,\n width: measured.pageWidth,\n height: capturedHeight,\n },\n }\n : {}),\n });\n } else {\n totalHeight = args.height;\n capturedHeight = args.height;\n png = await page.screenshot({ type: \"png\" });\n }\n return {\n httpStatus: response ? response.status() : null,\n httpStatusText: response ? response.statusText() : \"\",\n finalUrl: page.url(),\n png,\n pageWidth: measured.pageWidth,\n documentWidth: measured.documentWidth,\n totalHeight,\n capturedHeight,\n truncated,\n mediaReadiness,\n evaluated,\n };\n } finally {\n await browser.close().catch(() => undefined);\n }\n}\n","const DEFAULT_MAX_SAMPLED_PIXELS = 250_000;\nconst DEFAULT_MAX_REPEATED_VIEWPORT_SAMPLES = 100_000;\nconst MAX_REPEATED_VIEWPORTS = 12;\nconst MAX_REPEATED_PIXEL_REGIONS = 12;\nconst MIN_REPEATED_PIXEL_REGION_RATIO = 0.01;\nexport const SIGNIFICANT_PIXEL_DELTA = 32;\nexport const MAX_FULL_PAGE_HEIGHT_DELTA_RATIO = 0.05;\n\nexport interface PreviewComparisonSection {\n width: number;\n height: number;\n scrollOffset: number;\n}\n\nexport interface PreviewComparisonRegion {\n sectionIndex: number;\n sourceY: number;\n previewY: number;\n width: number;\n height: number;\n}\n\nexport interface PreviewComparisonPlan {\n regions: PreviewComparisonRegion[];\n capturedPreviewHeight: number;\n comparedHeight: number;\n}\n\nexport interface PixelComparisonRegion extends PreviewComparisonRegion {\n sourcePixels: Uint8Array;\n previewPixels: Uint8Array;\n}\n\n/** One horizontal slice of the compared document.\n *\n * Whole-page scalars tell an agent THAT a page differs but never WHERE, so a\n * page-clone rework round has to re-eyeball two full-page screenshots and\n * guess which section regressed. Bands carry the same metrics per\n * document-Y range, turning one opaque number into a ranked worklist. */\nexport interface PixelComparisonBand {\n /** Index in document order, top to bottom. */\n bandIndex: number;\n /** Inclusive start / exclusive end in SOURCE document CSS pixels. */\n sourceStartY: number;\n sourceEndY: number;\n sampledPixels: number;\n exactPixelRatio: number;\n significantPixelRatio: number;\n meanAbsoluteChannelError: number;\n maximumChannelError: number;\n /** Left-to-right split of this band. A vertical band localizes a defect to\n * a section; columns separate a whole-section problem from a\n * one-column problem — the difference between \"the footer is wrong\" and\n * \"the footer's middle columns sit ~110px left of the source\". Empty when\n * column splitting is disabled. */\n columns: PixelComparisonColumn[];\n}\n\n/** One cell of a band, left to right. */\nexport interface PixelComparisonColumn {\n columnIndex: number;\n /** Inclusive start / exclusive end in CSS pixels from the left edge. */\n startX: number;\n endX: number;\n sampledPixels: number;\n significantPixelRatio: number;\n maximumChannelError: number;\n}\n\nexport interface PixelComparisonMetrics {\n sampledPixels: number;\n sampleStride: number;\n meanAbsoluteChannelError: number;\n normalizedMeanAbsoluteError: number;\n exactPixelRatio: number;\n significantPixelRatio: number;\n significantPixelDelta: number;\n maximumChannelError: number;\n /** Per-band breakdown in document order. Empty only when banding was\n * explicitly disabled with `bandCount: 0`. */\n bands: PixelComparisonBand[];\n}\n\n/** Default vertical resolution of the banded breakdown. 16 bands over a\n * typical 6000px storefront home page is ~375px per band — fine enough to\n * separate hero from nav from footer, coarse enough to stay readable in a\n * tool result. */\nexport const DEFAULT_COMPARISON_BAND_COUNT = 16;\n\n/** A band grid shared by every region of one document.\n *\n * comparePreviewPixels is called once per captured region, and each call used\n * to derive its own grid from the region it was given — so a page stitched\n * from 11 viewport sections came back with 11 x 16 bands of ~57px instead of\n * 16 bands spanning the document. Callers that compare a document in pieces\n * must compute this once from the whole plan and pass the same value to every\n * region. */\nexport interface BandGrid {\n /** Document-space Y the first band starts at. */\n originY: number;\n /** Height of each band in document CSS pixels. */\n bandHeight: number;\n /** Number of bands spanning the document. */\n bandCount: number;\n /** Real end of the compared document. A single region does not know the\n * whole extent, so clipping the final band needs this from the plan. */\n documentEndY: number;\n /** Real width of the compared document, for clipping the final column. */\n documentWidth: number;\n}\n\n/** Build the shared grid from the full compared extent. */\nexport function planBandGrid(\n regions: readonly { sourceY: number; height: number; width?: number }[],\n bandCount = DEFAULT_COMPARISON_BAND_COUNT,\n): BandGrid | undefined {\n if (bandCount <= 0 || regions.length === 0) return undefined;\n const originY = Math.min(...regions.map((region) => region.sourceY));\n const endY = Math.max(\n ...regions.map((region) => region.sourceY + region.height),\n );\n const documentHeight = endY - originY;\n if (documentHeight <= 0) return undefined;\n return {\n originY,\n bandHeight: Math.ceil(documentHeight / bandCount),\n bandCount,\n documentEndY: endY,\n documentWidth: Math.max(...regions.map((region) => region.width ?? 0)),\n };\n}\n\ninterface ColumnAccumulator {\n sampledPixels: number;\n significantPixels: number;\n maximumChannelError: number;\n}\n\ninterface BandAccumulator {\n sampledPixels: number;\n exactPixels: number;\n significantPixels: number;\n channelError: number;\n maximumChannelError: number;\n columns: ColumnAccumulator[];\n}\n\n/** Default horizontal resolution. Four columns separates a centered hero from\n * a left/right split and isolates a single misplaced footer column, without\n * turning one comparison into 64 numbers to read. */\nexport const DEFAULT_COMPARISON_COLUMN_COUNT = 4;\n\nfunction finalizeBands(\n accumulators: BandAccumulator[],\n bandHeight: number,\n originY: number,\n columnWidth = 0,\n // Real extent of the compared document. Band height and column width are\n // computed with Math.ceil, so on a non-divisible document the final cell\n // would otherwise report an edge past the image — an end of 2512px for a\n // 2500px document. Coordinates a specialist scrolls to must exist.\n limitY = Number.POSITIVE_INFINITY,\n limitX = Number.POSITIVE_INFINITY,\n): PixelComparisonBand[] {\n const bands: PixelComparisonBand[] = [];\n for (const [bandIndex, accumulator] of accumulators.entries()) {\n // A band with no sampled pixels contributed nothing to the comparison\n // (stride skipped it, or the document ended); reporting it as a perfect\n // match would be a lie by omission.\n if (accumulator.sampledPixels === 0) continue;\n bands.push({\n bandIndex,\n sourceStartY: originY + bandIndex * bandHeight,\n sourceEndY: Math.min(originY + (bandIndex + 1) * bandHeight, limitY),\n sampledPixels: accumulator.sampledPixels,\n exactPixelRatio: roundMetric(\n accumulator.exactPixels / accumulator.sampledPixels,\n ),\n significantPixelRatio: roundMetric(\n accumulator.significantPixels / accumulator.sampledPixels,\n ),\n meanAbsoluteChannelError: roundMetric(\n accumulator.channelError / (accumulator.sampledPixels * 3),\n ),\n maximumChannelError: accumulator.maximumChannelError,\n columns: accumulator.columns.flatMap((column, columnIndex) =>\n column.sampledPixels === 0\n ? []\n : [\n {\n columnIndex,\n startX: columnIndex * columnWidth,\n endX: Math.min((columnIndex + 1) * columnWidth, limitX),\n sampledPixels: column.sampledPixels,\n significantPixelRatio: roundMetric(\n column.significantPixels / column.sampledPixels,\n ),\n maximumChannelError: column.maximumChannelError,\n },\n ],\n ),\n });\n }\n return bands;\n}\n\nexport function previewHeightDeltaRatio(\n sourceHeight: number,\n previewHeight: number,\n): number {\n if (\n !Number.isInteger(sourceHeight) ||\n !Number.isInteger(previewHeight) ||\n sourceHeight <= 0 ||\n previewHeight <= 0\n ) {\n throw new Error(\"Comparison heights must be positive integers.\");\n }\n return Math.abs(previewHeight - sourceHeight) / sourceHeight;\n}\n\nfunction boundedIntervalLength(\n start: number,\n length: number,\n boundary: number,\n): number {\n return Math.max(0, Math.min(start + length, boundary) - Math.max(start, 0));\n}\n\nfunction unionHeight(\n sections: PreviewComparisonSection[],\n boundary: number,\n): number {\n let coveredUntil = 0;\n let coveredHeight = 0;\n for (const section of sections) {\n const start = Math.max(0, section.scrollOffset);\n const end = Math.min(boundary, section.scrollOffset + section.height);\n if (end <= start) continue;\n const uncoveredStart = Math.max(start, coveredUntil);\n if (end > uncoveredStart) coveredHeight += end - uncoveredStart;\n coveredUntil = Math.max(coveredUntil, end);\n }\n return coveredHeight;\n}\n\n/**\n * Turn viewport captures into non-overlapping source/preview crops.\n *\n * Scroll-splice captures deliberately overlap their last viewport when the\n * document height is not a multiple of the viewport height. Comparing that\n * overlap twice would bias the score toward the footer, so this plan assigns\n * every document row to at most one region.\n */\nexport function planPreviewComparison(\n sourceWidth: number,\n sourceHeight: number,\n previewHeight: number,\n sections: PreviewComparisonSection[],\n): PreviewComparisonPlan {\n if (\n !Number.isInteger(sourceWidth) ||\n !Number.isInteger(sourceHeight) ||\n !Number.isInteger(previewHeight) ||\n sourceWidth <= 0 ||\n sourceHeight <= 0 ||\n previewHeight <= 0\n ) {\n throw new Error(\"Comparison dimensions must be positive integers.\");\n }\n\n const sorted = sections\n .map((section, sectionIndex) => ({ ...section, sectionIndex }))\n .sort(\n (left, right) =>\n left.scrollOffset - right.scrollOffset ||\n left.sectionIndex - right.sectionIndex,\n );\n for (const section of sorted) {\n if (\n !Number.isInteger(section.width) ||\n !Number.isInteger(section.height) ||\n !Number.isInteger(section.scrollOffset) ||\n section.width <= 0 ||\n section.height <= 0 ||\n section.scrollOffset < 0\n ) {\n throw new Error(\"Comparison section dimensions are invalid.\");\n }\n if (section.width !== sourceWidth) {\n throw new Error(\n `Source and preview widths must match exactly (${sourceWidth}px source vs ${section.width}px preview).`,\n );\n }\n }\n\n const comparisonBoundary = Math.min(sourceHeight, previewHeight);\n const regions: PreviewComparisonRegion[] = [];\n let coveredUntil = 0;\n for (const section of sorted) {\n const sourceY = Math.max(section.scrollOffset, coveredUntil);\n const availableHeight = boundedIntervalLength(\n sourceY,\n section.scrollOffset + section.height - sourceY,\n comparisonBoundary,\n );\n if (availableHeight <= 0) continue;\n regions.push({\n sectionIndex: section.sectionIndex,\n sourceY,\n previewY: sourceY - section.scrollOffset,\n width: sourceWidth,\n height: availableHeight,\n });\n coveredUntil = sourceY + availableHeight;\n }\n\n return {\n regions,\n capturedPreviewHeight: unionHeight(sorted, previewHeight),\n comparedHeight: regions.reduce(\n (height, region) => height + region.height,\n 0,\n ),\n };\n}\n\nfunction assertPixelBuffer(\n name: string,\n pixels: Uint8Array,\n width: number,\n height: number,\n): void {\n const expectedBytes = width * height * 4;\n if (pixels.byteLength < expectedBytes) {\n throw new Error(\n `${name} pixel buffer is ${pixels.byteLength} bytes; expected at least ${expectedBytes}.`,\n );\n }\n}\n\nfunction estimatedSamples(\n regions: PreviewComparisonRegion[],\n stride: number,\n): number {\n return regions.reduce((total, region) => {\n const firstY = (stride - (region.sourceY % stride) + stride) % stride;\n if (firstY >= region.height) return total;\n const rows = Math.ceil((region.height - firstY) / stride);\n const columns = Math.ceil(region.width / stride);\n return total + rows * columns;\n }, 0);\n}\n\nfunction roundMetric(value: number): number {\n return Number(value.toFixed(6));\n}\n\nexport interface RepeatedViewportPixelRegion {\n x: number;\n y: number;\n width: number;\n height: number;\n viewportAreaRatio: number;\n}\n\nexport interface RepeatedViewportPixelEvidence {\n exactPixelRatio: number;\n regions: RepeatedViewportPixelRegion[];\n}\n\n/**\n * Measure pixels that repeat at the same viewport-relative position across\n * independently stitched source cells. The callback keeps memory bounded to\n * two viewport bitmaps instead of decoding a potentially huge full-page image\n * into a second raw buffer.\n */\nexport function repeatedViewportExactPixelEvidence(\n width: number,\n height: number,\n viewportCount: number,\n readViewportPixels: (\n index: number,\n sampledWidth: number,\n sampledHeight: number,\n ) => Uint8Array,\n maxSampledPixels = DEFAULT_MAX_REPEATED_VIEWPORT_SAMPLES,\n): RepeatedViewportPixelEvidence {\n if (\n !Number.isInteger(width) ||\n !Number.isInteger(height) ||\n !Number.isInteger(viewportCount) ||\n !Number.isInteger(maxSampledPixels) ||\n width <= 0 ||\n height <= 0 ||\n viewportCount < 2 ||\n maxSampledPixels <= 0\n ) {\n throw new Error(\n \"Repeated-viewport comparison requires positive dimensions, at least two viewports, and a positive sample budget.\",\n );\n }\n const comparedViewportCount = Math.min(viewportCount, MAX_REPEATED_VIEWPORTS);\n let stride = Math.max(\n 1,\n Math.ceil(Math.sqrt((width * height) / maxSampledPixels)),\n );\n while (\n Math.ceil(width / stride) * Math.ceil(height / stride) >\n maxSampledPixels\n ) {\n stride += 1;\n }\n const sampledWidth = Math.ceil(width / stride);\n const sampledHeight = Math.ceil(height / stride);\n const sampleCount = sampledWidth * sampledHeight;\n const exactPairCounts = new Uint8Array(sampleCount);\n let previous = readViewportPixels(0, sampledWidth, sampledHeight);\n assertPixelBuffer(\n \"Repeated viewport 1\",\n previous,\n sampledWidth,\n sampledHeight,\n );\n\n for (\n let viewportIndex = 1;\n viewportIndex < comparedViewportCount;\n viewportIndex += 1\n ) {\n const current = readViewportPixels(\n viewportIndex,\n sampledWidth,\n sampledHeight,\n );\n assertPixelBuffer(\n `Repeated viewport ${viewportIndex + 1}`,\n current,\n sampledWidth,\n sampledHeight,\n );\n let sampleIndex = 0;\n for (let y = 0; y < sampledHeight; y += 1) {\n for (let x = 0; x < sampledWidth; x += 1) {\n const offset = (y * sampledWidth + x) * 4;\n if (\n previous[offset] === current[offset] &&\n previous[offset + 1] === current[offset + 1] &&\n previous[offset + 2] === current[offset + 2]\n ) {\n exactPairCounts[sampleIndex] = exactPairCounts[sampleIndex]! + 1;\n }\n sampleIndex += 1;\n }\n }\n previous = current;\n }\n\n const pairCount = comparedViewportCount - 1;\n const requiredExactPairs = Math.ceil(pairCount * 0.8);\n let repeatedPixels = 0;\n const repeatedPixelMask = new Uint8Array(sampleCount);\n for (\n let sampleIndex = 0;\n sampleIndex < exactPairCounts.length;\n sampleIndex += 1\n ) {\n if (exactPairCounts[sampleIndex]! < requiredExactPairs) continue;\n repeatedPixelMask[sampleIndex] = 1;\n repeatedPixels += 1;\n }\n\n const visited = new Uint8Array(sampleCount);\n const regions: RepeatedViewportPixelRegion[] = [];\n const neighborOffsets = [-1, 1, -sampledWidth, sampledWidth];\n for (let start = 0; start < repeatedPixelMask.length; start += 1) {\n if (repeatedPixelMask[start] !== 1 || visited[start] === 1) continue;\n const pending = [start];\n visited[start] = 1;\n let pixelCount = 0;\n let minX = sampledWidth;\n let minY = sampledHeight;\n let maxX = 0;\n let maxY = 0;\n while (pending.length > 0) {\n const sampleIndex = pending.pop()!;\n const y = Math.floor(sampleIndex / sampledWidth);\n const x = sampleIndex - y * sampledWidth;\n pixelCount += 1;\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x);\n maxY = Math.max(maxY, y);\n for (const neighborOffset of neighborOffsets) {\n const neighbor = sampleIndex + neighborOffset;\n if (\n neighbor < 0 ||\n neighbor >= repeatedPixelMask.length ||\n repeatedPixelMask[neighbor] !== 1 ||\n visited[neighbor] === 1\n ) {\n continue;\n }\n const neighborY = Math.floor(neighbor / sampledWidth);\n const neighborX = neighbor - neighborY * sampledWidth;\n if (Math.abs(neighborX - x) + Math.abs(neighborY - y) !== 1) continue;\n visited[neighbor] = 1;\n pending.push(neighbor);\n }\n }\n const viewportAreaRatio = pixelCount / sampleCount;\n if (viewportAreaRatio < MIN_REPEATED_PIXEL_REGION_RATIO) continue;\n const x = Math.floor((minX * width) / sampledWidth);\n const y = Math.floor((minY * height) / sampledHeight);\n const right = Math.ceil(((maxX + 1) * width) / sampledWidth);\n const bottom = Math.ceil(((maxY + 1) * height) / sampledHeight);\n regions.push({\n x,\n y,\n width: right - x,\n height: bottom - y,\n viewportAreaRatio: roundMetric(viewportAreaRatio),\n });\n }\n regions.sort(\n (left, right) => right.viewportAreaRatio - left.viewportAreaRatio,\n );\n return {\n exactPixelRatio: roundMetric(repeatedPixels / sampleCount),\n regions: regions.slice(0, MAX_REPEATED_PIXEL_REGIONS),\n };\n}\n\nexport function repeatedViewportExactPixelRatio(\n width: number,\n height: number,\n viewportCount: number,\n readViewportPixels: (\n index: number,\n sampledWidth: number,\n sampledHeight: number,\n ) => Uint8Array,\n maxSampledPixels = DEFAULT_MAX_REPEATED_VIEWPORT_SAMPLES,\n): number {\n return repeatedViewportExactPixelEvidence(\n width,\n height,\n viewportCount,\n readViewportPixels,\n maxSampledPixels,\n ).exactPixelRatio;\n}\n\nexport function previewComparisonSampleStride(\n regions: PreviewComparisonRegion[],\n maxSampledPixels = DEFAULT_MAX_SAMPLED_PIXELS,\n): number {\n if (!Number.isInteger(maxSampledPixels) || maxSampledPixels <= 0) {\n throw new Error(\"maxSampledPixels must be a positive integer.\");\n }\n if (regions.length === 0) {\n throw new Error(\"No overlapping source and preview pixels were captured.\");\n }\n let stride = Math.max(\n 1,\n Math.ceil(\n Math.sqrt(\n regions.reduce(\n (pixels, region) => pixels + region.width * region.height,\n 0,\n ) / maxSampledPixels,\n ),\n ),\n );\n while (estimatedSamples(regions, stride) > maxSampledPixels) stride += 1;\n return stride;\n}\n\n/**\n * Compare decoded BGRA/RGBA pixel planes without allocating a full diff image.\n * Electron uses four bytes per pixel; channel order does not affect absolute\n * error because source and preview use the same decoder. Alpha is excluded so\n * transparent RGB implementation details cannot distort visual similarity.\n */\nexport function comparePreviewPixels(\n regions: PixelComparisonRegion[],\n maxSampledPixels = DEFAULT_MAX_SAMPLED_PIXELS,\n fixedStride?: number,\n bandCount = DEFAULT_COMPARISON_BAND_COUNT,\n columnCount = DEFAULT_COMPARISON_COLUMN_COUNT,\n bandGrid?: BandGrid,\n): PixelComparisonMetrics {\n if (\n fixedStride !== undefined &&\n (!Number.isInteger(fixedStride) || fixedStride <= 0)\n ) {\n throw new Error(\"fixedStride must be a positive integer.\");\n }\n if (!Number.isInteger(bandCount) || bandCount < 0) {\n throw new Error(\"bandCount must be a non-negative integer.\");\n }\n if (!Number.isInteger(columnCount) || columnCount < 0) {\n throw new Error(\"columnCount must be a non-negative integer.\");\n }\n const stride =\n fixedStride ?? previewComparisonSampleStride(regions, maxSampledPixels);\n\n let sampledPixels = 0;\n let exactPixels = 0;\n let significantPixels = 0;\n let channelError = 0;\n let maximumChannelError = 0;\n\n // Band geometry spans the full compared document, not each region, so band\n // indices stay comparable across the stitched regions of one page.\n const documentStartY = regions.length\n ? Math.min(...regions.map((region) => region.sourceY))\n : 0;\n const documentEndY = regions.length\n ? Math.max(...regions.map((region) => region.sourceY + region.height))\n : 0;\n const documentHeight = Math.max(0, documentEndY - documentStartY);\n // A caller-supplied grid wins: it is the only way bands from separately\n // compared regions of one document line up.\n const gridOriginY = bandGrid ? bandGrid.originY : documentStartY;\n const effectiveBandCount = bandGrid ? bandGrid.bandCount : bandCount;\n const bandHeight = bandGrid\n ? bandGrid.bandHeight\n : bandCount > 0 && documentHeight > 0\n ? Math.ceil(documentHeight / bandCount)\n : 0;\n const documentWidth = regions.length\n ? Math.max(...regions.map((region) => region.width))\n : 0;\n const columnWidth =\n columnCount > 0 && documentWidth > 0\n ? Math.ceil(documentWidth / columnCount)\n : 0;\n const accumulators: BandAccumulator[] =\n bandHeight > 0\n ? Array.from({ length: effectiveBandCount }, () => ({\n sampledPixels: 0,\n exactPixels: 0,\n significantPixels: 0,\n channelError: 0,\n maximumChannelError: 0,\n columns:\n columnWidth > 0\n ? Array.from({ length: columnCount }, () => ({\n sampledPixels: 0,\n significantPixels: 0,\n maximumChannelError: 0,\n }))\n : [],\n }))\n : [];\n\n for (const region of regions) {\n assertPixelBuffer(\n \"Source\",\n region.sourcePixels,\n region.width,\n region.height,\n );\n assertPixelBuffer(\n \"Preview\",\n region.previewPixels,\n region.width,\n region.height,\n );\n const firstY = (stride - (region.sourceY % stride) + stride) % stride;\n for (let y = firstY; y < region.height; y += stride) {\n for (let x = 0; x < region.width; x += stride) {\n const offset = (y * region.width + x) * 4;\n const channel0 = Math.abs(\n region.sourcePixels[offset]! - region.previewPixels[offset]!,\n );\n const channel1 = Math.abs(\n region.sourcePixels[offset + 1]! - region.previewPixels[offset + 1]!,\n );\n const channel2 = Math.abs(\n region.sourcePixels[offset + 2]! - region.previewPixels[offset + 2]!,\n );\n const pixelMaximum = Math.max(channel0, channel1, channel2);\n channelError += channel0 + channel1 + channel2;\n maximumChannelError = Math.max(maximumChannelError, pixelMaximum);\n sampledPixels += 1;\n if (pixelMaximum === 0) exactPixels += 1;\n if (pixelMaximum >= SIGNIFICANT_PIXEL_DELTA) significantPixels += 1;\n\n if (bandHeight > 0) {\n const bandIndex = Math.min(\n accumulators.length - 1,\n Math.floor((region.sourceY + y - gridOriginY) / bandHeight),\n );\n const accumulator = accumulators[bandIndex]!;\n accumulator.sampledPixels += 1;\n accumulator.channelError += channel0 + channel1 + channel2;\n if (pixelMaximum === 0) accumulator.exactPixels += 1;\n if (pixelMaximum >= SIGNIFICANT_PIXEL_DELTA) {\n accumulator.significantPixels += 1;\n }\n accumulator.maximumChannelError = Math.max(\n accumulator.maximumChannelError,\n pixelMaximum,\n );\n if (columnWidth > 0) {\n const column =\n accumulator.columns[\n Math.min(\n accumulator.columns.length - 1,\n Math.floor(x / columnWidth),\n )\n ]!;\n column.sampledPixels += 1;\n if (pixelMaximum >= SIGNIFICANT_PIXEL_DELTA) {\n column.significantPixels += 1;\n }\n column.maximumChannelError = Math.max(\n column.maximumChannelError,\n pixelMaximum,\n );\n }\n }\n }\n }\n }\n\n if (sampledPixels === 0) {\n throw new Error(\"No source and preview pixels landed on the sample grid.\");\n }\n const meanAbsoluteChannelError = channelError / (sampledPixels * 3);\n return {\n sampledPixels,\n sampleStride: stride,\n meanAbsoluteChannelError: roundMetric(meanAbsoluteChannelError),\n normalizedMeanAbsoluteError: roundMetric(meanAbsoluteChannelError / 255),\n exactPixelRatio: roundMetric(exactPixels / sampledPixels),\n significantPixelRatio: roundMetric(significantPixels / sampledPixels),\n significantPixelDelta: SIGNIFICANT_PIXEL_DELTA,\n maximumChannelError,\n bands: finalizeBands(\n accumulators,\n bandHeight,\n gridOriginY,\n columnWidth,\n bandGrid ? bandGrid.documentEndY : documentEndY,\n bandGrid ? bandGrid.documentWidth : documentWidth,\n ),\n };\n}\n\nexport function combinePreviewPixelMetrics(\n metrics: PixelComparisonMetrics[],\n): PixelComparisonMetrics {\n if (metrics.length === 0) {\n throw new Error(\"No pixel-comparison metrics were provided.\");\n }\n const sampleStride = metrics[0]!.sampleStride;\n const significantPixelDelta = metrics[0]!.significantPixelDelta;\n if (\n metrics.some(\n (metric) =>\n metric.sampleStride !== sampleStride ||\n metric.significantPixelDelta !== significantPixelDelta,\n )\n ) {\n throw new Error(\n \"Pixel-comparison metrics must use the same stride and threshold.\",\n );\n }\n const sampledPixels = metrics.reduce(\n (total, metric) => total + metric.sampledPixels,\n 0,\n );\n const weighted = (value: (metric: PixelComparisonMetrics) => number) =>\n metrics.reduce(\n (total, metric) => total + value(metric) * metric.sampledPixels,\n 0,\n ) / sampledPixels;\n const meanAbsoluteChannelError = weighted(\n (metric) => metric.meanAbsoluteChannelError,\n );\n return {\n sampledPixels,\n sampleStride,\n meanAbsoluteChannelError: roundMetric(meanAbsoluteChannelError),\n normalizedMeanAbsoluteError: roundMetric(meanAbsoluteChannelError / 255),\n exactPixelRatio: roundMetric(weighted((metric) => metric.exactPixelRatio)),\n significantPixelRatio: roundMetric(\n weighted((metric) => metric.significantPixelRatio),\n ),\n significantPixelDelta,\n maximumChannelError: Math.max(\n ...metrics.map((metric) => metric.maximumChannelError),\n ),\n bands: mergePixelComparisonBands(metrics),\n };\n}\n\n/** Merge per-metric bands by document-Y range.\n *\n * Callers combine metrics computed over different regions of the SAME\n * document, so two inputs can carry bands covering the same Y range. Those\n * are summed; disjoint ranges are concatenated. Ratios are recomputed from\n * summed counts rather than averaged, so a band sampled unevenly across\n * inputs still reports its true ratio. */\n/** Merge two column lists of the same band by their X range, summing counts so\n * a ratio stays true rather than averaging two unequal samples. */\nfunction mergeColumns(\n left: PixelComparisonColumn[],\n right: PixelComparisonColumn[],\n): PixelComparisonColumn[] {\n const byRange = new Map<number, PixelComparisonColumn & { hits: number }>();\n for (const column of [...left, ...right]) {\n const existing = byRange.get(column.startX);\n if (!existing) {\n byRange.set(column.startX, {\n ...column,\n hits: column.significantPixelRatio * column.sampledPixels,\n });\n continue;\n }\n existing.sampledPixels += column.sampledPixels;\n existing.hits += column.significantPixelRatio * column.sampledPixels;\n existing.maximumChannelError = Math.max(\n existing.maximumChannelError,\n column.maximumChannelError,\n );\n }\n return [...byRange.values()]\n .map(({ hits, ...column }) => ({\n ...column,\n significantPixelRatio: roundMetric(hits / column.sampledPixels),\n }))\n .sort((a, b) => a.startX - b.startX)\n .map((column, columnIndex) => ({ ...column, columnIndex }));\n}\n\nfunction mergePixelComparisonBands(\n metrics: PixelComparisonMetrics[],\n): PixelComparisonBand[] {\n const byRange = new Map<\n string,\n {\n band: PixelComparisonBand;\n exactPixels: number;\n significantPixels: number;\n channelError: number;\n }\n >();\n for (const metric of metrics) {\n for (const band of metric.bands ?? []) {\n const key = `${band.sourceStartY}:${band.sourceEndY}`;\n const existing = byRange.get(key);\n const exactPixels = band.exactPixelRatio * band.sampledPixels;\n const significantPixels = band.significantPixelRatio * band.sampledPixels;\n const channelError =\n band.meanAbsoluteChannelError * band.sampledPixels * 3;\n if (!existing) {\n byRange.set(key, {\n band: { ...band },\n exactPixels,\n significantPixels,\n channelError,\n });\n continue;\n }\n existing.band.sampledPixels += band.sampledPixels;\n existing.band.maximumChannelError = Math.max(\n existing.band.maximumChannelError,\n band.maximumChannelError,\n );\n existing.exactPixels += exactPixels;\n existing.significantPixels += significantPixels;\n existing.channelError += channelError;\n existing.band.columns = mergeColumns(existing.band.columns, band.columns);\n }\n }\n return [...byRange.values()]\n .map(({ band, exactPixels, significantPixels, channelError }) => ({\n ...band,\n exactPixelRatio: roundMetric(exactPixels / band.sampledPixels),\n significantPixelRatio: roundMetric(\n significantPixels / band.sampledPixels,\n ),\n meanAbsoluteChannelError: roundMetric(\n channelError / (band.sampledPixels * 3),\n ),\n }))\n .sort((left, right) => left.sourceStartY - right.sourceStartY)\n .map((band, index) => ({ ...band, bandIndex: index }));\n}\n","import { parse, type DefaultTreeAdapterMap } from \"parse5\";\nimport type {\n RepeatedViewportPixelEvidence,\n RepeatedViewportPixelRegion,\n} from \"./preview-comparison\";\nimport type { DurableSourcePageEvidence } from \"./source-page-evidence\";\n\n/**\n * Pure source-baseline checks shared by Mist's `compare_preview_to_source`\n * tool and the Fluid CLI's `fluid theme compare`. Both read the same durable\n * crawl bundle, so both must refuse (or flag) the same baselines.\n */\n\n/** Largest decoded source bitmap one comparison will hold in memory. */\nexport const COMPARISON_MAX_SOURCE_PIXELS = 32_000_000;\nconst COPY_MISMATCH_EXCERPT_CHARS = 240;\n// Height or length alone is not a verdict. A full-page source becomes\n// inadmissible only when signed DOM evidence also shows a large blocking\n// dialog that can repeat across its viewport tiles, or when that dialog covers\n// almost the whole first viewport.\nconst BLOCKING_OVERLAY_MIN_VIEWPORT_AREA_RATIO = 0.35;\nconst FULL_VIEWPORT_OVERLAY_AREA_RATIO = 0.85;\nconst REPEATED_OVERLAY_MIN_HEIGHT_DRIFT_VIEWPORTS = 1;\n// Serialized HTML cannot prove computed visibility. Treat its open-dialog\n// state only as a candidate and require one coherent, overlay-shaped repeated\n// pixel region. This prevents unrelated repeated pixels across the page from\n// accumulating into visibility proof. In the retained validation corpus,\n// visible repeated overlays produced a coherent 0.105-0.476 region while\n// clean screenshots with stale open markup stayed at or below 0.062.\nconst SERIALIZED_DIALOG_MIN_PANEL_AREA_RATIO = 0.35;\nconst SERIALIZED_DIALOG_MIN_EDGE_BANNER_AREA_RATIO = 0.1;\nconst SERIALIZED_DIALOG_MIN_EDGE_BANNER_WIDTH_RATIO = 0.8;\nconst SERIALIZED_DIALOG_EDGE_TOLERANCE_RATIO = 0.02;\nconst SERIALIZED_DIALOG_MIN_LANDMARK_REGION_IOU = 0.5;\n\nexport interface SourceBaselineAdmissibility {\n status: \"usable\" | \"contaminated\" | \"needs_adjudication\";\n eligibleForVisualPass: boolean;\n blockingOverlayCount: number;\n serializedDialogNeedsAdjudicationCount: number;\n screenshotViewportHeights: number;\n screenshotHeightDriftViewports: number;\n serializedDialogRepeatedPixelRatio: number | null;\n serializedDialogRepeatedRegions: RepeatedViewportPixelRegion[];\n reasons: string[];\n recovery: string | null;\n}\n\ntype ParsedHtmlNode = DefaultTreeAdapterMap[\"node\"];\ntype ParsedHtmlElement = DefaultTreeAdapterMap[\"element\"];\ntype ParsedHtmlTemplate = DefaultTreeAdapterMap[\"template\"];\n\nfunction isParsedHtmlElement(node: ParsedHtmlNode): node is ParsedHtmlElement {\n return \"tagName\" in node && \"attrs\" in node;\n}\n\nfunction parsedHtmlAttributes(element: ParsedHtmlElement): Map<string, string> {\n return new Map(\n element.attrs.map(({ name, value }) => [name.toLowerCase(), value]),\n );\n}\n\nfunction isDeclarativeShadowTemplate(\n element: ParsedHtmlElement,\n attributes: ReadonlyMap<string, string>,\n): element is ParsedHtmlTemplate {\n if (element.tagName.toLowerCase() !== \"template\") return false;\n const mode = attributes.get(\"shadowrootmode\")?.toLowerCase();\n return mode === \"open\" || mode === \"closed\";\n}\n\nfunction hasInertSerializedStyle(style: string): boolean {\n const declarations = new Map(\n style\n .split(\";\")\n .map((declaration) => declaration.split(\":\", 2))\n .filter(\n (entry): entry is [string, string] =>\n entry.length === 2 &&\n entry[0] !== undefined &&\n entry[1] !== undefined,\n )\n .map(([property, value]) => [\n property.trim().toLowerCase(),\n value\n .replace(/\\s*!important\\s*$/iu, \"\")\n .trim()\n .toLowerCase(),\n ]),\n );\n return (\n declarations.get(\"display\") === \"none\" ||\n [\"hidden\", \"collapse\"].includes(declarations.get(\"visibility\") ?? \"\") ||\n declarations.get(\"content-visibility\") === \"hidden\" ||\n Number.parseFloat(declarations.get(\"opacity\") ?? \"1\") === 0\n );\n}\n\nexport function serializedOpenDialogLabels(html: string): Array<{ label: string }> {\n const labels = new Set<string>();\n const root = parse(html);\n const visit = (node: ParsedHtmlNode, inertAncestor: boolean): void => {\n if (labels.size >= 20) return;\n let inert = inertAncestor;\n if (isParsedHtmlElement(node)) {\n const tag = node.tagName.toLowerCase();\n const attributes = parsedHtmlAttributes(node);\n if (tag === \"template\") {\n if (!isDeclarativeShadowTemplate(node, attributes) || inert) return;\n for (const child of node.content.childNodes) {\n visit(child, inert);\n if (labels.size >= 20) break;\n }\n return;\n }\n if ([\"script\", \"style\", \"noscript\"].includes(tag)) return;\n inert =\n inert ||\n attributes.has(\"hidden\") ||\n hasInertSerializedStyle(attributes.get(\"style\") ?? \"\");\n if (tag === \"dialog\" && attributes.has(\"open\") && !inert) {\n const label = (attributes.get(\"aria-label\") ?? \"\")\n .replace(/\\s+/gu, \" \")\n .trim();\n labels.add(label || \"open dialog\");\n }\n }\n if (inert) return;\n for (const child of \"childNodes\" in node ? node.childNodes : []) {\n visit(child, inert);\n if (labels.size >= 20) break;\n }\n };\n visit(root, false);\n return [...labels].map((label) => ({ label }));\n}\n\nexport function normalizedRoute(value: string, base?: string): string {\n const url = new URL(value, base ?? \"http://mist.local\");\n return `${url.pathname}${url.search}`;\n}\n\n/**\n * Refuse a mixed capture: the sidecar must be bound to the exact screenshot\n * bytes being compared, at the requested viewport, for a successful source\n * route that maps to the local route being rendered.\n */\nexport function assertSourceEvidenceMatchesComparison(\n durable: DurableSourcePageEvidence,\n source: { relativePath: string; sha256: string },\n args: { width: number; height: number; path: string; sourceRoute?: string },\n retryHint = \" If the source platform intentionally redirects or localizes this page to a different pathname than the Fluid preview, retry with source_route set to the signed source pathname and keep path set to the local Fluid route.\",\n): void {\n const screenshotPath = durable.screenshot.path\n .replaceAll(\"\\\\\", \"/\")\n .replace(/^\\.\\//, \"\");\n if (\n screenshotPath !== source.relativePath ||\n durable.screenshot.sha256 !== source.sha256\n ) {\n throw new Error(\n \"Rendered source evidence is not bound to the supplied source screenshot path and SHA-256.\",\n );\n }\n if (\n durable.requestedViewport.width !== args.width ||\n durable.requestedViewport.height !== args.height ||\n durable.screenshot.width !== args.width\n ) {\n throw new Error(\n `Rendered source evidence was captured at ${durable.requestedViewport.width}×${durable.requestedViewport.height} with a ${durable.screenshot.width}px-wide screenshot, but this comparison requested ${args.width}×${args.height}.`,\n );\n }\n const expectedSourceRoute = args.sourceRoute ?? args.path;\n if (\n normalizedRoute(durable.finalUrl) !==\n normalizedRoute(expectedSourceRoute, durable.finalUrl) ||\n durable.statusCode >= 400\n ) {\n throw new Error(\n `Rendered source evidence is for ${normalizedRoute(durable.finalUrl)} (HTTP ${durable.statusCode}), not the requested successful source route ${normalizedRoute(expectedSourceRoute, durable.finalUrl)}.${args.sourceRoute === undefined ? retryHint : \"\"}`,\n );\n }\n}\n\nexport function copyMismatchExcerpt(value: string | null): string {\n if (value === null) return \"<missing line>\";\n const bounded = value.slice(0, COPY_MISMATCH_EXCERPT_CHARS);\n return JSON.stringify(\n bounded.length === value.length ? bounded : `${bounded}…`,\n );\n}\n\nexport function sourceLandmarkViewportAreaRatio(\n landmark: DurableSourcePageEvidence[\"rendered\"][\"landmarks\"][number],\n viewport: DurableSourcePageEvidence[\"requestedViewport\"],\n): number {\n const left = Math.max(0, landmark.rect.x);\n const top = Math.max(0, landmark.rect.y);\n const right = Math.min(viewport.width, landmark.rect.x + landmark.rect.width);\n const bottom = Math.min(\n viewport.height,\n landmark.rect.y + landmark.rect.height,\n );\n const visibleWidth = Math.max(0, right - left);\n const visibleHeight = Math.max(0, bottom - top);\n return (visibleWidth * visibleHeight) / (viewport.width * viewport.height);\n}\n\nexport function sourceLandmarkLabel(\n landmark: DurableSourcePageEvidence[\"rendered\"][\"landmarks\"][number],\n): string {\n const value = landmark.ariaLabel || landmark.text || landmark.tag;\n const normalized = value.replace(/\\s+/g, \" \").trim();\n return normalized.slice(0, 120) || \"dialog\";\n}\n\nexport function assessSourceBaselineAdmissibility(\n evidence: DurableSourcePageEvidence,\n hasSerializedHtml: boolean,\n serializedOpenDialogs: ReadonlyArray<{ label: string }>,\n serializedDialogPixelEvidence: RepeatedViewportPixelEvidence | null,\n): SourceBaselineAdmissibility {\n const viewport = evidence.requestedViewport;\n const screenshotViewportHeights =\n evidence.screenshot.height / viewport.height;\n const screenshotHeightDriftViewports =\n Math.abs(evidence.screenshot.height - evidence.rendered.document.height) /\n viewport.height;\n const serializedOpenDialogLabelSet = new Set(\n serializedOpenDialogs.map(({ label }) => label.toLowerCase()),\n );\n const serializedDialogRepeatedPixelRatio =\n serializedDialogPixelEvidence?.exactPixelRatio ?? null;\n const serializedDialogRepeatedRegions =\n serializedDialogPixelEvidence?.regions ?? [];\n const serializedOverlayRegions = serializedDialogRepeatedRegions.filter(\n (region) => isOverlayShapedRepeatedRegion(region, viewport),\n );\n const primarySerializedOverlayRegion = serializedOverlayRegions[0];\n const renderedNativeDialogLandmarks = evidence.rendered.landmarks.filter(\n (landmark) => landmark.tag.toLowerCase() === \"dialog\",\n );\n const renderedBlockingOverlays = evidence.rendered.landmarks\n .filter((landmark) => {\n const tag = landmark.tag.toLowerCase();\n const role = landmark.role?.toLowerCase();\n if (tag === \"dialog\") {\n if (landmark.open === false) return false;\n if (landmark.open === true) return true;\n if (hasSerializedHtml) {\n return (\n serializedOpenDialogLabelSet.has(\n sourceLandmarkLabel(landmark).toLowerCase(),\n ) &&\n serializedOverlayRegions.some((region) =>\n repeatedRegionMatchesLandmark(region, landmark.rect),\n )\n );\n }\n return true;\n }\n return role === \"dialog\" || role === \"alertdialog\";\n })\n .map((landmark) => ({\n label: sourceLandmarkLabel(landmark),\n areaRatio: sourceLandmarkViewportAreaRatio(landmark, viewport),\n source: \"rendered\" as const,\n }))\n .filter(\n ({ areaRatio }) => areaRatio >= BLOCKING_OVERLAY_MIN_VIEWPORT_AREA_RATIO,\n );\n const unassociatedSerializedOpenDialogs = serializedOpenDialogs\n .filter(({ label }) => {\n const normalizedLabel = label.toLowerCase();\n const hasMatchingRenderedLandmark = renderedNativeDialogLandmarks.some(\n (landmark) =>\n sourceLandmarkLabel(landmark).toLowerCase() === normalizedLabel,\n );\n return (\n !hasMatchingRenderedLandmark && serializedOverlayRegions.length > 0\n );\n })\n .map(({ label }) => label);\n const blockingOverlays = renderedBlockingOverlays;\n const hasFullViewportOverlay = renderedBlockingOverlays.some(\n ({ areaRatio }) => areaRatio >= FULL_VIEWPORT_OVERLAY_AREA_RATIO,\n );\n const hasMultiViewportBlockingOverlay =\n blockingOverlays.length > 0 && screenshotViewportHeights > 1.5;\n const hasHeightDriftWithBlockingOverlay =\n blockingOverlays.length > 0 &&\n screenshotHeightDriftViewports >=\n REPEATED_OVERLAY_MIN_HEIGHT_DRIFT_VIEWPORTS;\n const contaminated =\n hasFullViewportOverlay ||\n hasMultiViewportBlockingOverlay ||\n hasHeightDriftWithBlockingOverlay;\n const needsAdjudication =\n !contaminated && unassociatedSerializedOpenDialogs.length > 0;\n const status = contaminated\n ? \"contaminated\"\n : needsAdjudication\n ? \"needs_adjudication\"\n : \"usable\";\n const reasons = [\n ...(contaminated\n ? [\n ...blockingOverlays.map(\n ({ label, areaRatio }) =>\n `Blocking dialog \"${label}\" covers ${(areaRatio * 100).toFixed(1)}% of the requested viewport.`,\n ),\n ...(hasMultiViewportBlockingOverlay\n ? [\n `The full-page source spans ${screenshotViewportHeights.toFixed(2)} viewport heights while a blocking dialog was open, so stitched pixels may repeat the overlay.`,\n ]\n : []),\n ...(hasHeightDriftWithBlockingOverlay\n ? [\n `The screenshot differs from the signed rendered-document height by ${screenshotHeightDriftViewports.toFixed(2)} viewport heights while a blocking dialog was open, so stitched pixels may repeat the overlay.`,\n ]\n : []),\n ]\n : []),\n ...(needsAdjudication\n ? [\n `The signed source HTML contains ${unassociatedSerializedOpenDialogs.map((label) => `\"${label}\"`).join(\", \")} as open dialog markup, and a coherent repeated region covers ${((primarySerializedOverlayRegion?.viewportAreaRatio ?? 0) * 100).toFixed(2)}% of the viewport at x=${primarySerializedOverlayRegion?.x ?? 0}, y=${primarySerializedOverlayRegion?.y ?? 0}, width=${primarySerializedOverlayRegion?.width ?? 0}, height=${primarySerializedOverlayRegion?.height ?? 0}. No rendered dialog geometry links that region to the markup, so Mist cannot classify it as a painted dialog automatically.`,\n ]\n : []),\n ];\n const recoveryLabel =\n blockingOverlays[0]?.label ?? unassociatedSerializedOpenDialogs[0];\n return {\n status,\n eligibleForVisualPass: status === \"usable\",\n blockingOverlayCount: blockingOverlays.length,\n serializedDialogNeedsAdjudicationCount:\n unassociatedSerializedOpenDialogs.length,\n screenshotViewportHeights,\n screenshotHeightDriftViewports,\n serializedDialogRepeatedPixelRatio,\n serializedDialogRepeatedRegions,\n reasons,\n recovery:\n status === \"usable\"\n ? null\n : `Inspect the retained pixels, then dismiss ${recoveryLabel ? `\"${recoveryLabel}\"` : \"the observed overlay\"} with a concrete rendered selector and recrawl, or use clean bounded source cells. Until then this source is not eligible for an automatic visual pass.`,\n };\n}\n\nexport function isOverlayShapedRepeatedRegion(\n region: RepeatedViewportPixelRegion,\n viewport: { width: number; height: number },\n): boolean {\n if (region.viewportAreaRatio >= SERIALIZED_DIALOG_MIN_PANEL_AREA_RATIO) {\n return true;\n }\n const widthRatio = region.width / viewport.width;\n const edgeTolerance =\n viewport.height * SERIALIZED_DIALOG_EDGE_TOLERANCE_RATIO;\n const touchesVerticalEdge =\n region.y <= edgeTolerance ||\n region.y + region.height >= viewport.height - edgeTolerance;\n return (\n region.viewportAreaRatio >= SERIALIZED_DIALOG_MIN_EDGE_BANNER_AREA_RATIO &&\n widthRatio >= SERIALIZED_DIALOG_MIN_EDGE_BANNER_WIDTH_RATIO &&\n touchesVerticalEdge\n );\n}\n\nexport function repeatedRegionMatchesLandmark(\n region: RepeatedViewportPixelRegion,\n landmark: { x: number; y: number; width: number; height: number },\n): boolean {\n const left = Math.max(region.x, landmark.x);\n const top = Math.max(region.y, landmark.y);\n const right = Math.min(region.x + region.width, landmark.x + landmark.width);\n const bottom = Math.min(\n region.y + region.height,\n landmark.y + landmark.height,\n );\n const intersection = Math.max(0, right - left) * Math.max(0, bottom - top);\n const regionArea = region.width * region.height;\n const landmarkArea = landmark.width * landmark.height;\n const union = regionArea + landmarkArea - intersection;\n return (\n union > 0 &&\n intersection / union >= SERIALIZED_DIALOG_MIN_LANDMARK_REGION_IOU\n );\n}\n","import {\n MAX_FULL_PAGE_HEIGHT_DELTA_RATIO,\n type PixelComparisonBand,\n} from \"./preview-comparison\";\nimport type { SourceBaselineAdmissibility } from \"./source-baseline-admissibility\";\n\n/**\n * The verdict half of a source-vs-preview comparison, shared by Mist's\n * `compare_preview_to_source` tool and the Fluid CLI's `fluid theme compare`\n * so both refuse the same evidence and rank the same bands.\n */\n\nexport interface ComparisonParitySummary {\n exact: boolean;\n sourceCount: number;\n previewCount: number;\n}\n\nexport interface ComparisonOutcomeInput {\n mode: \"viewport\" | \"full\";\n copyMode: \"exact\" | \"diagnostic\";\n geometryMode: \"strict\" | \"diagnostic\";\n mediaMode: \"strict\" | \"diagnostic\";\n sourceAdmissibility: Pick<SourceBaselineAdmissibility, \"status\">;\n sourceShadowRootsTruncated: boolean | undefined;\n previewShadowRootsTruncated: boolean | undefined;\n httpStatus: number | null;\n truncated: boolean;\n horizontalOverflow: boolean;\n mediaHasFailures: boolean;\n /** Null in viewport mode, where image parity is diagnostic-only. */\n imageParity: ComparisonParitySummary | null;\n videoParity: ComparisonParitySummary;\n copyTruncated: boolean;\n copy: { exact: boolean; firstMismatchLine: number | null };\n heightDeltaRatio: number;\n}\n\nexport interface ComparisonOutcome {\n /** Hard refusals. Any entry makes the comparison a failed tool call. */\n operationalFailures: string[];\n /** Differences retained for specialist judgment by the requested policy. */\n specialistDiagnostics: string[];\n}\n\nexport function classifyComparisonOutcome(\n input: ComparisonOutcomeInput,\n): ComparisonOutcome {\n const {\n mode,\n copyMode,\n geometryMode,\n mediaMode,\n imageParity,\n videoParity,\n heightDeltaRatio,\n } = input;\n const heightOverThreshold =\n mode === \"full\" && heightDeltaRatio > MAX_FULL_PAGE_HEIGHT_DELTA_RATIO;\n const operationalFailures = [\n ...(input.sourceAdmissibility.status === \"contaminated\"\n ? [\"source visual baseline is contaminated\"]\n : []),\n ...(input.sourceShadowRootsTruncated === undefined\n ? [\"source open-shadow-root traversal metadata is missing\"]\n : input.sourceShadowRootsTruncated\n ? [\"source open-shadow-root evidence exceeded its traversal limit\"]\n : []),\n ...(input.previewShadowRootsTruncated === undefined\n ? [\"local open-shadow-root traversal metadata is missing\"]\n : input.previewShadowRootsTruncated\n ? [\"local open-shadow-root evidence exceeded its traversal limit\"]\n : []),\n ...(input.httpStatus !== null && input.httpStatus >= 400\n ? [`local route returned HTTP ${input.httpStatus}`]\n : []),\n ...(input.truncated ? [\"full-page capture was truncated\"] : []),\n ...(input.horizontalOverflow ? [\"local page has horizontal overflow\"] : []),\n ...(input.mediaHasFailures ? [\"local media was unresolved\"] : []),\n ...(mediaMode === \"strict\" && imageParity && !imageParity.exact\n ? [\n `source/local image parity failed (${imageParity.sourceCount} source, ${imageParity.previewCount} local)`,\n ]\n : []),\n ...(mediaMode === \"strict\" && !videoParity.exact\n ? [\n `source/local video parity failed (${videoParity.sourceCount} source, ${videoParity.previewCount} local)`,\n ]\n : []),\n ...(input.copyTruncated\n ? [\"source or local rendered copy exceeded the evidence limit\"]\n : []),\n ...(copyMode === \"exact\" && !input.copy.exact\n ? [\n `rendered copy differs at line ${input.copy.firstMismatchLine ?? \"unknown\"}`,\n ]\n : []),\n ...(geometryMode === \"strict\" && heightOverThreshold\n ? [\n `local document height differs from source by ${(heightDeltaRatio * 100).toFixed(2)}% (maximum ${MAX_FULL_PAGE_HEIGHT_DELTA_RATIO * 100}%)`,\n ]\n : []),\n ];\n const specialistDiagnostics = [\n ...(input.sourceAdmissibility.status === \"needs_adjudication\"\n ? [\n \"serialized open-dialog markup and a repeated source region lack attributable rendered geometry; inspect the retained source pixels and recrawl after dismissing any painted overlay before claiming a visual pass\",\n ]\n : []),\n ...(mediaMode === \"diagnostic\" && imageParity && !imageParity.exact\n ? [\n `source/local image parity differs (${imageParity.sourceCount} source, ${imageParity.previewCount} local)`,\n ]\n : []),\n ...(mediaMode === \"diagnostic\" && !videoParity.exact\n ? [\n `source/local video parity differs (${videoParity.sourceCount} source, ${videoParity.previewCount} local)`,\n ]\n : []),\n ...(geometryMode === \"diagnostic\" && heightOverThreshold\n ? [\n `local document height differs from source by ${(heightDeltaRatio * 100).toFixed(2)}%`,\n ]\n : []),\n ];\n return { operationalFailures, specialistDiagnostics };\n}\n\n/** Bands that carry any significant-pixel delta, worst first. */\nexport function rankComparisonBands(\n bands: readonly PixelComparisonBand[],\n): PixelComparisonBand[] {\n return [...bands]\n .filter((band) => band.significantPixelRatio > 0)\n .sort(\n (left, right) => right.significantPixelRatio - left.significantPixelRatio,\n );\n}\n\n/**\n * The worst-first band line. A whole-page ratio says THAT the clone is off;\n * these Y ranges say WHERE, so a rework round can scroll straight to the\n * worst band instead of re-reading two full-page screenshots. Returns an\n * empty string when banding was disabled; the leading space matches Mist's\n * sentence-joined tool output.\n */\nexport function formatBandedDiffSummary(\n bands: readonly PixelComparisonBand[],\n): string {\n const rankedBands = rankComparisonBands(bands);\n if (rankedBands.length === 0) {\n return bands.length\n ? \" Banded diff: no band carries a significant-pixel delta.\"\n : \"\";\n }\n return ` Banded diff (worst first, source document Y): ${rankedBands\n .slice(0, 5)\n .map((band) => {\n // Name the worst column only when the delta is actually lopsided; on an\n // evenly-wrong band it is noise.\n const worst = [...band.columns].sort(\n (left, right) =>\n right.significantPixelRatio - left.significantPixelRatio,\n )[0];\n const lopsided =\n worst && worst.significantPixelRatio > band.significantPixelRatio * 1.5;\n return `${band.sourceStartY}-${band.sourceEndY}px ${(\n band.significantPixelRatio * 100\n ).toFixed(1)}%${\n lopsided\n ? ` (worst x ${worst.startX}-${worst.endX}px at ${(\n worst.significantPixelRatio * 100\n ).toFixed(1)}%)`\n : \"\"\n }`;\n })\n .join(\", \")}${\n rankedBands.length > 5\n ? ` (+${rankedBands.length - 5} more bands with deltas)`\n : \"\"\n }. Fix the worst band first, then re-compare; every band is in the structured \\`bands\\` array.`;\n}\n","import { createHash } from \"node:crypto\";\nimport { readdir, readFile, realpath, stat } from \"node:fs/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { CRAWL_BASELINE_DIR } from \"@fluid-app/mist-core/crawl-evidence\";\nimport {\n normalizedRoute,\n serializedOpenDialogLabels,\n} from \"@fluid-app/mist-core/source-baseline-admissibility\";\nimport {\n parseDurableSourcePageEvidence,\n type DurableSourcePageEvidence,\n} from \"@fluid-app/mist-core/source-page-evidence\";\n\n/** Same read limits Mist applies to a durable source bundle. */\nconst SOURCE_IMAGE_MAX_BYTES = 50 * 1024 * 1024;\nconst SOURCE_PAGE_EVIDENCE_MAX_BYTES = 16 * 1024 * 1024;\nconst SOURCE_HTML_MAX_BYTES = 10 * 1024 * 1024;\nconst SIDECAR_SUFFIX = \".page-evidence.json\";\nconst BASELINE_SEGMENTS = CRAWL_BASELINE_DIR.split(\"/\");\n\nexport class SourceBundleError extends Error {\n constructor(\n message: string,\n readonly candidates?: Array<Record<string, unknown>>,\n ) {\n super(message);\n this.name = \"SourceBundleError\";\n }\n}\n\nexport interface SourceBundle {\n /** Directory that holds `.mist-desktop/source-baselines/`. */\n root: string;\n sidecarPath: string;\n sidecarRelativePath: string;\n sidecarSha256: string;\n durable: DurableSourcePageEvidence;\n screenshot: {\n fullPath: string;\n relativePath: string;\n bytes: Buffer;\n sha256: string;\n };\n hasSerializedHtml: boolean;\n serializedOpenDialogs: Array<{ label: string }>;\n}\n\nexport interface SidecarFilter {\n /** The signed source route to look for (`--source-route`, else `--route`). */\n sourceRoute: string;\n viewport?: { width: number; height: number };\n}\n\nfunction isInside(directory: string, candidate: string): boolean {\n const rel = relative(directory, candidate);\n return rel.length > 0 && !rel.startsWith(\"..\") && !rel.startsWith(sep);\n}\n\nasync function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Turn `--source` into one sidecar. Accepts the sidecar itself, its\n * screenshot, a `source-baselines` directory, or the project root above it.\n * A directory can hold several captures (one per route and viewport), so it\n * picks the newest one for the requested route and viewport, and refuses\n * rather than guessing when none match.\n */\nexport async function resolveSidecarPath(\n source: string,\n filter: SidecarFilter,\n): Promise<string> {\n const input = resolve(source);\n if (!(await isDirectory(input))) {\n if (input.endsWith(SIDECAR_SUFFIX)) return input;\n const sibling = `${input.replace(/\\.[^.\\\\/]+$/, \"\")}${SIDECAR_SUFFIX}`;\n try {\n await stat(sibling);\n return sibling;\n } catch {\n throw new SourceBundleError(\n `${source} is not a .page-evidence.json sidecar or a bundle directory. Pass the sidecar \\`fluid theme crawl --evidence\\` printed, or its directory.`,\n );\n }\n }\n const baselineDir = (await isDirectory(join(input, ...BASELINE_SEGMENTS)))\n ? join(input, ...BASELINE_SEGMENTS)\n : input;\n const names = (await readdir(baselineDir)).filter((name) =>\n name.endsWith(SIDECAR_SUFFIX),\n );\n if (names.length === 0) {\n throw new SourceBundleError(\n `No .page-evidence.json sidecar found in ${baselineDir}. Capture one with \\`fluid theme crawl <url> --evidence <dir> --viewport WxH --full-page\\`.`,\n );\n }\n const candidates: Array<{\n path: string;\n capturedAt: string;\n route: string;\n viewport: string;\n matches: boolean;\n }> = [];\n for (const name of names) {\n const path = join(baselineDir, name);\n try {\n const raw = JSON.parse(await readFile(path, \"utf8\")) as {\n capturedAt?: unknown;\n finalUrl?: unknown;\n requestedViewport?: { width?: unknown; height?: unknown };\n };\n const finalUrl = typeof raw.finalUrl === \"string\" ? raw.finalUrl : \"\";\n const width = raw.requestedViewport?.width;\n const height = raw.requestedViewport?.height;\n const route = finalUrl ? normalizedRoute(finalUrl) : \"\";\n const routeMatches =\n route !== \"\" &&\n route === normalizedRoute(filter.sourceRoute, finalUrl);\n const viewportMatches =\n !filter.viewport ||\n (width === filter.viewport.width && height === filter.viewport.height);\n candidates.push({\n path,\n capturedAt: typeof raw.capturedAt === \"string\" ? raw.capturedAt : \"\",\n route,\n viewport: `${String(width)}x${String(height)}`,\n matches: routeMatches && viewportMatches,\n });\n } catch {\n // An unreadable sidecar is simply not a candidate; a direct path to it\n // still fails loudly in readSourceBundle.\n }\n }\n const matching = candidates\n .filter((candidate) => candidate.matches)\n .sort((left, right) => right.capturedAt.localeCompare(left.capturedAt));\n if (matching.length === 0) {\n throw new SourceBundleError(\n `No sidecar in ${baselineDir} matches source route ${filter.sourceRoute}${filter.viewport ? ` at ${filter.viewport.width}x${filter.viewport.height}` : \"\"}. Pass --source-route when the source lives at a different path than --route, --viewport to choose a capture, or the sidecar path itself.`,\n candidates.map(({ path, capturedAt, route, viewport }) => ({\n sidecar: path,\n captured_at: capturedAt,\n route,\n viewport,\n })),\n );\n }\n return matching[0]!.path;\n}\n\nasync function readBounded(\n path: string,\n maxBytes: number,\n label: string,\n): Promise<Buffer> {\n const info = await stat(path);\n if (!info.isFile()) throw new SourceBundleError(`${label} is not a file.`);\n if (info.size > maxBytes) {\n throw new SourceBundleError(\n `${label} is ${(info.size / (1024 * 1024)).toFixed(1)}MB; the comparison limit is ${maxBytes / (1024 * 1024)}MB.`,\n );\n }\n return readFile(path);\n}\n\nfunction sha256(bytes: Buffer): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\n/**\n * Read and verify one durable bundle: the sidecar must parse, and every file\n * it names must resolve inside the same `source-baselines` directory. The\n * screenshot's path/hash binding is checked by the shared\n * `assertSourceEvidenceMatchesComparison`.\n */\nexport async function readSourceBundle(sidecarPath: string): Promise<SourceBundle> {\n const canonicalSidecar = await realpath(sidecarPath);\n const baselineDir = dirname(canonicalSidecar);\n const tail = baselineDir.split(sep).slice(-BASELINE_SEGMENTS.length);\n if (tail.join(\"/\") !== CRAWL_BASELINE_DIR) {\n throw new SourceBundleError(\n `The sidecar must live in a ${CRAWL_BASELINE_DIR}/ directory (where \\`fluid theme crawl --evidence\\` and Mist write it); ${sidecarPath} does not.`,\n );\n }\n const root = resolve(baselineDir, ...BASELINE_SEGMENTS.map(() => \"..\"));\n const sidecarBytes = await readBounded(\n canonicalSidecar,\n SOURCE_PAGE_EVIDENCE_MAX_BYTES,\n \"Rendered source evidence\",\n );\n let durable: DurableSourcePageEvidence;\n try {\n durable = parseDurableSourcePageEvidence(sidecarBytes.toString(\"utf8\"));\n } catch (error) {\n throw new SourceBundleError(\n `Rendered source evidence is invalid: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n const resolveReceipt = async (receiptPath: string, label: string) => {\n const relativePath = receiptPath.replaceAll(\"\\\\\", \"/\").replace(/^\\.\\//, \"\");\n const full = resolve(root, ...relativePath.split(\"/\"));\n const canonical = await realpath(full).catch(() => {\n throw new SourceBundleError(`${label} ${relativePath} is missing.`);\n });\n if (!isInside(baselineDir, canonical)) {\n throw new SourceBundleError(\n `${label} ${relativePath} escapes the bundle's ${CRAWL_BASELINE_DIR}/ directory.`,\n );\n }\n return { relativePath, fullPath: canonical };\n };\n\n const screenshotReceipt = await resolveReceipt(\n durable.screenshot.path,\n \"Source screenshot\",\n );\n const screenshotBytes = await readBounded(\n screenshotReceipt.fullPath,\n SOURCE_IMAGE_MAX_BYTES,\n \"Source screenshot\",\n );\n\n let serializedOpenDialogs: Array<{ label: string }> = [];\n let hasSerializedHtml = false;\n const htmlReceipt = durable.documents.html;\n if (htmlReceipt) {\n const html = await resolveReceipt(htmlReceipt.path, \"Signed source HTML\");\n const htmlBytes = await readBounded(\n html.fullPath,\n SOURCE_HTML_MAX_BYTES,\n \"Signed source HTML evidence\",\n );\n if (\n htmlBytes.length !== htmlReceipt.byteLength ||\n sha256(htmlBytes) !== htmlReceipt.sha256\n ) {\n throw new SourceBundleError(\n \"Signed source HTML evidence changed or does not match its SHA-256 receipt.\",\n );\n }\n hasSerializedHtml = true;\n serializedOpenDialogs = serializedOpenDialogLabels(htmlBytes.toString(\"utf8\"));\n }\n\n return {\n root,\n sidecarPath: canonicalSidecar,\n sidecarRelativePath: `${CRAWL_BASELINE_DIR}/${basename(canonicalSidecar)}`,\n sidecarSha256: sha256(sidecarBytes),\n durable,\n screenshot: {\n fullPath: screenshotReceipt.fullPath,\n relativePath: screenshotReceipt.relativePath,\n bytes: screenshotBytes,\n sha256: sha256(screenshotBytes),\n },\n hasSerializedHtml,\n serializedOpenDialogs,\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n combinePreviewPixelMetrics,\n comparePreviewPixels,\n DEFAULT_COMPARISON_BAND_COUNT,\n DEFAULT_COMPARISON_COLUMN_COUNT,\n planBandGrid,\n planPreviewComparison,\n previewComparisonSampleStride,\n previewHeightDeltaRatio,\n repeatedViewportExactPixelEvidence,\n type PixelComparisonMetrics,\n type RepeatedViewportPixelEvidence,\n} from \"@fluid-app/mist-core/preview-comparison\";\nimport {\n assertSourceEvidenceMatchesComparison,\n assessSourceBaselineAdmissibility,\n COMPARISON_MAX_SOURCE_PIXELS,\n copyMismatchExcerpt,\n normalizedRoute,\n type SourceBaselineAdmissibility,\n} from \"@fluid-app/mist-core/source-baseline-admissibility\";\nimport {\n classifyComparisonOutcome,\n formatBandedDiffSummary,\n} from \"@fluid-app/mist-core/source-comparison-report\";\nimport {\n compareRenderedPageCopy,\n compareRenderedPageImages,\n compareRenderedPageVideos,\n parseRenderedSourcePageEvidence,\n RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT,\n selectRenderedPageCopy,\n type DurableSourcePageEvidence,\n} from \"@fluid-app/mist-core/source-page-evidence\";\nimport { cropRows, decodeImage, sampleTile, type DecodedImage } from \"./image.js\";\nimport {\n PlaywrightUnavailableError,\n type PlaywrightLike,\n type PreviewCapture,\n} from \"./preview-browser.js\";\nimport {\n readSourceBundle,\n resolveSidecarPath,\n SourceBundleError,\n type SourceBundle,\n} from \"./source-bundle.js\";\n\nexport const DEFAULT_PREVIEW_URL = \"http://127.0.0.1:9292\";\n/** Same overall bound Mist puts on one comparison. */\nexport const COMPARE_TIMEOUT_MS = 120_000;\n\nexport interface CompareOptions {\n /** Sidecar, screenshot, `source-baselines` dir, or the root above it. */\n source: string;\n /** Local route to render. */\n route: string;\n /** Signed source pathname, when the source maps to a different path. */\n sourceRoute?: string;\n /** Base URL of the running `fluid theme dev` preview. */\n url: string;\n mode: \"full\" | \"viewport\";\n copyMode: \"exact\" | \"diagnostic\";\n geometryMode: \"strict\" | \"diagnostic\";\n mediaMode: \"strict\" | \"diagnostic\";\n /** Picks a capture when the source directory holds several. */\n viewport?: { width: number; height: number };\n}\n\nexport interface CompareDeps {\n loadPlaywright: () => Promise<PlaywrightLike>;\n capture: (\n playwright: PlaywrightLike,\n args: {\n url: string;\n width: number;\n height: number;\n mode: \"full\" | \"viewport\";\n evidenceScript: string;\n },\n ) => Promise<PreviewCapture>;\n now: () => Date;\n uuid: () => string;\n}\n\n/** Mirrors the structured comparison Mist signs into its receipt. */\nexport interface SourcePreviewComparison extends PixelComparisonMetrics {\n version: 1;\n kind: \"source-preview\";\n mode: \"full-page\" | \"viewport\";\n policy: { geometry: CompareOptions[\"geometryMode\"]; media: CompareOptions[\"mediaMode\"] };\n source: {\n sha256: string;\n width: number;\n height: number;\n admissibility: SourceBaselineAdmissibility;\n };\n preview: {\n sha256: string;\n width: number;\n height: number;\n documentWidth: number;\n capturedHeight: number;\n sections: number;\n truncated: boolean;\n horizontalOverflow: boolean;\n };\n httpStatus: number | null;\n comparedHeight: number;\n heightDelta: number;\n heightDeltaRatio: number;\n sourceCoverageRatio: number;\n previewCoverageRatio: number;\n media: Record<string, unknown>;\n copy: Record<string, unknown>;\n}\n\nexport interface CompareReport {\n ok: boolean;\n /** `completed`: evidence is available (not a visual pass). `failed`: a\n * hard refusal from the comparison policy. `refused`: the comparison\n * could not run on this evidence at all. */\n outcome: \"completed\" | \"failed\" | \"refused\";\n /** Worst-first band line; empty when nothing differs. */\n summary: string;\n refusals: string[];\n diagnostics: string[];\n comparison?: SourcePreviewComparison;\n copy_mismatch?: { line: number | null; source: string; local: string };\n image_mismatch?: { index: number | null; reasons: string[] };\n video_mismatch?: { index: number | null; reasons: string[] };\n source?: {\n root: string;\n screenshot: string;\n page_evidence: string;\n page_evidence_sha256: string;\n final_url: string;\n status_code: number;\n viewport: { width: number; height: number };\n };\n local?: {\n url: string;\n final_url?: string;\n http_status?: number | null;\n screenshot?: string;\n };\n /** The prose report Mist returns to its agent, for parity. */\n message?: string;\n error?: { code: string; message: string; [key: string]: unknown };\n}\n\nfunction refuse(\n code: string,\n message: string,\n extra: Partial<CompareReport> & Record<string, unknown> = {},\n): CompareReport {\n const { error: _ignored, ...rest } = extra;\n return {\n ok: false,\n outcome: \"refused\",\n summary: \"\",\n refusals: [message],\n diagnostics: [],\n ...rest,\n error: { code, message },\n };\n}\n\nfunction ratio(numerator: number, denominator: number): number {\n if (denominator <= 0) return 0;\n return Number((numerator / denominator).toFixed(6));\n}\n\nfunction withTimeout<T>(operation: Promise<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () =>\n reject(\n new Error(\n `Preview comparison timed out after ${COMPARE_TIMEOUT_MS / 1000} seconds. Retry, and check responsive media or the dev server logs if it repeats.`,\n ),\n ),\n COMPARE_TIMEOUT_MS,\n );\n operation.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nfunction joinUrl(base: string, route: string): string {\n return new URL(route, base.endsWith(\"/\") ? base : `${base}/`).toString();\n}\n\n/** Repeated-pixel evidence for serialized open dialogs, sampled from the\n * decoded source the same way Mist samples its nativeImage tiles. */\nfunction serializedDialogRepeatedPixelEvidence(\n sourceImage: DecodedImage,\n evidence: DurableSourcePageEvidence,\n serializedOpenDialogs: ReadonlyArray<{ label: string }>,\n): RepeatedViewportPixelEvidence | null {\n if (serializedOpenDialogs.length === 0) return null;\n const viewportHeight = evidence.requestedViewport.height;\n const fullViewportCount = Math.floor(sourceImage.height / viewportHeight);\n if (fullViewportCount < 2) return null;\n return repeatedViewportExactPixelEvidence(\n sourceImage.width,\n viewportHeight,\n fullViewportCount,\n (index, sampledWidth, sampledHeight) =>\n sampleTile(\n sourceImage,\n index * viewportHeight,\n viewportHeight,\n sampledWidth,\n sampledHeight,\n ),\n );\n}\n\nfunction summarizeMedia(readiness: PreviewCapture[\"mediaReadiness\"]): {\n note: string;\n hasFailures: boolean;\n} {\n const unresolved =\n readiness.images.failed +\n readiness.images.pending +\n readiness.videos.failed +\n readiness.videos.pending;\n if (unresolved === 0) {\n return {\n note: ` Media readiness: ${readiness.images.ready}/${readiness.images.total} images and ${readiness.videos.ready}/${readiness.videos.total} videos loaded before capture.`,\n hasFailures: false,\n };\n }\n return {\n note: ` MEDIA QA FAILED: ${readiness.images.failed} failed and ${readiness.images.pending} still-pending images; ${readiness.videos.failed} failed and ${readiness.videos.pending} still-pending videos${readiness.timedOut ? \" after the bounded readiness wait\" : \"\"}. The comparison must not be accepted as passing visual evidence.`,\n hasFailures: true,\n };\n}\n\n/**\n * `fluid theme compare`: render the local preview at the bundle's exact\n * viewport and report the same banded pixel diff, copy checks, and refusals\n * as Mist's `compare_preview_to_source`.\n */\nexport async function runCompare(\n options: CompareOptions,\n deps: CompareDeps,\n): Promise<CompareReport> {\n let bundle: SourceBundle;\n try {\n const sidecarPath = await resolveSidecarPath(options.source, {\n sourceRoute: options.sourceRoute ?? options.route,\n ...(options.viewport ? { viewport: options.viewport } : {}),\n });\n bundle = await readSourceBundle(sidecarPath);\n } catch (error) {\n if (error instanceof SourceBundleError) {\n return refuse(\"source_evidence_invalid\", error.message, {\n ...(error.candidates ? { candidates: error.candidates } : {}),\n });\n }\n return refuse(\n \"source_evidence_invalid\",\n `Couldn't read source comparison evidence: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n const durable = bundle.durable;\n const { width, height } = durable.requestedViewport;\n const sourceSummary: NonNullable<CompareReport[\"source\"]> = {\n root: bundle.root,\n screenshot: bundle.screenshot.fullPath,\n page_evidence: bundle.sidecarPath,\n page_evidence_sha256: bundle.sidecarSha256,\n final_url: durable.finalUrl,\n status_code: durable.statusCode,\n viewport: { width, height },\n };\n try {\n assertSourceEvidenceMatchesComparison(\n durable,\n bundle.screenshot,\n {\n width,\n height,\n path: options.route,\n ...(options.sourceRoute ? { sourceRoute: options.sourceRoute } : {}),\n },\n \" If the source platform intentionally redirects or localizes this page to a different pathname than the Fluid preview, retry with --source-route set to the signed source pathname and keep --route set to the local Fluid route.\",\n );\n } catch (error) {\n return refuse(\n \"mixed_capture\",\n `Couldn't read source comparison evidence: ${error instanceof Error ? error.message : String(error)}`,\n { source: sourceSummary },\n );\n }\n\n let playwright: PlaywrightLike;\n try {\n playwright = await deps.loadPlaywright();\n } catch (error) {\n if (error instanceof PlaywrightUnavailableError) {\n return {\n ...refuse(\"playwright_missing\", error.message, { source: sourceSummary }),\n error: {\n code: \"playwright_missing\",\n message: error.message,\n install: error.installCommand,\n },\n };\n }\n throw error;\n }\n\n const localUrl = joinUrl(options.url, options.route);\n const local: NonNullable<CompareReport[\"local\"]> = { url: localUrl };\n let capture: PreviewCapture;\n try {\n capture = await withTimeout(\n deps.capture(playwright, {\n url: localUrl,\n width,\n height,\n mode: options.mode,\n evidenceScript: RENDERED_SOURCE_PAGE_EVIDENCE_COLLECT_SCRIPT,\n }),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return refuse(\n \"preview_capture_failed\",\n `Exact ${width}×${height} ${options.mode === \"full\" ? \"full-page\" : \"viewport\"} capture of ${localUrl} failed: ${message}. Is \\`fluid theme dev\\` running? Pass --url if it is not on ${DEFAULT_PREVIEW_URL}.`,\n { source: sourceSummary, local },\n );\n }\n local.final_url = capture.finalUrl;\n local.http_status = capture.httpStatus;\n if (capture.httpStatus === null) {\n return refuse(\n \"route_status_unknown\",\n `Couldn't verify the preview route ${JSON.stringify(options.route)}: the main-frame HTTP response status was unavailable. Comparison evidence requires a route with a known HTTP status.`,\n { source: sourceSummary, local },\n );\n }\n if (new URL(capture.finalUrl).origin !== new URL(localUrl).origin) {\n return refuse(\n \"route_escaped\",\n `The preview route ${options.route} navigated off the local preview to ${capture.finalUrl}.`,\n { source: sourceSummary, local },\n );\n }\n\n try {\n const renderedEvidence = parseRenderedSourcePageEvidence(capture.evaluated);\n const sourceImage = decodeImage(bundle.screenshot.bytes, \"Source baseline\");\n if (sourceImage.width * sourceImage.height > COMPARISON_MAX_SOURCE_PIXELS) {\n throw new Error(\n `Source baseline contains ${(sourceImage.width * sourceImage.height).toLocaleString()} decoded pixels; one comparison is capped at ${COMPARISON_MAX_SOURCE_PIXELS.toLocaleString()} to protect lower-memory computers. Capture a shorter route instead.`,\n );\n }\n if (sourceImage.width !== width) {\n throw new Error(\n `Source baseline is ${sourceImage.width}px wide, but this comparison requested ${width}px. Re-crawl the source at the exact same viewport instead of resizing evidence.`,\n );\n }\n if (capture.pageWidth !== width) {\n throw new Error(\n `Local preview rendered at ${capture.pageWidth}px instead of the requested ${width}px.`,\n );\n }\n if (\n durable.screenshot.width !== sourceImage.width ||\n durable.screenshot.height !== sourceImage.height ||\n durable.screenshot.byteLength !== bundle.screenshot.bytes.length\n ) {\n throw new Error(\n \"Rendered source evidence screenshot dimensions or byte length do not match the decoded source baseline.\",\n );\n }\n if (\n renderedEvidence.viewport.width !== width ||\n renderedEvidence.viewport.height !== height\n ) {\n throw new Error(\n `Local rendered-copy evidence used ${renderedEvidence.viewport.width}×${renderedEvidence.viewport.height}, not the requested ${width}×${height} viewport.`,\n );\n }\n if (normalizedRoute(renderedEvidence.url) !== normalizedRoute(capture.finalUrl)) {\n throw new Error(\n `Local rendered-copy evidence came from ${normalizedRoute(renderedEvidence.url)}, not the verified preview route ${normalizedRoute(capture.finalUrl)}.`,\n );\n }\n const previewImage = decodeImage(capture.png, \"Local preview capture\");\n if (\n previewImage.width !== capture.pageWidth ||\n previewImage.height !== capture.capturedHeight\n ) {\n throw new Error(\n `Local preview decoded at ${previewImage.width}×${previewImage.height}, not the expected ${capture.pageWidth}×${capture.capturedHeight} CSS-pixel capture. Refusing to resize it.`,\n );\n }\n\n const sourcePixelHeight =\n options.mode === \"full\"\n ? sourceImage.height\n : Math.min(sourceImage.height, height);\n const sourceDocumentHeight =\n options.mode === \"full\" ? durable.rendered.document.height : sourcePixelHeight;\n const plan = planPreviewComparison(\n sourceImage.width,\n sourcePixelHeight,\n capture.totalHeight,\n [{ width: previewImage.width, height: previewImage.height, scrollOffset: 0 }],\n );\n const sampleStride = previewComparisonSampleStride(plan.regions);\n const bandGrid = planBandGrid(plan.regions);\n const pixelMetrics = combinePreviewPixelMetrics(\n plan.regions.map((region) =>\n comparePreviewPixels(\n [\n {\n ...region,\n sourcePixels: cropRows(sourceImage, region.sourceY, region.height),\n previewPixels: cropRows(previewImage, region.previewY, region.height),\n },\n ],\n Number.MAX_SAFE_INTEGER,\n sampleStride,\n DEFAULT_COMPARISON_BAND_COUNT,\n DEFAULT_COMPARISON_COLUMN_COUNT,\n bandGrid,\n ),\n ),\n );\n\n const previewSha256 = createHash(\"sha256\").update(capture.png).digest(\"hex\");\n const horizontalOverflow = capture.documentWidth > capture.pageWidth;\n const heightDelta = capture.totalHeight - sourceDocumentHeight;\n const heightDeltaRatio = previewHeightDeltaRatio(\n sourceDocumentHeight,\n capture.totalHeight,\n );\n const selectedCopy = selectRenderedPageCopy(durable.rendered, renderedEvidence);\n const copy = compareRenderedPageCopy(\n selectedCopy.sourceText,\n selectedCopy.previewText,\n );\n const imageParity =\n options.mode === \"full\"\n ? compareRenderedPageImages(durable.rendered, renderedEvidence)\n : null;\n const videoParity = compareRenderedPageVideos(durable.rendered, renderedEvidence);\n const serializedDialogPixelEvidence = serializedDialogRepeatedPixelEvidence(\n sourceImage,\n durable,\n bundle.serializedOpenDialogs,\n );\n const sourceAdmissibility = assessSourceBaselineAdmissibility(\n durable,\n bundle.hasSerializedHtml,\n bundle.serializedOpenDialogs,\n serializedDialogPixelEvidence,\n );\n const mediaSummary = summarizeMedia(capture.mediaReadiness);\n const copyTruncated =\n selectedCopy.sourceTruncated || selectedCopy.previewTruncated;\n const { operationalFailures, specialistDiagnostics } =\n classifyComparisonOutcome({\n mode: options.mode,\n copyMode: options.copyMode,\n geometryMode: options.geometryMode,\n mediaMode: options.mediaMode,\n sourceAdmissibility,\n sourceShadowRootsTruncated: durable.rendered.shadowRootsTruncated,\n previewShadowRootsTruncated: renderedEvidence.shadowRootsTruncated,\n httpStatus: capture.httpStatus,\n truncated: capture.truncated,\n horizontalOverflow,\n mediaHasFailures: mediaSummary.hasFailures,\n imageParity,\n videoParity,\n copyTruncated,\n copy,\n heightDeltaRatio,\n });\n\n const comparison: SourcePreviewComparison = {\n version: 1,\n kind: \"source-preview\",\n mode: options.mode === \"full\" ? \"full-page\" : \"viewport\",\n policy: { geometry: options.geometryMode, media: options.mediaMode },\n source: {\n sha256: bundle.screenshot.sha256,\n width: sourceImage.width,\n height: sourceImage.height,\n admissibility: sourceAdmissibility,\n },\n preview: {\n sha256: previewSha256,\n width: capture.pageWidth,\n height: capture.totalHeight,\n documentWidth: capture.documentWidth,\n capturedHeight: plan.capturedPreviewHeight,\n sections: 1,\n truncated: capture.truncated,\n horizontalOverflow,\n },\n httpStatus: capture.httpStatus,\n comparedHeight: plan.comparedHeight,\n heightDelta,\n heightDeltaRatio,\n sourceCoverageRatio: ratio(plan.comparedHeight, sourcePixelHeight),\n previewCoverageRatio: ratio(plan.capturedPreviewHeight, capture.totalHeight),\n ...pixelMetrics,\n media: {\n failedImages: capture.mediaReadiness.images.failed,\n pendingImages: capture.mediaReadiness.images.pending,\n failedVideos: capture.mediaReadiness.videos.failed,\n pendingVideos: capture.mediaReadiness.videos.pending,\n timedOut: capture.mediaReadiness.timedOut,\n ...(imageParity\n ? {\n imageParity: {\n exact: imageParity.exact,\n sourceImages: imageParity.sourceCount,\n previewImages: imageParity.previewCount,\n sourceIdentified: imageParity.sourceIdentified,\n previewIdentified: imageParity.previewIdentified,\n sourceTruncated: imageParity.sourceTruncated,\n previewTruncated: imageParity.previewTruncated,\n firstMismatchIndex: imageParity.mismatches[0]?.index ?? null,\n },\n }\n : {}),\n videoParity: {\n exact: videoParity.exact,\n sourceVideos: videoParity.sourceCount,\n previewVideos: videoParity.previewCount,\n sourceIdentified: videoParity.sourceIdentified,\n previewIdentified: videoParity.previewIdentified,\n sourceTruncated: videoParity.sourceTruncated,\n previewTruncated: videoParity.previewTruncated,\n firstMismatchIndex: videoParity.mismatches[0]?.index ?? null,\n },\n },\n copy: {\n mode: options.copyMode,\n granularity: selectedCopy.granularity,\n sourceEvidenceSha256: bundle.sidecarSha256,\n sourceTextSha256: copy.sourceTextSha256,\n previewTextSha256: copy.previewTextSha256,\n exact: copy.exact,\n sourceCharacters: copy.sourceCharacters,\n previewCharacters: copy.previewCharacters,\n sourceLines: copy.sourceLines,\n previewLines: copy.previewLines,\n firstMismatchLine: copy.firstMismatchLine,\n sourceTruncated: selectedCopy.sourceTruncated,\n previewTruncated: selectedCopy.previewTruncated,\n },\n };\n\n // Keep the local capture beside the source evidence, like Mist's\n // `.mist-desktop/screenshots/compare-*.png`.\n const routeSlug =\n options.route\n .split(/[?#]/, 1)[0]\n ?.replace(/[^a-zA-Z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 80) || \"home\";\n const stamp = deps\n .now()\n .toISOString()\n .replace(/[:.]/g, \"-\")\n .replace(\"T\", \"_\")\n .slice(0, 23);\n const screenshotsDir = join(bundle.root, \".mist-desktop\", \"screenshots\");\n const localScreenshot = join(\n screenshotsDir,\n `compare-${routeSlug}-${stamp}-${deps.uuid().slice(0, 8)}.png`,\n );\n await mkdir(screenshotsDir, { recursive: true });\n await writeFile(localScreenshot, capture.png);\n local.screenshot = localScreenshot;\n\n const bandSummary = formatBandedDiffSummary(pixelMetrics.bands ?? []);\n const firstImageMismatch = imageParity?.mismatches[0];\n const firstVideoMismatch = videoParity.mismatches[0];\n const copyLine = copy.exact\n ? ` Rendered copy is exact (${copy.sourceLines} lines, ${copy.sourceCharacters.toLocaleString()} characters; SHA-256 ${copy.sourceTextSha256}).`\n : ` RENDERED COPY MISMATCH at line ${copy.firstMismatchLine ?? \"unknown\"}: source ${copyMismatchExcerpt(copy.sourceMismatch)}; local ${copyMismatchExcerpt(copy.previewMismatch)}.${options.copyMode === \"diagnostic\" ? \" This was diagnostic mode and cannot satisfy an exact-copy gate.\" : \"\"}`;\n const admissibilityLine =\n sourceAdmissibility.status === \"usable\"\n ? ` SOURCE BASELINE ADMISSIBILITY: usable. Screenshot-height drift is ${sourceAdmissibility.screenshotHeightDriftViewports.toFixed(2)} viewport heights; no blocking dialog met the contamination threshold.`\n : ` SOURCE BASELINE ADMISSIBILITY: ${sourceAdmissibility.status}. ${sourceAdmissibility.reasons.join(\" \")} ${sourceAdmissibility.recovery ?? \"\"}`.trimEnd();\n const message = `Structured source-vs-preview comparison completed at ${width}×${height} (${options.mode}). Local route: ${capture.finalUrl} (HTTP ${capture.httpStatus}${capture.httpStatusText ? ` ${capture.httpStatusText}` : \"\"}). Source screenshot ${sourceImage.width}×${sourceImage.height}; signed source document ${durable.rendered.document.width}×${sourceDocumentHeight}; local document ${capture.documentWidth}×${capture.totalHeight}; height delta ${heightDelta >= 0 ? \"+\" : \"\"}${heightDelta}px (${(heightDeltaRatio * 100).toFixed(2)}%); compared ${plan.comparedHeight}px.${admissibilityLine}\\nMACHINE OUTCOME: ${operationalFailures.length > 0 ? \"failed\" : \"completed — available comparison evidence, not an automatic visual-fidelity pass\"}.\\n Sampled ${pixelMetrics.sampledPixels.toLocaleString()} pixels at stride ${pixelMetrics.sampleStride}: normalized mean absolute error ${pixelMetrics.normalizedMeanAbsoluteError.toFixed(6)}, significant-pixel ratio ${(pixelMetrics.significantPixelRatio * 100).toFixed(2)}% (channel delta ≥ ${pixelMetrics.significantPixelDelta}), exact-pixel ratio ${(pixelMetrics.exactPixelRatio * 100).toFixed(2)}%.${bandSummary}${copyLine}${mediaSummary.note}${operationalFailures.length > 0 ? ` QA EVIDENCE IS INCOMPLETE: ${operationalFailures.join(\"; \")}.` : \"\"}${specialistDiagnostics.length > 0 ? ` SPECIALIST REVIEW REQUIRED: ${specialistDiagnostics.join(\"; \")}.` : \"\"} Pixel deltas are diagnostic evidence, not a standalone verdict: verify interactions, console, and server logs before declaring parity.`;\n\n return {\n ok: operationalFailures.length === 0,\n outcome: operationalFailures.length === 0 ? \"completed\" : \"failed\",\n summary: bandSummary.trim(),\n refusals: operationalFailures,\n diagnostics: specialistDiagnostics,\n comparison,\n ...(copy.exact\n ? {}\n : {\n copy_mismatch: {\n line: copy.firstMismatchLine,\n source: copyMismatchExcerpt(copy.sourceMismatch),\n local: copyMismatchExcerpt(copy.previewMismatch),\n },\n }),\n ...(imageParity && !imageParity.exact\n ? {\n image_mismatch: {\n index: firstImageMismatch?.index ?? null,\n reasons: firstImageMismatch?.reasons ?? [\n \"the source or local media inventory was truncated\",\n ],\n },\n }\n : {}),\n ...(videoParity.exact\n ? {}\n : {\n video_mismatch: {\n index: firstVideoMismatch?.index ?? null,\n reasons: firstVideoMismatch?.reasons ?? [\n \"the source or local media inventory was truncated\",\n ],\n },\n }),\n source: sourceSummary,\n local,\n message,\n };\n } catch (error) {\n return refuse(\n \"comparison_refused\",\n `Couldn't compare the source and local preview pixels: ${error instanceof Error ? error.message : String(error)}`,\n { source: sourceSummary, local },\n );\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { Command, InvalidArgumentError, Option } from \"commander\";\nimport { capturePreview, loadPlaywright } from \"../page-clone/preview-browser.js\";\nimport {\n DEFAULT_PREVIEW_URL,\n runCompare,\n type CompareDeps,\n type CompareOptions,\n} from \"../page-clone/run-compare.js\";\nimport { parseViewport } from \"./crawl.js\";\n\nexport function defaultCompareDeps(): CompareDeps {\n return {\n loadPlaywright: () => loadPlaywright(),\n capture: capturePreview,\n now: () => new Date(),\n uuid: () => randomUUID(),\n };\n}\n\nfunction parseRoute(value: string): string {\n if (!value.startsWith(\"/\")) {\n throw new InvalidArgumentError('Routes start with \"/\", for example \"/\" or \"/collections/all\".');\n }\n return value;\n}\n\nfunction parseBaseUrl(value: string): string {\n try {\n const url = new URL(value);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") throw new Error();\n return url.toString();\n } catch {\n throw new InvalidArgumentError(\"Use an http(s) URL, for example http://127.0.0.1:9292.\");\n }\n}\n\ninterface CompareCommandOptions {\n source: string;\n route: string;\n sourceRoute?: string;\n url: string;\n mode: CompareOptions[\"mode\"];\n copy: CompareOptions[\"copyMode\"];\n geometry: CompareOptions[\"geometryMode\"];\n media: CompareOptions[\"mediaMode\"];\n viewport?: { width: number; height: number };\n}\n\nexport function createCompareCommand(\n deps: () => CompareDeps = defaultCompareDeps,\n): Command {\n return new Command(\"compare\")\n .description(\n \"Render the local `fluid theme dev` preview at a crawl bundle's exact viewport and print a banded pixel diff with copy/media checks as JSON (exits 1 on a refusal)\",\n )\n .requiredOption(\n \"--source <path>\",\n \"Evidence bundle: a .page-evidence.json sidecar, its directory, or the --evidence dir given to `fluid theme crawl`\",\n )\n .option(\"--route <path>\", \"Local route to compare\", parseRoute, \"/\")\n .option(\n \"--source-route <path>\",\n \"Signed source pathname when it differs from --route (e.g. /en-us for /)\",\n parseRoute,\n )\n .option(\"--url <url>\", \"Base URL of the running preview\", parseBaseUrl, DEFAULT_PREVIEW_URL)\n .addOption(\n new Option(\"--mode <mode>\", \"Compare the whole page or the top viewport\")\n .choices([\"full\", \"viewport\"])\n .default(\"full\"),\n )\n .addOption(\n new Option(\"--copy <mode>\", \"exact fails on any ordered rendered-copy mismatch\")\n .choices([\"exact\", \"diagnostic\"])\n .default(\"exact\"),\n )\n .addOption(\n new Option(\"--geometry <mode>\", \"strict fails when document height differs by more than 5%\")\n .choices([\"strict\", \"diagnostic\"])\n .default(\"strict\"),\n )\n .addOption(\n new Option(\"--media <mode>\", \"strict fails on image/video parity differences\")\n .choices([\"strict\", \"diagnostic\"])\n .default(\"strict\"),\n )\n .option(\n \"--viewport <WxH>\",\n \"Choose the capture at this viewport when --source holds several\",\n parseViewport,\n )\n .action(async (opts: CompareCommandOptions) => {\n const report = await runCompare(\n {\n source: opts.source,\n route: opts.route,\n ...(opts.sourceRoute ? { sourceRoute: opts.sourceRoute } : {}),\n url: opts.url,\n mode: opts.mode,\n copyMode: opts.copy,\n geometryMode: opts.geometry,\n mediaMode: opts.media,\n ...(opts.viewport ? { viewport: opts.viewport } : {}),\n },\n deps(),\n );\n console.log(JSON.stringify(report, null, 2));\n if (!report.ok) process.exitCode = 1;\n });\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createPushCommand } from \"./push.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createInitCommand } from \"./init.js\";\nimport { createNavigateCommand } from \"./navigate.js\";\nimport { createSkillsCommand } from \"./skills.js\";\nimport { createCrawlCommand } from \"./crawl.js\";\nimport { createCompareCommand } from \"./compare.js\";\n\nexport function registerThemeCommand(ctx: PluginContext): void {\n const cmd = new Command(\"theme\").description(\n \"Theme developer workflow — dev server, push, pull, lint, init, skills, crawl, compare\",\n );\n\n cmd.addCommand(createDevCommand());\n cmd.addCommand(createPushCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createInitCommand());\n cmd.addCommand(createNavigateCommand());\n cmd.addCommand(createSkillsCommand());\n cmd.addCommand(createCrawlCommand());\n cmd.addCommand(createCompareCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerThemeCommand } from \"./commands/theme.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-theme-dev\",\n version: \"0.1.66\",\n register(ctx: PluginContext) {\n registerThemeCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,eAAe,OAA8B;AAC3D,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,QAAO;AAGT,QAAO;;;;;;;;AAST,SAAgB,eAAe,MAAkC;AAC/D,KAAI,CAAC,KACH,QAAO;AAGT,QAAQ,KAAK,UAAU;;;;;;;ACOzB,IAAa,WAAb,MAAa,iBAAiB,MAAM;CAClC;;;;;CAMA;;;;;CAMA;CAEA;CAEA,YACE,SACA,QACA,MACA,WACA,MACA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,YAAY;AAEjB,MAAI,uBAAuB,MAEvB,OAMA,kBAAkB,MAAM,SAAS;;CAIvC,SAOE;AACA,SAAO;GACL,MAAM,KAAK;GACX,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,MAAM,KAAK;GACX,MAAM,KAAK;GACX,WAAW,KAAK;GACjB;;;AAIL,SAAS,mBAAmB,OAAoC;AAC9D,KAAI,OAAO,UAAU,SACnB;CAGF,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;;AAGxC,SAAS,wBAAwB,SAAsC;AACrE,QACE,mBAAmB,QAAQ,IAAI,eAAe,CAAC,IAC/C,mBAAmB,QAAQ,IAAI,aAAa,CAAC,IAC7C,mBAAmB,QAAQ,IAAI,eAAe,CAAC;;AAInD,SAAS,yBAAyB,MAAmC;AACnE,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAC1D;CAGF,MAAM,SAAS;CACf,MAAM,OAAO,OAAO;AAEpB,QACE,mBAAmB,OAAO,WAAW,IACrC,mBAAmB,OAAO,UAAU,KACnC,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,GACpD,mBAAoB,KAAiC,WAAW,IACjE,mBAAoB,KAAiC,UAAU,GAC/D,KAAA;;;;;AAOR,SAAgB,WAAW,OAAmC;AAC5D,QAAO,iBAAiB;;;;;AA4C1B,SAAgB,kBACd,QACqB;CACrB,MAAM,EACJ,SACA,cACA,aACA,iBAAiB,EAAE,EACnB,aACA,OACA,cACA,qBAAqB,UACnB;CACJ,MAAM,oBAAoB,KAAK,IAAI,GAAG,cAAc,cAAc,EAAE;CACpE,MAAM,0BAA0B,KAAK,IAAI,GAAG,cAAc,eAAe,EAAE;;;;CAK3E,eAAe,aACb,eACiC;EACjC,MAAM,UAAkC;GACtC,QAAQ;GACR,gBAAgB;GAChB,GAAG;GACH,GAAG;GACJ;AAGD,MAAI,cAAc;GAChB,MAAM,QAAQ,MAAM,cAAc;AAClC,OAAI,MACF,SAAQ,gBAAgB,UAAU;;AAItC,SAAO;;;;;;;CAQT,SAAS,QAAQ,UAA0B;AACzC,SAAO,GAAG,UAAU;;;;;;CAOtB,SAAS,SACP,UACA,QACQ;EACR,MAAM,UAAU,QAAQ,SAAS;AAEjC,MAAI,CAAC,UAAU,OAAO,KAAK,OAAO,CAAC,WAAW,EAC5C,QAAO;EAGT,MAAM,cAAc,IAAI,iBAAiB;AAEzC,SAAO,QAAQ,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW;AAC/C,OAAI,UAAU,KAAA,KAAa,UAAU,KACnC;AAGF,OAAI,MAAM,QAAQ,MAAM,EAAE;IAMxB,MAAM,WAAW,IAAI,SAAS,KAAK,GAAG,MAAM,GAAG,IAAI;AACnD,UAAM,SAAS,SAAS,YAAY,OAAO,UAAU,OAAO,KAAK,CAAC,CAAC;cAC1D,OAAO,UAAU,SAE1B,QAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,QAAQ,cAAc;AACpD,QAAI,aAAa,KAAA,KAAa,aAAa,KACzC;AAGF,QAAI,MAAM,QAAQ,SAAS,CACzB,UAAS,SAAS,SAChB,YAAY,OAAO,GAAG,IAAI,GAAG,OAAO,MAAM,OAAO,KAAK,CAAC,CACxD;QAED,aAAY,OAAO,GAAG,IAAI,GAAG,OAAO,IAAI,OAAO,SAAS,CAAC;KAE3D;OAEF,aAAY,OAAO,KAAK,OAAO,MAAM,CAAC;IAExC;EAEF,MAAM,KAAK,YAAY,UAAU;AACjC,SAAO,KAAK,GAAG,QAAQ,GAAG,OAAO;;;;;;CAOnC,eAAe,eACb,UACA,QACA,MACoB;EACpB,MAAM,kBAAkB,wBAAwB,SAAS,QAAQ;AAEjE,MAAI,SAAS,WAAW,OAAO,YAC7B,cAAa;AAGf,MAAI,CAAC,SAAS,IAAI;GAGhB,MAAM,YAAY,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAGvD,OAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,EAAE;IAI7C,IAAI;AACJ,QAAI;AACF,cAAS,KAAK,MAAM,UAAU;YACxB;AACN,WAAM,IAAI,SACR,UAAU,MAAM,GAAG,IAAI,IACrB,GAAG,OAAO,8BAA8B,SAAS,UACnD,SAAS,QACT,MACA,gBACD;;IAGH,MAAM,OAAO,eAAe,OAAO;AA2BnC,UAAM,IAAI,UAvBE,cACD;KACL,MAAM,cACJ,OAAO,KAAK,UAAU,YAAY,KAAK,UAAU,OAC5C,KAAK,MAAgC,UACtC,KAAA;KACN,MAAM,cACJ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;KAChD,MAAM,UACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAA;KACpD,MAAM,eACJ,OAAO,KAAK,kBAAkB,WAC1B,KAAK,gBACL,KAAA;AACN,YACE,WACA,gBACA,gBACC,OAAO,gBAAgB,WAAW,cAAc,KAAA;QAEjD,GACJ,KAAA,MAGK,GAAG,OAAO,8BAA8B,SAAS,UACxD,SAAS,QACT,OAAO,eAAe,KAAK,GAAI,QAC/B,mBAAmB,yBAAyB,OAAO,EACnD,KACD;SAED,OAAM,IAAI,SACR,GAAG,OAAO,8BAA8B,SAAS,UACjD,SAAS,QACT,MACA,gBACD;;AAIL,MACE,SAAS,WAAW,OACpB,SAAS,QAAQ,IAAI,iBAAiB,KAAK,IAE3C,QAAO;AAKT,MAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,EAAE;GAC7C,MAAM,eAAe,MAAM,SAAS,MAAM;AAE1C,OAAI;AAEF,WADa,KAAK,MAAM,aAAa;WAE/B;AACN,QAAI,mBACF,OAAM,IAAI,SACR,oCACA,SAAS,QACT,MACA,gBACD;AAKH,WAAO,eAAgB,eAA8B;;;AAKzD,SAAO;;CAGT,SAAS,uBAAuB,cAA8B;AAC5D,SAAO,0BAA0B,MAAM,eAAe;;CAGxD,eAAe,oBAAoB,cAAqC;EACtE,MAAM,UAAU,uBAAuB,aAAa;AACpD,MAAI,WAAW,EACb;AAGF,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,QAAQ,CAAC;;CAG9D,eAAe,sBACb,KACA,cACA,QACmB;EACnB,IAAI,aAAa;AAEjB,SAAO,KACL,KAAI;AACF,UAAO,MAAM,MAAM,KAAK,aAAa;WAC9B,cAAc;AACrB,OAAI,QAAQ,WAAW,cAAc,kBACnC,OAAM;AAGR,iBAAc;AACd,SAAM,oBAAoB,WAAW;AAErC,OAAI,QAAQ,QACV,OAAM;;;;;;CASd,eAAe,QACb,UACA,UAA0B,EAAE,EACR;EACpB,MAAM,EACJ,SAAS,OACT,SAAS,eACT,QACA,MACA,QACA,aACE;EAEJ,MAAM,MAAM,SAAS,SAAS,UAAU,OAAO,GAAG,QAAQ,SAAS;EAEnE,MAAM,UAAU,MAAM,aAAa,cAAc;EAEjD,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS;AACrD,OAAI,YAAa,cAAa,cAAc;AAC5C,OAAI,MAAO,cAAa,QAAQ;AAChC,OAAI,SAAU,cAAa,WAAW;GACtC,MAAM,iBACJ,QAAQ,WAAW,QAAQ,KAAK,UAAU,KAAK,GAAG;AACpD,OAAI,eAAgB,cAAa,OAAO;AACxC,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,sBAAsB,KAAK,cAAc,OAAO;WAC1D,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;;;;CAMzD,eAAe,oBACb,UACA,UACA,UAEI,EAAE,EACc;EACpB,MAAM,EACJ,SAAS,QACT,SAAS,eACT,QACA,aACE;EAEJ,MAAM,MAAM,QAAQ,SAAS;EAC7B,MAAM,UAAU,MAAM,aAAa,cAAc;AAGjD,SAAO,QAAQ;EAEf,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS,MAAM;IAAU;AACrE,OAAI,YAAa,cAAa,cAAc;AAC5C,OAAI,MAAO,cAAa,QAAQ;AAChC,OAAI,SAAU,cAAa,WAAW;AACtC,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,sBAAsB,KAAK,cAAc,OAAO;WAC1D,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;AAIzD,QAAO;EACI;EACY;EAGrB,MACE,UACA,QACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR,GAAI,UAAU,EAAE,QAAQ;GACzB,CAAC;EAEJ,OACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,MACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,QACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,SACE,UACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACT,CAAC;EACL;;;;;AChmBH,SAAgB,aAAqB;AACnC,QAAO,QAAQ,IAAI,qBAAqB;;AAK1C,IAAI,iBAAiB,QAAQ,SAAS;AACtC,eAAe,gBAAgB,eAAgD;AAC7E,kBAAiB,eAAe,WACxB,IAAI,SAAS,YAAY,WAAW,SAAS,IAAM,CAAC,CAC3D;AACD,OAAM;AACN,QAAO,iBAAiB,cAAc,IAAI;;AAG5C,SAAgB,gBAAgB,eAAmC;CACjE,MAAM,SAAS,kBAAkB;EAC/B,SAAS,YAAY;EACrB,oBACE,QAAQ,IAAI,6BAA6B,MACrC,gBAAgB,cAAc,GAC7B,iBAAiB,cAAc,IAAI;EAC3C,CAAC;CACF,MAAM,UAAU,OACd,UACA,UAA0B,EAAE,KACL;AACvB,OAAK,IAAI,UAAU,IAAK,WAAW;AACjC,WAAQ,QAAQ,gBAAgB;AAChC,OAAI;AACF,WAAO,MAAM,OAAO,QAAmB,UAAU,QAAQ;YAClD,OAAO;AAGd,SACG,QAAQ,UAAU,WAAW,SAC9B,EAAE,iBAAiB,aACnB,CAAC;KAAC;KAAK;KAAK;KAAI,CAAC,SAAS,MAAM,OAAO,IACvC,WAAW,KACX,QAAQ,QAAQ,QAEhB,OAAM;AAER,UAAMA,aAAM,MAAM,KAAK,SAAS,KAAA,GAAW,EAAE,QAAQ,QAAQ,QAAQ,CAAC;;;;AAI5E,QAAO;EACL,GAAG;EACH;EACA,MAAM,UAAU,QAAQ,YACtB,QAAQ,UAAU;GAAE,GAAG;GAAS,QAAQ;GAAO;GAAQ,CAAC;EAC3D;;AAGH,SAAgB,eAAuB;CACrC,MAAM,QAAQ,cAAc;AAC5B,KAAI,CAAC,OAAO;AACV,UAAQ,MAAM,0CAA0C;AACxD,UAAQ,KAAK,EAAE;;AAEjB,QAAO;;;;AC3CT,MAAM,aAAa;AAEnB,SAAS,WAA0B;AAEjC,QADe,YAAY,CACZ,QAAQ,eAAiC,EAAE;;;AAI5D,SAAS,iBAAiB,KAAqB;CAC7C,MAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,QAAO,QAAQ,KAAK,MAAM,IAAI,MAAM,MAAM,EAAE;;;;;;;AAQ9C,SAAS,aACP,UACA,KACA,OAC6B;CAC7B,MAAM,OAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,YAAY,EAAE,CAAC,CACjD,KAAI,WAAW,iBAAiB,EAAE,CAAC,CAAE,MAAK,KAAK;AAEjD,MAAK,OAAO;AACZ,QAAO;;;;;;;AAQT,SAAgB,YACd,SACA,WACQ;AACR,QAAO,GAAG,WAAW,UAAU,GAAG;;;;;;;AAQpC,SAAgB,YAAY,KAAsC;CAChE,MAAM,QAAQ,UAAU;CACxB,MAAM,WAAW,MAAM,YAAY;AACnC,KAAI,SAAU,QAAO;AAErB,KAAI,MAAM,YAAY;EACpB,MAAM,WAAwB;GAC5B,IAAI,MAAM;GACV,MAAM,MAAM,gBAAgB,gBAAgB,MAAM;GACnD;AACD,gBAAc,WAAW;GAEvB,MAAM,EAAE,YAAY,KAAK,cAAc,OAAO,GAAG,SADhC,OAAO,QAAQ,eAAiC,EAAE;AAEnE,UAAO;IACL,GAAG;IACH,SAAS;KACP,GAAG,OAAO;MACT,aAAa;MACZ,GAAG;MACH,WAAW,aAAa,KAAK,WAAW,KAAK,SAAS;MACtD,gBAAgB,SAAS;MAC1B;KACF;IACF;IACD;AACF,SAAO;;;;AAOX,SAAgB,YAAY,KAAa,OAA0B;AACjE,eAAc,WAAW;EACvB,MAAM,UAAW,OAAO,QAAQ,eAAiC,EAAE;AACnE,SAAO;GACL,GAAG;GACH,SAAS;IACP,GAAG,OAAO;KACT,aAAa;KACZ,GAAG;KACH,WAAW,aAAa,QAAQ,WAAW,KAAK,MAAM;KACtD,gBAAgB,MAAM;KACvB;IACF;GACF;GACD;;;AAIJ,SAAgB,cAAc,KAAmB;AAC/C,eAAc,WAAW;EACvB,MAAM,UAAW,OAAO,QAAQ,eAAiC,EAAE;EACnE,MAAM,UAAU,QAAQ,YAAY;AACpC,MAAI,CAAC,QAAS,QAAO;EACrB,MAAM,GAAG,MAAM,UAAU,GAAG,SAAS,QAAQ,aAAa,EAAE;EAC5D,MAAM,OAAsB;GAAE,GAAG;GAAS,WAAW;GAAM;AAE3D,MAAI,QAAQ,mBAAmB,QAAQ,GACrC,MAAK,iBAAiB,KAAA;AAExB,SAAO;GACL,GAAG;GACH,SAAS;IAAE,GAAG,OAAO;KAAU,aAAa;IAAM;GACnD;GACD;;;;;;;AAQJ,SAAgB,kBAAkB,IAAkB;AAClD,eAAc,WAAW;EACvB,MAAM,UAAW,OAAO,QAAQ,eAAiC,EAAE;AACnE,SAAO;GACL,GAAG;GACH,SAAS;IACP,GAAG,OAAO;KACT,aAAa;KAAE,GAAG;KAAS,gBAAgB;KAAI;IACjD;GACF;GACD;;;;;;;AAQJ,SAAgB,oBAAwC;CACtD,MAAM,QAAQ,UAAU;AACxB,QAAO,MAAM,kBAAkB,MAAM;;;;ACrJvC,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YACE,SACA,SAA0B,KAC1B;AACA,QAAM,QAAQ;AAFL,OAAA,SAAA;;;AAKb,SAAgBC,WAAS,OAAkD;AACzE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAE7E,SAAgB,KAAK,OAAiC;AACpD,QAAO,OAAO,UAAU,YAAY,OAAO,cAAc,MAAM,IAAI,QAAQ;;;AAG7E,SAAgB,YAAY,MAAsB;AAChD,QAAO,SAAS,cACZ,MACA,KAAK,WAAW,aAAa,GAC3B,KAAK,MAAM,EAAmB,GAC9B;;AAER,SAAgB,iBAAiB,OAAiC;AAChE,KACE,OAAO,UAAU,YACjB,CAAC,MAAM,WAAW,IAAI,IACtB,MAAM,WAAW,KAAK,IACtB,MAAM,SAAS,KAAK,CAEpB,QAAO;AAET,QADe,IAAI,IAAI,OAAO,yBAAyB,CACzC,aAAa,SAAS,CAAC,MAAM,WAAW,YAAY;;AAEpE,SAAgB,oBAAoB,OAAuC;AACzE,KACE,CAACA,WAAS,MAAM,IAChB,CAAC,MAAM,QAAQ,MAAM,cAAc,IACnC,CAAC,MAAM,QAAQ,MAAM,eAAe,CAEpC,OAAM,IAAI,mBACR,mEACD;CAEH,MAAM,QAAQ,MAAM,cAAc,KAAK,SAA4B;AACjE,MACE,CAACA,WAAS,KAAK,IACf,CAAC,KAAK,KAAK,GAAG,IACd,OAAO,KAAK,SAAS,YACrB,CAAC,MAAM,QAAQ,KAAK,MAAM,CAE1B,OAAM,IAAI,mBAAmB,gCAAgC;EAC/D,MAAM,QAAQ,KAAK,MAAM,KAAK,SAA4B;AACxD,OACE,CAACA,WAAS,KAAK,IACf,CAAC,KAAK,KAAK,GAAG,IACd,OAAO,KAAK,UAAU,YACtB,CAAC,iBAAiB,KAAK,KAAK,IAC5B,OAAO,KAAK,cAAc,UAE1B,OAAM,IAAI,mBAAmB,gCAAgC;AAC/D,UAAO;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,YAAY,KAAK,KAAK;IAC5B,WAAW,KAAK;IACjB;IACD;AACF,MAAI,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,MAAM,OACnD,OAAM,IAAI,mBAAmB,+BAA+B;AAC9D,SAAO;GAAE,IAAI,KAAK;GAAI,MAAM,KAAK;GAAM;GAAO;GAC9C;AACF,KAAI,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,GAAG,CAAC,CAAC,SAAS,MAAM,OACjD,OAAM,IAAI,mBAAmB,qCAAqC;AAUpE,QAAO;EAAE,eAAe;EAAO,gBATf,MAAM,eAAe,KAAK,WAA4B;AACpE,OACE,CAACA,WAAS,OAAO,IACjB,OAAO,OAAO,QAAQ,YACtB,CAAC,qBAAqB,KAAK,OAAO,IAAI,CAEtC,OAAM,IAAI,mBAAmB,kCAAkC;AACjE,UAAO,EAAE,KAAK,OAAO,KAAK;IAC1B;EACsD;;AAE1D,SAAgB,eACd,QACwB;CACxB,MAAM,OAAO,aAAa,QAAQ,kBAAkB;AACpD,KAAI,CAAC,KACH,OAAM,IAAI,mBACR,4CACA,IACD;CACH,MAAM,aAAa,OAAO,IAAI,iBAAiB;AAC/C,KACE,OAAO,OAAO,iBAAiB,CAAC,SAAS,KACxC,eAAe,QAAQ,CAAC,iBAAiB,KAAK,WAAW,CAE1D,OAAM,IAAI,mBAAmB,kCAAkC,IAAI;AACrE,QAAO;EACL,iBAAiB;EACjB,YAAY,aAAa,QAAQ,aAAa;EAC9C,aAAa,aAAa,QAAQ,cAAc;EAChD,GAAI,aAAa,EAAE,gBAAgB,YAAY,GAAG,EAAE;EACrD;;AAEH,SAAS,aACP,QACA,KACoB;CACpB,MAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,KAAI,UAAU,KAAM,QAAO,KAAA;AAC3B,KACE,OAAO,OAAO,IAAI,CAAC,WAAW,KAC9B,CAAC,aAAa,KAAK,MAAM,IACzB,CAAC,KAAK,OAAO,MAAM,CAAC,CAEpB,OAAM,IAAI,mBAAmB,WAAW,IAAI,IAAI,IAAI;AACtD,QAAO,OAAO,MAAM;;AAEtB,SAAgB,iBACd,MACA,WACQ;CACR,MAAM,MAAM,IAAI,IAAI,MAAM,yBAAyB;AACnD,KAAI,IAAI,WAAW,yBACjB,OAAM,IAAI,mBAAmB,iCAAiC,IAAI;AACpE,KAAI,IAAI,aAAa,eAAe,CAAC,IAAI,SAAS,WAAW,aAAa,CACxE,KAAI,WAAW,YAAY,IAAI;AACjC,MAAK,MAAM,OAAO;EAChB;EACA;EACA;EACA;EACD,EAAW;EACV,MAAM,QAAQ,UAAU;AACxB,MAAI,UAAU,KAAA,EAAW,KAAI,aAAa,OAAO,IAAI;MAChD,KAAI,aAAa,IAAI,KAAK,OAAO,MAAM,CAAC;;AAE/C,QAAO,IAAI,WAAW,IAAI,SAAS,IAAI;;AAEzC,SAAgB,kBACd,OACA,SACA,WACA,QACA,YAA2B,MAC+B;CAC1D,MAAM,UAAUA,WAAS,MAAM,GAAG,MAAM,UAAU,KAAA;AAClD,KACE,CAACA,WAAS,QAAQ,IAClB,QAAQ,qBAAqB,KAC7B,QAAQ,SAAS,YACjB,QAAQ,aAAa,WACrB,QAAQ,oBAAoB,UAAU,mBACtC,QAAQ,oBAAoB,UAC5B,QAAQ,sBAAsB,aAC9B,OAAO,QAAQ,iBAAiB,YAChC,EACE,QAAQ,oBAAoB,QAC5B,OAAO,QAAQ,oBAAoB,aAErC,EACE,QAAQ,wBAAwB,QAChC,OAAO,QAAQ,wBAAwB,UAGzC,OAAM,IAAI,mBACR,4EACD;AAEH,QAAO;EACL,MAAM,QAAQ;EACd,QAAQ,QAAQ,mBAAmB;EACnC,gBAAgB,QAAQ,uBAAuB;EAChD;;;;;ACzLH,eAAsB,oBACpB,SACA,SACwC;CACxC,IAAI;AACJ,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,sBAAsB;GAC5D,UAAU;GACV,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;AACF,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,8BAA8B;AAChE,UAAQ,MAAM,SAAS,MAAM;SACvB;AACN,UAAQ,KACN,yFACD;AACD;;AAEF,KAAI,CAACC,WAAS,MAAM,IAAI,MAAM,SAAS,SACrC,OAAM,IAAI,MACR,mEACD;AAEH,KAAI,MAAM,aAAa,QACrB,OAAM,IAAI,MACR,4CAA4C,QAAQ,0CACrD;AAEH,KACE,CAAC,MAAM,QAAQ,MAAM,yBAAyB,IAC9C,CAAC,MAAM,yBAAyB,OAC7B,QAAuB,OAAO,QAAQ,SACxC,CAED,OAAM,IAAI,MACR,4DACD;AAEH,QAAO;EACL,GAAG,oBAAoB,MAAM;EAC7B,0BAA0B,MAAM;EACjC;;AAGH,SAAgB,cACd,UACyC;AACzC,KAAI,CAAC,SAAU,QAAO,EAAE;AACxB,QAAO,SAAS,cAAc,SAAS,SAAS;EAC9C,MAAM,SAAS,YAAY,KAAK,KAAK,KAAK,KAAK,GAAG;EAClD,MAAM,YAAY,EAAE,iBAAiB,KAAK,IAAI;AAC9C,SAAO,CACL,GAAG,KAAK,MAAM,KAAK,UAAU;GAC3B,OAAO,GAAG,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,WAAW,KAAK,KAAK;GAC3E,OAAO,iBAAiB,KAAK,MAAM,UAAU;GAC9C,EAAE,EACH,GAAG,SAAS,eACT,QAAQ,WACP,SAAS,yBAAyB,SAAS,OAAO,IAAI,CACvD,CACA,KAAK,WAAW;AACf,UAAO;IACL,OAAO,GAAG,SAAS,OAAO;IAC1B,OAAO,iBAAiB,IAAI,OAAO,OAAO,UAAU;IACrD;IACD,CACL;GACD;;;AAIJ,eAAsB,0BACpB,SACA,SACA,MACe;AAEf,KAAI,CAAC,cADY,MAAM,oBAAoB,SAAS,QAAQ,CAChC,CAAC,MAAM,WAAW,OAAO,UAAU,KAAK,CAClE,OAAM,IAAI,MACR,2FACD;;;;;;;;;;;ACwNL,eAAsB,sBACpB,QACA,QAGA;AACA,QAAO,OAAO,IAAI,2BAA2B,OAAO;;;;;;;;;AAUtD,eAAsB,uBACpB,QACA,MAKA;AACA,QAAO,OAAO,KAAK,2BAA2B,KAAK;;;;;;;;;;AA4CrD,eAAsB,oBACpB,QACA,IACA,QAGA;AACA,QAAO,OAAO,IAAI,2BAA2B,MAAM,OAAO;;;;;;;;;;AA+C5D,eAAsB,uCACpB,QACA,IACA,QAGA;AACA,QAAO,OAAO,IACZ,2BAA2B,GAAG,wBAC9B,OACD;;;;;;;;;;AA2BH,eAAsB,oCACpB,QACA,IACA,MAKA;AACA,QAAO,OAAO,KACZ,2BAA2B,GAAG,yBAC9B,KACD;;;;;;;;;AAkKH,eAAsB,wBACpB,QACA,IAGA;AACA,QAAO,OAAO,KAAK,2BAA2B,GAAG,UAAU;;;;;;;;;AAU7D,eAAsB,eACpB,QACA,IAGA;AACA,QAAO,OAAO,IAAI,2BAA2B,GAAG,eAAe;;;;;;;;;AAcjE,eAAsB,mBACpB,QACA,sBAGA;AACA,QAAO,OAAO,IACZ,2BAA2B,qBAAqB,YACjD;;;;;;;;;;AAWH,eAAsB,oBACpB,QACA,sBACA,MAKA;AACA,QAAO,OAAO,IACZ,2BAA2B,qBAAqB,aAChD,KACD;;;;;;;;;;AAWH,eAAsB,oBACpB,QACA,sBACA,MAKA;AACA,QAAO,OAAO,OACZ,2BAA2B,qBAAqB,aAChD,EAAE,MAAM,CACT;;;;;;;;;AAuEH,eAAsB,mBACpB,QACA,MAKA;AACA,QAAO,OAAO,KAAK,uBAAuB,KAAK;;;;;;;;;AA8DjD,eAAsB,oBACpB,QACA,IAGA;AACA,QAAO,OAAO,OAAO,uBAAuB,KAAK;;;;ACn2BnD,SAAS,aACP,OACA,SACkB;AAClB,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,QAAQ,MAAM,aAAa;AACjC,QAAO,QAAQ,QAAQ,MAAM,EAAE,MAAM,aAAa,CAAC,SAAS,MAAM,CAAC;;AAcrE,MAAM,qBAA6C;CACjD,SAAS;CACT,cAAc;CACd,cAAc;CACd,SAAS;CACT,cAAc;CACd,oBAAoB;CACpB,qBAAqB;CACtB;AAED,MAAM,gBAAgB;CACpB;EAAE,OAAO;EAAQ,MAAM;EAAS;CAChC;EAAE,OAAO;EAAQ,MAAM;EAAc;CACrC;EAAE,OAAO;EAAkB,MAAM;EAAc;CAC/C;EAAE,OAAO;EAAQ,MAAM;EAAS;CAChC;EAAE,OAAO;EAAQ,MAAM;EAAc;CACrC;EAAE,OAAO;EAAoB,MAAM;EAAoB;CACvD;EAAE,OAAO;EAAqB,MAAM;EAAqB;CAC1D;AAED,MAAM,kBAAkB;CACtB;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACD;EACE,OAAO;EACP,MAAM;EACN,UAAU;EACV,UAAU;EACX;CACF;AAED,eAAe,sBACb,KACA,SACA,eAC0B;CAC1B,MAAM,SAAS,IAAI,gBAAgB;EACjC,sBAAsB,OAAO,QAAQ;EACrC,gBAAgB;EAChB,WAAW;EACZ,CAAC;AAIF,SAHa,MAAM,IAAI,IACrB,oCAAoC,SACrC,EACW,aAAa,EAAE;;AAG7B,eAAe,eACb,KACA,SACA,eACA,UACwB;CACxB,MAAM,YAAY,MAAM,sBAAsB,KAAK,SAAS,cAAc;AAC1E,KAAI,UAAU,UAAU,EAAG,QAAO;CAMlC,MAAM,EAAE,eAAe,MAAM,QAC3B;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAToB,UAAU,KAAK,OAAO;GAC5C,OAAO,GAAG,EAAE,OAAO,EAAE,UAAU,eAAe;GAC9C,OAAO,EAAE;GACV,EAAE;EAOC,UAAU,OAAe,YACvB,QAAQ,QAAQ,aAAa,OAAO,QAAQ,CAAC;EAChD,EACD,EAAE,UAAU,CACb;AAED,QAAO,cAAc;;AAGvB,SAAgB,wBAAiC;AAC/C,QAAO,IAAI,QAAQ,WAAW,CAC3B,YAAY,8DAA8D,CAC1E,OAAO,iBAAiB,mBAAmB,YAAY,CACvD,OAAO,iBAAiB,mBAAmB,OAAO,CAClD,OAAO,oBAAoB,0CAA0C,CACrE,OAAO,OAAO,SAAyD;AACtE,gBAAc;EAEd,MAAM,UAAU,KAAK,QAAQ,OAAO,KAAK,MAAM,GAAG,mBAAmB;AAErE,MAAI,CAAC,SAAS;AACZ,WAAQ,MACN,0EACD;AACD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,UAAU,UAAU,KAAK,KAAK,GAAG,KAAK;AAE5C,QAAM,cAAc,iBAAiB,EAAE,SAAS,QAAQ;GACxD;;;AAIN,eAAsB,cACpB,KACA,SACA,SACe;CAYf,MAAM,UAAoB,CACxB,GAAG,cAAc,KAAK,OAAO;EAAE,OAAO,EAAE;EAAO,OAAO,EAAE;EAAM,EAAE,EAChE,GAAG,gBAAgB,KAAK,OAAO;EAC7B,OAAO,GAAG,EAAE,MAAM;EAClB,OAAO;GACL,cAAc,EAAE;GAChB,UAAU,EAAE;GACZ,UAAU,EAAE;GACZ,OAAO,EAAE;GACV;EACF,EAAE,CACJ;CAED,IAAI,YAAY;CAChB,MAAM,iBAAiB;AACrB,cAAY;AACZ,SAAO;;CAGT,MAAM,WAAW,MAAM,oBAAoB,SAAS,QAAQ;AAC5D,SAAQ,KAAK,GAAG,cAAc,SAAS,CAAC;CACxC,MAAM,cAAc,UAAU,eAAe,QAC1C,WAAW,CAAC,SAAS,yBAAyB,SAAS,OAAO,IAAI,CACpE;AACD,KAAI,aAAa,OACf,SAAQ,IACN,sCAAsC,YAAY,KAAK,WAAW,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC,GAC1F;CAEH,MAAM,EAAE,SAAS,MAAM,QACrB;EACE,MAAM;EACN,MAAM;EACN,SAAS;EACT;EACA,UAAU,OAAe,YACvB,QAAQ,QAAQ,aAAa,OAAO,QAAQ,CAAC;EAChD,EACD,EAAE,UAAU,CACb;AAED,KAAI,CAAC,KAAM;CAEX,IAAI;CACJ,IAAI;AAEJ,KAAI,OAAO,SAAS,UAAU;AAC5B,MAAI,KAAK,WAAW,aAAa,CAC/B,OAAM,0BAA0B,SAAS,SAAS,KAAK;AAEzD,SAAO;AACP,kBAAgB,mBAAmB;QAC9B;AACL,kBAAgB,KAAK;EAMrB,MAAM,aALO,MAAMC,uCACjB,KACA,SACA;GAAE,WAAW,KAAK;GAAc,UAAU;GAAI,CAC/C,EACsB,wBAAwB,EAAE;AAEjD,MAAI,CAAC,UAAU,QAAQ;AACrB,WAAQ,IAAI,MAAM,KAAK,MAAM,uCAAuC;AACpE,UAAO,KAAK;SACP;GACL,MAAM,kBAAkB,UAAU,KAAK,OAAO;IAC5C,OAAO,EAAE,SAAS,EAAE,QAAQ;IAC5B,OAAO,EAAE;IACV,EAAE;GACH,MAAM,EAAE,SAAS,MAAM,QACrB;IACE,MAAM;IACN,MAAM;IACN,SAAS,YAAY,KAAK,MAAM,aAAa;IAC7C,SAAS;IACT,UAAU,OAAe,YACvB,QAAQ,QAAQ,aAAa,OAAO,QAAQ,CAAC;IAChD,EACD,EAAE,UAAU,CACb;AACD,OAAI,aAAa,CAAC,KAAM;AACxB,UAAO,KAAK,SAAS,QAAQ,MAAM,mBAAmB,KAAe,CAAC;;;CAI1E,IAAI,gBAAgB;AACpB,KAAI,eAAe;EACjB,MAAM,aAAa,MAAM,eACvB,KACA,SACA,eACA,SACD;AACD,MAAI,WACF,iBAAgB,sBAAsB;;AAI1C,KAAI,UAAW;CACf,MAAM,MAAM,GAAG,UAAU,OAAO;AAChC,SAAQ,IAAI,oBAAoB,IAAI,IAAI;CACxC,MAAM,QAAQ,MAAM,OAAO,SAAS;AACpC,OAAM,KAAK,IAAI;;;;AClQjB,MAAM,cAAc;;;;;;;;AASpB,SAAS,iBAAiB,SAAyB;AAIjD,QAHa,QACV,QAAQ,4BAA4B,GAAG,CACvC,QAAQ,YAAY,GAAG,CACd,QAAQ,kBAAkB,GAAG;;AAG3C,SAAS,WAAW,WAA2B;AAC7C,QAAO,KAAK,WAAW,YAAY;;;AAIrC,SAAgB,gBAAgB,WAAuC;CACrE,MAAM,OAAO,WAAW,UAAU;AAClC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;EACF,MAAM,MAAM,aAAa,MAAM,QAAQ;EACvC,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,OAAO,OAAO,YAAY,SAC5B,QAAO,UAAU,iBAAiB,OAAO,QAAQ;AAEnD,SAAO;SACD;AACN,SAAO;;;;;;;;AASX,SAAgB,sBACd,WAC0B;CAC1B,MAAM,OAAO,WAAW,UAAU;AAClC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;EACF,MAAM,MAAM,aAAa,MAAM,QAAQ;AACvC,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;;;;;;;;;;;;AAcX,SAAgB,aACd,UACA,OACS;AACT,QACE,CAAC,YAAY,SAAS,YAAY,MAAM,MAAM,MAAM,WAAW;;;AAKnE,SAAgB,wBACd,UACA,OACQ;AACR,QACE,GAAG,MAAM,KAAK,KAAK,MAAM,GAAG,yDAAyD,SAAS,UAAU,KAAK,SAAS,QAAQ;;;AAMlI,SAAgB,iBAAiB,WAAmB,QAA2B;AAE7E,eADa,WAAW,UAAU,EACd,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,MAAM,QAAQ;;;;ACtHtE,MAAM,aAAqC;CACzC,WAAW;CACX,SAAS;CACT,QAAQ;CACR,OAAO;CACP,SAAS;CACT,QAAQ;CACR,OAAO;CACP,QAAQ;CACT;AAED,MAAM,eAAuC;CAC3C,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACT;AAOD,SAAgB,YAAY,KAAuB;CACjD,MAAM,OAAO,WAAW;AACxB,KAAI,KAAM,QAAO;EAAE,MAAM;EAAM,QAAQ;EAAM;CAE7C,MAAM,SAAS,aAAa;AAC5B,KAAI,OAAQ,QAAO;EAAE,MAAM;EAAQ,QAAQ;EAAO;AAElD,QAAO;EAAE,MAAM;EAA4B,QAAQ;EAAO;;;;AC3C5D,MAAM,4BAA4B,IAAI,IAAI;CACxC;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,MAAa,yBAA8C,IAAI,IAAI;CACjE;CACA;CACA;CACD,CAAC;AAEF,SAAgB,qBAAqB,cAA+B;CAClE,MAAM,CAAC,UAAU,0BAA0B,aAAa,CAAC,MAAM,IAAI;AACnE,QAAO,WAAW,KAAA,KAAa,uBAAuB,IAAI,OAAO;;AAGnE,MAAM,gCAAgC,IAAI,IAAI;CAC5C;CACA;CACA;CACD,CAAC;AAEF,SAAS,gBAAgB,KAAsB;AAE7C,QADiB,IAAI,MAAM,IAAI,CACf,OACb,YAAY,QAAQ,SAAS,KAAK,YAAY,OAAO,YAAY,KACnE;;AAGH,SAAgB,0BAA0B,OAAuB;AAC/D,QAAO,MAAM,WAAW,MAAM,IAAI;;;;;;;AAQpC,SAAgB,mBAAmB,cAA+B;CAChE,MAAM,MAAM,0BAA0B,aAAa;AACnD,KAAI,CAAC,gBAAgB,IAAI,CAAE,QAAO;AAClC,KAAI,0BAA0B,IAAI,IAAI,CAAE,QAAO;AAC/C,KAAI,qBAAqB,IAAI,CAAE,QAAO;CAEtC,MAAM,WAAW,IAAI,MAAM,IAAI;CAC/B,MAAM,SAAS,SAAS;CACxB,MAAM,WAAW,SAAS,GAAG,GAAG;AAChC,KAAI,CAAC,UAAU,CAAC,YAAY,SAAS,SAAS,EAAG,QAAO;AAExD,KACE,WAAW,YACX,WAAW,YACX,WAAW,aACX,WAAW,UAEX,QAAO;AAGT,QAAO,SAAS,UAAU,KAAK,8BAA8B,IAAI,SAAS;;;;AEJ5E,MAAa,sBAAyC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE5D,CAAC,MAAM;;;;;;;;;;ACtDR,SAAgB,0BAA0B,MAAsB;AAC9D,QAAO,2BAA2B,KAAK;;AAGzC,SAAgB,iBAAiB,UAAmC;CAClE,MAAM,cAA4B,EAAE;CACpC,MAAM,sBAAM,IAAI,KAAa;AAE7B,MAAK,IAAI,QAAQ,GAAG,QAAQ,SAAS,QAAQ,SAAS;EACpD,MAAM,MAAM,SAAS;EACrB,MAAM,UACJ,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,GACzD,MACD,EAAE;EAER,MAAM,KAAK,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK,KAAA;EACzD,MAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO,KAAA;AAE/D,MAAI,OAAO,KAAA,KAAa,GAAG,MAAM,KAAK,GACpC,aAAY,KAAK;GACf,UAAU;GACV,SAAS;GACT,QAAQ;IAAE,MAAM;IAAW;IAAO,aAAa;IAAM,OAAO;IAAM;GACnE,CAAC;WACO,MAAM,IAAI,IAAI,GAAG,CAC1B,aAAY,KAAK;GACf,UAAU;GACV,SAAS,oCAAoC,GAAG;GAChD,QAAQ;IAAE,MAAM;IAAW;IAAO,WAAW;IAAI,OAAO;IAAM;GAC/D,CAAC;WACO,GACT,KAAI,IAAI,GAAG;AAGb,MAAI,CAAC,KACH,aAAY,KAAK;GACf,UAAU;GACV,SAAS,qBAAqB,MAAM,MAAM;GAC1C,QAAQ;IAAE,MAAM;IAAW;IAAO,WAAW;IAAI,OAAO;IAAQ;GACjE,CAAC;WACO,CAAC,oBAAoB,SAAS,KAAK,CAC5C,aAAY,KAAK;GACf,UAAU;GACV,SAAS,0BAA0B,KAAK;GACxC,QAAQ;IAAE,MAAM;IAAW;IAAO,aAAa;IAAM,OAAO;IAAQ;GACrE,CAAC;;AAIN,QAAO;;;;ACxDT,SAAgB,eAAe,QAAiC;CAC9D,MAAM,cAA4B,EAAE;CACpC,MAAM,wBAAQ,IAAI,KAAa;AAE/B,MAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;EAClD,MAAM,MAAM,OAAO;EACnB,MAAM,QACJ,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI,GACzD,MACD,EAAE;EAER,MAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,KAAA;EAC3D,MAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,KAAA;EAC3D,MAAM,WAAW,MAAM;AAEvB,MAAI,CAAC,KACH,aAAY,KAAK;GACf,UAAU;GACV,SAAS,4BAA4B,MAAM;GAC3C,QAAQ;IAAE,MAAM;IAAS;IAAO,OAAO;IAAQ;GAChD,CAAC;WACO,MAAM,IAAI,KAAK,CACxB,aAAY,KAAK;GACf,UAAU;GACV,SAAS,sCAAsC,KAAK;GACpD,QAAQ;IAAE,MAAM;IAAS;IAAO,WAAW;IAAM,OAAO;IAAQ;GACjE,CAAC;MAEF,OAAM,IAAI,KAAK;AAMjB,MAAI,CAAC,QAAQ,SAAS,UAAU,SAAS,YAAY,EAD7B,CAAC,QAAQ,CAAC,UAEhC,aAAY,KAAK;GACf,UAAU;GACV,SAAS,mBAAmB,QAAQ,MAAM;GAC1C,QAAQ;IAAE,MAAM;IAAS;IAAO,WAAW;IAAM,OAAO;IAAQ;GACjE,CAAC;AAGJ,MAAI,SACF,KAAI,CAAC,MAAM,QAAQ,SAAS,CAE1B,aAAY,KAAK;GACf,UAAU;GACV,SAAS,mBAAmB,QAAQ,MAAM;GAC1C,QAAQ;IAAE,MAAM;IAAS;IAAO,WAAW;IAAM,OAAO;IAAY;GACrE,CAAC;MAEF,aAAY,KAAK,GAAG,iBAAiB,SAAS,CAAC;AAMnD,MAAI,MAAM,QAAQ,MAAM,OAAO,CAC7B,aAAY,KAAK,GAAG,eAAe,MAAM,OAAoB,CAAC;;AAIlE,QAAO;;;;AC5DT,SAAS,oBAAoB,MAAsB;AACjD,QAAO,KAAK,QACV,8DACA,GACD;;AAKH,SAAS,wBAAwB,UAA0B;CACzD,IAAI,QAAQ;CACZ,MAAM,QAID,EAAE;CAEP,IAAI,IAAI;AAER,QAAO,IAAI,SAAS,QAAQ;EAC1B,MAAM,KAAK,SAAS,WAAW,EAAE;AAGjC,MAAI,OAAO,MAAQ,OAAO,MAAQ,OAAO,MAAQ,OAAO,GAAM;AAC5D;AACA;;AAGF,MAAI,OAAO,KAAM;AAEf,SAAM,KAAK;IAAE,MAAM;IAAU,sBAAM,IAAI,KAAK;IAAE,cAAc;IAAM,CAAC;AAEnE;aACS,OAAO,KAAM;AAEtB,SAAM,KAAK;AAEX;aACS,OAAO,IAAM;AAGtB,SAAM,KAAK;IAAE,MAAM;IAAS,sBAAM,IAAI,KAAK;IAAE,cAAc;IAAO,CAAC;AACnE;aACS,OAAO,IAAM;AAEtB,SAAM,KAAK;AAEX;aACS,OAAO,GAEhB;WACS,OAAO,IAAM;GAEtB,MAAM,MAAM,MAAM,MAAM,SAAS;AACjC,OAAI,KAAK,SAAS,SAChB,KAAI,eAAe;AAGrB;aACS,OAAO,IAAM;GAEtB,IAAI,IAAI,IAAI;AACZ,UAAO,IAAI,SAAS,QAAQ;AAC1B,QACE,SAAS,WAAW,EAAE,KAAK,MAC3B,SAAS,WAAW,IAAI,EAAE,KAAK,GAE/B;AAEF;;GAEF,MAAM,MAAM,SAAS,MAAM,IAAI,GAAG,EAAE;AACpC,OAAI,IAAI;GAER,MAAM,MAAM,MAAM,MAAM,SAAS;AACjC,OAAI,KAAK,SAAS,YAAY,IAAI,cAAc;AAC9C,QAAI,QAAQ,YAAY,IAAI,KAAK,IAAI,IAAI,CACvC;AAEF,QAAI,KAAK,IAAI,IAAI;AACjB,QAAI,eAAe;;QAOrB;;AAIJ,QAAO;;AAST,SAAgB,mBACd,MACA,SACc;CACd,MAAM,mBAAmB,SAAS,oBAAoB;CACtD,MAAM,cAA4B,EAAE;CAGpC,MAAM,QADW,oBAAoB,KAAK,CACnB,MACrB,4DACD;AACD,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,WAAW,MAAM,MAAM;CAE7B,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,SAAS;UACtB,GAAG;AACV,cAAY,KAAK;GACf,UAAU;GACV,SAAS,mBAAoB,EAAY;GAC1C,CAAC;AACF,SAAO;;CAIT,MAAM,QAAQ,wBAAwB,SAAS;AAC/C,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,IACzB,aAAY,KAAK;EACf,UAAU;EACV,SAAS;EACV,CAAC;AAIJ,KACE,WAAW,QACX,OAAO,WAAW,YAClB,MAAM,QAAQ,OAAO,SAAS,CAE9B,aAAY,KAAK,GAAG,iBAAiB,OAAO,SAAS,CAAC;AAIxD,KAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,YAAY,QAAQ;EACvE,MAAM,SAAS,OAAO;AAEtB,MAAI,qBAAqB,QACvB,KAAI,CAAC,MAAM,QAAQ,OAAO,CACxB,aAAY,KAAK;GACf,UAAU;GACV,SAAS;GACV,CAAC;MAEF,aAAY,KAAK,GAAG,eAAe,OAAO,CAAC;WAEpC,qBAAqB;OAE5B,MAAM,QAAQ,OAAO,IACrB,OAAO,WAAW,YAClB,WAAW,KAEX,aAAY,KAAK;IACf,UAAU;IACV,SAAS;IACV,CAAC;aAIA,MAAM,QAAQ,OAAO,CACvB,aAAY,KAAK,GAAG,eAAe,OAAO,CAAC;;AAKjD,QAAO;;;;ACjLT,MAAM,uBACJ;AAIF,MAAM,qBACJ;AAOF,MAAM,sBACJ;AAMF,MAAM,yBAAyB,IAAI,IAAI;CAAC;CAAU;CAAkB;CAAS,CAAC;AAK9E,MAAM,wBAAwB;;AAG9B,SAAgB,sBAAsB,MAAuB;AAC3D,QAAO,uBAAuB,IAAI,KAAK,IAAI,sBAAsB,KAAK,KAAK;;AAsB7E,SAAS,aAAa,QAAwB;AAC5C,QAAO,OACJ,QAAQ,sBAAsB,GAAG,CACjC,QAAQ,oBAAoB,GAAG;;;;;;;;AASpC,SAAgB,yBAAyB,QAAoC;CAC3E,MAAM,OAAO,aAAa,OAAO;CACjC,MAAM,UAAU,IAAI,OAAO,qBAAqB,IAAI;CACpD,MAAM,aAAiC,EAAE;CACzC,IAAI,QAAQ;CACZ,IAAI;AACJ,SAAQ,QAAQ,QAAQ,KAAK,KAAK,MAAM,MAAM;EAC5C,MAAM,OAAO,MAAM;AACnB,MAAI,CAAC,KAAM;AACX,aAAW,KAAK;GAAE;GAAM,IAAI,MAAM;GAAI,SAAS,MAAM;GAAI,OAAO;GAAS,CAAC;;AAE5E,QAAO;;;;;;;;;AAkCT,SAAgB,6BACd,WACA,sBACqB;CACrB,MAAM,UAA+B,EAAE;AACvC,MAAK,MAAM,YAAY,WAAW;EAChC,MAAM,2BAAW,IAAI,KAAa;AAClC,OAAK,MAAM,aAAa,yBAAyB,SAAS,QAAQ,EAAE;AAClE,OAAI,qBAAqB,IAAI,UAAU,KAAK,CAAE;AAC9C,OAAI,sBAAsB,UAAU,KAAK,CAAE;AAC3C,OAAI,SAAS,IAAI,UAAU,KAAK,CAAE;AAClC,YAAS,IAAI,UAAU,KAAK;AAC5B,WAAQ,KAAK;IACX,cAAc,SAAS;IACvB,aAAa,UAAU;IACvB,YAAY;KACV,UAAU;KACV,SAAS,+BAA+B,UAAU,KAAK;KACvD,QAAQ;MACN,MAAM;MACN,aAAa,UAAU;MACvB,OAAO,UAAU;MAClB;KACF;IACF,CAAC;;;AAGN,QAAO;;;;ACxHT,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CAEA,YAAY,cAAsB,MAAc;AAC9C,OAAK,eAAe;AACpB,OAAK,eAAe,0BAA0B,SAAS,MAAM,aAAa,CAAC;AAC3E,OAAK,OAAO,YAAY,QAAQ,aAAa,CAAC,aAAa,CAAC;;CAG9D,IAAI,OAAe;AACjB,SAAO,SAAS,KAAK,aAAa;;CAGpC,IAAI,SAAkB;AACpB,SAAO,KAAK,KAAK;;CAGnB,IAAI,WAAoB;AACtB,SAAO,KAAK,aAAa,SAAS,UAAU;;CAG9C,IAAI,SAAkB;AACpB,SAAO,KAAK,aAAa,SAAS,QAAQ;;CAG5C,IAAI,SAAkB;AACpB,SAAO,WAAW,KAAK,aAAa;;CAGtC,OAAe;AACb,SAAO,aAAa,KAAK,cAAc,QAAQ;;CAGjD,aAAqB;AACnB,SAAO,aAAa,KAAK,aAAa;;CAGxC,MAAM,SAAgC;AACpC,YAAU,QAAQ,KAAK,aAAa,EAAE,EAAE,WAAW,MAAM,CAAC;AAC1D,MAAI,OAAO,YAAY,SACrB,eAAc,KAAK,cAAc,SAAS,QAAQ;MAElD,eAAc,KAAK,cAAc,QAAQ;;CAI7C,WAAmB;EACjB,MAAM,UAAU,KAAK,SAAS,KAAK,MAAM,GAAG,KAAK,YAAY;AAC7D,SAAO,WAAW,SAAS,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM;;CAG3D,OAAe;AACb,SAAO,SAAS,KAAK,aAAa,CAAC;;CAGrC,IAAI,aAAsB;EAKxB,MAAM,QAAQ,KAAK,aAAa,MAAM,QAAQ;AAC9C,SAAO,MAAM,UAAU,KAAK,CAAC,kBAAkB,IAAI,MAAM,GAAI;;CAG/D,iBAA+B;AAC7B,MAAI,CAAC,KAAK,SAAU,QAAO,EAAE;EAE7B,MAAM,mBAAqC,KAAK,aAC5C,WACA;AAEJ,SAAO,mBAAmB,KAAK,MAAM,EAAE,EAAE,kBAAkB,CAAC;;;;;ACnGhE,MAAM,cAAc;AAOpB,IAAa,cAAb,MAAyB;CACvB;CAEA,YAAY,MAAc;AACxB,OAAK,WAAW,KAAK,MAAM,KAAK,MAAM,YAAY,CAAC;;CAGrD,OAAO,cAA+B;EACpC,IAAI,SAAS;AACb,OAAK,MAAM,EAAE,SAAS,aAAa,KAAK,SACtC,KAAI,KAAK,MAAM,SAAS,aAAa,CACnC,UAAS,CAAC;AAGd,SAAO;;CAGT,MAAc,UAA6B;AACzC,MAAI,CAAC,WAAW,SAAS,CAAE,QAAO,EAAE;AACpC,SAAO,aAAa,UAAU,QAAQ,CACnC,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC,CACtC,KAAK,MAAM;GACV,MAAM,UAAU,EAAE,WAAW,IAAI;GACjC,IAAI,UAAU,UAAU,EAAE,MAAM,EAAE,GAAG;AACrC,OAAI,QAAQ,WAAW,IAAI,CAAE,WAAU,QAAQ,MAAM,EAAE;AACvD,UAAO;IAAE;IAAS;IAAS;IAC3B;;CAGN,MAAc,SAAiB,MAAuB;AACpD,MAAI,QAAQ,SAAS,IAAI,CACvB,QAAO,KAAK,WAAW,QAAQ,IAAI,SAAS,QAAQ,MAAM,GAAG,GAAG;AAElE,MAAI,QAAQ,SAAS,IAAI,CACvB,QAAO,KAAK,QAAQ,SAAS,KAAK;AAEpC,SAAO,KAAK,QAAQ,SAAS,KAAK,IAAI,KAAK,QAAQ,SAAS,SAAS,KAAK,CAAC;;CAG7E,QAAgB,SAAiB,KAAsB;EACrD,MAAM,KAAK,QACR,MAAM,KAAK,CACX,KAAK,MACJ,EACG,QAAQ,qBAAqB,OAAO,CACpC,QAAQ,OAAO,QAAQ,CACvB,QAAQ,OAAO,OAAO,CAC1B,CACA,KAAK,KAAK;AACb,SAAO,IAAI,OAAO,IAAI,GAAG,GAAG,CAAC,KAAK,IAAI;;;;;ACvD1C,MAAM,gBAAgB;CAAC;CAAa;CAAU;CAAS;AACvD,MAAM,uBAAuB;AAE7B,IAAa,YAAb,MAAuB;CACrB;CACA;CAEA,YAAY,MAAc;AACxB,OAAK,OAAO,QAAQ,KAAK;AACzB,OAAK,SAAS,IAAI,YAAY,KAAK,KAAK;;CAG1C,UAAmB;AACjB,SACE,WAAW,KAAK,KAAK,MAAM,qBAAqB,CAAC,IACjD,cAAc,MAAM,MAAM;AACxB,OAAI;AACF,WAAO,SAAS,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC,aAAa;WAC3C;AACN,WAAO;;IAET;;CAIN,QAAqB;AACnB,SAAO,KAAK,KAAK,KAAK,KAAK,CAAC,QACzB,MACC,mBAAmB,EAAE,aAAa,IAClC,CAAC,KAAK,OAAO,OAAO,EAAE,aAAa,CACtC;;CAGH,eAAe,YAAyC;AAEtD,SAAO,mBADM,KAAK,KAAK,WAAW,CACH,aAAa;;CAG9C,KAAK,YAA2C;AAC9C,MAAI,sBAAsB,UAAW,QAAO;AAI5C,SAAO,IAAI,UAHC,WAAW,WAAW,GAC9B,aACA,KAAK,KAAK,MAAM,WAAW,EACL,KAAK,KAAK;;CAGtC,KAAa,KAA0B;EACrC,MAAM,UAAuB,EAAE;AAC/B,OAAK,MAAM,SAAS,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC,EAAE;AAC7D,OAAI,MAAM,KAAK,WAAW,IAAI,CAAE;GAChC,MAAM,OAAO,KAAK,KAAK,MAAM,KAAK;AAClC,OAAI,MAAM,aAAa,EAAE;AACvB,QAAI,MAAM,SAAS,eAAgB;AACnC,YAAQ,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC;cACvB,MAAM,QAAQ,CACvB,SAAQ,KAAK,IAAI,UAAU,MAAM,KAAK,KAAK,CAAC;;AAGhD,SAAO;;;;;;AC3DX,SAAgB,yBAA6C;CAC3D,IAAI,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AACvD,QAAO,MAAM;AACX,OAAK,MAAM,aAAa,CACtB,KAAK,WAAW,mBAAmB,EACnC,KAAK,WAAW,QAAQ,mBAAmB,CAC5C,CACC,KAAI,WAAW,KAAK,WAAW,oBAAoB,CAAC,CAAE,QAAO;EAE/D,MAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,WAAW,UAAW,QAAO,KAAA;AACjC,cAAY;;;AAGhB,IAAa,qBAAb,MAAgC;CAC9B;CACA,YAAY,YAA6B,wBAAwB,EAAE;AAAtC,OAAA,YAAA;AAC3B,OAAK,UAAU,EAAE;AACjB,MAAI,CAAC,UAAW;AAChB,MAAI;GACF,MAAM,QAAiB,KAAK,MAC1B,aAAa,KAAK,WAAW,oBAAoB,EAAE,OAAO,CAC3D;AACD,OACE,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,aAAa,UACf,MAAM,YAAY,KAClB,EAAE,aAAa,UACf,CAAC,MAAM,QAAQ,MAAM,QAAQ,CAE7B;AACF,OACE,CAAC,MAAM,QAAQ,OACZ,WACC,OAAO,WAAW,YAAY,iBAAiB,KAAK,OAAO,CAC9D,CAED;AACF,OAAI,CAAC,WAAW,KAAK,WAAW,qBAAqB,CAAC,CAAE;AACxD,QAAK,UAAU,MAAM;UACf;;CAIV,KAAK,MAAkC;AACrC,MACE,CAAC,KAAK,aACN,CAAC,wDAAwD,KAAK,KAAK,CAEnE,QAAO,KAAA;AACT,MAAI;AACF,UAAO,aAAa,KAAK,KAAK,WAAW,KAAK,CAAC;UACzC;AACN;;;;;;AC3DN,SAAgB,qBAAqB,MAAmC;AACtE,QAAO;;;+BAGsB,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDxD,SAAgB,gBACd,MACA,MACQ;CACR,MAAM,SAAS,qBAAqB,KAAK;AACzC,KAAI,KAAK,SAAS,UAAU,CAC1B,QAAO,KAAK,QAAQ,WAAW,GAAG,OAAO,WAAW;AAEtD,QAAO,OAAO;;;;AC/ChB,SAAgBE,aAAW,OAAuB;AAChD,QAAO,MAAM,QACX,aACC,UACE;EAAE,KAAK;EAAS,KAAK;EAAQ,KAAK;EAAQ,MAAK;EAAU,KAAK;EAAS,EACtE,SACG,KACR;;;AAGH,SAAgB,aAAa,SAAiB,SAAyB;CACrE,MAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,KAAI,CAAC,gBAAgB,KAAK,QAAQ,CAChC,OAAM,IAAI,MAAM,6BAA6B;AAC/C,KAAI,IAAI,SAAS,WAAW,OAAO,CACjC,KAAI,WAAW,GAAG,QAAQ,WAAW,IAAI,SAAS,MAAM,EAAE;AAC5D,QAAO,IAAI;;AAWb,SAAgB,iBACd,QACA,MACQ;CACR,MAAM,WAAW,MAAM,OAAO,KAAK;CACnC,MAAM,WAAsB,EAAE;CAC9B,SAAS,MAAM,QAAsB;AACnC,OAAK,MAAM,SAAS,OAAO,WACzB,KAAI,aAAa,OAAO;AACtB,YAAS,KAAK,MAAM;AACpB,SAAM,MAAM;;;AAIlB,OAAM,SAAS;CACf,MAAM,OAAO,SAAS,MAAM,OAAO,GAAG,YAAY,OAAO;AAGzD,MAAK,MAAM,MAAM,UAAU;AACzB,MAAI,GAAG,YAAY,UAAU,GAAG,WAC9B,IAAG,WAAW,WAAW,OAAO,GAAG,WAAW,WAAW,QAAQ,GAAG,EAAE,EAAE;AAE1E,MAAI,GAAG,YAAY,UAAU;GAC3B,MAAM,MAAM,GAAG,MAAM,MAAM,SAAS,KAAK,SAAS,MAAM,EAAE,SAAS;AACnE,OACE,yDAAyD,KAAK,IAAI,IAClE,GAAG,WAEH,IAAG,WAAW,WAAW,OACvB,GAAG,WAAW,WAAW,QAAQ,GAAG,EACpC,EACD;;AAEL,MACE,GAAG,YAAY,0BACf,GAAG,YAAY,mBACf;AACA,MAAG,QAAQ,GAAG,MAAM,QACjB,SAAS,KAAK,SAAS,aAAa,KAAK,SAAS,OACpD;AACD,MAAG,MAAM,KAAK;IAAE,MAAM;IAAW,OAAO;IAAQ,CAAC;AACjD,OAAI,KAAK,UACP,IAAG,MAAM,KAAK;IACZ,MAAM;IACN,OAAO,IAAI,IAAI,KAAK,MAAM,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE;IACzD,CAAC;;AAEN,MAAI,GAAG,YAAY,QAAQ;AACzB,MAAG,QAAQ,GAAG,MAAM,QAAQ,SAAS,KAAK,SAAS,SAAS;AAC5D,MAAG,MAAM,KAAK;IACZ,MAAM;IACN,OAAO,GAAG,KAAK,YAAY;IAC5B,CAAC;;EAEJ,MAAM,aAAa,GAAG,MAAM,MAAM,SAAS,KAAK,SAAS,aAAa;AACtE,MAAI,WAAY,YAAW,QAAQ,GAAG,KAAK,YAAY;AACvD,MAAI,GAAG,YAAY,OAAO,GAAG,YAAY,QAAQ;GAC/C,MAAM,OAAO,GAAG,MAAM,MAAM,MAAM,EAAE,SAAS,OAAO;AACpD,OAAI,KAAM,MAAK,QAAQ,YAAY,KAAK,OAAO,KAAK;;;CAGxD,MAAM,UAAU,cACd,8BAA8BA,aAAW,KAAK,YAAY,CAAC,mDAC5D;AACD,MAAK,WAAW,QAAQ,GAAG,QAAQ,WAAW;CAC9C,MAAM,OAAO,cACX,eAAeA,aAAW,IAAI,IAAI,KAAK,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,IACjE;AACD,MAAK,WAAW,QAAQ,GAAG,KAAK,WAAW;CAG3C,MAAM,SAAS,cAAc,OAAO,OAAO;AAC3C,MAAK,WAAW,KAAK,GAAG,OAAO,WAAW;CAC1C,MAAM,QAAQ,cAAc,kBAAkB,CAAC,WAAW;AAC1D,KAAI,SAAS,gBAAgB,OAAO;AAClC,QAAM,WAAW,KAAK;GACpB,UAAU;GACV,OAAO,OAAO;GACd,YAAY;GACb,CAAC;AACF,OAAK,WAAW,KAAK,MAAM;;CAI7B,MAAM,QAAQ,cACZ,uGACD;AACD,MAAK,WAAW,QAAQ,GAAG,MAAM,WAAW;CAE5C,MAAM,aAAa,cAAc;;;;;;;oBADjB,KAAK,UAAU,KAAK,UAAU,CAAC,QAAQ,MAAM,UAAU,CAQ7C;;;;;;cAMf;AACX,MAAK,WAAW,QAAQ,GAAG,WAAW,WAAW;AACjD,QAAO,gBAAgB,UAAU,SAAS,EAAE,KAAK,WAAW;;AAE9D,SAAgB,YAAY,MAAc,MAAiC;AACzE,KAAI,KAAK,WAAW,IAAI,CACtB,QACE,KAAK,cAAc,iBAAiB,KAAK,MAAM,KAAK,UAAU,GAAG;CAErE,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,MAAM,IAAI,IAAI,KAAK,MAAM,KAAK,OAAO,CAAC;SACjD;AACN,SAAO;;AAET,KAAI,CAAC,CAAC,SAAS,SAAS,CAAC,SAAS,OAAO,SAAS,CAAE,QAAO;CAC3D,MAAM,WAAW,sBAAsB,KAAK,KAAK,IAAI,KAAK,WAAW,KAAK;CAC1E,MAAM,cAAc,YAAY,OAAO,WAAW,KAAK;AACvD,KAAI,YAAY,CAAC,eAAe,OAAO,WAAW,KAAK,YACrD,QAAO;AACT,QAAO,WAAW,YAAY,OAAO,SAAS;CAC9C,MAAM,WACJ,eACA,mCAAmC,KAAK,OAAO,SAAS,IACxD,OAAO,SAAS,WAAW,aAAa,IACxC,KAAK,SAAS,cAAc,MAAM,SAChC,KAAK,MAAM,MAAM,MAAM,EAAE,SAAS,OAAO,SAAS,CACnD,IACD,KAAK,SAAS,eAAe,MAC1B,WACC,OAAO,aAAa,IAAI,OAAO,SAC/B,OAAO,SAAS,WAAW,IAAI,OAAO,IAAI,GAAG,CAChD;CACH,MAAM,OAAO,OAAO,WAAW,OAAO,SAAS,OAAO;AACtD,QACE,KAAK,eACJ,WAAW,iBAAiB,MAAM,KAAK,UAAU,GAAG;;;;;ACjKzD,SAAgB,sBACd,KACA,MACS;CACT,MAAM,OAAO,IAAI,OAAO;CACxB,MAAM,QAAQ,IAAI,IAAI;EAAC;EAAM;EAAa;EAAa;EAAQ,CAAC;AAChE,MAAK,SAAS,aAAa,SAAS,SAAS,IAAI,OAAO,cAAc;EACpE,MAAM,UAAU,IAAI,OAAO,aAAa,QAAQ,YAAY,GAAG;AAC/D,QAAM,IAAI,QAAQ,SAAS,IAAI,GAAG,IAAI,QAAQ,KAAK,QAAQ;;CAE7D,IAAI;AACJ,KAAI;AACF,QAAM,IAAI,IAAI,UAAU,IAAI,QAAQ,OAAO;SACrC;AACN,SAAO;;AAET,KACE,CAAC,MAAM,IAAI,IAAI,SAAS,IACxB,OAAO,IAAI,QAAQ,GAAG,KAAK,QAC3B,IAAI,YACJ,IAAI,YACJ,IAAI,aAAa,IAEjB,QAAO;AACT,KAAI,IAAI,QAAQ,UAAU,IAAI,QAAQ,WAAW,IAAI,OAAQ,QAAO;AACpE,QAAO,IAAI,QAAQ,sBAAsB;;AAG3C,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CACA,YACE,KACA,OACA,YACA,eAAgC,IAAI,oBAAoB,EACxD;AAJiB,OAAA,MAAA;AACA,OAAA,QAAA;AACA,OAAA,aAAA;AACA,OAAA,eAAA;AAEjB,OAAK,SAAS,aAAa,YAAY,EAAE,MAAM,QAAQ;;;CAGzD,MAAM,mBAAkC;AACtC,MAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAAS;AACrC,MAAI,KAAK,QAAS,OAAM,KAAK;AAC7B,OAAK,WAAW,KAAA;AAEhB,OAAK,WADQ,MAAM,KAAK,cAAc;;CAGxC,MAAc,eAA+C;AAC3D,SAAO,oBACL,MAAM,KAAK,IAAI,IAAI,8BAA8B,EAC/C,sBAAsB,KAAK,MAAM,IAClC,CAAC,CACH;;CAEH,MAAc,cAA8C;AAC1D,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,OAAK,YAAY,KAAK,cAAc;AACpC,MAAI;AACF,QAAK,WAAW,MAAM,KAAK;AAC3B,UAAO,KAAK;YACJ;AACR,QAAK,UAAU,KAAA;;;CAGnB,MAAM,OAAO,KAAsB,KAAuC;EACxE,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,OAAO;AACjE,MAAI,IAAI,aAAa,eAAe,CAAC,IAAI,SAAS,WAAW,aAAa,CACxE,QAAO;AACT,MAAI,UAAU,iBAAiB,oBAAoB;AACnD,MAAI,UAAU,mBAAmB,cAAc;AAC/C,MAAI,UAAU,0BAA0B,UAAU;AAClD,MAAI;AACF,OAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ;AACjD,QAAI,UAAU,SAAS,YAAY;AACnC,UAAM,IAAI,mBAAmB,gCAAgC,IAAI;;AAEnE,OAAI,IAAI,SAAS,WAAW,sBAAsB,EAAE;IAClD,MAAM,QAAQ,KAAK,aAAa,KAC9B,IAAI,SAAS,MAAM,GAA6B,CACjD;AACD,QAAI,CAAC,MACH,OAAM,IAAI,mBACR,iEACA,IACD;AACH,QAAI,UAAU,gBAAgB,iCAAiC;AAC/D,QAAI,IAAI,IAAI,WAAW,SAAS,KAAA,IAAY,MAAM;AAClD,WAAO;;GAET,MAAM,WAAW,MAAM,KAAK,aAAa;AACzC,OAAI,IAAI,aAAa,uBAAuB;AAC1C,QAAI,UAAU,gBAAgB,mBAAmB;AACjD,QAAI,IACF,IAAI,WAAW,SACX,KAAA,IACA,KAAK,UAAU;KACb,UAAU,KAAK,MAAM;KACrB,MAAM;KACN,GAAG;KACH,0BAA0B,SAAS,eAChC,QAAQ,WACP,KAAK,aAAa,QAAQ,SAAS,OAAO,IAAI,CAC/C,CACA,KAAK,WAAW,OAAO,IAAI;KAC/B,CAAC,CACP;AACD,WAAO;;AAET,QACG,IAAI,aAAa,eAAe,IAAI,aAAa,iBAClD,CAAC,IAAI,aAAa,IAAI,kBAAkB,EACxC;IACA,MAAM,UAAU,SAAS,eAAe,QAAQ,WAC9C,KAAK,aAAa,QAAQ,SAAS,OAAO,IAAI,CAC/C;IACD,MAAM,QAAQ,SAAS,cACpB,KAAK,SAAS;KACb,MAAM,eAAe,CACnB,GAAG,KAAK,MAAM,KAAK,UAAU;MAC3B,MAAM,KAAK;MACX,OAAO,KAAK,SAAS,KAAK,YAAY,KAAK;MAC5C,EAAE,EACH,GAAG,QAAQ,KAAK,YAAY;MAC1B,MAAM,IAAI,OAAO;MACjB,OAAO,OAAO,MAAM;MACrB,EAAE,CACJ;AACD,YAAO,OAAOC,aAAW,KAAK,KAAK,CAAC,WAAW,aAAa,KAAK,SAAS,gBAAgBA,aAAW,iBAAiB,KAAK,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAIA,aAAW,KAAK,MAAM,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC;MAChN,CACD,KAAK,GAAG;AACX,QAAI,UAAU,gBAAgB,2BAA2B;AACzD,QAAI,IACF,IAAI,WAAW,SACX,KAAA,IACA,2JAA2J,SAAS,0CACzK;AACD,WAAO;;GAET,MAAM,YAAY,eAAe,IAAI,aAAa;GAClD,MAAM,OAAO,SAAS,cAAc,MACjC,MAAM,EAAE,OAAO,UAAU,gBAC3B;AACD,OAAI,CAAC,KACH,OAAM,IAAI,mBACR,iFACA,IACD;GACH,MAAM,OAAO,IAAI,SAAS,MAAM,EAAmB,IAAI;GACvD,MAAM,SAAS,SAAS,eAAe,MACpC,MAAM,SAAS,IAAI,EAAE,SAAS,KAAK,WAAW,IAAI,EAAE,IAAI,GAAG,CAC7D;GACD,MAAM,OAAO,SAAS,KAAA,IAAY,KAAK,MAAM,MAAM,MAAM,EAAE,SAAS,KAAK;AACzE,OAAI,CAAC,QAAQ,CAAC,OACZ,OAAM,IAAI,mBACR,qFACA,IACD;AACH,OAAI,UAAU,CAAC,KAAK,aAAa,QAAQ,SAAS,OAAO,IAAI,CAC3D,OAAM,IAAI,mBACR,iFACA,IACD;GAiBH,MAAM,OAAO,iBAPE,kBATE,MAAM,KAAK,IAAI,KAC9B,uBAAuB,KAAK,MAAM,GAAG,kBACrC;IACE,GAAG;IACH,GAAI,OACA,EAAE,iBAAiB,KAAK,IAAI,GAC5B,EAAE,mBAAmB,OAAQ,KAAK;IACvC,CACF,EAGC,KAAK,MAAM,IACX,WACA,MAAM,MAAM,MACZ,QAAQ,OAAO,KAChB,EACqC;IACpC,QAAQ,KAAK;IACb,aAAa,IAAI;IACjB,MAAM,OAAO,IAAI;IACjB;IACA;IACA,YAAY,KAAK;IACjB,WAAW,QAAQ;IACpB,CAAC;AACF,OAAI,UAAU,gBAAgB,2BAA2B;AACzD,OAAI,IAAI,IAAI,WAAW,SAAS,KAAA,IAAY,KAAK;WAC1C,OAAO;GAEd,MAAM,SACJ,iBAAiB,qBACb,MAAM,SACNC,WAAS,MAAM,IACb,OAAO,MAAM,WAAW,YACxB;IAAC;IAAK;IAAK;IAAK;IAAI,CAAC,SAAS,MAAM,OAAO,GAC3C,MAAM,SACN;GACR,MAAM,UACJ,iBAAiB,qBACb,MAAM,UACN,WAAW,OAAO,WAAW,MAC3B,qFACA;AACR,OAAI,UAAU,QAAQ,EAAE,gBAAgB,6BAA6B,CAAC;AACtE,OAAI,IAAI,IAAI,WAAW,SAAS,KAAA,IAAY,QAAQ;;AAEtD,SAAO;;;;;ACnOX,IAAa,YAAb,MAAuB;CACrB,4BAAoB,IAAI,KAAqB;CAE7C,IAAI,KAA2B;AAC7B,MAAI,UAAU,KAAK;GACjB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACZ,+BAA+B;GAChC,CAAC;AACF,MAAI,MAAM,QAAQ;AAClB,OAAK,UAAU,IAAI,IAAI;AACvB,MAAI,GAAG,eAAe,KAAK,UAAU,OAAO,IAAI,CAAC;;CAGnD,UAAU,MAAoB;EAC5B,MAAM,UAAU,SAAS,KAAK;AAC9B,OAAK,MAAM,OAAO,KAAK,UACrB,KAAI;AACF,OAAI,MAAM,QAAQ;UACZ;AACN,QAAK,UAAU,OAAO,IAAI;;;CAKhC,QAAc;AACZ,OAAK,MAAM,OAAO,KAAK,UACrB,KAAI;AACF,OAAI,KAAK;UACH;AAIV,OAAK,UAAU,OAAO;;CAGxB,IAAI,OAAe;AACjB,SAAO,KAAK,UAAU;;;;;ACtC1B,MAAa,0BAA0B;;;;;;;;;;;;;AAuCvC,SAAgB,+BACd,KACA,YAC0B;CAG1B,MAAM,wBAAQ,IAAI,KAAqB;CAEvC,MAAM,oBAAoB,aAAqB,UAAmB;EAChE,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAQ,KACN,uCAAuC,YAAY,iBAAiB,WAAW,IAAI,QAAQ,0EAC5F;;;CAIH,MAAM,SAAS,OACb,gBACqC;EACrC,IAAI;AACJ,MAAI;AAIF,gBAHa,MAAM,IAAI,IAEpB,oCAAoC,mBAAmB,YAAY,GAAG,EACxD;WACV,OAAO;AACd,oBAAiB,aAAa,MAAM;AACpC,UAAO;;AAET,MAAI,CAAC,UAAW,QAAO;EACvB,MAAM,EAAE,MAAM,gBAAgB,kBAAkB;EAChD,MAAM,gBAAgB,UAAU;AAChC,MAAI,CAAC,iBAAiB,kBAAkB,WACtC,QAAO;GAAE,MAAM;GAAO,IAAI;GAAa;EAGzC,MAAM,SAAS,IAAI,gBAAgB;GACjC,sBAAsB,OAAO,WAAW;GACxC,gBAAgB;GACjB,CAAC;EACF,IAAI;AACJ,MAAI;AAIF,iBAHa,MAAM,IAAI,IACrB,oCAAoC,SACrC,EACiB,aAAa,EAAE;WAC1B,OAAO;AACd,oBAAiB,aAAa,MAAM;AACpC,UAAO;;EAGT,MAAM,UAAU,WAAW,QACxB,MACC,EAAE,yBAAyB,cAC3B,EAAE,mBAAmB,iBACrB,EAAE,SAAS,KACd;EACD,MAAM,CAAC,eAAe;AACtB,MAAI,QAAQ,WAAW,KAAK,YAC1B,QAAO;GAAE,MAAM;GAAO,IAAI,OAAO,YAAY,GAAG;GAAE;AAKpD,SAAO;GACL,MAAM;GACN,QAAQ,QAAQ,WAAW,IAAI,YAAY;GAC3C;GACA;GACA;GACA,cAAc,QAAQ,KAAK,MAAM,EAAE,GAAG;GACvC;;AAGH,QAAO;EACL,MAAM,QAAQ,aAAa;GACzB,MAAM,SAAS,MAAM,IAAI,YAAY;AACrC,OAAI,OAAQ,QAAO;IAAE,MAAM;IAAO,IAAI;IAAQ;GAC9C,MAAM,SAAS,MAAM,OAAO,YAAY;AACxC,OAAI,CAAC,OAAQ,QAAO;IAAE,MAAM;IAAO,IAAI;IAAa;AACpD,OAAI,OAAO,SAAS,MAAO,OAAM,IAAI,aAAa,OAAO,GAAG;AAC5D,UAAO;;EAET,QAAQ;AACN,SAAM,OAAO;;EAEhB;;;;ACrHH,MAAM,aAAa,IAAI,IAAI;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAWF,eAAsB,aACpB,KACA,KACA,MACe;CACf,MAAM,cAAc,GAAG,KAAK,QAAQ;CAEpC,MAAM,UAAkC,EAAE;AAC1C,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,IAAI,QAAQ,CAC9C,KAAI,CAAC,WAAW,IAAI,EAAE,aAAa,CAAC,IAAI,OAAO,MAAM,SACnD,SAAQ,KAAK;AAGjB,SAAQ,UAAU;AAClB,SAAQ,mBAAmB,OAAO,KAAK,QAAQ;AAC/C,SAAQ,gBAAgB;AACxB,SAAQ,qBAAqB;CAE7B,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,OAAO;AACjE,KAAI,aAAa,IAAI,OAAO,IAAI;AAChC,KAAI,aAAa,IAAI,MAAM,IAAI;CAE/B,MAAM,sBAAsB,IAAI,aAAa,IAAI,wBAAwB;AACzE,KAAI,uBAAuB,KAAK,mBAAmB;EACjD,MAAM,WAAW,MAAM,KAAK,kBAAkB,QAAQ,oBAAoB;AAC1E,MAAI,SAAS,SAAS,MACpB,KAAI,aAAa,IAAI,yBAAyB,SAAS,GAAG;WACjD,cAAc,IAAI,EAAE;GAI7B,MAAM,OAAO,gBACX,uBAAuB,UAAU,KAAK,QAAQ,EAC9C,KAAK,WACN;AACD,OAAI,UAAU,KAAK;IACjB,gBAAgB;IAChB,kBAAkB,OAAO,OAAO,WAAW,KAAK,CAAC;IAClD,CAAC;AACF,OAAI,IAAI,KAAK;AACb;;;CAIJ,MAAM,UAAU,KAAK,gBAAgB,IAAI,EAAE;CAC3C,MAAM,QAAQ,IAAI,WAAW,SAAS,IAAI,WAAW;CACrD,IAAI,SAAS,IAAI,UAAU;CAC3B,IAAI;AAEJ,KAAI,QAAQ,SAAS,KAAK,OAAO;AAC/B,WAAS;EACT,MAAM,SAAS,IAAI,iBAAiB;AACpC,SAAO,IAAI,WAAW,IAAI,UAAU,MAAM;AAC1C,OAAK,MAAM,KAAK,QACd,QAAO,IAAI,qBAAqB,EAAE,aAAa,IAAI,EAAE,MAAM,CAAC;EAE9D,MAAM,QAAQ,cAAc;AAC5B,MAAI,MAAO,SAAQ,mBAAmB,UAAU;AAChD,UAAQ,kBAAkB;AAC1B,SAAO,OAAO,UAAU;AACxB,UAAQ,oBAAoB,OAAO,OAAO,WAAW,KAAK,CAAC;YAClD,CAAC,OAAO;AACjB,SAAO,MAAM,SAAS,IAAI;AAC1B,MAAI,KAAK,SAAS,EAChB,SAAQ,oBAAoB,OAAO,KAAK,OAAO;;AAInD,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,UAAgC;GACpC,UAAU;GACV,MAAM;GACN,MAAM,IAAI,YAAY,IAAI,UAAU;GACpC;GACA;GACD;EAED,MAAM,WAAW,MAAM,QAAQ,UAAU,aAAa;GAEpD,MAAM,UADc,SAAS,QAAQ,mBAAmB,IAC7B,SAAS,YAAY;GAEhD,MAAM,kBAAqD,EAAE;AAC7D,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,SAAS,QAAQ,CACnD,KAAI,CAAC,WAAW,IAAI,EAAE,aAAa,CAAC,IAAI,MAAM,KAAA,EAC5C,iBAAgB,KAAK;AAIzB,OAAI,QAAQ;IACV,MAAM,SAAmB,EAAE;AAC3B,aAAS,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AAC1D,aAAS,GAAG,aAAa;KACvB,IAAI,OAAO,OAAO,OAAO,OAAO,CAAC,SAAS,QAAQ;AAClD,YAAO,gBAAgB,MAAM,KAAK,WAAW;AAC7C,qBAAgB,oBAAoB,OAAO,OAAO,WAAW,KAAK,CAAC;AACnE,SAAI,UAAU,SAAS,cAAc,KAAK,gBAAgB;AAC1D,SAAI,IAAI,KAAK;AACb,cAAS;MACT;UACG;AACL,QAAI,UAAU,SAAS,cAAc,KAAK,gBAAgB;AAC1D,aAAS,KAAK,IAAI;AAClB,aAAS,GAAG,OAAO,QAAQ;;IAE7B;AAEF,WAAS,GAAG,UAAU,QAAQ;AAC5B,UAAO,IAAI;IACX;AAEF,MAAI,KAAM,UAAS,MAAM,KAAK;AAC9B,WAAS,KAAK;GACd;;AAGJ,MAAM,mBAAmB,IAAI,IAAI;CAAC;CAAY;CAAU;CAAQ,CAAC;;AAGjE,SAAS,cAAc,KAA+B;AACpD,KAAI,IAAI,WAAW,SAAS,IAAI,WAAW,OAAQ,QAAO;AAE1D,OADc,IAAI,OAAO,KAAK,MAAM,IAAI,CAAC,MAAM,KACtC,WAAW,QAAQ,CAAE,QAAO;CACrC,MAAM,OAAO,IAAI,QAAQ;AAEzB,KAAI,OAAO,SAAS,SAAU,QAAO,iBAAiB,IAAI,KAAK;CAC/D,MAAM,SAAS,IAAI,QAAQ;AAC3B,QAAO,OAAO,WAAW,YAAY,OAAO,SAAS,YAAY;;AAGnE,SAAS,uBACP,UACA,YACQ;AASR,QACE,6DARA,aAAa,WAAW,SAAS,YAAY,CAAC,IAC1C,WAAW,SAAS,cAAc,CAAC,KAAK,WAAW,SAAS,KAAK,CAAC,GAQvD,iCANf,SAAS,WAAW,YAChB,cAAc,WAAW,2EACzB,cAAc,WAAW,mDACpB,SAAS,aAAa,KAAK,MAAM,CAAC,+DAGa,gBACxC,wBAAwB;;AAI5C,SAAS,WAAW,OAAuB;AACzC,QAAO,MAAM,QAAQ,aAAa,MAAM,KAAK,EAAE,WAAW,EAAE,CAAC,GAAG;;AAGlE,SAAS,SAAS,KAAuC;AACvD,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,SAAmB,EAAE;AAC3B,MAAI,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AACrD,MAAI,GAAG,aAAa,QAAQ,OAAO,OAAO,OAAO,CAAC,CAAC;AACnD,MAAI,GAAG,SAAS,OAAO;GACvB;;;;ACvLJ,MAAa,6BACX;AAOF,SAAS,yBAAyB,OAItB;AACV,KAAI,MAAM,WAAW,IAAK,QAAO;AACjC,KAAI,kCAAkC,KAAK,MAAM,QAAQ,CAAE,QAAO;AAClE,KAAI,MAAM,QAAQ,OAAO,MAAM,SAAS,UAAU;EAChD,MAAM,iBACJ,MAAM,KACN;AACF,MACE,kBACA,OAAO,eAAe,QAAQ,YAC9B,kCAAkC,KAAK,eAAe,IAAI,CAE1D,QAAO;;AAGX,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,YAAY,GAAoB;AAC9C,KAAI,WAAW,EAAE,EAAE;EACjB,MAAM,SAAS,EAAE,SAAS,KAAK,EAAE,OAAO,KAAK;EAC7C,MAAM,OAAO,yBAAyB,EAAE,GACpC,4DAA4D,2BAA2B,8DACvF;AACJ,SAAO,GAAG,EAAE,UAAU,SAAS;;AAEjC,KAAI,aAAa,MAAO,QAAO,EAAE;AACjC,QAAO,OAAO,EAAE;;;;ACxClB,SAAS,kBAAkB,MAAiB,UAA0B;AACpE,QAAO,SAAS,KAAK,MAAM,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI;;AAG3D,SAAgB,WACd,MACA,SACqB;CACrB,MAAM,UAAU,SAAS,MAAM,KAAK,MAAM;EACxC,eAAe;EACf,UAAU,aAAqB;AAC7B,OAAI,SAAS,SAAS,eAAe,CAAE,QAAO;AAC9C,OAAI;IACF,MAAM,MAAM,kBAAkB,MAAM,SAAS;AAE7C,YADiB,IAAI,MAAM,QAAQ,CAAC,KAAK,IAAI,IAC7B,WAAW,IAAI,IAAI,KAAK,OAAO,OAAO,IAAI;WACpD;AACN,WAAO;;;EAGX,YAAY;EACZ,kBAAkB;GAAE,oBAAoB;GAAI,cAAc;GAAI;EAC/D,CAAC;CAEF,IAAI,UAAU,QAAQ,SAAS;CAC/B,MAAM,WAAW,OAA4B;AAM3C,YAAU,QAAQ,KAAK,GAAG,CAAC,OAAO,MAAM;AACtC,WAAQ,MAAM,uCAAuC,YAAY,EAAE,GAAG;IACtE;;AAGJ,SAAQ,GAAG,WAAW,aAAa;EACjC,MAAM,MAAM,kBAAkB,MAAM,SAAS;AAC7C,MAAI,CAAC,KAAK,eAAe,SAAS,IAAI,KAAK,OAAO,OAAO,IAAI,CAAE;EAC/D,MAAM,YAAY,KAAK,KAAK;AAC5B,gBAAc,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC;GAChE;AAEF,SAAQ,GAAG,QAAQ,aAAa;EAC9B,MAAM,MAAM,kBAAkB,MAAM,SAAS;AAC7C,MAAI,CAAC,KAAK,eAAe,SAAS,IAAI,KAAK,OAAO,OAAO,IAAI,CAAE;EAC/D,MAAM,YAAY,KAAK,KAAK;AAC5B,gBAAc,QAAQ,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC;GAChE;AAEF,SAAQ,GAAG,WAAW,aAAa;EACjC,MAAM,MAAM,kBAAkB,MAAM,SAAS;AAC7C,MAAI,CAAC,KAAK,eAAe,SAAS,IAAI,KAAK,OAAO,OAAO,IAAI,CAAE;EAC/D,MAAM,YAAY,KAAK,KAAK;AAC5B,gBAAc,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,UAAU,CAAC;GAChE;AAEF,cAAa,QAAQ,OAAO;;;;ACtE9B,MAAM,WAAW;CAAC;CAAsB;CAAqB;CAAe;AAE5E,IAAa,6BAAb,cAAgD,MAAM;CACpD,cAAc;AACZ,QACE,yEACD;;;;;;;AAQL,IAAa,sBAAb,MAAiC;CAC/B;CACA;CAEA,YAAY,MAAc;AACxB,OAAK,OAAO,QAAQ,KAAK;AACzB,OAAK,WAAW,KAAK,UAAU;;CAGjC,kBAAwB;EACtB,MAAM,UAAU,KAAK,UAAU;AAC/B,OAAK,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,MAAM,EAAE,GAAG,KAAK,SAAS,MAAM,CAAC,CAAC,CACrE,MAAK,QAAQ,IAAI,IAAI,IAAI,WAAW,KAAK,SAAS,IAAI,IAAI,IAAI,MAC5D,OAAM,IAAI,4BAA4B;;CAK5C,OAAO,MAAyB,QAA0B;AACxD,OAAK,MAAM,OAAO,KAChB,KAAI,KAAK,YAAY,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,IAAI,MACvD,OAAM,IAAI,4BAA4B;AAG1C,UAAQ;AACR,OAAK,MAAM,OAAO,KAAM,MAAK,SAAS,IAAI,KAAK,KAAK,YAAY,IAAI,CAAC;;CAGvE,YAAoB,KAA4B;EAC9C,MAAM,OAAO,QAAQ,KAAK,MAAM,IAAI;AACpC,MAAI,CAAC,KAAK,WAAW,KAAK,OAAO,IAAI,CACnC,OAAM,IAAI,4BAA4B;AAExC,OAAK,IAAI,SAAS,OAAQ,SAAS,QAAQ,OAAO,EAAE;AAClD,OAAI;AACF,QAAI,UAAU,OAAO,CAAC,gBAAgB,CACpC,OAAM,IAAI,4BAA4B;YACjC,OAAO;AACd,QACE,EACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS,UAGjB,OAAM;;AAEV,OAAI,WAAW,KAAK,KAAM;;AAE5B,MAAI;AACF,UAAO,WAAW,SAAS,CAAC,OAAO,aAAa,KAAK,CAAC,CAAC,OAAO,MAAM;WAC7D,OAAO;AACd,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS,SAEf,QAAO;AACT,SAAM;;;CAIV,WAA+C;EAC7C,MAAM,yBAAS,IAAI,KAA4B;EAC/C,MAAM,SAAS,cAA4B;AACzC,QAAK,MAAM,SAAS,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,EAAE;AACnE,QAAI,MAAM,KAAK,WAAW,IAAI,IAAI,MAAM,SAAS,eAC/C;AACF,QAAI,MAAM,gBAAgB,CAAE,OAAM,IAAI,4BAA4B;IAClE,MAAM,OAAO,KAAK,WAAW,MAAM,KAAK;AACxC,QAAI,MAAM,aAAa,CAAE,OAAM,KAAK;SAC/B;KACH,MAAM,MAAM,SAAS,KAAK,MAAM,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI;AAC1D,YAAO,IAAI,KAAK,KAAK,YAAY,IAAI,CAAC;;;;AAI5C,QAAM,KAAK,KAAK;AAChB,OAAK,MAAM,OAAO,SAAU,QAAO,IAAI,KAAK,KAAK,YAAY,IAAI,CAAC;AAClE,SAAO;;;;;ACjGX,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,YAAqD;AAC/D,QACE,4FAA4F,WACzF,KAAK,UAAU,KAAK,MAAM,KAAK,KAAK,GAAG,CACvC,KAAK,KAAK,GACd;AALkB,OAAA,aAAA;AAMnB,OAAK,OAAO;;;;AAKhB,SAAgB,uBAAuB,OAAgC;CACrE,MAAM,mCAAmB,IAAI,KAA0B;AACvD,MAAK,MAAM,WAAW,OAAO;EAC3B,MAAM,OAAO,0BAA0B,QAAQ;EAC/C,MAAM,YAAY,KAAK,aAAa;EACpC,MAAM,gBAAgB,iBAAiB,IAAI,UAAU,oBAAI,IAAI,KAAa;AAC1E,gBAAc,IAAI,KAAK;AACvB,mBAAiB,IAAI,WAAW,cAAc;;CAGhD,MAAM,aAAa,CAAC,GAAG,iBAAiB,QAAQ,CAAC,CAC9C,QAAQ,kBAAkB,cAAc,OAAO,EAAE,CACjD,KAAK,kBAAkB,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CACjD,MAAM,CAAC,OAAO,KAAK,CAAC,QAAQ,QAAQ,KAAK,cAAc,MAAM,CAAC;AAEjE,KAAI,WAAW,SAAS,EAAG,OAAM,IAAI,mBAAmB,WAAW;;;;AClBrE,MAAM,gBAAgB;AACtB,MAAM,mBAAmB;;;;;AAMzB,MAAa,gCAAgC;;;;;;;;;AAoC7C,IAAa,qBAAb,MAAgC;CAC9B;CACA;CAEA,YAAY,WAAmB;AAC7B,OAAK,OAAO,KAAK,WAAW,cAAc;AAC1C,OAAK,SAAS,aAAa,KAAK,KAAK,CAAC;;CAGxC,SAAe;AACb,OAAK,SAAS,aAAa,KAAK,KAAK,CAAC;;CAGxC,OAAiB;AACf,SAAO,OAAO,KAAK,KAAK,OAAO;;CAGjC,UAA2C;AACzC,SAAO,OAAO,QAAQ,KAAK,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CACtD,KACA,SAAS,KAAK,CACf,CAAC;;;;;;;CAQJ,YAAY,OAAqC,EAAE,EAAU;EAC3D,MAAM,UAAU,KAAK,SAAS,CAC3B,QAAQ,GAAG,UAAU,CAAC,KAAK,kBAAkB,CAAC,KAAK,QAAQ,CAC3D,UAAU,CAAC,OAAO,CAAC,WAClB,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,EACxC;AACH,SAAO,WAAW,SAAS,CAAC,OAAO,KAAK,UAAU,QAAQ,CAAC,CAAC,OAAO,MAAM;;CAG3E,IAAI,KAAsB;AACxB,SAAO,KAAK,OAAO,SAAS,KAAA;;CAG9B,IAAI,KAAyC;EAC3C,MAAM,OAAO,KAAK,OAAO;AACzB,SAAO,OAAO,SAAS,KAAK,GAAG,KAAA;;CAGjC,IAAI,KAAa,MAA4B;AAC3C,MAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,iBAAiB,KAAK,CAClD,OAAM,IAAI,MAAM,2BAA2B,MAAM;AAEnD,OAAK,OAAO,OAAO,SAAS,KAAK;;CAGnC,OAAO,KAAmB;AACxB,SAAO,KAAK,OAAO;;CAGrB,QAAc;EACZ,MAAM,WAAuC;GAC3C,SAAS;GACT,QAAQ,WAAW,KAAK,OAAO;GAChC;EACD,MAAM,WAAW,GAAG,KAAK,KAAK,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC;AAEhE,MAAI;AACF,aAAU,QAAQ,KAAK,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAClD,iBAAc,UAAU,KAAK,UAAU,UAAU,MAAM,EAAE,GAAG,MAAM;IAChE,UAAU;IACV,MAAM;IACP,CAAC;AACF,cAAW,UAAU,KAAK,KAAK;WACxB,OAAO;AACd,OAAI;AACF,eAAW,SAAS;WACd;AAGR,SAAM;;;;AAKZ,SAAS,aAAa,MAA0C;AAC9D,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,eAAe;AAE7C,KAAI;AACF,SAAOC,gBAAc,KAAK,MAAM,aAAa,MAAM,QAAQ,CAAC,CAAC;UACtD,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,QAAM,IAAI,MAAM,kBAAkB,cAAc,IAAI,UAAU;;;AAIlE,SAASA,gBAAc,OAA4C;AACjE,KAAI,CAACC,WAAS,MAAM,IAAI,MAAM,eAAe,iBAC3C,OAAM,IAAI,MAAM,oBAAoB,mBAAmB;CAGzD,MAAM,YAAY,MAAM;AACxB,KAAI,CAACA,WAAS,UAAU,CAAE,OAAM,IAAI,MAAM,4BAA4B;CAEtE,MAAM,SAAyC,EAAE;AACjD,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,UAAU,EAAE;AACtD,MAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,iBAAiB,QAAQ,CACrD,OAAM,IAAI,MAAM,2BAA2B,MAAM;AAEnD,SAAO,OAAO,SAAS,QAAQ;;AAGjC,QAAO;EAAE,SAAS;EAAkB;EAAQ;;AAG9C,SAAS,gBAA4C;AACnD,QAAO;EAAE,SAAS;EAAkB,QAAQ,EAAE;EAAE;;AAGlD,SAAS,WACP,QACgC;AAChC,QAAO,OAAO,YACZ,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,KAAK,SAAS,KAAK,CAAC,CAAC,CACnE;;AAGH,SAAS,SAAS,MAAsC;AACtD,QAAO;EACL,eAAe,KAAK;EACpB,GAAI,OAAO,KAAK,aAAa,WAAW,EAAE,UAAU,KAAK,UAAU,GAAG,EAAE;EACxE,GAAI,OAAO,KAAK,QAAQ,WAAW,EAAE,KAAK,KAAK,KAAK,GAAG,EAAE;EACzD,GAAI,OAAO,KAAK,gBAAgB,WAC5B,EAAE,aAAa,KAAK,aAAa,GACjC,EAAE;EACN,GAAI,OAAO,KAAK,gBAAgB,WAC5B,EAAE,aAAa,KAAK,aAAa,GACjC,EAAE;EACN,GAAI,OAAO,KAAK,oBAAoB,WAChC,EAAE,iBAAiB,KAAK,iBAAiB,GACzC,EAAE;EACN,GAAI,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;EACrE,GAAI,OAAO,KAAK,WAAW,WAAW,EAAE,QAAQ,KAAK,QAAQ,GAAG,EAAE;EAClE,GAAI,KAAK,YAAY,OAAO,EAAE,SAAS,MAAM,GAAG,EAAE;EAClD,GAAI,OAAO,KAAK,iBAAiB,WAC7B,EAAE,cAAc,KAAK,cAAc,GACnC,EAAE;EACP;;AAGH,SAAS,iBAAiB,OAAyC;AACjE,QACEA,WAAS,MAAM,IACf,OAAO,MAAM,qBAAqB,YAClC,OAAO,UAAU,MAAM,iBAAiB,IACxC,MAAM,mBAAmB,MACxB,MAAM,gBAAgB,KAAA,KACpB,OAAO,MAAM,gBAAgB,YAC5B,MAAM,YAAY,SAAS,OAC9B,MAAM,WAAW,KAAA,KACf,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,SAAS,OAC5D,MAAM,mBAAmB,KAAA,KACvB,OAAO,MAAM,mBAAmB,YAC/B,MAAM,eAAe,SAAS,OACjC,MAAM,mBAAmB,KAAA,KACvB,OAAO,MAAM,mBAAmB,YAC/B,OAAO,UAAU,MAAM,eAAe,IACtC,MAAM,iBAAiB,OAC1B,MAAM,uBAAuB,KAAA,KAC3B,OAAO,MAAM,uBAAuB,YACnC,MAAM,mBAAmB,SAAS,OACrC,MAAM,eAAe,KAAA,KAAa,OAAO,MAAM,eAAe,cAC9D,MAAM,cAAc,KAAA,KAClB,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS,OAClE,MAAM,eAAe,KAAA,KAAa,OAAO,MAAM,eAAe,eAC9D,MAAM,oBAAoB,KAAA,KACxB,OAAO,MAAM,oBAAoB,YAChC,MAAM,gBAAgB,SAAS;;AAIvC,SAAgB,gBAAgB,KAAsB;AACpD,KAAI,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,CAAE,QAAO;CAErD,MAAM,WAAW,IAAI,MAAM,IAAI;AAC/B,QACE,SAAS,OAAO,YAChB,SAAS,WAAW,KACpB,SAAS,OAAO,KAAA,KAChB,SAAS,GAAG,SAAS,KACrB,SAAS,OAAO,OAChB,SAAS,OAAO;;AAIpB,SAASA,WAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;AC3O7E,MAAM,yBACJ;AAEF,SAAgB,gBAAgB,KAAsB;AACpD,QAAO,uBAAuB,KAAK,IAAI;;;;;;;ACZzC,SAAgB,sBACd,UACA,gBACA,eAAkC,EAAE,EACpC,eAAoC,gBACvB;CACb,MAAM,+BAAe,IAAI,KAAa;AACtC,MAAK,MAAM,SAAS,CAClB,GAAGC,UAAQ,SAAS,UAAU,EAC9B,GAAGA,UAAQ,SAAS,gBAAgB,CACrC,CACC,KAAI,OAAO,MAAM,SAAS,SAAU,cAAa,IAAI,MAAM,KAAK;AAElE,MAAK,MAAM,QAAQA,UAAQ,SAAS,MAAM,EAAE;AAC1C,OAAK,MAAM,QAAQA,UAAQ,KAAK,MAAM,CACpC,KAAI,OAAO,KAAK,kBAAkB,SAChC,cAAa,IAAI,KAAK,cAAc;EAExC,MAAM,SAAS,KAAK;AACpB,MAAI,WAAW,OAAQ;AACvB,MACE,OAAO,WAAW,YAClB,UACA,CAAC,CAAC,UAAU,QAAQ,CAAC,SAAS,OAAO,EACrC;AACA,gBAAa,IAAI,WAAW,OAAO,SAAS;AAC5C;;EAEF,MAAM,WAAW;GAAC;GAAQ;GAAU;GAAQ,CACzC,QACE,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,EACrE,CACA,KAAK,SAAS,WAAW,KAAK,SAAS,CACvC,MAAM,SAAS,eAAe,IAAI,KAAK,IAAI,aAAa,SAAS,KAAK,CAAC;AAC1E,MAAI,SAAU,cAAa,IAAI,SAAS;;AAK1C,MAAK,MAAM,QAAQ,cAAc;AAC/B,MAAI,CAAC,KAAK,SAAS,gBAAgB,CAAE;AACrC,OAAK,MAAM,UAAU,CAAC,kBAAkB,aAAa,EAAE;GACrD,MAAM,UAAU,KAAK,QAAQ,kBAAkB,OAAO;AAGtD,QADE,WAAW,eAAe,eAAe,gBAC5B,IAAI,QAAQ,IAAI,aAAa,SAAS,QAAQ,CAC3D,cAAa,IAAI,QAAQ;;;AAI/B,MAAK,MAAM,QAAQ,aACjB,KAAI,qBAAqB,KAAK,CAAE,cAAa,OAAO,KAAK;AAE3D,QAAO;;AAGT,SAASA,UAAQ,SAAkD;AAEjE,SAD8B,MAAM,QAAQ,QAAQ,GAAG,UAAU,EAAE,EACjD,QACf,UACC,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM,CACvE;;;;AC7DH,MAAa,2BAA2B;;AAWxC,SAAgB,iBACd,WACA,cACA,eAAkC,EAAE,EACrB;CACf,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,WAAW,WAAW,MACzB,SAAS,KAAK,iBAAiB,yBACjC;AACD,KAAI,CAAC,YAAY,UAAU,OAAO,OAAA,qBAAgC,CAChE,QAAO,kBAAkB,aAAa;CAGxC,MAAM,kBAAkB,SAAS,MAAM;CACvC,MAAM,SAAkB,KAAK,MAAM,gBAAgB;AAGnD,KAAI,MAAM,QAAQ,OAAO,CAAE,QAAO,kBAAkB,aAAa;AACjE,KAAI,CAACC,WAAS,OAAO,CACnB,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,EAAE,UAAU,YAAY,wBAAwB,OAAO;CAC7D,MAAM,UACJ,QAAQ,SAAS,IACb,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,MACrC;CAEN,MAAM,eAAe,sBACnB,UACA,IAAI,IAAI,WAAW,KAAK,SAAS,KAAK,aAAa,CAAC,EACpD,cACA,IAAI,IAAI,aAAa,KAAK,SAAS,KAAK,aAAa,CAAC,CACvD;AAED,MAAK,MAAM,QAAQ,aACjB,KAAI,aAAa,IAAI,KAAK,CACxB,OAAM,IAAI,MACR,8DAA8D,OAC/D;CAIL,MAAM,gBAAgB,aAAa,MAChC,SACC,KAAK,iBAAA,wBACL,aAAa,IAAI,KAAK,aAAa,CACtC;AAID,KAAI,CAAC,iBAAiB,QAAQ,WAAW,EACvC,QAAO,aAAa,KAAK,UAAU,EAAE,MAAM,EAAE;CAG/C,MAAM,iBAAiB,IAAI,IACzB,WAAW,KAAK,SAAS,CAAC,KAAK,cAAc,KAAK,CAAC,CACpD;CACD,MAAM,cAAc,CAAC,GAAG,aAAa,CAAC,MAAM,SAAS;EACnD,MAAM,OAAO,eAAe,IAAI,KAAK;AACrC,SAAO,CAAC,QAAQ,CAAC,KAAK,UAAU,UAAU,OAAO,OAAO,KAAK;GAC7D;AACF,KAAI,aAAa;AACf,MAAI,CAAC,cAAe,QAAO,aAAa,KAAK,UAAU,EAAE,MAAM,EAAE;AACjE,QAAM,IAAI,MACR,qDAAqD,cACtD;;CAEH,MAAM,cAAc,CAAC,GAAG,aAAa,CAAC,KACnC,SAAS,eAAe,IAAI,KAAK,CACnC;AACD,KAAI,QAAQ,SAAS,EAInB,UAAS,MAAM,QAAQ;CAEzB,MAAM,UAAyB,aAC5B,QACE,SACC,KAAK,iBAAA,wBACL,CAAC,aAAa,IAAI,KAAK,aAAa,CACvC,CACA,KAAK,UAAU,EAAE,MAAM,EAAE;AAC5B,SAAQ,KAAK;EACX,MAAM;EACN,qBAAqB;GAAE;GAAS,OAAO;GAAa;EACrD,CAAC;AACF,QAAO;;;;;;;;;;;AAYT,SAAgB,wBAAwB,UAGtC;CACA,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,mCAAmB,IAAI,KAAc;CAC3C,MAAM,QAAQ,UAAmB;AAC/B,MAAI,CAACA,WAAS,MAAM,IAAI,OAAO,MAAM,SAAS,SAAU,QAAO;AAC/D,MAAI,CAAC,qBAAqB,MAAM,KAAK,CAAE,QAAO;AAC9C,UAAQ,IAAI,MAAM,KAAK;AACvB,MAAI,cAAc,MAAO,kBAAiB,IAAI,MAAM,SAAS;AAC7D,SAAO;;CAGT,MAAM,OAAgC,EAAE,GAAG,UAAU;AACrD,MAAK,MAAM,OAAO,CAAC,aAAa,kBAAkB,CAChD,KAAI,MAAM,QAAQ,KAAK,KAAK,CAAE,MAAK,OAAO,KAAK,KAAK,OAAO,KAAK;AAElE,KAAI,QAAQ,SAAS,EAAG,QAAO;EAAE;EAAU,SAAS,EAAE;EAAE;AAExD,KAAI,iBAAiB,OAAO,KAAK,MAAM,QAAQ,KAAK,MAAM,EAAE;EAC1D,MAAM,OAAO,IAAI,IAAa,CAC5B,GAAG,QAAQ,KAAK,MAAM,CAAC,KAAK,SAAS,KAAK,SAAS,EACnD,GAAG,QAAQ,KAAK,gBAAgB,CAAC,KAAK,UAAU,MAAM,SAAS,CAChE,CAAC;AACF,OAAK,QAAQ,KAAK,MAAM,QACrB,SACC,CAACA,WAAS,KAAK,IACf,CAAC,iBAAiB,IAAI,KAAK,IAAI,IAC/B,KAAK,IAAI,KAAK,IAAI,CACrB;;AAEH,QAAO;EAAE,UAAU;EAAM,SAAS,CAAC,GAAG,QAAQ;EAAE;;AAGlD,SAAS,QAAQ,SAAkD;AACjE,QAAO,MAAM,QAAQ,QAAQ,GAAG,QAAQ,OAAOA,WAAS,GAAG,EAAE;;AAG/D,SAAS,kBAAkB,OAA4C;AACrE,QAAO,CAAC,GAAG,MAAM,CACd,MACE,MAAM,UACL,OAAO,KAAK,iBAAiB,yBAAyB,GACtD,OAAO,MAAM,iBAAiB,yBAAyB,CAC1D,CACA,KAAK,UAAU,EAAE,MAAM,EAAE;;AAG9B,SAASA,WAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;AAI7E,SAAgB,kCACd,cACM;AACN,KAAI,aAAa,SAAA,qBAAkC,CACjD,OAAM,IAAI,MACR,iJACD;;;;ACxHL,MAAM,8BAA8B;;;;;;;;;;AAoBpC,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,WAA0C;AACpD,QACE,YACI,iDAAiD,UAAU,qBAC3D,mCACL;AALyB,OAAA,YAAA;AAM1B,OAAK,OAAO;;;AAIhB,IAAa,SAAb,MAAoB;CAClB,gCAAwB,IAAI,KAAqB;CACjD,wCAAgC,IAAI,KAAqB;CACzD,qCAA6B,IAAI,KAA6B;CAC9D,uCAA+B,IAAI,KAA+B;CAClE,sCAA8B,IAAI,KAA6B;CAC/D,qBAA6B;CAC7B,wBAAgC;CAChC,qBAA4C;CAC5C;CAEA,YACE,KACA,SACA,WACA,eACA;AAJQ,OAAA,MAAA;AACA,OAAA,UAAA;AACA,OAAA,YAAA;AAGR,OAAK,wBAAwB;;CAG/B,IAAY,gBAAoC;AAC9C,OAAK,0BAA0B,IAAI,mBAAmB,KAAK,UAAU,KAAK;AAC1E,SAAO,KAAK;;CAKd,MAAM,iBAAgC;EAKpC,MAAM,OAAQ,MAAMC,mBAClB,KAAK,KACL,KAAK,QACN;AAID,OAAK,gBAAgB,KAAK,+BAA+B,EAAE,CAAC;AAC5D,OAAK,qBAAqB,KAAK,uBAAuB;AACtD,OAAK,wBAAwB;;;;;;CAO/B,YAA2B;AACzB,SAAO,KAAK;;CAGd,gBAAwB,WAAmC;AACzD,yBACE,UAAU,SAAS,aAAc,SAAS,MAAM,CAAC,SAAS,IAAI,GAAG,EAAE,CAAE,CACtE;AACD,OAAK,mBAAmB,OAAO;AAC/B,OAAK,qBAAqB,OAAO;AACjC,OAAK,MAAM,YAAY,WAAW;AAChC,OAAI,CAAC,SAAS,IAAK;AAEnB,QAAK,mBAAmB,IAAI,SAAS,KAAK,SAAS;GACnD,MAAM,QAAQ,KAAK,qBAAqB,IAAI,SAAS,IAAI,IAAI,EAAE;AAC/D,SAAM,KAAK,SAAS;AACpB,QAAK,qBAAqB,IAAI,SAAS,KAAK,MAAM;;AAEpD,OAAK,qBAAqB;;CAG5B,kBAA0B,UAAgC;AACxD,MAAI,CAAC,SAAS,IAAK;AACnB,OAAK,mBAAmB,IAAI,SAAS,KAAK,SAAS;AACnD,OAAK,qBAAqB,IAAI,SAAS,KAAK,CAAC,SAAS,CAAC;AACvD,OAAK,qBAAqB;;CAG5B,qBAA6B,cAA4B;AACvD,OAAK,mBAAmB,OAAO,aAAa;AAC5C,OAAK,qBAAqB,OAAO,aAAa;AAC9C,OAAK,qBAAqB;;CAM5B,IAAY,YAAiC;AAC3C,MAAI,KAAK,mBAAoB,MAAK,sBAAsB;AACxD,SAAO,KAAK;;CAGd,IAAY,kBAA+C;AACzD,MAAI,KAAK,mBAAoB,MAAK,sBAAsB;AACxD,SAAO,KAAK;;CAGd,uBAAqC;AACnC,OAAK,qBAAqB;AAC1B,OAAK,cAAc,OAAO;AAC1B,OAAK,oBAAoB,OAAO;AAEhC,OAAK,MAAM,CAAC,KAAK,aAAa,KAAK,oBAAoB;AAGrD,OAAI,KAAK,mBAAmB,IAAI,GAAG,IAAI,SAAS,CAAE;AAElD,QAAK,oBAAoB,IAAI,KAAK,SAAS;AAC3C,OAAI,SAAS,SAAU,MAAK,cAAc,IAAI,KAAK,SAAS,SAAS;;;CAIzE,WAAW,MAA0B;AACnC,SAAO,KAAK,UAAU,KAAK,KAAK,UAAU,IAAI,KAAK,aAAa;;CAGlE,aAAuB;AACrB,SAAO,CAAC,GAAG,KAAK,gBAAgB,MAAM,CAAC;;;CAIzC,wBAAgC,KAAsB;EACpD,MAAM,WAAW,KAAK,gBAAgB,IAAI,IAAI;AAC9C,SAAO,UAAU,WAAW,QAAQ,uBAAuB,SAAS;;;CAItE,kBAA0C;AACxC,SAAO,OAAO,YAAY,KAAK,UAAU;;;CAI3C,kBAA0C;EACxC,MAAM,OAA+B,EAAE;AACvC,OAAK,MAAM,CAAC,KAAK,aAAa,KAAK,iBAAiB;AAClD,OAAI,CAAC,uBAAuB,SAAS,CAAE;GACvC,MAAM,MAAM,SAAS;AACrB,OAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,MAAK,OAAO;;AAE7D,SAAO;;;CAIT,eAAe,kBAAiD;AAC9D,MAAI,CAAC,KAAK,yBAAyB,CAAC,KAAK,mBAAoB,QAAO;AAEpE,SAAO;GACL,SAAS,KAAK;GACd,WAAW,KAAK;GAChB;GACA,WAAW,CAAC,GAAG,KAAK,qBAAqB,QAAQ,CAAC,CAC/C,MAAM,CACN,IAAI,oBAAoB;GAC5B;;CAGH,kBAAkB,OAA6B;AAC7C,MAAI,MAAM,YAAY,KAAK,QACzB,OAAM,IAAI,MACR,sCAAsC,MAAM,QAAQ,SAAS,KAAK,UACnE;AAEH,OAAK,gBAAgB,MAAM,UAAU,IAAI,wBAAwB,CAAC;AAClE,OAAK,qBAAqB,MAAM;AAChC,OAAK,wBAAwB;;CAG/B,MAAc,8BAA6C;AACzD,MAAI,CAAC,KAAK,sBAAuB,OAAM,KAAK,gBAAgB;;;;;;CAO9D,MAAM,kBAAkB,OAA8B,EAAE,EAAmB;AACzE,OAAK,cAAc,QAAQ;AAC3B,QAAM,KAAK,6BAA6B;EAExC,MAAM,QAA4B,EAAE;AACpC,OAAK,MAAM,CAAC,KAAK,SAAS,KAAK,cAAc,SAAS,EAAE;AACtD,OAAI,KAAK,UAAU,OAAO,OAAO,IAAI,CAAE;AAIvC,OAAI,KAAK,UAAU,KAAK,IAAI,CAAC,OAAQ;GAErC,MAAM,iBACJ,KAAK,4BAA4B,KAAK,KAAK,IAC3C,KAAK,gBAAgB,IAAI,IAAI;AAC/B,OACE,kBACA,CAAC,KAAK,yBAAyB,gBAAgB,KAAK,CAEpD;AAEF,OAAI,kBAAkB,CAAC,KAAK,QAAS;GAErC,MAAM,WAAW,sBAAsB,KAAK;AAC5C,SAAM,KAAK;IACT;IACA;IACA,GAAI,iBAAiB,EAAE,gBAAgB,GAAG,EAAE;IAC5C,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;IACjC,CAAC;;AAGJ,MAAI,MAAM,SAAS,GAAG;AACpB,SAAM,KAAK,qBAAqB,MAAM;AACtC,SAAM,KAAK,sBAAsB,MAAM;AACvC,SAAM,KAAK,gBAAgB;;AAE7B,MAAI,KAAK,WAAY,MAAM,KAAK,sCAAsC,CACpE,OAAM,KAAK,gBAAgB;AAE7B,OAAK,gCAAgC;AAErC,SAAO,MAAM;;CAGf,MAAc,qBAAqB,OAA0C;EAC3E,MAAM,gCAAgB,IAAI,KAAiC;AAC3D,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,SAAU;GACnB,MAAM,cAAc,cAAc,IAAI,KAAK,KAAK,cAAc,IAAI,EAAE;AACpE,eAAY,KAAK,KAAK;AACtB,iBAAc,IAAI,KAAK,KAAK,eAAe,YAAY;;EAGzD,IAAI,kBAAkB;AACtB,OAAK,MAAM,CAAC,eAAe,gBAAgB,eAAe;GACxD,IAAI;AACJ,OAAI;AACF,mBAAe,MAAM,KAAK,wBAAwB,cAAc;YACzD,OAAO;AACd,UAAM,IAAI,MACR,6CAA6C,cAAc,IAAI,YAAY,MAAM,GAClF;;AAGH,QAAK,MAAM,QAAQ,aAAa;IAC9B,MAAM,iBAAiB,aAAa,IAAI,cAAc,KAAK,IAAI,CAAC;AAChE,QAAI,CAAC,eACH,OAAM,IAAI,MACR,sCAAsC,KAAK,IAAI,aAAa,gBAC7D;IAKH,MAAM,WAAgC;KACpC,GAAG;KACH,GAAI,OAAO,KAAK,KAAK,QAAQ,WAAW,EAAE,KAAK,KAAK,KAAK,KAAK,GAAG,EAAE;KACpE;AACD,SAAK,WAAW;AAChB,SAAK,cAAc,IAAI,KAAK,KAAK;KAC/B,GAAG,KAAK;KACR,GAAG;KACJ,CAAC;AACF,sBAAkB;;;AAItB,MAAI,gBAAiB,MAAK,cAAc,OAAO;;CAGjD,MAAc,wBACZ,eAC2C;EAC3C,MAAM,OAAO,MAAMC,eAAsB,KAAK,KAAK,cAAc;AACjE,MAAI,CAACC,WAAS,KAAK,IAAI,CAAC,MAAM,QAAQ,KAAK,kBAAkB,CAC3D,OAAM,IAAI,MAAM,uDAAuD;EAGzE,MAAM,yBAAS,IAAI,KAAkC;AACrD,OAAK,MAAM,SAAS,KAAK,mBAAmB;GAC1C,MAAM,QAAQ,wBAAwB,MAAM;AAC5C,OAAI,MAAO,QAAO,IAAI,MAAM,UAAU,MAAM,SAAS;;AAEvD,SAAO;;CAGT,MAAc,sBACZ,OACe;EACf,MAAM,SAAmB,EAAE;EAC3B,IAAI,WAAW;EAEf,MAAM,SAAS,YAAY;AACzB,UAAO,WAAW,MAAM,QAAQ;IAC9B,MAAM,OAAO,MAAM;AACnB,gBAAY;AACZ,QAAI,CAAC,QAAQ,CAAC,KAAK,SAAU;AAE7B,QAAI;AACF,WAAM,KAAK,qBAAqB,KAAK;aAC9B,OAAO;AACd,YAAO,KAAK,GAAG,KAAK,IAAI,IAAI,YAAY,MAAM,GAAG;;;;AAKvD,QAAM,QAAQ,IACZ,MAAM,KACJ,EAAE,QAAQ,KAAK,IAAI,6BAA6B,MAAM,OAAO,EAAE,EAC/D,OACD,CACF;AAED,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,MACR,kBAAkB,OAAO,OAAO,2EAA2E,OAAO,KAAK,KAAK,GAC7H;;CAIL,MAAc,qBAAqB,MAAuC;EACxE,MAAM,WAAW,KAAK;AACtB,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM,kCAAkC;EAEjE,MAAM,mBAAmB,KAAK,gBAAgB;AAC9C,MAAI,KAAK,kBAAkB,OAAO,qBAAqB,SACrD,OAAM,IAAI,MAAM,2CAA2C;EAqB7D,MAAM,oBAAoB,sBAlBb,MAAMC,mBAA0B,KAAK,KAAK,EACrD,eAAe;GACb,KAAK,SAAS;GACd,UAAU,cAAc,KAAK,IAAI;GACjC,cAAc,SAAS;GACvB,cAAc,SAAS;GACvB,GAAI,SAAS,kBACT,EAAE,mBAAmB,SAAS,iBAAiB,GAC/C,EAAE;GACN,GAAI,SAAS,YAAY,KAAA,IACrB,EAAE,UAAU,SAAS,SAAS,GAC9B,EAAE;GACN,GAAI,SAAS,SAAS,EAAE,QAAQ,SAAS,QAAQ,GAAG,EAAE;GACtD,eAAe,KAAK;GACpB,iBAAiB;GAClB,EACF,CAAC,CAEmD;AACrD,MAAI,CAAC,kBACH,OAAM,IAAI,MAAM,oDAAoD;AAGtE,MAAI,OAAO,qBAAqB,SAAU;AAE1C,MAAI;AACF,SAAMC,oBAA2B,KAAK,KAAK,iBAAiB;WACrD,OAAO;AACd,OAAI,gBAAgB,MAAM,CAAE;AAC5B,OAAI;AACF,UAAMA,oBAA2B,KAAK,KAAK,kBAAkB;WACvD;AAGR,SAAM;;;;;;;CAQV,uBAA+B,KAA+B;AAC5D,MAAI,KAAK,mBAAmB,IAAI,GAAG,IAAI,SAAS,CAAE,QAAO,EAAE;AAC3D,SAAO,KAAK,qBAAqB,IAAI,IAAI,IAAI,EAAE;;CAGjD,4BACE,KACA,MAC4B;AAC5B,SAAO,KAAK,uBAAuB,IAAI,CAAC,MACrC,aACC,uBAAuB,SAAS,IAChC,CAAC,KAAK,yBAAyB,UAAU,KAAK,CACjD;;;CAIH,MAAc,uCAAyD;EACrE,MAAM,oBAAgE,EAAE;AAExE,OAAK,MAAM,CAAC,KAAK,SAAS,KAAK,cAAc,SAAS,EAAE;AACtD,OAAI,KAAK,UAAU,OAAO,OAAO,IAAI,IAAI,CAAC,KAAK,IAAK;GAEpD,MAAM,YAAY,KAAK,uBAAuB,IAAI,CAAC,OACjD,uBACD;AACD,OAAI,UAAU,SAAS,EAAG;GAE1B,MAAM,oBAAoB,UAAU,QACjC,aAAa,SAAS,QAAQ,KAAK,IACrC;AACD,OAAI,kBAAkB,WAAW,EAAG;GAEpC,MAAM,SAAS,KAAK,iBAAiB,mBAAmB,IAAI;GAC5D,MAAM,WAAW,KAAK,4BAA4B,QAAQ,IAAI;AAC9D,QAAK,MAAM,YAAY,WAAW;IAChC,MAAM,aAAa,KAAK,4BAA4B,UAAU,IAAI;AAClE,QAAI,eAAe,SACjB,mBAAkB,KAAK;KAAE;KAAK;KAAY,CAAC;;;AAIjD,MAAI,kBAAkB,WAAW,EAAG,QAAO;EAE3C,MAAM,SAAmB,EAAE;EAC3B,IAAI,eAAe;EACnB,MAAM,SAAS,YAAY;AACzB,UAAO,eAAe,kBAAkB,QAAQ;IAC9C,MAAM,WAAW,kBAAkB;AACnC,oBAAgB;AAChB,QAAI,CAAC,SAAU;AAEf,QAAI;AACF,WAAMA,oBAA2B,KAAK,KAAK,SAAS,WAAW;aACxD,OAAO;AACd,SAAI,CAAC,gBAAgB,MAAM,CACzB,QAAO,KAAK,GAAG,SAAS,IAAI,IAAI,YAAY,MAAM,GAAG;;;;AAM7D,QAAM,QAAQ,IACZ,MAAM,KACJ,EACE,QAAQ,KAAK,IACX,6BACA,kBAAkB,OACnB,EACF,EACD,OACD,CACF;AAED,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,MACR,oBAAoB,OAAO,OAAO,wCAAwC,OAAO,KAAK,KAAK,GAC5F;AAGH,SAAO;;CAGT,iBACE,WACA,KACgB;EAChB,IAAI,SAAS,UAAU;AACvB,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,gCAAgC,MAAM;EAEnE,IAAI,WAAW,KAAK,4BAA4B,QAAQ,IAAI;AAC5D,OAAK,MAAM,YAAY,UAAU,MAAM,EAAE,EAAE;GACzC,MAAM,aAAa,KAAK,4BAA4B,UAAU,IAAI;AAClE,OAAI,aAAa,UAAU;AACzB,aAAS;AACT,eAAW;;;AAGf,SAAO;;CAGT,4BACE,UACA,KACQ;EACR,MAAM,aAAa,gBAAgB,SAAS,YAAY;AACxD,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,6CAA6C,MAAM;AAErE,SAAO;;;CAIT,qCAA2C;AACzC,OAAK,cAAc,QAAQ;EAC3B,IAAI,UAAU;AAEd,OAAK,MAAM,CAAC,KAAK,SAAS,KAAK,cAAc,SAAS,EAAE;AACtD,OAAI,KAAK,UAAU,OAAO,OAAO,IAAI,CAAE;AACvC,OAAI,KAAK,UAAU,KAAK,IAAI,CAAC,OAAQ;GAErC,MAAM,WAAW,KAAK,gBAAgB,IAAI,IAAI;AAC9C,OAAI,CAAC,uBAAuB,SAAS,CACnC,OAAM,IAAI,MAAM,+CAA+C,MAAM;GAGvE,MAAM,WAA2B;IAAE,GAAG;IAAM,eAAe,KAAK;IAAS;AACzE,UAAO,SAAS;AAChB,UAAO,SAAS;AAChB,UAAO,OAAO,UAAU,aAAa,SAAS,CAAC;AAC/C,QAAK,cAAc,IAAI,KAAK,SAAS;AACrC,aAAU;;AAGZ,MAAI,QAAS,MAAK,cAAc,OAAO;;;;;;;;CAWzC,MAAM,WACJ,MACA,SACA,OAAmC,EAAE,EACb;AACxB,MAAI,KAAK,QAAQ;GACf,MAAM,UAAU,KAAK,MAAM;GAC3B,MAAM,WAAW,MAAM,KAAK,YAC1B;IAAE,KAAK,KAAK;IAAc;IAAS,EACnC,QACD;AACD,QAAK,kBAAkB;IACrB,GAAG;IACH,KAAK,KAAK;IACV;IACA,UAAU,SAAS,YAAY,KAAK,UAAU;IAC/C,CAAC;AACF,UAAO;;AAGT,MAAI,yBAAyB,KAAK,CAChC,OAAM,IAAI,MACR,kDAAkD,KAAK,eACxD;EAGH,MAAM,WAAW,MAAM,KAAK,iBAAiB,MAAM,QAAQ;AAC3D,OAAK,kBAAkB,SAAS;AAChC,MAAI,gBAAgB,KAAK,aAAa,CACpC,MAAK,sBAAsB,MAAM,UAAU,KAAK,aAAa;AAE/D,SAAO;;;CAIT,6BAA0D;AACxD,SAAO,KAAK;;;CAId,MAAM,0BACJ,QACA,SACsD;AACtD,MAAI,CAAC,OAAO,oBACV,OAAM,IAAI,MAAM,6CAA6C;EAE/D,MAAM,EAAE,SAAS,UAAU,OAAO;EAClC,MAAM,gBAAgB,MAAM,KAAK,UAAU;GAAE;GAAM,SAAS,KAAK,MAAM;GAAE,EAAE;EAC3E,MAAM,WAAW,MAAM,KAAK,YAC1B;GACE,KAAK;GACL;GACA,cAAc,cAAc,KACzB,EAAE,MAAM,SAAS,uBAAuB;IACvC,KAAK,KAAK;IACV,SAAS;IACV,EACF;GACF,EACD,SACA,cAAc,KAAK,EAAE,WAAW,KAAK,aAAa,CACnD;AAGD,OAAK,kBAAkB,SAAS;AAChC,MAAI,SAAS,OAAO,SAAS,WAAW,KACtC,MAAK,sBAAsB,IACzB,SAAS,KACT,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,QAAQ,CACrC;AAEH,SAAO,CAAC;GAAE,MAAM,OAAO;GAAM;GAAS,EAAE,GAAG,cAAc;;;;;;;;;;;;;;;;;;;;;;;;CAyB3D,MAAc,YACZ,UACA,SACA,oBACyB;EACzB,MAAM,OAAgC,EACpC,4BAA4B,UAC7B;AACD,MAAI,QAAS,MAAK,cAAc;AAEhC,MAAI;GACF,MAAM,WAAY,MAAMC,oBACtB,KAAK,KACL,KAAK,SACL,KACD;AAKD,OAAI,oBAAoB;IACtB,MAAM,eAAe,IAAI,KACtB,SAAS,oBAAoB,EAAE,EAAE,KAAK,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CACrE;IACD,MAAM,WAAW,SAAS;AAC1B,QACE,UAAU,QAAA,wBACV,SAAS,WAAW,QACpB,CAAC,SAAS,YACV,mBAAmB,MAChB,QACC,aAAa,IAAI,IAAI,EAAE,WAAW,QAClC,CAAC,aAAa,IAAI,IAAI,EAAE,SAC3B,CAED,OAAM,IAAI,MACR,oGACD;;AAGL,OAAI,SAAS,oBACX,MAAK,qBAAqB,SAAS;AAErC,QAAK,MAAM,kBAAkB,SAAS,oBAAoB,EAAE,EAAE;AAC5D,SAAK,kBAAkB,eAAe;AACtC,QAAI,eAAe,OAAO,eAAe,WAAW,KAClD,MAAK,sBAAsB,IACzB,eAAe,KACf,OAAO,eAAe,YAAY,WAC9B,eAAe,UACf,KAAK,UAAU,eAAe,QAAQ,CAC3C;;AAGL,UACE,SAAS,8BAA8B;IACrC,KAAK,OAAO,SAAS,WAAW,WAAW,SAAS,SAAS;IAC7D,UAAU;IACX;WAEI,GAAG;AACV,SAAM,KAAK,kBAAkB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCnC,MAAM,cAAgC;AACpC,MAAI;GACF,MAAM,WAAY,MAAM,KAAK,IAAI,KAC/B,2BAA2B,KAAK,QAAQ,kBACxC,EAAE,CACH;AACD,OAAI,SAAS,oBACX,MAAK,qBAAqB,SAAS;AAErC,UAAO;WACA,GAAG;AACV,WAAQ,KACN,+CAA+C,YAAY,EAAE,CAAC,mCAC/D;AACD,UAAO;;;CAIX,MAAM,cAAc,SAAmD;AACrE,MAAI,CAAC,QAAS;AAEd,MAAI;GACF,MAAM,WAAY,MAAM,KAAK,IAAI,KAC/B,2BAA2B,KAAK,QAAQ,wBACxC,EAAE,UAAU,SAAS,CACtB;AACD,OAAI,SAAS,WAAY,MAAK,qBAAqB,SAAS;WACrD,GAAG;AACV,SAAM,KAAK,kBAAkB,EAAE;;;CAInC,kBAA0B,GAAqB;AAI7C,OAFG,GAA2D,UAC3D,GAA0C,UAAU,YACxC,IAAK,QAAO;EAM3B,MAAM,OAAQ,GAAqD,MAC/D;AACJ,SAAO,IAAI,kBAAkB,MAAM,cAAc,KAAK;;CAGxD,MAAc,iBACZ,MACA,SACiC;EAWjC,MAAM,SATkB,MAAM,KAAK,IAAI,KAEpC,mBAAmB,EACpB,mBAAmB;GACjB,aAAa,2BAA2B,KAAK;GAC7C,WAAW,KAAK,KAAK;GACrB,MAAM,KAAK;GACZ,EACF,CAAC,EAC4B;EAG9B,MAAM,WAAW,MAAM,KAAK,IAAI,KAI7B,iCAAiC,EAAE,CAAC;EAGvC,MAAM,SAAS,KAAK,8BAA8B,MAAM,eAAe;EACvE,MAAM,WAAW,IAAI,UAAU;EAC/B,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,YAAY,CAA2B,EAAE,EACnE,MAAM,KAAK,KAAK,MACjB,CAAC;AACF,WAAS,OAAO,QAAQ,MAAM,KAAK,KAAK;AACxC,WAAS,OAAO,SAAS,SAAS,MAAM;AACxC,WAAS,OAAO,aAAa,SAAS,UAAU;AAChD,WAAS,OAAO,UAAU,OAAO,SAAS,OAAO,CAAC;AAClD,WAAS,OAAO,UAAU,OAAO;AACjC,WAAS,OAAO,YAAY,KAAK,KAAK;AACtC,WAAS,OAAO,aAAa,sCAAsC;EAEnE,MAAM,SAAS,MAAM,MACnB,kDACA;GACE,QAAQ;GACR,MAAM;GACP,CACF;AACD,MAAI,CAAC,OAAO,GAAI,OAAM,IAAI,MAAM,2BAA2B,OAAO,SAAS;EAC3E,MAAM,SAAU,MAAM,OAAO,MAAM;EAUnC,MAAM,kBAA2C,EAC/C,OAAO;GACL,IAAI,MAAM;GACV,kBAAkB,OAAO;GACzB,cAAc,OAAO;GACrB,WAAW,KAAK,KAAK;GACrB,MAAM,KAAK;GACX,WAAW,OAAO;GAClB,eAAe,MAAM;GACtB,EACF;AACD,MAAI,OAAO,OACR,iBAAgB,SAAqC,YACpD,OAAO;AACX,MAAI,OAAO,MACR,iBAAgB,SAAqC,WACpD,OAAO;EAEX,MAAM,eAAe,MAAM,KAAK,IAAI,KAEjC,qCAAqC,gBAAgB;EAMxD,MAAM,SAAS,MAAM,KAAK,YACxB;GACE,KAAK,KAAK;GACV,WAAW;IACT,gBAAgB,aAAa,MAAM;IACnC,cAAc,KAAK,KAAK;IACxB,cAAc,OAAO;IACrB,UAAU,KAAK;IACf,QAAQ,aAAa,MAAM;IAC3B,KAAK,aAAa,MAAM;IACxB,mBAAmB,OAAO;IAC3B;GACF,EACD,QACD;AAID,SAAO;GACL,GAAG;GACH,KAAK,OAAO,OAAO,KAAK;GACxB,KAAK,OAAO,OAAO,aAAa,MAAM;GACtC,cAAc,aAAa,MAAM;GACjC,eAAe;IACb,KAAK,aAAa,MAAM;IACxB,aAAa,KAAK,KAAK;IACvB,aAAa,OAAO;IACpB,iBAAiB,OAAO;IACxB,SAAS,KAAK;IACd,QAAQ,aAAa,MAAM;IAC5B;GACF;;CAGH,sBACE,MACA,UACA,SACM;AAGN,OAAK,cAAc,QAAQ;AAC3B,OAAK,cAAc,IAAI,KAAK,cAAc;GACxC,eAAe,KAAK;GACpB,GAAG,aAAa,SAAS;GACzB,GAAG,sBAAsB,MAAM,SAAS;GACxC,GAAI,UAAU,EAAE,SAAS,MAAM,GAAG,EAAE;GACpC,GAAI,OAAO,SAAS,iBAAiB,WACjC,EAAE,cAAc,SAAS,cAAc,GACvC,EAAE;GACP,CAAC;AACF,OAAK,cAAc,OAAO;AAC1B,aAAW,KAAK,aAAa;;CAG/B,8BAAsC,eAA+B;EACnE,MAAM,QAAQ,cAAc,MAAM,IAAI;EACtC,MAAM,YAAY,MAAM,MAAM;EAC9B,MAAM,WAAW,MAAM,MAAM;EAC7B,MAAM,YAAY,MAAM,MAAM;AAQ9B,SAAO,GAAG,UAAU,GAPsB;GACxC,QAAQ;GACR,QAAQ;GACR,OAAO;GACP,WAAW;GACX,OAAO;GACR,CACgC,aAAa,QAAQ,GAAG;;CAK3D,MAAM,iBACJ,cACA,SACe;AACf,oCAAkC,CAAC,aAAa,CAAC;AAGjD,OAAK,cAAc,QAAQ;AAC3B,MAAI,KAAK,cAAc,IAAI,aAAa,CAAE;EAE1C,MAAM,OAAgC,EACpC,4BAA4B,EAAE,KAAK,cAAc,EAClD;AACD,MAAI,QAAS,MAAK,cAAc;AAEhC,MAAI;GACF,MAAM,WAAY,MAAMC,oBACtB,KAAK,KACL,KAAK,SACL,KACD;AACD,OAAI,SAAS,oBACX,MAAK,qBAAqB,SAAS;WAE9B,GAAG;AACV,OAAI,CAAC,gBAAgB,EAAE,CAAE,OAAM,KAAK,kBAAkB,EAAE;;AAE1D,OAAK,qBAAqB,aAAa;;CAKzC,MAAM,cAAyC;EAI7C,MAAM,OAAQ,MAAMN,mBAClB,KAAK,KACL,KAAK,QACN;EAID,MAAM,YAAY,KAAK,+BAA+B,EAAE;AACxD,OAAK,gBAAgB,UAAU;AAC/B,OAAK,qBAAqB,KAAK,uBAAuB;AACtD,OAAK,wBAAwB;AAC7B,SAAO;;CAGT,MAAM,oBAAoB,KAA8B;EACtD,MAAM,OAAO,MAAM,MAAM,IAAI;AAC7B,MAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,6BAA6B,KAAK,SAAS;AACzE,SAAO,OAAO,KAAK,MAAM,KAAK,aAAa,CAAC;;;;;;;;CAS9C,MAAM,wBACJ,WACA,MACyE;AACzE,OAAK,cAAc,QAAQ;EAE3B,MAAM,8BAAc,IAAI,KAAa;EACrC,MAAM,sCAAsB,IAAI,KAAa;EAC7C,MAAM,oCAAoB,IAAI,KAAa;EAC3C,MAAM,wCAAwB,IAAI,KAAa;EAC/C,MAAM,gCAAgB,IAAI,KAAwB;EAClD,MAAM,SAAmB,EAAE;EAC3B,IAAI,kBAAkB;EAMtB,MAAM,2BAA2B,UAAU,QAAQ,aAAa;GAC9D,MAAM,MAAM,SAAS;AACrB,OAAI,CAAC,IAAK,QAAO;GAEjB,MAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,OAAI,CAAC,KAAK,gBAAgB,KAAK,KAAK,CAAE,QAAO;AAC7C,OAAI,CAAC,yBAAyB,UAAU,KAAK,KAAK,CAAE,QAAO;GAE3D,MAAM,WAAW,KAAK,cAAc,IAAI,IAAI;AAC5C,OAAI,KAAK,UAAU,OAAO,OAAO,IAAI,IAAI,UAAU,QAAS,QAAO;AACnE,UACE,CAAC,YACD,SAAS,QAAQ,SAAS,OAC1B,CAAC,sBAAsB,SAAS;IAElC;EACF,IAAI,gCAAgB,IAAI,KAAkC;EAC1D,MAAM,yCAAyB,IAAI,KAAa;AAChD,MAAI,yBAAyB,SAAS,GAAG;AACvC,OAAI;AACF,oBAAgB,MAAM,KAAK,wBAAwB,KAAK,QAAQ;YACzD,OAAO;AACd,QAAI,iBAAiB,2BAA4B,OAAM;AACvD,WAAO,KAAK,+BAA+B,YAAY,MAAM,GAAG;AAChE,SAAK,MAAM,YAAY,yBACrB,KAAI,SAAS,IAAK,wBAAuB,IAAI,SAAS,IAAI;;AAI9D,OAAI,uBAAuB,SAAS,EAClC,MAAK,MAAM,YAAY,0BAA0B;IAC/C,MAAM,MAAM,SAAS;AACrB,QAAI,CAAC,OAAO,cAAc,IAAI,cAAc,IAAI,CAAC,CAAE;AACnD,2BAAuB,IAAI,IAAI;AAC/B,WAAO,KACL,sCAAsC,IAAI,aAAa,KAAK,UAC7D;;;AAKP,OAAK,iBAAiB,iBAAiB;AACvC,OAAK,MAAM,YAAY,WAAW;GAChC,MAAM,MAAM,SAAS;AACrB,OAAI,CAAC,IAAK;GAEV,MAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,OAAI,CAAC,KAAK,gBAAgB,KAAK,KAAK,CAAE;AACtC,OAAI,CAAC,yBAAyB,UAAU,KAAK,KAAK,CAAE;AAEpD,qBAAkB,IAAI,IAAI;AAC1B,OAAI,KAAK,UAAU,OAAO,OAAO,IAAI,EAAE;AACrC,QAAI,KAAK,cAAc,IAAI,IAAI,CAAE,uBAAsB,IAAI,IAAI;AAC/D,gBAAY,IAAI,IAAI;AACpB;;AAMF,OAAI,KAAK,cAAc,IAAI,IAAI,EAAE,SAAS;AACxC,0BAAsB,IAAI,IAAI;AAC9B,gBAAY,IAAI,IAAI;AACpB;;AAMF,OAAI,uBAAuB,IAAI,IAAI,EAAE;IAKnC,MAAM,QAAQ,KAAK,cAAc,IAAI,IAAI;AACzC,QAAI,SAAS,MAAM,QAAQ,SAAS,KAAK;AACvC,UAAK,cAAc,OAAO,IAAI;AAC9B,uBAAkB;;AAEpB;;AAGF,OAAI;IACF,MAAM,WAAW,KAAK,cAAc,IAAI,IAAI;IAC5C,MAAM,WAAW,cAAc,IAAI,cAAc,IAAI,CAAC;AACtD,SAAK,cAAc,IAAI,KAAK;KAC1B,GAAG;KACH,eAAe,KAAK;KACpB,GAAG;KACH,GAAG,aAAa,SAAS;KAC1B,CAAC;AACF,sBAAkB;AAClB,wBAAoB,IAAI,IAAI;AAC5B,gBAAY,IAAI,IAAI;AACpB,kBAAc,IAAI,KAAK,KAAK;YACrB,OAAO;AACd,QAAI,iBAAiB,2BAA4B,OAAM;AACvD,WAAO,KAAK,eAAe,IAAI,IAAI,YAAY,MAAM,GAAG;;;AAI5D,MAAI,KAAK,OACP,MAAK,MAAM,CAAC,KAAK,SAAS,KAAK,cAAc,SAAS,EAAE;AACtD,OACE,kBAAkB,IAAI,IAAI,IAC1B,sBAAsB,IAAI,IAAI,IAC9B,KAAK,UAAU,OAAO,OAAO,IAAI,IACjC,KAAK,QAEL;AAEF,QAAK,cAAc,OAAO,IAAI;AAC9B,qBAAkB;;AAItB,MAAI,gBACF,KAAI;AAGF,OAAI,KAAK,gBACP,MAAK,gBAAgB,OAAO,CAAC,qBAAqB,QAChD,KAAK,cAAc,OAAO,CAC3B;OACE,MAAK,cAAc,OAAO;WACxB,OAAO;AACd,OAAI,iBAAiB,2BAA4B,OAAM;AACvD,UAAO,KAAK,kCAAkC,YAAY,MAAM,GAAG;AACnE,QAAK,MAAM,OAAO,oBAAqB,aAAY,OAAO,IAAI;AAC9D,UAAO;IAAE;IAAa,QAAQ;IAAG;IAAQ;;EAI7C,IAAI,SAAS;AACb,OAAK,MAAM,CAAC,KAAK,SAAS,cACxB,KAAI;AACF,OAAI,KAAK,OACP,KAAI,KAAK,gBACP,MAAK,gBAAgB,OAAO,CAAC,IAAI,QAC/B,WAAW,KAAK,aAAa,CAC9B;OACE,YAAW,KAAK,aAAa;AAEpC;WACO,OAAO;AACd,OAAI,iBAAiB,2BAA4B,OAAM;AACvD,UAAO,KAAK,eAAe,IAAI,IAAI,YAAY,MAAM,GAAG;;AAM5D,SAAO;GAAE;GAAa;GAAQ;GAAQ;;CAGxC,gBAAwB,KAAa,MAA0B;AAC7D,SACE,CAAC,IAAI,SAAS,KAAK,IACnB,CAAC,IAAI,MAAM,QAAQ,CAAC,SAAS,KAAK,KACjC,KAAK,iBAAiB,KAAK,UAAU,QACpC,KAAK,aAAa,WAAW,KAAK,UAAU,OAAO,IAAI;;CAI7D,yBACE,UACA,MACS;AACT,MAAI,CAAC,uBAAuB,SAAS,CAAE,QAAO;AAC9C,MAAI,KAAK,QAAQ,KAAA,EAAW,QAAO,SAAS,QAAQ,KAAK;AACzD,MAAI,KAAK,aAAa,KAAA,EAAW,QAAO,SAAS,aAAa,KAAK;AACnE,SAAO;;CAGT,iCAA+C;EAC7C,MAAM,aAAa,KAAK,cACrB,SAAS,CACT,QACE,CAAC,SACA,CAAC,KAAK,UAAU,OAAO,OAAO,IAAI,IAClC,CAAC,KAAK,UAAU,KAAK,IAAI,CAAC,OAC7B,CACA,KAAK,CAAC,SAAS,IAAI,CACnB,QAAQ,QAAQ,CAAC,uBAAuB,KAAK,gBAAgB,IAAI,IAAI,CAAC,CAAC;AAC1E,MAAI,WAAW,SAAS,EACtB,OAAM,IAAI,MACR,yCAAyC,WAAW,KAAK,KAAK,GAC/D;;CAML,MAAM,YACJ,OAkBI,EAAE,EACe;EACrB,MAAM,aAAa,KAAK,UAAU,OAAO;AACzC,yBAAuB,WAAW,KAAK,SAAS,KAAK,aAAa,CAAC;EACnE,MAAM,SAAqB;GACzB,UAAU;GACV,SAAS;GACT,YAAY;GACZ,QAAQ;GACR,QAAQ,EAAE;GACV,kBAAkB;GACnB;AAGD,MAAI,KAAK,UAAU;AACjB,QAAK,MAAM,QAAQ,YAAY;AAC7B,QAAI,CAAC,KAAK,SAAU;IAEpB,MAAM,SADc,KAAK,gBAAgB,CACd,QAAQ,MAAM,EAAE,aAAa,QAAQ;AAChE,SAAK,MAAM,KAAK,OACd,QAAO,OAAO,KAAK,GAAG,KAAK,aAAa,IAAI,EAAE,UAAU;;AAG5D,OAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,WAAO,mBAAmB;AAC1B,WAAO;;;AAIX,MAAI,KAAK,YACP,MAAK,kBAAkB,KAAK,YAAY;MAExC,OAAM,KAAK,gBAAgB;AAM7B,MAAI,CAAC,KAAK,cAAe,OAAM,KAAK,cAAc,KAAK,QAAQ;EAO/D,IAAI,UACF,KAAK,YACJ,KAAK,sBAAsB,CAAC,KAAK,cAAc,KAAK,WAAW,GAAG;EAErE,MAAM,aAAa,IAAI,IAAI,WAAW,KAAK,SAAS,KAAK,aAAa,CAAC;AACvE,MAAI,KAAK,OACP,MAAK,MAAM,OAAO,KAAK,cAAc,MAAM,CAAE,YAAW,IAAI,IAAI;EAElE,MAAM,WAAW,KAAK,SAClB,KAAK,YAAY,CAAC,QACf,QACC,QAAA,wBACA,KAAK,wBAAwB,IAAI,IACjC,CAAC,WAAW,IAAI,IAAI,IACpB,CAAC,KAAK,UAAU,OAAO,OAAO,IAAI,CACrC,GACD,EAAE;EAEN,MAAM,iBAAiB,iBACrB,KAAK,WACL,WAAW,QAAQ,MAAM,EAAE,UAAU,KAAK,WAAW,EAAE,CAAC,EACxD,SACD;AAED,MAAI,KAAK,mBAAmB;AAC1B,UAAO,SAAS,MAAM,KAAK,kBAAkB,KAAK,kBAAkB;AAIpE,aAAU,KAAK,sBAAsB;aAC5B,KAAK,uBAAuB;AACrC,QAAK,cAAc,QAAQ;AAC3B,QAAK,gCAAgC;;EAKvC,MAAM,WAAW,eAAe,QAC7B,EAAE,MAAM,0BACP,uBAAwB,KAAK,UAAU,KAAK,WAAW,KAAK,CAC/D;EACD,MAAM,cAAc,SAAS,QAC1B,OAAO,WACN,QAAQ,KAAK,OAAO,qBAAqB,MAAM,UAAU,IAC3D,EACD;EACD,IAAI,OAAO;AACX,OAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,EAAE,SAAS;AACjB,OACE,KAAK,iBAAA,wBACL,OAAO,OAAO,SAAS,GACvB;AACA,WAAO,OAAO,KACZ,iEACD;AACD;;AAEF,OAAI;AACF,QAAI,OAAO,oBACT,OAAM,KAAK,0BAA0B,QAAQ,QAAQ;QAErD,OAAM,KAAK,WAAW,MAAM,SAAS,EACnC,cAAc,KAAK,qBACpB,CAAC;AAKJ,cAAU,KAAK;AACf,WAAO,YAAY,KAAK,OAAO,qBAAqB,MAAM,UAAU;YAC7D,GAAG;AACV,QAAI,aAAa,kBAAmB,OAAM;AAC1C,WAAO,OAAO,KAAK,UAAU,KAAK,aAAa,IAAI,YAAY,EAAE,GAAG;;AAEtE,WAAQ,KAAK,OAAO,qBAAqB,MAAM,UAAU;AACzD,QAAK,aAAa,MAAM,YAAY;;AAGtC,MAAI,KAAK,UAAU,OAAO,OAAO,WAAW,EAC1C,MAAK,MAAM,OAAO,SAChB,KAAI;AACF,SAAM,KAAK,iBAAiB,KAAK,QAAQ;AACzC,aAAU,KAAK;AACf,UAAO;WACA,GAAG;AACV,OAAI,aAAa,kBAAmB,OAAM;AAC1C,UAAO,OAAO,KAAK,UAAU,IAAI,IAAI,YAAY,EAAE,GAAG;;AAK5D,SAAO;;CAKT,MAAM,cACJ,OAII,EAAE,EACqC;EAC3C,MAAM,YAAY,MAAM,KAAK,aAAa;EAC1C,MAAM,qBAAqB,MAAM,KAAK,wBAAwB,WAAW,EACvE,QAAQ,KAAK,UAAU,OACxB,CAAC;EACF,MAAM,SAA2C;GAC/C,UAAU;GACV,SAAS;GACT,YAAY;GACZ,QAAQ,mBAAmB;GAC3B,SAAS;GACT,QAAQ,CAAC,GAAG,mBAAmB,OAAO;GACtC,kBAAkB;GACnB;EAED,IAAI,OAAO;AACX,OAAK,MAAM,YAAY,WAAW;AAChC,OAAI,mBAAmB,YAAY,IAAI,SAAS,IAAI,EAAE;AACpD,SAAK,aAAa,EAAE,MAAM,UAAU,OAAO;AAC3C;;AAEF,OAAI,KAAK,MAAM,IAAI,SAAS,IAAI,EAAE;AAChC,WAAO;AACP,SAAK,aAAa,EAAE,MAAM,UAAU,OAAO;AAC3C;;GAGF,MAAM,OAAO,KAAK,UAAU,KAAK,SAAS,IAAI;AAG9C,OAAI,CAAC,KAAK,gBAAgB,SAAS,KAAK,KAAK,EAAE;AAC7C,WAAO,OAAO,KAAK,YAAY,SAAS,IAAI,2BAA2B;AACvE,SAAK,aAAa,EAAE,MAAM,UAAU,OAAO;AAC3C;;AAGF,OAAI;AACF,QAAI,SAAS,kBAAkB,kBAAkB,SAAS,KAAK;KAC7D,MAAM,MAAM,MAAM,KAAK,oBAAoB,SAAS,IAAI;AACxD,UAAK,MAAM,IAAI;eAEf,SAAS,YAAY,KAAA,KACrB,SAAS,YAAY,MACrB;KACA,MAAM,UACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,QAAQ;AACtC,UAAK,MAAM,QAAQ;;AAErB,WAAO;YACA,GAAG;AACV,WAAO,OAAO,KAAK,YAAY,SAAS,IAAI,IAAI,YAAY,EAAE,GAAG;;AAEnE,QAAK,aAAa,EAAE,MAAM,UAAU,OAAO;;AAG7C,MAAI,KAAK,QAAQ;GACf,MAAM,aAAa,IAAI,IAAI,UAAU,KAAK,MAAM,EAAE,IAAI,CAAC;AACvD,QAAK,MAAM,QAAQ,KAAK,UAAU,OAAO,EAAE;AACzC,QAAI,WAAW,IAAI,KAAK,aAAa,CAAE;AAOvC,QAAI,gBAAgB,KAAK,aAAa,CAAE;AAExC,QAAI;AACF,gBAAW,KAAK,aAAa;AAC7B,YAAO;YACD;;;AAMZ,SAAO;;;AAIX,SAAS,yBACP,UACA,KACA,MACS;AACT,QACE,gBAAgB,IAAI,IAAI,uBAAuB,SAAS,IAAI,CAAC,KAAK;;AAItE,SAAS,yBAAyB,MAA0B;AAC1D,QACE,CAAC,KAAK,UACN,KAAK,aAAa,WAAW,UAAU,IACvC,CAAC,gBAAgB,KAAK,aAAa;;AAIvC,SAAS,uBACP,UAC4B;AAC5B,QACE,UAAU,kBAAkB,kBAC5B,OAAO,SAAS,QAAQ,YACxB,SAAS,IAAI,SAAS;;AAI1B,SAAS,wBAAwB,OAA4C;AAC3E,QAAO;EACL,KAAK,MAAM;EACX,UAAU,MAAM;EAChB,SAAS,MAAM,iBAAiB,KAAK;EACrC,eAAe,MAAM;EACrB,aAAa,MAAM;EACnB,KAAK,MAAM;EACZ;;AAGH,SAAS,oBAAoB,UAA+C;AAC1E,QAAO;EACL,KAAK,SAAS;EACd,UAAU,SAAS;EACnB,gBAAgB,SAAS,WAAW;EACpC,cAAc,SAAS;EACvB,YAAY,SAAS;EACrB,KAAK,SAAS;EACf;;AAGH,SAAS,gBAAgB,OAAyB;AAChD,QAAO,WAAW,MAAM,IAAI,MAAM,WAAW;;AAG/C,SAAS,aACP,UAC0C;AAC1C,QAAO;EACL,GAAI,OAAO,SAAS,aAAa,YAAY,SAAS,SAAS,SAAS,IACpE,EAAE,UAAU,SAAS,UAAU,GAC/B,EAAE;EACN,GAAI,OAAO,SAAS,QAAQ,YAAY,SAAS,IAAI,SAAS,IAC1D,EAAE,KAAK,SAAS,KAAK,GACrB,EAAE;EACP;;AAGH,SAAS,cAAc,KAAqB;AAC1C,QAAO,IAAI,MAAM,EAAiB;;AAGpC,SAAS,sBACP,MACiC;AACjC,KACE,OAAO,KAAK,QAAQ,YACpB,KAAK,IAAI,WAAW,KACpB,OAAO,KAAK,gBAAgB,YAC5B,KAAK,YAAY,WAAW,KAC5B,OAAO,KAAK,gBAAgB,YAC5B,CAAC,OAAO,UAAU,KAAK,YAAY,IACnC,KAAK,eAAe,EAEpB;AAGF,QAAO;EACL,KAAK,KAAK;EACV,aAAa,KAAK;EAClB,aAAa,KAAK;EAClB,GAAI,OAAO,KAAK,oBAAoB,WAChC,EAAE,iBAAiB,KAAK,iBAAiB,GACzC,EAAE;EACN,GAAI,OAAO,KAAK,YAAY,WAAW,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;EACrE,GAAI,OAAO,KAAK,WAAW,WAAW,EAAE,QAAQ,KAAK,QAAQ,GAAG,EAAE;EACnE;;AAGH,SAAS,sBACP,MACA,UACqB;AACrB,KAAI,SAAS,cAAe,QAAO,SAAS;AAC5C,KAAI,CAAC,SAAS,IACZ,OAAM,IAAI,MAAM,8BAA8B,KAAK,eAAe;AAGpE,QAAO;EACL,KAAK,SAAS;EACd,aAAa,KAAK,KAAK;EACvB,aAAa,KAAK,MAAM;EACxB,SAAS,KAAK;EACd,GAAI,OAAO,SAAS,iBAAiB,WACjC,EAAE,QAAQ,SAAS,cAAc,GACjC,EAAE;EACP;;AAGH,SAAS,wBACP,OACiE;AACjE,KAAI,CAACE,WAAS,MAAM,CAAE,QAAO,KAAA;CAE7B,MAAM,WAAW,eAAe,MAAM,YAAY;CAClD,MAAM,MAAM,eAAe,MAAM,OAAO;CACxC,MAAM,cAAc,eAAe,MAAM,gBAAgB;CACzD,MAAM,cAAc,gBAAgB,MAAM,gBAAgB;AAC1D,KAAI,CAAC,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,YAAa,QAAO,KAAA;CAE9D,MAAM,kBAAkB,eAAe,MAAM,qBAAqB;CAClE,MAAM,UAAU,eAAe,MAAM,YAAY;CACjD,MAAM,SAAS,eAAe,MAAM,UAAU;AAC9C,QAAO;EACL;EACA,UAAU;GACR;GACA;GACA;GACA,GAAI,kBAAkB,EAAE,iBAAiB,GAAG,EAAE;GAC9C,GAAI,YAAY,KAAA,IAAY,EAAE,SAAS,GAAG,EAAE;GAC5C,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;GAC7B;EACF;;AAGH,SAAS,sBAAsB,OAAoC;AACjE,KAAI,CAACA,WAAS,MAAM,IAAI,CAACA,WAAS,MAAM,iBAAiB,CACvD;AAEF,QAAO,gBAAgB,MAAM,iBAAiB,MAAM;;AAGtD,SAAS,gBAAgB,OAAoC;AAC3D,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,MAAM,IAAI,QAAQ,EAClE,QAAO;AAET,KAAI,OAAO,UAAU,YAAY,CAAC,QAAQ,KAAK,MAAM,CAAE,QAAO,KAAA;CAE9D,MAAM,SAAS,OAAO,MAAM;AAC5B,QAAO,OAAO,cAAc,OAAO,IAAI,SAAS,IAAI,SAAS,KAAA;;AAG/D,SAAS,eAAe,OAAoC;AAC1D,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;;AAGjE,SAAS,eAAe,OAAoC;AAC1D,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG7C,SAASA,WAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;ACpoD7E,SAAgB,8BAA8B,SAA0B;CACtE,IAAI,eAAe;CACnB,IAAI,kBAAkB;AAEtB,MAAK,MAAM,SAAS,QAAQ,SAAS,qBAAqB,CACxD,SAAQ,MAAM,IAAd;EACE,KAAK;AACH,mBAAgB;AAChB;EACF,KAAK;AACH,OAAI,eAAe,EAAG,iBAAgB;AACtC;EACF,KAAK;AACH,sBAAmB;AACnB;EACF,KAAK;AACH,OAAI,kBAAkB,EAAG,oBAAmB;AAC5C;;AAIN,QAAO,eAAe,KAAK,kBAAkB;;AAQ/C,MAAM,aAAa,IAAI,IAAI;CACzB,CAAC,WAAW,aAAa;CACzB,CAAC,QAAQ,UAAU;CACnB,CAAC,WAAW,aAAa;CACzB,CAAC,OAAO,SAAS;CACjB,CAAC,QAAQ,UAAU;CACnB,CAAC,MAAM,QAAQ;CACf,CAAC,aAAa,eAAe;CAC7B,CAAC,cAAc,gBAAgB;CAC/B,CAAC,YAAY,cAAc;CAC3B,CAAC,OAAO,SAAS;CACjB,CAAC,UAAU,YAAY;CACvB,CAAC,SAAS,WAAW;CACrB,CAAC,cAAc,gBAAgB;CAC/B,CAAC,YAAY,cAAc;CAC3B,CAAC,UAAU,YAAY;CACxB,CAAC;AAEF,MAAM,eAAe,IAAI,IAAI,WAAW,QAAQ,CAAC;AACjD,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;AAkBF,SAAgB,8BACd,SAC4B;CAC5B,MAAM,QAAqB,EAAE;CAC7B,MAAM,cAA0C,EAAE;CAClD,IAAI,OAAO;CACX,IAAI,mBAAmB;CAEvB,MAAM,cAAc,MAAc,YAA0B;EAC1D,MAAM,OAAO,MAAM,GAAG,GAAG;AAEzB,MAAI,QAAQ,kBAAkB,IAAI,KAAK,KAAK,EAAE;AAC5C,OAAI,SAAS,KAAK,cAAe,OAAM,KAAK;AAC5C;;EAGF,MAAM,gBAAgB,WAAW,IAAI,KAAK;AAC1C,MAAI,eAAe;AACjB,SAAM,KAAK;IAAE;IAAM;IAAe,MAAM;IAAS,CAAC;AAClD;;AAGF,MAAI,CAAC,aAAa,IAAI,KAAK,CAAE;AAE7B,MAAI,CAAC,MAAM;AACT,eAAY,KAAK;IACf,UAAU;IACV,SAAS,6BAA6B,KAAK,eAAe,QAAQ;IACnE,CAAC;AACF;;AAGF,MAAI,SAAS,KAAK,eAAe;AAC/B,eAAY,KAAK;IACf,UAAU;IACV,SAAS,6BAA6B,KAAK,eAAe,QAAQ,QAAQ,KAAK,KAAK,iBAAiB,KAAK,KAAK,uBAAuB,KAAK,cAAc;IAC1J,CAAC;AACF;;AAGF,QAAM,KAAK;;AAGb,MAAK,MAAM,SAAS,QAAQ,SAC1B,uDACD,EAAE;EACD,MAAM,OAAO,MAAM,IAAI,aAAa;AACpC,MAAI,CAAC,KAAM;EAEX,MAAM,QAAQ,MAAM,SAAS;AAG7B,OAAK,IAAI,SAAS,kBAAkB,SAAS,OAAO,SAClD,KAAI,QAAQ,WAAW,OAAO,KAAK,GAAI,SAAQ;AAEjD,qBAAmB;EAEnB,MAAM,OAAO,MAAM,GAAG,GAAG;AACzB,MAAI,SAAS,YAAY,EAAE,QAAQ,kBAAkB,IAAI,KAAK,KAAK,GAAG;GAIpE,MAAM,aAAa,MAAM,GACtB,QAAQ,sBAAsB,GAAG,CACjC,QAAQ,UAAU,GAAG,CACrB,MAAM,KAAK;AACd,QAAK,MAAM,CAAC,QAAQ,cAAc,WAAW,SAAS,EAAE;IACtD,MAAM,gBAAgB,0BAA0B,KAAK,UAAU,GAAG;AAClE,QAAI,CAAC,cAAe;AACpB,eAAW,cAAc,aAAa,EAAE,OAAO,OAAO;;QAGxD,YAAW,MAAM,KAAK;;AAI1B,MAAK,MAAM,QAAQ,MAAM,SAAS,CAChC,aAAY,KAAK;EACf,UAAU;EACV,SAAS,2BAA2B,KAAK,KAAK,eAAe,KAAK,KAAK,iBAAiB,KAAK,cAAc;EAC5G,CAAC;AAGJ,QAAO;;;;;;;;;;;;;ACrKT,IAAa,iBAAb,cAAoC,MAAM;CACxC,YACE,MACA,MACA;AACA,QAAM,0BAA0B,MAAM,KAAK,CAAC;AAH5B,OAAA,OAAA;AACA,OAAA,OAAA;AAGhB,OAAK,OAAO;;;AAIhB,SAAgB,0BAA0B,MAAc,MAAsB;AAC5E,QACE,QAAQ,KAAK,MAAM,KAAK;;;;;;;AAW5B,SAAgB,mBAAmB,MAAc,MAA6B;AAC5E,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,SAAS,IAAI,cAAc;AAEjC,SAAO,KAAK,UAAU,QAA+B;AACnD,OAAI,IAAI,SAAS,gBAAgB,IAAI,SAAS,SAC5C,QAAO,IAAI,eAAe,MAAM,KAAK,CAAC;OAEtC,QAAO,IAAI;IAEb;AAEF,SAAO,KAAK,mBAAmB;AAC7B,UAAO,YAAY,SAAS,CAAC;IAC7B;AAEF,SAAO,OAAO,MAAM,KAAK;GACzB;;;;AC3BJ,SAAS,YAAoB;AAC3B,yBAAO,IAAI,MAAM,EAAC,mBAAmB,SAAS,EAAE,QAAQ,OAAO,CAAC;;AAsBlE,eAAsB,eACpB,KACA,OACA,WACA,MACA,SACqB;CACrB,MAAM,MAAM,IAAI,WAAW;CAC3B,MAAM,gBAAgB,IAAI,cAAc,KAAK,OAAO,KAAK,WAAW;CACpE,MAAM,SAAS,IAAI,OAAO,KAAK,MAAM,IAAI,UAAU;CACnD,MAAM,oBAAoB,+BAA+B,KAAK,MAAM,GAAG;CACvE,IAAI,kBAAkB;CACtB,MAAM,8BAA8B;AAClC,MAAI,CAAC,gBAAiB;AACtB,oBAAkB;AAClB,MAAI;AACF,QAAK,4BAA4B;UAC3B;;CAIV,MAAM,0BAA0B;AAC9B,MAAI,CAAC,gBAAiB;AACtB,MAAI,KAAK,eAAe;GACtB,MAAM,QAAQ,OAAO,eACnB,IAAI,mBAAmB,UAAU,KAAK,CAAC,aAAa,CACrD;AACD,OAAI,MACF,KAAI;AACF,SAAK,cAAc,MAAM;WACnB;AACN,2BAAuB;;;;CAM/B,MAAM,iCAAiB,IAAI,KAAa;AAGxC,SAAQ,IAAI,mBAAmB,MAAM,KAAK,KAAK,MAAM,GAAG,IAAI;CAC5D,MAAM,YAAY,MAAc,UAAkB;AAChD,UAAQ,OAAO,MAAM,iBAAiB,KAAK,GAAG,MAAM,SAAS;;CAE/D,MAAM,8BACJ,OAAO,YAAY;EACjB,QAAQ;EACR,UAAU,KAAK;EACf,mBAAmB,EAAE,SAAS,MAAM;EACpC,qBAAqB;EACrB,YAAY;EACb,CAAC;CACJ,IAAI;AACJ,KAAI,KAAK,aAAa;EACpB,IAAI,uBAAuB;AAC3B,MAAI;AACF,UAAO,kBAAkB,KAAK,YAAY;AAC1C,SAAM,OAAO,cAAc,KAAK,YAAY,UAAU;AACtD,0BAAuB;WAChB,OAAO;AACd,OAAI,EAAE,iBAAiB,mBAAoB,OAAM;;AAGnD,eAAa,uBACT,MAAM,OAAO,YAAY;GACvB,QAAQ;GACR,UAAU,KAAK;GACf,mBAAmB,EAAE,SAAS,MAAM;GACpC,qBAAqB;GACrB,SAAS,OAAO,WAAW;GAC3B,aAAa,KAAK;GAClB,eAAe;GACf,YAAY;GACb,CAAC,GACF,MAAM,uBAAuB;OAEjC,cAAa,MAAM,uBAAuB;AAE5C,SAAQ,OAAO,MAAM,KAAK;AAC1B,KAAI,WAAW,SAAS,EACtB,SAAQ,IAAI,WAAW,WAAW,OAAO,6BAA6B;AAExE,KAAI,WAAW,kBAAkB;AAC/B,UAAQ,MACN,+BAA+B,WAAW,OAAO,OAAO,oCACzD;AACD,OAAK,MAAM,KAAK,WAAW,OAAQ,SAAQ,MAAM,KAAK,IAAI;AAC1D,UAAQ,KAAK,EAAE;YACN,WAAW,OAAO,SAAS,GAAG;AACvC,yBAAuB;AACvB,OAAK,MAAM,KAAK,WAAW,OAAQ,SAAQ,MAAM,KAAK,IAAI;AAC1D,MAAI,WAAW,WAAW,WAAW,YAAY,EAAG,SAAQ,KAAK,EAAE;;AAErE,KAAI,WAAW,OAAO,WAAW,EAC/B,oBAAmB;CA8BrB,MAAM,eAAe;CACrB,IAAI,gBAAgB;CACpB,IAAI,cAAoD;CACxD,IAAI,eAA8B,QAAQ,SAAS;CAQnD,IAAI,UAAU;CACd,IAAI,sBAAsB;CAC1B,MAAM,qBAAqB,UAAmC;AAC5D,MAAI,oBAAqB;AACzB,wBAAsB;AACtB,yBAAuB;AACvB,UAAQ,MACN,8DAA8D,MAAM,QAAQ,gFAC7E;;CAEH,MAAM,iBAAuB;AAC3B,iBAAe,aAAa,KAAK,YAAY;GAC3C,MAAM,WAAW,MAAM,OAAO,aAAa;AAC3C,aAAU,CAAC;AACX,OAAI,SAAU,oBAAmB;IACjC;;CAEJ,MAAM,qBAA2B;AAC/B,MAAI,CAAC,YAAa;AAClB,eAAa,YAAY;AACzB,gBAAc;AACd,YAAU;;CAEZ,MAAM,qBAA2B;AAC/B,MAAI,YAAa,cAAa,YAAY;AAC1C,gBAAc,iBAAiB;AAC7B,iBAAc;AACd,aAAU;KACT,aAAa;;CAGlB,MAAM,cAAc,WAClB,WACA,OAAO,UAAU,OAAO,SAAS,cAAc;AAC7C,MAAI,YAAY,gBAAgB,aAE9B,eAAc;WACL,aAAa;AAEtB,gBAAa,YAAY;AACzB,iBAAc;;AAEhB,kBAAgB;AAGhB,QAAM;AAGN,MAAI,SAAS;AACX,aAAU;AACV,SAAM;;AAER,MAAI,oBAAqB;EAEzB,IAAI;AACJ,MAAI;AACF,0BACE,UAAU,OAAO,CAAC,KAAK,SAAS,KAAK,aAAa,CACnD;AACD,qCACE,QACG,QAAQ,SAAS,CAAC,UAAU,OAAO,OAAO,KAAK,aAAa,CAAC,CAC7D,KAAK,SAAS,KAAK,aAAa,CACpC;AACD,aAAU,iBACR,WACA,CAAC,GAAG,UAAU,GAAG,MAAM,EACvB,QACG,QAAQ,SAAS,CAAC,UAAU,OAAO,OAAO,KAAK,aAAa,CAAC,CAC7D,KAAK,SAAS,KAAK,aAAa,CACpC;WACM,OAAO;AACd,WAAQ,MAAM,6BAA6B,OAAO,MAAM,GAAG;AAC3D;;EAGF,IAAI,cAAc;EAClB,MAAM,iBAAiB,QAAQ,MAC5B,WACC,OAAO,uBACP,OAAO,KAAK,iBAAiB,qBAChC;AAED,OAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,EAAE,SAAS;GACjB,MAAM,cAAc,CAClB,MACA,GAAI,OAAO,qBAAqB,SAAS,EAAE,CAC5C;AAED,OAAI,KAAK,SACP,MAAK,MAAM,aAAa,aAAa;AACnC,QAAI,CAAC,UAAU,SAAU;AACzB,SAAK,MAAM,cAAc,UAAU,gBAAgB,EAAE;KACnD,MAAM,SACJ,WAAW,aAAa,UACpB,iBACA;AACN,aAAQ,KACN,MAAM,OAAO,IAAI,UAAU,aAAa,IAAI,WAAW,UACxD;;;AAKP,QAAK,MAAM,eAAe,YACxB,gBAAe,IAAI,YAAY,aAAa;AAC9C,OAAI;IAKF,MAAM,gBAAgB,OAAO,sBACzB,MAAM,OAAO,0BAA0B,QAAQ,OAAO,WAAW,CAAC,GAClE,CACE;KACE;KACA,SAAS,MAAM,OAAO,WAAW,MAAM,OAAO,WAAW,EAAE,EACzD,cAAc,MACf,CAAC;KACH,CACF;AACL,kBAAc;AACd,SAAK,MAAM,EACT,MAAM,cACN,SAAS,qBACN,eAAe;AAClB,aAAQ,IACN,cAAc,aAAa,aAAa,IAAI,WAAW,CAAC,GACzD;AAOD,SAAI,gBAAgB,aAAa,aAAa,CAC5C,SAAQ,KACN,OAAO,aAAa,aAAa,oIAClC;AAQH,SACE,aAAa,YACb,oBAAoB,QACpB,8BAA8B,gBAAgB,CAE9C,SAAQ,KACN,OAAO,aAAa,aAAa,mGAClC;AAEH,SAAI,aAAa,YAAY,oBAAoB,KAC/C,MAAK,MAAM,cAAc,8BACvB,gBACD,CACC,SAAQ,KACN,OAAO,aAAa,aAAa,IAAI,WAAW,UACjD;;YAIA,GAAG;AACV,QAAI,aAAa,mBAAmB;AAClC,uBAAkB,EAAE;AACpB;;AAEF,2BAAuB;AACvB,YAAQ,MACN,8BAA8B,KAAK,aAAa,IAAI,IACrD;AAED,0BAAsB,eAAe;AACrC,QAAI,oBACF,SAAQ,MACN,gEACD;AACH;aACQ;AACR,SAAK,MAAM,eAAe,YACxB,gBAAe,OAAO,YAAY,aAAa;;;AAIrD,MAAI,oBAAqB;AAEzB,OAAK,MAAM,QAAQ,SAAS;AAC1B,OAAI,UAAU,OAAO,OAAO,KAAK,aAAa,CAAE;AAChD,OAAI;AACF,UAAM,OAAO,iBAAiB,KAAK,cAAc,OAAO,WAAW,CAAC;AACpE,kBAAc;AACd,YAAQ,IAAI,eAAe,KAAK,eAAe;YACxC,OAAO;AACd,QAAI,iBAAiB,mBAAmB;AACtC,uBAAkB,MAAM;AACxB;;AAEF,2BAAuB;AACvB,0BAAsB,eAAe;AACrC,YAAQ,MACN,4BAA4B,KAAK,aAAa,IAAI,QACnD;AACD,QAAI,oBACF,SAAQ,MACN,gEACD;AACH;;;AAIJ,MAAI,oBAAqB;AAEzB,MAAI,aAAa;AACf,sBAAmB;AACnB,OAAI;AACF,UAAM,cAAc,kBAAkB;WAChC;AACN,YAAQ,MACN,4FACD;;;AAIL,MAAI,MAAM,SAAS,KAAK,QAAQ,SAAS,EAAG,mBAAkB,OAAO;AAErE,MAAI,QAAQ,SAAS,EACnB,KAAI,UAAU,KAAK,UAAU,EAAE,aAAa,MAAM,CAAC,CAAC;WAC3C,QAAQ,SAAS,EAC1B,KAAI,UACF,KAAK,UAAU,EACb,UAAU,QAAQ,SAAS,EAAE,MAAM,0BACjC,CAAC,MAAM,GAAI,qBAAqB,SAAS,EAAE,CAAE,CAAC,KAC3C,gBAAgB,YAAY,aAC9B,CACF,EACF,CAAC,CACH;AAGH,gBAAc;GAEjB;CAGD,MAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,MAAI,CAAC,sBAAsB,KAAK,KAAK,KAAK,EAAE;AAC1C,OAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,OAAI,IAAI,+BAA+B;AACvC;;AAEF,MAAI,MAAM,cAAc,OAAO,KAAK,IAAI,CAAE;AAC1C,MAAI,IAAI,QAAQ,eAAe;AAC7B,OAAI,IAAI,IAAI;AACZ;;AAGF,MAAI;AACF,SAAM,aAAa,KAAK,KAAK;IAC3B,SAAS,MAAM;IACf,SAAS,MAAM;IACf,YAAY,KAAK;IACjB;IACA,oBACE,CAAC,GAAG,eAAe,CAChB,KAAK,MAAM,UAAU,KAAK,EAAE,CAAC,CAC7B,QAAQ,MAAM,EAAE,OAAO,CACvB,KAAK,OAAO;KACX,cAAc,EAAE;KAChB,YAAY,EAAE,MAAM;KACrB,EAAE;IACR,CAAC;WACK,GAAG;AACV,WAAQ,MAAM,WAAW,IAAI,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI;AACxD,OAAI,CAAC,IAAI,aAAa;IAUpB,MAAM,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC1D,QAAI,UAAU,KAAK,EAAE,gBAAgB,6BAA6B,CAAC;AACnE,QAAI,IACF,mDAAmD,MAAM,QAAQ,cAAc,QAAQ,0SAIxF;;;GAGL;AAOF,OAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,SAAO,KAAK,UAAU,QAA+B;AACnD,OAAI,IAAI,SAAS,gBAAgB,IAAI,SAAS,UAAU;AACtD,YAAQ,MAAM,0BAA0B,KAAK,MAAM,KAAK,KAAK,CAAC;AAC9D,YAAQ,KAAK,EAAE;;AAEjB,UAAO,IAAI;IACX;AACF,SAAO,OAAO,KAAK,MAAM,KAAK,YAAY,SAAS,CAAC;GACpD;CAEF,MAAM,UAAU,UAAU,KAAK,KAAK,GAAG,KAAK;AAC5C,WAAU,QAAQ;AAGlB,QAAO,SAAS,OAAO;AACrB,MAAI,OAAO;AACX,eAAa;AACb,SAAO,OAAO;;;;;ACvelB,MAAM,mBAAmB;AACzB,MAAM,gBAAgB,KAAK,gBAAgB,oBAAoB;AAkB/D,SAAgB,sBACd,WACA,SACA,kBACuB;AACvB,KAAI;EAIF,MAAM,QAAQ,cAHC,KAAK,MAClB,aAAa,KAAK,WAAW,cAAc,EAAE,QAAQ,CACtD,CACkC;AACnC,MAAI,MAAM,YAAY,QAAS,QAAO;AACtC,MAAI,MAAM,qBAAqB,iBAAkB,QAAO;AACxD,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,uBACd,WACA,OACM;CACN,MAAM,WAAW;EAAE,SAAS;EAAkB,GAAG;EAAO;AACxD,eAAc,SAAS;CAEvB,MAAM,OAAO,KAAK,WAAW,cAAc;CAC3C,MAAM,WAAW,GAAG,KAAK,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC;AAC3D,KAAI;AACF,YAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC7C,gBAAc,UAAU,GAAG,KAAK,UAAU,SAAS,CAAC,KAAK;GACvD,UAAU;GACV,MAAM;GACP,CAAC;AACF,aAAW,UAAU,KAAK;UACnB,OAAO;AACd,SAAO,UAAU,EAAE,OAAO,MAAM,CAAC;AACjC,QAAM;;;AAIV,SAAgB,wBAAwB,WAAyB;AAC/D,QAAO,KAAK,WAAW,cAAc,EAAE,EAAE,OAAO,MAAM,CAAC;;AAGzD,eAAsB,+BACpB,WACA,QACA,SACA,WACgC;AAChC,KAAI;AACF,MAAI,CAAE,MAAM,OAAO,SAAS,CAAG,QAAO;EAEtC,MAAM,gBAAgB,IAAI,mBAAmB,UAAU,KAAK;EAC5D,MAAM,YAAmC,EAAE;AAC3C,OAAK,MAAM,OAAO,MAAM,OAAO,WAAW,EAAE;GAC1C,MAAM,UAAU,MAAM,OAAO,WAAW,IAAI;AAC5C,OAAI,CAAC,QAAS,QAAO;GACrB,MAAM,QAAQ,cAAc,IAAI,IAAI;AACpC,OAAI,OAAO;AACT,QACE,MAAM,WACN,CAAC,MAAM,OACP,CAAC,QAAQ,OAAO,OAAO,KAAA,wBAAmC,CAAC,CAE3D,QAAO;AAET,cAAU,KAAK;KACb;KACA,UAAU,MAAM,YAAY;KAC5B,gBAAgB;KAChB,cAAc;KACd,KAAK,MAAM;KACZ,CAAC;AACF;;AAGF,OAAI,QAAQ,OAAO,OAAO,KAAA,wBAAmC,CAAC,CAC5D,QAAO;AACT,aAAU,KAAK;IACb;IACA,UAAU,WAAW,SAAS,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM;IAC5D,gBAAgB;IACjB,CAAC;;AAGJ,SAAO;GACL;GACA;GACA,kBAAkB,cAAc,aAAa;GAC7C;GACD;SACK;AACN,SAAO;;;AAIX,SAAS,cAAc,UAAmC;AACxD,KAAI,CAACK,WAAS,SAAS,IAAI,SAAS,eAAe,iBACjD,OAAM,IAAI,MAAM,oBAAoB,mBAAmB;CAGzD,MAAM,UAAU,SAAS;CACzB,MAAM,YAAY,SAAS;CAC3B,MAAM,mBAAmB,SAAS;CAClC,MAAM,eAAe,SAAS;AAC9B,KAAI,CAAC,kBAAkB,QAAQ,CAAE,OAAM,IAAI,MAAM,mBAAmB;AACpE,KAAI,CAAC,iBAAiB,UAAU,CAAE,OAAM,IAAI,MAAM,qBAAqB;AACvE,KAAI,CAAC,iBAAiB,iBAAiB,CACrC,OAAM,IAAI,MAAM,6BAA6B;AAE/C,KAAI,CAAC,MAAM,QAAQ,aAAa,CAAE,OAAM,IAAI,MAAM,oBAAoB;CAEtE,MAAM,YAAY,aAAa,IAAI,cAAc;AACjD,wBAAuB,UAAU,KAAK,aAAa,SAAS,IAAI,CAAC;AACjE,QAAO;EAAE;EAAS;EAAW;EAAkB;EAAW;;AAG5D,SAAS,cAAc,UAAwC;AAC7D,KAAI,CAACA,WAAS,SAAS,CAAE,OAAM,IAAI,MAAM,mBAAmB;CAE5D,MAAM,MAAM,SAAS;CACrB,MAAM,WAAW,SAAS;CAC1B,MAAM,iBAAiB,SAAS;CAChC,MAAM,eAAe,SAAS;CAC9B,MAAM,aAAa,SAAS;CAC5B,MAAM,MAAM,SAAS;AACrB,KAAI,CAAC,iBAAiB,IAAI,IAAI,IAAI,SAAS,KAAK,CAC9C,OAAM,IAAI,MAAM,uBAAuB;AAEzC,KAAI,aAAa,QAAQ,CAAC,iBAAiB,SAAS,CAClD,OAAM,IAAI,MAAM,4BAA4B;AAE9C,KAAI,OAAO,mBAAmB,UAC5B,OAAM,IAAI,MAAM,kCAAkC;AAEpD,KACE,iBAAiB,KAAA,KACjB,iBAAiB,QACjB,CAAC,iBAAiB,aAAa,CAE/B,OAAM,IAAI,MAAM,wBAAwB;AAE1C,KACE,eAAe,KAAA,KACf,eAAe,QACf,CAAC,kBAAkB,WAAW,CAE9B,OAAM,IAAI,MAAM,sBAAsB;AAExC,KAAI,QAAQ,KAAA,KAAa,QAAQ,QAAQ,CAAC,iBAAiB,IAAI,CAC7D,OAAM,IAAI,MAAM,uBAAuB;AAGzC,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACD;;AAGH,SAASA,WAAS,WAA0D;AAC1E,QAAO,OAAO,cAAc,YAAY,cAAc;;AAGxD,SAAS,iBAAiB,WAAyC;AACjE,QAAO,OAAO,cAAc,YAAY,UAAU,SAAS;;AAG7D,SAAS,kBAAkB,WAAyC;AAClE,QACE,OAAO,cAAc,YACrB,OAAO,UAAU,UAAU,IAC3B,YAAY;;;;;;;;;;;;;;;;;;;;;;ACtLhB,IAAa,aAAb,MAAa,WAAW;CAOtB,aAA0C,KAAA;CAE1C,YACE,WACA,QACA;AAFiB,OAAA,YAAA;AACA,OAAA,SAAA;;;;;;;;;;;CAYnB,aAAa,KAAK,WAAmB,SAAsC;EACzE,MAAM,YAAY,KAAK,WAAW,eAAe;EACjD,MAAM,SAAS,KAAK,WAAW,OAAO;EACtC,MAAM,cAAc,KAAK,WAAW,WAAW;AAM/C,MAAI,WAAW,OAAO;OACL,kBAAkB,YAAY,KAC9B,QACb,QAAO,QAAQ;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;;EAIpD,MAAM,OAAO,IAAI,WAAW,WAAW,OAAO;AAE9C,MAAI,CAAC,WAAW,OAAO,EAAE;AACvB,aAAU,WAAW,EAAE,WAAW,MAAM,CAAC;AACzC,SAAM,KAAK,IAAI;IAAC;IAAQ;IAAU;IAAM;IAAQ;IAAO,EAAE,EACvD,KAAK,WACN,CAAC;;AAMJ,gBAAc,aAAa,GAAG,QAAQ,KAAK,QAAQ;EAQnD,MAAM,eAAe,KAAK,WAAW,aAAa;AAClD,MAAI,CAAC,WAAW,aAAa,CAC3B,eACE,cACA,0EACD;AAEH,QAAM,+BAA+B,UAAU;AAE/C,SAAO;;;CAIT,MAAM,UAA4B;AAChC,MAAI,KAAK,eAAe,KAAA,EAAW,QAAO,KAAK;AAC/C,MAAI;AACF,SAAM,KAAK,IAAI;IAAC;IAAa;IAAY;IAAO,CAAC;AACjD,QAAK,aAAa;UACZ;AACN,QAAK,aAAa;;AAEpB,SAAO,KAAK;;;;CAKd,MAAM,gBAAkC;AACtC,MAAI,CAAE,MAAM,KAAK,SAAS,CAAG,QAAO;EACpC,MAAM,EAAE,WAAW,MAAM,KAAK,IAAI;GAAC;GAAO;GAAM;GAAe,CAAC;AAChE,SAAO,OAAO,SAAS,OAAO,CAAC,MAAM,KAAK;;;;;;;CAQ5C,MAAM,YAA+B;AACnC,MAAI,CAAE,MAAM,KAAK,SAAS,CAAG,QAAO,EAAE;EAWtC,MAAM,EAAE,WAAW,MAAM,KAAK,IAAI;GAChC;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,SAAO,OACJ,SAAS,OAAO,CAChB,MAAM,KAAK,CACX,QAAQ,SAAS,KAAK,SAAS,EAAE;;;;;;;CAQtC,MAAM,WAAW,MAAsC;AACrD,MAAI,CAAE,MAAM,KAAK,SAAS,CAAG,QAAO;AACpC,MAAI;GACF,MAAM,EAAE,WAAW,MAAM,KAAK,IAAI;IAAC;IAAY;IAAM,QAAQ;IAAO,CAAC;AACrE,UAAO;UACD;AACN,UAAO;;;;;;;;CASX,MAAM,UAAU,SAA2C;EACzD,MAAM,MAAM,OAAO,YAAY,WAAW,OAAO,KAAK,QAAQ,GAAG;EACjE,MAAM,EAAE,WAAW,MAAM,KAAK,IAAI;GAAC;GAAe;GAAM;GAAU,EAAE,EAClE,OAAO,KACR,CAAC;AACF,SAAO,OAAO,SAAS,OAAO,CAAC,MAAM;;;;;;;;;;CAWvC,MAAM,YACJ,OACA,SACiB;EACjB,MAAM,YAAY,YAAY,KAAK,QAAQ,EAAE,gBAAgB,CAAC,GAAG;AAEjE,MAAI;AACF,OAAI,MAAM,SAAS,EAGjB,OAAM,KAAK,IAAI;IAAC;IAAgB;IAAM;IAAe,EAAE;IACrD,OAAO,OAAO,KACZ,MAAM,KAAK,EAAE,MAAM,UAAU,UAAU,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK,GAAG,CAClE;IACD,KAAK,EAAE,gBAAgB,WAAW;IACnC,CAAC;GAGJ,MAAM,WACJ,MAAM,KAAK,IAAI,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,gBAAgB,WAAW,EAAE,CAAC,EACtE,OACC,SAAS,OAAO,CAChB,MAAM;GAET,MAAM,SAAU,MAAM,KAAK,SAAS,IAC/B,MAAM,KAAK,IAAI,CAAC,aAAa,OAAO,CAAC,EAAE,OAAO,SAAS,OAAO,CAAC,MAAM,GACtE;GAEJ,MAAM,aAAa;IAAC;IAAe;IAAS;IAAM;IAAQ;AAC1D,OAAI,OAAQ,YAAW,KAAK,MAAM,OAAO;GAEzC,MAAM,aACJ,MAAM,KAAK,IAAI,YAAY,EACzB,KAAK;IACH,iBAAiB;IACjB,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;IACtB,EACF,CAAC,EACF,OACC,SAAS,OAAO,CAChB,MAAM;AAET,SAAM,KAAK,IAAI;IAAC;IAAc;IAAmB;IAAU,CAAC;AAC5D,QAAK,aAAa;AAClB,UAAO;YACC;AAER,OAAI;AACF,WAAO,WAAW,EAAE,OAAO,MAAM,CAAC;AAClC,WAAO,UAAU,UAAU,GAAG,UAAU,SAAS,EAAgB,EAAE;KACjE,WAAW;KACX,OAAO;KACR,CAAC;WACI;;;;;;;;;;;;;;;;;;;;CAuBZ,MAAM,OACJ,MACA,OACA,QACA,OACoD;EACpD,MAAM,MAAM,YAAY,KAAK,QAAQ,EAAE,eAAe,CAAC;EACvD,MAAM,YAAY,KAAK,KAAK,QAAQ;EACpC,MAAM,WAAW,KAAK,KAAK,OAAO;EAClC,MAAM,aAAa,KAAK,KAAK,SAAS;AAEtC,MAAI;AACF,iBAAc,WAAW,MAAM;AAC/B,iBAAc,UAAU,QAAQ,OAAO,MAAM,EAAE,CAAC;AAChD,iBAAc,YAAY,OAAO;AAOjC,OAAI;IACF,MAAM,EAAE,WAAW,MAAM,KAAK,IAAI;KAChC;KACA;KACA,GAAI,UAAU,UACV,CAAC,SAAS,GACV,UAAU,WACR,CAAC,WAAW,GACZ,EAAE;KACR;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACD,CAAC;AACF,WAAO;KAAE,QAAQ;KAAQ,cAAc;KAAO;YACvC,KAAK;IACZ,MAAM,IAAI;IAeV,MAAM,SACJ,EAAE,kBAAkB,SAChB,EAAE,SACF,EAAE,UAAU,OACV,OAAO,KAAK,EAAE,OAAO,GACrB,OAAO,MAAM,EAAE;IACvB,MAAM,kBACJ,OAAO,EAAE,SAAS,YAAY,EAAE,QAAQ,KAAK,EAAE,QAAQ;AAOzD,QAAI,mBAAmB,MACrB,QAAO;KAAE;KAAQ,cAAc;KAAO;AAExC,QAAI,mBAAmB,OAAO,SAAS,EACrC,QAAO;KAAE;KAAQ,cAAc;KAAM;AAEvC,UAAM;;YAEA;AACR,OAAI;AACF,WAAO,KAAK;KAAE,WAAW;KAAM,OAAO;KAAM,CAAC;WACvC;;;;;;;;;;;CAcZ,MAAM,oBACJ,OACA,SACe;EACf,MAAM,UAAgD,EAAE;AACxD,OAAK,MAAM,EAAE,MAAM,aAAa,MAC9B,SAAQ,KAAK;GAAE;GAAM,KAAK,MAAM,KAAK,UAAU,QAAQ;GAAE,CAAC;AAE5D,QAAM,KAAK,YAAY,SAAS,QAAQ;;CAG1C,MAAc,IACZ,MACA,OAII,EAAE,EACuC;EAG7C,MAAM,QAAQ,MAAM,OADlB,KAAK,OAAO,SAAS,OAAO;GAAC;GAAa,KAAK;GAAQ,GAAG;GAAK,EAC5B;GACnC,KAAK,KAAK,OAAO,KAAK;GACtB,KAAK;IAAE,GAAG,QAAQ;IAAK,GAAG,KAAK;IAAK;GACpC,OAAO;IAAC;IAAQ;IAAQ;IAAO;GAChC,CAAC;AAEF,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,SAAmB,EAAE;GAC3B,MAAM,SAAmB,EAAE;AAC3B,SAAM,OAAO,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AAC9D,SAAM,OAAO,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AAC9D,SAAM,GAAG,SAAS,OAAO;AACzB,SAAM,MAAM,GAAG,SAAS,OAAO;AAC/B,SAAM,GAAG,UAAU,SAAS;IAC1B,MAAM,MAAM,OAAO,OAAO,OAAO;IACjC,MAAM,MAAM,OAAO,OAAO,OAAO;AACjC,QAAI,SAAS,EACX,SAAQ;KAAE,QAAQ;KAAK,QAAQ;KAAK,CAAC;SAChC;KACL,MAAM,oBAAI,IAAI,MACZ,OAAO,KAAK,KAAK,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,SAAS,OAAO,GACnE;AACD,OAAE,OAAO,QAAQ;AACjB,OAAE,SAAS;AACX,OAAE,SAAS;AACX,YAAO,EAAE;;KAEX;AACF,SAAM,MAAM,IAAI,KAAK,MAAM;IAC3B;;;;;;;;AASN,SAAgB,YAAY,SAA0B;AAIpD,QADa,QAAQ,SAAS,GAAG,KAAK,IAAI,QAAQ,QAAQ,IAAK,CAAC,CACpD,SAAS,EAAE;;;AAIzB,SAAgB,aAAa,MAA6B;AACxD,KAAI;AACF,SAAO,aAAa,KAAK;SACnB;AACN,SAAO;;;;;;;;AASX,SAAS,kBAAkB,aAAoC;AAC7D,KAAI;EACF,MAAM,SAAS,SAAS,aAAa,aAAa,QAAQ,CAAC,MAAM,EAAE,GAAG;AACtE,SAAO,OAAO,SAAS,OAAO,GAAG,SAAS;SACpC;AACN,SAAO;;;;;;;;;;AAWX,eAAe,+BACb,WACe;AASf,KAAI,CARc,MAAM,IAAI,SAAkB,YAAY;EACxD,MAAM,QAAQ,MAAM,OAAO,CAAC,aAAa,wBAAwB,EAAE;GACjE,KAAK;GACL,OAAO;IAAC;IAAU;IAAQ;IAAO;GAClC,CAAC;AACF,QAAM,GAAG,UAAU,SAAS,QAAQ,SAAS,EAAE,CAAC;AAChD,QAAM,GAAG,eAAe,QAAQ,MAAM,CAAC;GACvC,CACc;CAEhB,MAAM,gBAAgB,KAAK,WAAW,aAAa;CACnD,IAAI,WAAW;AACf,KAAI;AACF,aAAW,aAAa,eAAe,QAAQ;SACzC;AACN,aAAW;;CAGb,MAAM,QAAQ,SAAS,MAAM,KAAK,CAAC,KAAK,SAAS,KAAK,MAAM,CAAC;AAG7D,KAAI,MAAM,SAAS,gBAAgB,IAAI,MAAM,SAAS,eAAe,CACnE;CAGF,MAAM,YACJ,SAAS,WAAW,KAAK,SAAS,SAAS,KAAK,GAAG,KAAK;AAC1D,eACE,eACA,GAAG,WAAW,UAAU,kBACxB,QACD;;;;ACpeH,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,SAAS,WAAW,GAA6B;CAC/C,MAAM,SAAS,EAAE,WAAW,WAAW,IAAI,MAAM,MAAM,WAAW,KAAK;AACvE,QAAO,GAAG,EAAE,KAAK,KAAK,EAAE,GAAG,GAAG;;AAGhC,SAAS,aACP,WACA,SACkB;CAClB,MAAM,UAA4B,UAAU,KAAK,OAAO;EACtD,OAAO,WAAW,EAAE;EACpB,OAAO,EAAE;EACV,EAAE;AACH,KAAI,QACF,SAAQ,KAAK;EACX,OAAO,MAAM,IAAI,yBAAyB;EAC1C,OAAO;EACR,CAAC;AAEJ,QAAO;;AAGT,eAAe,gBACb,KACA,MACA,aAIC;CACD,MAAM,OAAO,MAAMC,sBAA6B,KAAK;EACnD,UAAU;EACV;EACA,GAAI,cAAc,EAAE,cAAc,aAAa,GAAG,EAAE;EACrD,CAAC;AAGF,QAAO;EAAE,QAFI,KAAK,sBAAsB,EAAE;EAEnB,SAAS,QADb,KAAK,MAAM,eAAe;EACM;;AAGrD,eAAsB,YACpB,KACA,SAC2B;CAC3B,MAAM,YAAgC,EAAE;CACxC,IAAI,OAAO;CACX,IAAI,UAAU;CACd,IAAI,eAAe;CAGnB,IAAI,cAAc;CAClB,IAAI,gBAAoC,EAAE;AAE1C,QAAO,MAAM;AACX,MAAI,WAAW,UAAU,SAAS,OAAO,WAAW;GAClD,MAAM,SAAS,MAAM,gBAAgB,KAAK,KAAK;AAC/C,aAAU,KAAK,GAAG,OAAO,OAAO;AAChC,aAAU,OAAO;;AAGnB,MAAI,CAAC,UAAU,QAAQ;AACrB,WAAQ,MAAM,mBAAmB;AACjC,WAAQ,KAAK,EAAE;;EAGjB,MAAM,UAAU,aAAa,WAAW,QAAQ;EAEhD,MAAM,EAAE,OAAO,MAAM,QACnB;GACE,MAAM;GACN,MAAM;GACN;GACA,SAAS;GACT;GACA,SAAS,OAAO,OAAe,YAA8B;AAC3D,QAAI,CAAC,OAAO;AACV,mBAAc;AACd,qBAAgB,EAAE;AAClB,YAAO;;AAGT,QAAI,UAAU,aAAa;AACzB,mBAAc;AACd,SAAI;AAEF,uBADe,MAAM,gBAAgB,KAAK,GAAG,MAAM,EAC5B;aACjB;AACN,sBAAgB,EAAE;;;AAItB,WAAO,cAAc,KAAK,OAAO;KAC/B,OAAO,WAAW,EAAE;KACpB,OAAO,EAAE;KACV,EAAE;;GAEN,EACD,EAAE,gBAAgB,QAAQ,KAAK,IAAI,EAAE,CACtC;AAED,MAAI,OAAO,iBAAiB;AAC1B,kBAAe,UAAU;AACzB;AACA;;AAGF,MAAI,CAAC,IAAI;AACP,WAAQ,MAAM,qBAAqB;AACnC,WAAQ,KAAK,EAAE;;EAIjB,MAAM,QACJ,UAAU,MAAM,MAAM,EAAE,OAAO,GAAG,IAClC,cAAc,MAAM,MAAM,EAAE,OAAO,GAAG;AACxC,MAAI,MAAO,QAAO;AAIlB,UADa,MAAMC,oBAA2B,KAAK,GAAG,EAC1C;;;AAIhB,eAAsB,UACpB,KACA,YAC2B;CAE3B,MAAM,QAAQ,OAAO,WAAW;AAChC,KAAI,OAAO,UAAU,MAAM,IAAI,QAAQ,EACrC,KAAI;EACF,MAAM,OAAO,MAAMA,oBAA2B,KAAK,MAAM;AACzD,MAAI,KAAK,kBAAmB,QAAO,KAAK;SAClC;CAMV,IAAI,OAAO;CACX,IAAI,UAAU;AACd,QAAO,SAAS;EACd,MAAM,SAAS,MAAM,gBAAgB,KAAK,MAAM,WAAW;EAC3D,MAAM,QAAQ,OAAO,OAAO,MACzB,MAAM,EAAE,KAAK,aAAa,KAAK,WAAW,aAAa,CACzD;AACD,MAAI,MAAO,QAAO;AAClB,YAAU,OAAO;AACjB;;AAGF,SAAQ,MAAM,mCAAmC,aAAa;AAC9D,SAAQ,KAAK,EAAE;;;;ACnJjB,MAAM,iBAAiB;;;;;AAMvB,SAAgB,cAAc,UAA0C;CACtE,IAAI,MAAM,QAAQ,YAAY,QAAQ,KAAK,CAAC;AAG5C,QAAO,MAAM;EACX,MAAM,YAAY,KAAK,KAAK,eAAe;AAC3C,MAAI,WAAW,UAAU,CACvB,KAAI;GACF,MAAM,MAAM,aAAa,WAAW,QAAQ;GAC5C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,UAAO;IAAE,MAAM;IAAK;IAAQ;UACtB;AACN,UAAO;;EAGX,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAGR,QAAO;;;;;;;;;;;;AAaT,SAAgB,wBACd,WACe;CACf,MAAM,MAAM,QAAQ,QAAQ,KAAK,CAAC;CAClC,MAAM,WAAW,KAAK,UAAU,MAAM,QAAQ;CAC9C,MAAM,MAAM,SAAS,UAAU,IAAI;AAGnC,KAAI,IAAI,WAAW,KAAK,IAAI,QAAQ,IAAK,QAAO;CAIhD,MAAM,eAAe,IAAI,MAAM,IAAI,CAAC;AACpC,KAAI,CAAC,aAAc,QAAO;AAE1B,QAAO,KAAK,UAAU,aAAa;;;;;AClCrC,SAAgB,yBAAyB,eAAiC;AACxE,QACE,CAAC,iBAAiB,QAAQ,IAAI,2CAA2C;;;;;;;;;;AAY7E,eAAsB,uBACpB,KACA,eACA,MACgE;AAChE,KAAI,kBAAkB,KAAA,EACpB,KAAI;AAMF,SAAO;GAAE,QALI,MAAMC,oCACjB,KACA,eACA,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAChC,EACoB;GAAmB,iBAAiB;GAAM;UACxD,OAAO;AACd,MACE,CAAC,WAAW,MAAM,IACjB,MAAM,WAAW,OAAO,MAAM,WAAW,IAE1C,OAAM;AAGR,UAAQ,KACN,wHACD;;AAOL,QAAO;EAAE,QAHI,MAAMC,uBAA8B,KAAK,EACpD,mBAAmB;GAAE;GAAM,QAAQ;GAAe,EACnD,CAAC,EACmB;EAAmB,iBAAiB;EAAO;;AAGlE,eAAe,eACb,KACA,YACA,YACA,eACgE;AAChE,KAAI,YAAY;EACd,MAAM,QAAQ,MAAM,UAAU,KAAK,WAAW;AAE9C,oBAAkB,MAAM,GAAG;AAC3B,SAAO;GAAE;GAAO,iBAAiB;GAAO;;CAK1C,MAAM,SAAS,YAAY,WAAW;AAItC,KAAI,UAAU,OAAO,kBAAkB,eAAe;AACpD,MAAI;GAEF,MAAM,YADO,MAAMC,oBAA2B,KAAK,OAAO,GAAG,EACvC;AACtB,OAAI,YAAY,SAAS,WAAW,eAAe;AACjD,YAAQ,IAAI,6BAA6B,SAAS,KAAK;AAEvD,gBAAY,YAAY;KACtB,GAAG;KACH,IAAI,SAAS;KACb,MAAM,SAAS;KACf,GAAI,kBAAkB,KAAA,IAAY,EAAE,GAAG,EAAE,eAAe;KACzD,CAAC;AACF,WAAO;KAAE,OAAO;KAAU,iBAAiB;KAAO;;UAE9C;AAIR,gBAAc,WAAW;;CAI3B,MAAM,EAAE,aAAa,MAAM,OAAO;CAQlC,MAAM,WAAW,MAAM,uBAAuB,KAAK,eALjD,gBAFW,UAAU,CAAC,MAAM,IAAI,CAAC,MAAM,MAElB,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,MAChE,GACA,GACD,CAEoE;CACvE,MAAM,EAAE,UAAU;AAMlB,aAAY,YALkB;EAC5B,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,GAAI,kBAAkB,KAAA,IAAY,EAAE,GAAG,EAAE,eAAe;EACzD,CACgC;AACjC,SAAQ,IAAI,sBAAsB,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG;AAC9D,QAAO;;AAGT,SAAgB,mBAA4B;AAC1C,QAAO,IAAI,QAAQ,MAAM,CACtB,YAAY,6CAA6C,CACzD,OAAO,iBAAiB,qBAAqB,YAAY,CACzD,OAAO,iBAAiB,qBAAqB,OAAO,CACpD,OACC,4BACA,6CACD,CACA,OAAO,eAAe,mCAAmC,CACzD,OAAO,wBAAwB,gCAAgC,YAAY,CAC3E,OAAO,cAAc,6CAA6C,CAClE,OAAO,iBAAiB,wBAAwB,IAAI,CACpD,OACC,OAAO,SAQD;AACJ,gBAAc;EAGd,IAAI,WAAW,KAAK;AACpB,MAAI,aAAa,KAAK;GACpB,MAAM,YAAY,eAAe;AACjC,OAAI,UACF,YAAW,wBAAwB,UAAU,IAAI;;EAIrD,MAAM,YAAY,IAAI,UAAU,SAAS;AACzC,MAAI,CAAC,UAAU,SAAS,EAAE;AACxB,WAAQ,MAAM,IAAI,SAAS,yCAAyC;AACpE,WAAQ,KAAK,EAAE;;EAGjB,MAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,MAAI,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,OAAO,OAAO;AACvD,WAAQ,MACN,kBAAkB,KAAK,KAAK,4CAC7B;AACD,WAAQ,KAAK,EAAE;;AAOjB,MAAI;AACF,SAAM,mBAAmB,KAAK,MAAM,KAAK;WAClC,GAAG;AACV,OAAI,aAAa,eACf,SAAQ,MAAM,EAAE,QAAQ;OAExB,SAAQ,MAAM,sCAAsC,IAAI;AAE1D,WAAQ,KAAK,EAAE;;EAGjB,MAAM,aAAa,KAAK,eAAe,QAAQ,QAAQ;EACvD,MAAM,MAAM,iBAAiB;EAC7B,MAAM,SAAS,gBAAgB,UAAU,KAAK;EAG9C,IAAI;AACJ,MAAI,QAAQ,QACV,WAAU,OAAO;OACZ;AAIL,cAHmB,MAAM,IAAI,IAC3B,+BACD,EACoB,MAAM,SAAS,aAAa;AACjD,OAAI,CAAC,SAAS;AACZ,YAAQ,MACN,wEACD;AACD,YAAQ,KAAK,EAAE;;;EAQnB,MAAM,aAAa,YAAY,SAAS,UAAU,KAAK;EACvD,MAAM,YAAY,KAAK,QACnB,MAAM,eAAe,KAAK,YAAY,KAAK,MAAM,GACjD,MAAM,eAAe,KAAK,YAAY,KAAA,GAAW,QAAQ,QAAQ;EACrE,MAAM,EAAE,UAAU;EAClB,MAAM,gBAAgB,IAAI,mBAAmB,UAAU,KAAK;EAC5D,MAAM,kBAAkB,yBAAyB,QAAQ,KAAK,MAAM,CAAC;EACrE,IAAI,qBAAqB,kBACrB,sBACE,UAAU,MACV,MAAM,IACN,cAAc,aAAa,CAC5B,GACD;AACJ,MACE,CAAC,sBACD,mBACA,UAAU,mBACV,QAAQ,WACR,OAAO,QAEP,KAAI;AAKF,wBAAqB,MAAM,+BACzB,WALmB,MAAM,WAAW,KACpC,UAAU,MACV,OAAO,QACR,EAIC,MAAM,IACN,OAAO,QACR;AACD,OAAI,mBACF,wBAAuB,UAAU,MAAM,mBAAmB;UAEtD;AACN,wBAAqB;;EAGzB,MAAM,YAAY,kCAAkC,MAAM,GAAG;EAE7D,IAAI;EAEJ,MAAM,gBAAgB;AACpB,WAAQ;AACR,WAAQ,KAAK,EAAE;;AAEjB,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAE9B,SAAO,MAAM,eACX,KACA;GACE,IAAI,MAAM;GACV,MAAM,MAAM;GACZ;GACA;GACD,EACD,WACA;GACE,MAAM,KAAK;GACX;GACA;GACA,UAAU,CAAC,KAAK;GAChB,GAAI,qBAAqB,EAAE,aAAa,oBAAoB,GAAG,EAAE;GACjE,GAAI,kBACA;IACE,gBAAgB,UACd,uBAAuB,UAAU,MAAM,MAAM;IAC/C,gCACE,wBAAwB,UAAU,KAAK;IAC1C,GACD,EAAE;GACP,GACA,YAAY;AACX,WAAQ,IAAI,mBAAmB,UAAU;AACzC,WAAQ,IAAI,iBAAiB,YAAY;AACzC,WAAQ,IAAI,mCAAmC;AAE/C,OAAI,KAAK,SACF,eAAc,KAAK,MAAM,IAAI,QAAQ,CAAC,OACxC,UAAmB;AAClB,YAAQ,MACN,iBAAiB,QACb,MAAM,UACN,wCACL;KAEJ;IAGN;AAGD,QAAM,IAAI,cAAc,GAAG;GAE9B;;;;;;;;;;;;;;;;ACxSL,eAAsB,uBACpB,KACA,SACA,WACA,MAMgC;CAChC,MAAM,SAAS,IAAI,OAAO,KAAK,SAAS,UAAU;CAClD,MAAM,oBACJ,IAAI,mBAAmB,UAAU,KAAK,CAAC,aAAa;CACtD,MAAM,SAAS;EACb,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,mBAAmB,EAAE,SAAS,MAAM;EACpC,qBAAqB;EACrB,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,YAAY,GAAG,EAAE;EAC3D;CAID,MAAM,QAAQ,sBAAsB,UAAU,MAAM,SAAS,aAAa,CAAC;CAC3E,IAAI,UAAU;AACd,KAAI,MACF,KAAI;AACF,SAAO,kBAAkB,MAAM;AAC/B,QAAM,OAAO,cAAc,MAAM,UAAU;AAC3C,YAAU;UACH,OAAO;AACd,MAAI,EAAE,iBAAiB,mBAAoB,OAAM;;CAGrD,IAAI;AACJ,KAAI;AACF,WACE,WAAW,QACP,MAAM,OAAO,YAAY;GACvB,GAAG;GACH,SAAS,OAAO,WAAW;GAC3B,aAAa;GACb,eAAe;GAChB,CAAC,GACF,MAAM,OAAO,YAAY;GAAE,GAAG;GAAQ,oBAAoB;GAAM,CAAC;UAChE,OAAO;AACd,MAAI,EAAE,iBAAiB,mBAAoB,OAAM;AACjD,0BAAwB,UAAU,KAAK;AACvC,SAAO;GACL,UAAU;GACV,SAAS;GACT,QAAQ;GACR,QAAQ,CACN,2GACD;GACD,kBAAkB;GAClB,UAAU;GACX;;CAGH,MAAM,SAAS,CAAC,GAAG,OAAO,OAAO;AACjC,KAAI,OAAO,oBAAoB,OAAO,SAAS,EAC7C,yBAAwB,UAAU,KAAK;UAC9B,CAAE,MAAM,OAAO,aAAa,CAIrC,QAAO,KACL,uEACD;MACI;EACL,MAAM,QAAQ,OAAO,eAAe,aAAa,CAAC;AAClD,MAAI,MAAO,wBAAuB,UAAU,MAAM,MAAM;;AAG1D,QAAO;EACL,UAAU,OAAO;EACjB,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf;EACA,kBAAkB,OAAO;EACzB,UAAU;EACX;;;;;;;;;;;;;ACzFH,eAAsB,kBACpB,WACA,QACkB;CAClB,MAAM,UAAuB,EAAE;CAC/B,MAAM,UAAoB,EAAE;CAE5B,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,6BAAa,IAAI,KAAwB;CAC/C,MAAM,gBAAgB,IAAI,mBAAmB,UAAU,KAAK;CAC5D,IAAI;AACJ,KAAI;AACF,cAAY,MAAM,OAAO,WAAW;UAC7B,OAAO;AACd,QAAM,IAAI,MAAM,yCAAyC,EAAE,OAAO,OAAO,CAAC;;AAG5E,wBAAuB,CACrB,GAAG,WAAW,KAAK,SAAS,KAAK,aAAa,EAC9C,GAAG,UACJ,CAAC;CACF,MAAM,cAAc,IAAI,IAAI,UAAU;AAEtC,MAAK,MAAM,QAAQ,YAAY;AAC7B,MAAI,CAAC,KAAK,OAAQ;AAClB,aAAW,IAAI,KAAK,cAAc,KAAK;EAEvC,MAAM,WAAW,MAAM,OAAO,WAAW,KAAK,aAAa;AAC3D,MAAI,CAAC,YAAY,YAAY,IAAI,KAAK,aAAa,CACjD,OAAM,IAAI,MACR,0CAA0C,KAAK,eAChD;EAEH,MAAM,WAAW,KAAK,SAAS,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,YAAY;AAC3E,MAAI,YAAY,SAAS,OAAO,SAAS,CAAE;AAE3C,UAAQ,KAAK,KAAK;;AAGpB,MAAK,MAAM,OAAO,WAAW;AAC3B,MAAI,WAAW,IAAI,IAAI,CAAE;AACzB,MAAI,UAAU,OAAO,OAAO,IAAI,CAAE;AAGlC,MAAI,cAAc,IAAI,IAAI,CAAE;AAC5B,MAAI,gBAAgB,IAAI,CAAE;AAC1B,UAAQ,KAAK,IAAI;;AAGnB,QAAO;EAAE;EAAS;EAAS;;;;;;;;;AAU7B,SAAgB,wBAAwB,OAA8B;CACpE,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,OAAQ;EAClB,MAAM,MAAM,aAAa,KAAK,aAAa;AAC3C,MAAI,CAAC,IAAK;AACV,MAAI,uBAAuB,IAAI,CAAE,SAAQ,KAAK,KAAK,aAAa;;AAElE,QAAO;;;;;;;;;;;;AAaT,SAAgB,wBAAwB,OAAyB;AAC/D,QAAO,wDAAwD,MAAM,KAAK,IAAI;;AAGhF,MAAM,iBAAiB,OAAO,KAAK,UAAU;AAC7C,MAAM,eAAe,OAAO,KAAK,UAAU;AAC3C,MAAM,eAAe,OAAO,KAAK,UAAU;;;;;;;AAQ3C,SAAS,uBAAuB,KAAsB;AACpD,QACE,IAAI,SAAS,eAAe,IAC5B,IAAI,SAAS,aAAa,IAC1B,IAAI,SAAS,aAAa;;;;;;;;AAU9B,eAAsB,kBACpB,WACA,QACA,SACA,sBACe;CACf,MAAM,UAAgD,EAAE;CACxD,MAAM,gBAAgB,IAAI,mBAAmB,UAAU,KAAK;CAC5D,MAAM,+BAAe,IAAI,KAAa;AACtC,MAAK,MAAM,CAAC,MAAM,YAAY,IAAI,IAAI,qBAAqB,EAAE;AAC3D,UAAQ,KAAK;GAAE;GAAM,KAAK,MAAM,OAAO,UAAU,QAAQ;GAAE,CAAC;AAC5D,eAAa,IAAI,KAAK;;AAExB,MAAK,MAAM,QAAQ,UAAU,OAAO,EAAE;AACpC,MAAI,CAAC,KAAK,UAAU,aAAa,IAAI,KAAK,aAAa,CAAE;AACzD,eAAa,IAAI,KAAK,aAAa;EAInC,MAAM,MAAM,KAAK,SAAS,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,YAAY;AACtE,UAAQ,KAAK;GAAE,MAAM,KAAK;GAAc,KAAK,MAAM,OAAO,UAAU,IAAI;GAAE,CAAC;;CAE7E,IAAI;AACJ,MAAK,MAAM,OAAO,cAAc,MAAM,EAAE;AACtC,MAAI,aAAa,IAAI,IAAI,CAAE;AAC3B,8BAA4B,MAAM,OAAO,UACvC,8BACD;AACD,UAAQ,KAAK;GACX,MAAM;GACN,KAAK;GACN,CAAC;;AAIJ,KAAI,QAAQ,SAAS,KAAM,MAAM,OAAO,SAAS,CAC/C,OAAM,OAAO,YAAY,SAAS,QAAQ;;;;;;;;AAU9C,eAAsB,yBACpB,QACA,WACA,QACe;AACf,QAAO,oCAAoC;AAC3C,OAAM,kBACJ,WACA,QACA,2BAAU,IAAI,MAAM,EAAC,aAAa,GACnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxIH,eAAsB,uBAAuB,OAMb;CAC9B,MAAM,EAAE,QAAQ,WAAW,QAAQ,aAAa,YAAY;CAC5D,MAAM,SAA6B;EACjC,QAAQ;EACR,UAAU,EAAE;EACZ,YAAY,EAAE;EACd,QAAQ,EAAE;EACX;AAED,KAAI,MAAM,OAAO,SAAS,CAAE,QAAO;CAEnC,MAAM,UAAgD,EAAE;AAExD,MAAK,MAAM,YAAY,QAAQ;EAC7B,MAAM,OAAO,UAAU,KAAK,SAAS,IAAI;AAGzC,MAAI,CAAC,KAAK,aAAa,WAAW,UAAU,OAAO,IAAI,EAAE;AACvD,UAAO,OAAO,KAAK,YAAY,SAAS,IAAI,2BAA2B;AACvE;;AAGF,MAAI,CAAC,KAAK,QAAQ;AAChB,UAAO,WAAW,KAAK,SAAS,IAAI;AACpC;;EAGF,IAAI;AACJ,MAAI,SAAS,YAAY,KAAK,UAAU,KAAK,SAAS,SAEpD,WAAU,KAAK,SAAS,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,YAAY;MAEpE,KAAI;AACF,aAAU,MAAM,kBAAkB,UAAU,YAAY;WACjD,GAAG;AACV,UAAO,OAAO,KACZ,YAAY,SAAS,IAAI,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,GACxE;AACD;;AAGJ,MAAI,WAAW,KAGb;AAGF,UAAQ,KAAK;GAAE,MAAM,SAAS;GAAK,KAAK,MAAM,OAAO,UAAU,QAAQ;GAAE,CAAC;AAC1E,SAAO,SAAS,KAAK,SAAS,IAAI;;AAGpC,KAAI,QAAQ,SAAS,GAAG;AACtB,QAAM,OAAO,YAAY,SAAS,QAAQ;AAC1C,SAAO,SAAS;;AAGlB,QAAO;;AAGT,eAAe,kBACb,UACA,aACwB;AACxB,KAAI,SAAS,kBAAkB,kBAAkB,SAAS,IACxD,QAAO,YAAY,SAAS,IAAI;AAElC,KAAI,SAAS,WAAW,KAAM,QAAO;CACrC,MAAM,OACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,QAAQ;AACtC,QAAO,OAAO,KAAK,KAAK;;;;;;;;;;;;;;;;;;;;;;;AC5G1B,eAAsB,iCAAiC,OAKrC;CAChB,MAAM,EAAE,QAAQ,WAAW,cAAc,YAAY;AAErD,KAAI,MAAM,OAAO,SAAS,CAAE;CAE5B,MAAM,SAAS,sBAAsB,aAAa;AAClD,KAAI,CAAC,OAAQ;AACb,KAAI,OAAO,YAAY,QAAS;AAChC,KAAI,CAAC,OAAO,aAAa,OAAO,KAAK,OAAO,UAAU,CAAC,WAAW,EAAG;CAErE,MAAM,OAAiD,EAAE;AACzD,MAAK,MAAM,QAAQ,UAAU,OAAO,EAAE;AACpC,MAAI,CAAC,KAAK,OAAQ;EAClB,MAAM,SAAS,OAAO,UAAU,KAAK;AACrC,MAAI,CAAC,OAAQ;AACb,MAAI,KAAK,UAAU,KAAK,OAAQ;EAEhC,MAAM,UAAU,KAAK,SAAS,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,KAAK,YAAY;AAC1E,OAAK,KAAK;GAAE,MAAM,KAAK;GAAc;GAAS,CAAC;;AAGjD,KAAI,KAAK,WAAW,EAAG;AAEvB,OAAM,OAAO,oBACX,MACA,oDAAmC,IAAI,MAAM,EAAC,aAAa,GAC5D;;;;;ACpBH,MAAM,eAAyC;CAC7C,MAAM;CACN,MAAM;CACN,wBAAwB;CACxB,wBAAwB;CACzB;;;;;;;;;;AAcD,SAAgB,kBACd,MACA,OACA,uBAAa,IAAI,MAAM,EACvB,UAAkC,EAAE,EAC5B;AAER,QAAO,qBADQ,iBAAiB,QAAQ,IAAI,aAAa,OACrB,OAAO,KAAK;;;;;;;AAQlD,SAAgB,YACd,KACkB;AAClB,QAAO,mBAAmB,IAAI;;;;;;;;AAShC,eAAsB,eACpB,KACA,YAAY,KACe;AAG3B,QAAO,0BAA0B,iBAAiB,KAAK,UAAU,CAAC;;AAGpE,eAAe,iBACb,KACA,WAC2B;AAC3B,KAAI;AAYF,SAAO,YAXK,MAAM,QAAQ,KAAK,CAC7B,IAAI,IAA6B,UAAU,EAC3C,IAAI,SAAgB,GAAG,WAAW;AAClB,oBACN,uBAAO,IAAI,MAAM,mBAAmB,UAAU,IAAI,CAAC,EACzD,UACD,CAEK,SAAS;IACf,CACH,CAAC,CACqB;UAChB,KAAK;EACZ,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,UAAQ,KACN,mEAAmE,OAAO,GAC3E;AACD,SAAO;;;;;ACjEX,SAAgB,oBAA6B;AAC3C,QAAO,IAAI,QAAQ,OAAO,CACvB,YAAY,2CAA2C,CACvD,OAAO,4BAA4B,8BAA8B,CACjE,OAAO,kBAAkB,6CAA6C,CACtE,OACC,eACA,+DACD,CACA,OAAO,iBAAiB,kCAAkC,CAC1D,OACC,qBACA,gDACD,CACA,OACC,mBACA,oLAGD,CACA,OAAO,iBAAiB,wBAAwB,IAAI,CACpD,OACC,OAAO,SAQD;AACJ,gBAAc;EAEd,IAAI,WAAW,KAAK;AACpB,MAAI,aAAa,KAAK;GACpB,MAAM,YAAY,eAAe;AACjC,OAAI,UACF,YAAW,wBAAwB,UAAU,IAAI;;EAIrD,MAAM,YAAY,IAAI,UAAU,SAAS;AACzC,MAAI,CAAC,UAAU,SAAS,EAAE;AACxB,WAAQ,MAAM,IAAI,SAAS,yCAAyC;AACpE,WAAQ,KAAK,EAAE;;EAGjB,MAAM,MAAM,iBAAiB;EAC7B,MAAM,SAAS,gBAAgB,UAAU,KAAK;EAC9C,IAAI;AAEJ,MAAI,KAAK,aAAa;GACpB,MAAM,EAAE,SAAS,MAAM,QACrB;IACE,MAAM;IACN,MAAM;IACN,SAAS;IACV,EACD,EAAE,gBAAgB,QAAQ,KAAK,IAAI,EAAE,CACtC;AACD,OAAI,CAAC,MAAM;AACT,YAAQ,MAAM,0BAA0B;AACxC,YAAQ,KAAK,EAAE;;AAKjB,YAHa,MAAMC,uBAA8B,KAAK,EACpD,mBAAmB;IAAE;IAAM,QAAQ;IAAS,EAC7C,CAAC,EACW;AACb,WAAQ,IACN,8BAA8B,MAAM,KAAK,KAAK,MAAM,GAAG,GACxD;aACQ,KAAK,MACd,SAAQ,MAAM,UAAU,KAAK,KAAK,MAAM;WAC/B,QAAQ;AACjB,WAAQ,IACN,yCAAyC,MAAM,KAAK,OAAO,UAAU,CAAC,KAAK,OAAO,QAAQ,GAC3F;AAED,YADa,MAAMC,oBAA2B,KAAK,OAAO,QAAQ,EACrD;AACb,OAAI,MAAM,WAAW,cACnB,SAAQ,KACN,MAAM,OACJ,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,+PAE/B,CACF;QAGH,SAAQ,MAAM,YAAY,KAAK,4BAA4B;AAG7D,MAAI,UAAU,CAAC,aAAa,QAAQ,MAAM,EAAE;GAI1C,MAAM,MAAM,YAAY,YAAY,OAAO,SAAS,UAAU,KAAK,CAAC;AACpE,OAAI,KAAK,OAAO,MAAM,IAAI;AACxB,YAAQ,MACN,GAAG,MAAM,KAAK,KAAK,MAAM,GAAG,qDACzB,MAAM,cAAc,IAAI,GAAG,KAAK,MACjC,6JAA6J,MAAM,GAAG,mCACzK;AACD,YAAQ,KAAK,EAAE;;GAEjB,MAAM,gBAAgB,wBAAwB,UAAU,OAAO,CAAC;AAChE,OAAI,cAAc,SAAS,GAAG;AAC5B,YAAQ,MACN,KAAK,cAAc,OAAO,sDAAsD,cAAc,KAAK,KAAK,CAAC,gCAC1G;AACD,YAAQ,KAAK,EAAE;;AAEjB,OAAI,KAAK,SAAS;AAChB,YAAQ,MACN,GAAG,MAAM,KAAK,KAAK,MAAM,GAAG,sHAAsH,OAAO,UAAU,KAAK,OAAO,QAAQ,IACxL;AACD,YAAQ,KAAK,EAAE;;GAEjB,MAAM,aAAa,IACjB,gCAAgC,MAAM,KAAK,KAAK,MAAM,GAAG,IAC1D,CAAC,OAAO;GACT,MAAM,SAAS,MAAM,uBACnB,KACA,MAAM,IACN,WACA;IACE,UAAU,CAAC,KAAK;IAEhB,QAAQ,CAAC,KAAK,YAAY,CAAC,KAAK;IAChC,aAAa,MAAM,UAAU;AAC3B,gBAAW,OAAO,WAAW,KAAK,GAAG,MAAM,YAAY,MAAM,KAAK,KAAK,MAAM,GAAG;;IAEnF,CACF;AACD,OAAI,OAAO,iBACT,YAAW,KACT,6BAA6B,OAAO,OAAO,OAAO,kCACnD;YACQ,OAAO,OAAO,SAAS,EAChC,YAAW,KAAK,eAAe,OAAO,OAAO,OAAO,YAAY;OAEhE,YAAW,QACT,UAAU,OAAO,SAAS,oBAAoB,OAAO,QAAQ,0BAA0B,MAAM,GAAG,GACjG;AAEH,QAAK,MAAM,KAAK,OAAO,OAAQ,SAAQ,MAAM,KAAK,IAAI;AACtD,WAAQ,IACN,iCAAiC,OAAO,UAAU,KAAK,OAAO,QAAQ,gCACvE;AACD,OAAI,OAAO,OAAO,SAAS,EAAG,SAAQ,WAAW;AACjD;;EAGF,MAAM,SAAS,MAAM,WAAW,KAAK,UAAU,MAAM,MAAM,GAAG;AAQ9D,QAAM,iCAAiC;GACrC;GACA;GACA,cAAc,UAAU;GACxB,SAAS,MAAM;GAChB,CAAC;EAEF,MAAM,aAAa,UAAU,OAAO,CAAC,QAAQ,MAAM,EAAE,OAAO;EAI5D,IAAI,eAAiD;EACrD,MAAM,iBACH,iBAAiB,eAAe,IAAI;EAQvC,MAAM,aAAa,wBAAwB,WAAW;AACtD,MAAI,WAAW,SAAS,GAAG;AACzB,WAAQ,KAAK;AACb,WAAQ,IACN,MAAM,IACJ,KAAK,WAAW,OAAO,qDACxB,CACF;AACD,QAAK,MAAM,OAAO,WAAY,SAAQ,IAAI,KAAK,MAAM;AACrD,WAAQ,KAAK;AACb,WAAQ,IACN,qCAAqC,MAAM,KAAK,UAAU,CAAC,KAAK,MAAM,KAAK,UAAU,CAAC,YACvF;AACD,WAAQ,IAAI,iBAAiB,MAAM,KAAK,mBAAmB,CAAC,GAAG;AAC/D,WAAQ,KAAK;AACb,OAAI,KAAK,aACP,SAAQ,MAAM,wBAAwB,WAAW,CAAC;AAEpD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,SAAS,IAAI,OAAO,KAAK,MAAM,IAAI,UAAU;EAUnD,MAAM,qBAAqB,QAAQ,YAAY,MAAM;EACrD,IAAI,UACF,KAAK,SAAS,CAAC,qBAAqB,OAAQ,QAAQ,WAAW;EAOjE,MAAM,cAAc,WAAW,QAAS,MAAM,OAAO,SAAS;AAQ9D,MAAI,CAAC,KAAK,SAAS,CAAC,KAAK,eAAe,CAAC,aAAa;AACpD,OAAI,CAAC,KAAK,cAAc;AACtB,YAAQ,OAAO;AACf,YAAQ,MACN,MAAM,IACJ,gCAAgC,MAAM,KAAK,MAAM,MAAM,GAAG,IAC3D,CACF;AACD,YAAQ,OAAO;AACf,YAAQ,MACN,SAAS,MAAM,KAAK,uBAAuB,MAAM,KAAK,CAAC,+CACxD;AACD,YAAQ,MACN,wEACD;AACD,YAAQ,OAAO;AACf,YAAQ,MACN,oBAAoB,MAAM,KAAK,kBAAkB,CAAC,uCACnD;AACD,YAAQ,MACN,yFACD;AACD,YAAQ,OAAO;AACf,YAAQ,MACN,iFACD;AACD,YAAQ,MACN,qCAAqC,MAAM,KAAK,UAAU,CAAC,GAC5D;AACD,YAAQ,OAAO;AACf,YAAQ,KAAK,EAAE;;GAQjB,MAAM,kBAAkB,IACtB,0DAA0D,MAAM,KAAK,KAAK,MAAM,GAAG,IACpF,CAAC,OAAO;AACT,OAAI;IAEF,MAAM,aAAa,MAAM,uBAAuB;KAC9C;KACA;KACA,QAJgB,MAAM,OAAO,aAAa;KAK1C,cAAc,QAAQ,OAAO,oBAAoB,IAAI;KACrD,SAAS,kBACP,wBACA,MAAM,UAAU,CACjB;KACF,CAAC;AACF,cAAU,OAAO,WAAW,IAAI;IAChC,MAAM,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,UAAU;AAChE,QAAI,WAAW,WAAW,SAAS,EACjC,OAAM,KACJ,QAAQ,WAAW,WAAW,OAAO,gCACtC;AAEH,oBAAgB,QAAQ,uBAAuB,MAAM,KAAK,KAAK,CAAC,GAAG;AACnE,SAAK,MAAM,OAAO,WAAW,OAC3B,SAAQ,KAAK,KAAK,MAAM,OAAO,OAAO,CAAC,GAAG,MAAM;YAE3C,GAAG;AACV,oBAAgB,KACd,gDAAgD,YAAY,EAAE,GAC/D;AACD,YAAQ,KAAK,EAAE;;;EAMnB,MAAM,EAAE,SAAS,YAAY,MAAM,kBAAkB,WAAW,OAAO;AACvE,MAAI,CAAC,KAAK,SAAU,mCAAkC,QAAQ;EAC9D,MAAM,oBAAoB,IAAI,mBAAmB,UAAU,KAAK,CAAC,MAAM,CACpE;AAEH,MACE,QAAQ,WAAW,KACnB,QAAQ,WAAW,KACnB,sBAAsB,GACtB;AACA,WAAQ,IAAI,yDAAyD;AACrE,SAAM,eAAe;AACrB;;EAGF,MAAM,UAAU,IAAI,cAAc,MAAM,KAAK,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO;AAIvE,MAAI,CAAC,KAAK,OAAO;GACf,MAAM,mBAA6B,EAAE;AACrC,QAAK,MAAM,QAAQ,SAAS;AAC1B,QAAI,CAAC,KAAK,SAAU;AACpB,SAAK,MAAM,cAAc,KAAK,gBAAgB,CAC5C,KAAI,WAAW,aAAa,QAC1B,kBAAiB,KACf,GAAG,KAAK,aAAa,IAAI,WAAW,UACrC;AAGL,SAAK,MAAM,cAAc,8BACvB,KAAK,MAAM,CACZ,CACC,kBAAiB,KACf,GAAG,KAAK,aAAa,IAAI,WAAW,UACrC;;AAGL,OAAI,iBAAiB,SAAS,GAAG;AAC/B,YAAQ,KACN,6BAA6B,iBAAiB,OAAO,kCACtD;AACD,SAAK,MAAM,SAAS,iBAAkB,SAAQ,MAAM,KAAK,QAAQ;AACjE,YAAQ,KAAK,EAAE;;;AAGnB,MAAI;AACF,SAAM,OAAO,cAAc,QAAQ;AACnC,aAAU,OAAO,WAAW,IAAI;WACzB,GAAG;AACV,OAAI,aAAa,mBAAmB;AAClC,oBAAgB,QAAQ;AACxB,YAAQ,KAAK,EAAE;;AAEjB,SAAM;;EAGR,MAAM,UAAU,iBACd,WACA,SACA,KAAK,WAAW,EAAE,GAAG,QACtB;EACD,IAAI,SAAS;AACb,MAAI;AACF,YAAS,MAAM,OAAO,kBAAkB,EAAE,SAAS,MAAM,CAAC;AAI1D,aAAU,OAAO,WAAW,IAAI;WACzB,OAAO;AACd,WAAQ,KACN,2CAA2C,YAAY,MAAM,GAC9D;AACD,WAAQ,KAAK,EAAE;;AAGjB,MAAI,QAAQ,WAAW,KAAK,QAAQ,WAAW,KAAK,WAAW,GAAG;AAChE,OAAI;AACF,UAAM,yBAAyB,QAAQ,WAAW,OAAO;YAClD,OAAO;AACd,YAAQ,KACN,+CAA+C,YAAY,MAAM,GAClE;AACD,YAAQ,KAAK,EAAE;;AAEjB,WAAQ,QAAQ,oDAAoD;AACpE,SAAM,eAAe;AACrB;;EAGF,IAAI,WAAW;EACf,IAAI,eAAe;EACnB,MAAM,SAAmB,EAAE;EAC3B,IAAI,WAAW;EACf,MAAM,QACJ,QAAQ,QACL,OAAO,WACN,QAAQ,KAAK,OAAO,qBAAqB,MAAM,UAAU,IAC3D,EACD,IAAI,KAAK,WAAW,IAAI,QAAQ;AAEnC,OAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,EAAE,SAAS;AACjB,OACE,KAAK,iBAAA,wBACL,OAAO,SAAS,GAChB;AACA,WAAO,KACL,iEACD;AACD;;AAEF,OAAI;AACF,QAAI,OAAO,oBACT,OAAM,OAAO,0BAA0B,QAAQ,QAAQ;QAEvD,OAAM,OAAO,WAAW,MAAM,QAAQ;AAExC,cAAU,OAAO,WAAW,IAAI;AAChC,gBAAY,KAAK,OAAO,qBAAqB,MAAM,UAAU;YACtD,GAAG;AACV,QAAI,aAAa,mBAAmB;AAClC,aAAQ,MAAM;AACd,qBAAgB,KAAK,CAAC;AACtB,aAAQ,KAAK,EAAE;;AAEjB,WAAO,KAAK,UAAU,KAAK,aAAa,IAAI,YAAY,EAAE,GAAG;;AAE/D,eAAY,KAAK,OAAO,qBAAqB,MAAM,UAAU;AAC7D,WAAQ,OAAO,WAAW,SAAS,GAAG,MAAM;;AAG9C,MAAI,CAAC,KAAK,YAAY,OAAO,WAAW,EACtC,MAAK,MAAM,OAAO,SAAS;AACzB,OAAI;AACF,UAAM,OAAO,iBAAiB,KAAK,QAAQ;AAC3C,cAAU,OAAO,WAAW,IAAI;AAChC;YACO,GAAG;AACV,QAAI,aAAa,mBAAmB;AAClC,aAAQ,MAAM;AACd,qBAAgB,KAAK,CAAC;AACtB,aAAQ,KAAK,EAAE;;AAEjB,WAAO,KAAK,UAAU,IAAI,IAAI,YAAY,EAAE,GAAG;;AAEjD,WAAQ,OAAO,WAAW,EAAE,SAAS,GAAG,MAAM;;AAIlD,MAAI,OAAO,WAAW,EACpB,KAAI;AAGF,UAAO,oCAAoC;WACpC,OAAO;AACd,UAAO,KAAK,0BAA0B,YAAY,MAAM,GAAG;;AAY/D,MACE,OAAO,WAAW,MACjB,WAAW,KAAK,eAAe,KAAK,SAAS;OAE1C,CAAE,MAAM,OAAO,aAAa,CAC9B,QAAO,KACL,4EACD;;AAIL,MAAI,OAAO,QAAQ;AACjB,WAAQ,KAAK,eAAe,OAAO,OAAO,YAAY;AACtD,QAAK,MAAM,OAAO,OAAQ,SAAQ,MAAM,KAAK,MAAM;AASnD,WAAQ,WAAW;QAEnB,SAAQ,QACN,UAAU,SAAS,kBAAkB,OAAO,+BACzC,eAAe,IACZ,aAAa,aAAa,oBAC1B,KACP;AASH,MAAI,OAAO,WAAW,GAAG;GAKvB,MAAM,gBAA+B,CACnC,GAAG,QAAQ,KAAK,UAAU;IACxB,QAAQ;IACR,MAAM,KAAK;IACZ,EAAE,EACH,GAAI,KAAK,WACL,EAAE,GACF,QAAQ,KAAK,SAAS;IACpB,QAAQ;IACR,MAAM;IACP,EAAE,CACR;AACD,OAAI;AACF,UAAM,kBACJ,WACA,QACA,kBACE,QACA,MAAM,UAAU,kBAChB,IAAI,MAAM,EACV,cACD,EACD,OAAO,4BAA4B,CACpC;YACM,OAAO;AACd,YAAQ,MACN,+GAA+G,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,+GACvK;AACD,QAAI,KAAK,QAAS,SAAQ,MAAM,gCAAgC;AAChE,YAAQ,WAAW;AACnB;;;AAIJ,QAAM,eAAe;;;;;;;;;EAUrB,eAAe,gBAA+B;AAC5C,OAAI,QAAQ;AACV,qBAAiB,UAAU,MAAM;KAC/B,SAAS,MAAM;KACf,WAAW,MAAM;KACjB,SAAS,OAAO;KAChB,SAAS,WAAW,KAAA;KACpB,kBAAkB,IAAI,mBACpB,UAAU,KACX,CAAC,YAAY,EAAE,gBAAgB,MAAM,CAAC;KACxC,CAAC;AACF;;AAEF,OAAI,CAAC,KAAK,aAAc;AACxB,OAAI;IAIF,MAAM,aAHM,MAAM,IAAI,IAEnB,+BAA+B,EACZ,MAAM,SAAS;AACrC,QAAI,CAAC,UAAW;AAChB,qBAAiB,UAAU,MAAM;KAC/B,SAAS,MAAM;KACf,WAAW,MAAM;KACjB,SAAS;KACT,SAAS,WAAW,KAAA;KACpB,kBAAkB,IAAI,mBACpB,UAAU,KACX,CAAC,YAAY,EAAE,gBAAgB,MAAM,CAAC;KACxC,CAAC;WACI;;AAMV,MAAI,KAAK,WAAW,OAAO,WAAW,GAAG;GACvC,MAAM,aAAa,IAAI,oBAAoB,CAAC,OAAO;AACnD,OAAI;AACF,UAAMC,wBAA+B,KAAK,MAAM,GAAG;AACnD,eAAW,QAAQ,mBAAmB;YAC/B,GAAG;AACV,eAAW,KAAK,mBAAmB,IAAI;AAGvC,YAAQ,WAAW;;;GAI1B;;AAGL,SAAS,gBAAgB,SAAuC;AAC9D,SAAQ,KAAK,yDAAyD;AACtE,SAAQ,KAAK;AACb,SAAQ,IACN,KAAK,MAAM,KAAK,+BAA+B,CAAC,+BACjD;AACD,SAAQ,IACN,2BAA2B,MAAM,KAAK,2BAA2B,CAAC,uBACnE;AACD,SAAQ,KAAK;;;;AChpBf,SAAS,gBAAgB,WAA4B;AACnD,QAAO,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,CAAC,MAAM,UAAU;AACrE,MAAI,MAAM,KAAK,WAAW,IAAI,IAAI,MAAM,SAAS,eAC/C,QAAO;AACT,SACE,MAAM,gBAAgB,IACrB,MAAM,aAAa,IAAI,gBAAgB,KAAK,WAAW,MAAM,KAAK,CAAC;GAEtE;;;;AAKJ,eAAsB,yBACpB,MACA,SACkB;AAClB,KAAI;AACF,MAAI,gBAAgB,KAAK,CAAE,QAAO;EAClC,MAAM,SAAS,gBAAgB,KAAK;AACpC,MACE,CAAC,UACD,OAAO,YAAY,WACnB,CAAC,WAAW,KAAK,MAAM,gBAAgB,QAAQ,OAAO,CAAC,CAEvD,QAAO;AAKT,MAJiB,aACf,KAAK,MAAM,gBAAgB,WAAW,EACtC,OACD,CAAC,MAAM,KACS,OAAO,QAAQ,CAAE,QAAO;EACzC,MAAM,eAAe,KAAK,MAAM,qBAAqB;AACrD,MAAI,WAAW,aAAa,EAAE;GAC5B,MAAM,MAAe,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AACnE,OACE,CAAC,OACD,OAAO,QAAQ,YACf,EAAE,aAAa,QACf,IAAI,YAAY,KAChB,EAAE,YAAY,QACd,CAAC,IAAI,UACL,OAAO,IAAI,WAAW,YACtB,MAAM,QAAQ,IAAI,OAAO,CAEzB,QAAO;GACT,MAAM,WAAW,IAAI,mBAAmB,KAAK;AAC7C,OAAI,SAAS,SAAS,CAAC,MAAM,GAAG,UAAU,KAAK,QAAQ,CAAE,QAAO;AAChE,OACE,CAAC,OAAO,oBACR,OAAO,qBAAqB,SAAS,aAAa,CAElD,QAAO;aAET,OAAO,oBACP,OAAO,qBAAqB,IAAI,mBAAmB,KAAK,CAAC,aAAa,CAEtE,QAAO;EACT,MAAM,SAAS,MAAM,WAAW,KAAK,MAAM,QAAQ;AACnD,MAAI,CAAE,MAAM,OAAO,eAAe,CAAG,QAAO;EAC5C,MAAM,OAAO,MAAM,kBAAkB,IAAI,UAAU,KAAK,EAAE,OAAO;AACjE,SAAO,KAAK,QAAQ,WAAW,KAAK,KAAK,QAAQ,WAAW;SACtD;AACN,SAAO;;;;;;;ACxDX,SAAgB,uBACd,OACa;AACb,KACE,MAAM,cAAc,IAAA,qBAA6B,IACjD,CAAC,MAAM,QAAA,qBAAiC,CAExC,wBAAO,IAAI,KAAK;CAElB,MAAM,iCAAiB,IAAI,KAAa;AACxC,MAAK,MAAM,WAAW,MAAM,mBAAmB;AAC7C,MAAI,YAAY,KAAM;AACtB,iBAAe,IAAI,yBAAyB;AAC5C,MAAI,QAAQ,aAAa,IAAI,OAAO,KAClC,OAAM,IAAI,MACR,iEACD;EAEH,MAAM,WAAoB,KAAK,MAAM,QAAQ,SAAS,OAAO,CAAC;AAC9D,MAAI,CAAC,SAAS,SAAS,IAAI,SAAS,YAAY,EAC9C,OAAM,IAAI,MACR,iFACD;AAEH,OAAK,MAAM,SAAS;GAAC;GAAS;GAAS;GAAkB,CACvD,KAAI,CAAC,MAAM,QAAQ,SAAS,OAAO,CACjC,OAAM,IAAI,MACR,oDAAoD,MAAM,oBAC3D;AAGL,wBAAsB,SAAS;AAG/B,MAAK,SAAS,MAAoB,SAAS;QACpC,MAAM,QAAQ,MAAM,eACvB,KAAI,yDAAyD,KAAK,KAAK,CACrE,gBAAe,IAAI,KAAK;;AAG9B,OAAK,MAAM,QAAQ,sBAAsB,UAAU,MAAM,eAAe,EAAE;AACxE,OACE,CAAC,mBAAmB,KAAK,IACzB,KAAK,SAAS,KAAK,IACnB,CAAC,GAAG,KAAK,CAAC,MAAM,cAAc,UAAU,WAAW,EAAE,GAAG,GAAG,IAC3D,SAAS,4BAET,OAAM,IAAI,MACR,yDAAyD,KAAK,GAC/D;AAEH,kBAAe,IAAI,KAAK;;;AAG5B,QAAO,IAAI,IACT,CAAC,GAAG,eAAe,CAAC,QACjB,SAAS,CAAC,MAAM,cAAc,IAAI,KAAK,IAAI,MAAM,QAAQ,KAAK,CAChE,CACF;;AAGH,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;;AAI7E,SAAS,sBAAsB,UAAyC;CACtE,MAAM,WAAW,UAA8C;AAC7D,MAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,MAAM,SAAS,CACjD,OAAM,IAAI,MACR,kEACD;AAEH,SAAO;;AAET,MAAK,MAAM,SAAS,CAClB,GAAG,QAAQ,SAAS,aAAa,EAAE,CAAC,EACpC,GAAG,QAAQ,SAAS,gBAAgB,CACrC,CACC,KAAI,OAAO,MAAM,SAAS,YAAY,CAAC,MAAM,KAC3C,OAAM,IAAI,MACR,4DACD;AAGL,MAAK,MAAM,QAAQ,QAAQ,SAAS,MAAM,EAAE;AAC1C,MAAI,KAAK,gBAAgB,QAAQ,OAAO,KAAK,gBAAgB,SAC3D,OAAM,IAAI,MAAM,qDAAqD;AAEvE,OAAK,MAAM,QAAQ,QAAQ,KAAK,MAAM,CACpC,KACE,KAAK,kBAAkB,QACvB,OAAO,KAAK,kBAAkB,SAE9B,OAAM,IAAI,MACR,4DACD;;;;;;ACvFT,MAAM,oBAAoB;;;;;;;;AAS1B,MAAM,gBACJ;;;;;;;;;;;;;;;AA4EF,eAAsB,UACpB,OAC0B;CAC1B,MAAM,EAAE,WAAW,QAAQ,QAAQ,aAAa,eAAe;CAC/D,MAAM,WAAW,MAAM;CACvB,MAAM,SAA0B;EAC9B,SAAS;EACT,QAAQ;EACR,WAAW,EAAE;EACb,cAAc,EAAE;EAChB,SAAS;EACT,SAAS;EACT,QAAQ,EAAE;EACX;CAMD,MAAM,gBAMD,EAAE;CAQP,MAAM,gCAAgB,IAAI,KAAa;CACvC,MAAM,YAAY,KAAa,MAAiB,YAA6B;AAC3E,MAAI;AACF,OAAI,MAAM,gBACR,OAAM,gBAAgB,OAAO,CAAC,IAAI,QAAQ,KAAK,MAAM,QAAQ,CAAC;OAC3D,MAAK,MAAM,QAAQ;AACxB,UAAO;WACA,GAAG;AACV,OAAI,aAAa,2BAA4B,OAAM;AACnD,iBAAc,IAAI,IAAI;AACtB,UAAO,OAAO,KAAK,aAAa,IAAI,IAAI,OAAO,EAAE,CAAC,IAAI,gBAAgB;AACtE,UAAO;;;;;;;;;;CAUX,MAAM,SACJ,KACA,MACA,SACA,QACA,mBAAmB,UACV;AACT,MAAI,MAAM,SAAS;AACjB,iBAAc,KAAK;IAAE;IAAK;IAAM;IAAS;IAAQ;IAAkB,CAAC;AACpE;;AAEF,MAAI,SAAS,KAAK,MAAM,QAAQ,CAAE,SAAQ;;CAG5C,MAAM,gCAAgB,IAAI,KAAqB;CAC/C,MAAM,6BAAa,IAAI,KAAa;CACpC,IAAI,OAAO;AACX,MAAK,MAAM,YAAY,QAAQ;AAC7B,aAAW,IAAI,SAAS,IAAI;AAC5B,MAAI,MAAM,gBAAgB,IAAI,SAAS,IAAI,EAAE;AAC3C,gBAAa,EAAE,MAAM,OAAO,OAAO;AACnC;;AAEF,MAAI;GACF,MAAM,MAAM,MAAM,YAAY,UAAU,YAAY;AACpD,OAAI,IAAK,eAAc,IAAI,SAAS,KAAK,IAAI;WACtC,GAAG;AACV,UAAO,OAAO,KAAK,YAAY,SAAS,IAAI,IAAI,OAAO,EAAE,GAAG;;AAE9D,eAAa,EAAE,MAAM,OAAO,OAAO;;AAGrC,OAAM,iBAAiB,iBAAiB;CACxC,IAAI;AACJ,KAAI;AACF,MACE,WAAW,IAAA,qBAA6B,IACxC,CAAC,UAAU,OAAO,OAAA,qBAAgC,IAClD,CAAC,cAAc,IAAA,qBAA6B,CAE5C,OAAM,IAAI,MACR,6FACD;AAEH,wBAAsB,uBAAuB;GAC3C,mBAAmB,CACjB,aAAa,UAAU,KAAK,yBAAyB,CAAC,aAAa,EACnE,MAAM,OAAO,WAAW,yBAAyB,CAClD;GACD,gBAAgB,IAAI,IAAI,CACtB,GAAG,UAAU,OAAO,CAAC,KAAK,SAAS,KAAK,aAAa,EACrD,GAAI,MAAM,OAAO,WAAW,CAC7B,CAAC;GACF,eAAe;GACf,UAAU,SAAS,UAAU,OAAO,OAAO,KAAK;GACjD,CAAC;UACK,OAAO;AACd,SAAO,OAAO,KAAK,OAAO,MAAM,CAAC;AACjC,SAAO,oBAAoB;AAC3B,SAAO;;AAET,MAAK,MAAM,QAAQ,oBAAqB,eAAc,OAAO,KAAK;AAElE,MAAK,MAAM,CAAC,KAAK,cAAc,eAAe;EAC5C,MAAM,OAAO,UAAU,KAAK,IAAI;AAEhC,MAAI,CAAC,KAAK,aAAa,WAAW,UAAU,OAAO,IAAI,EAAE;AACvD,UAAO,OAAO,KAAK,aAAa,IAAI,2BAA2B;AAC/D,iBAAc,IAAI,IAAI;AACtB;;AAGF,MAAI,MAAM,OAAO;AAGf,SAAM,KAAK,MAAM,iBAAiB,OAAO,UAAU;AACnD;;EAGF,MAAM,WAAW,aAAa,KAAK,aAAa;EAChD,MAAM,UAAU,MAAM,OAAO,WAAW,IAAI;AAE5C,MAAI,YAAY,MAAM;AACpB,SAAM,KAAK,MAAM,iBAAiB,OAAO,UAAU;AACnD;;AAGF,MAAI,SAAS,OAAO,UAAU,EAAE;AAC9B,UAAO;AACP;;AAGF,MAAI,WAAW,SAAS,OAAO,QAAQ,EAAE;AACvC,SAAM,KAAK,MAAM,iBAAiB,OAAO,UAAU;AACnD;;AAGF,MAAI,WAAW,UAAU,OAAO,QAAQ,EAAE;AAMxC,OACE,MAAM,YAAY,YAClB,CAAC,YAAY,SAAS,IACtB,4BAA4B,SAAS,EACrC;AACA,UACE,KACA,MACA,iBACM,OAAO,aAAa,KAAK,GAAG,IAAI,6BAA6B,EACnE,kBACD;AACD;;AAGF,UAAO;AACP;;AAQF,MAJE,YAAY,SAAS,IACrB,YAAY,UAAU,KACrB,UAAU,YAAY,QAAQ,GAAG,QAEtB;AAEZ,OAAI,MAAM,YAAY,SAAS;AAE7B,WAAO,aAAa,KAAK,GAAG,IAAI,wBAAwB;AACxD;;AAKF,SACE,KACA,MACA,iBACM;AACJ,QAAI,MAAM,YAAY,SACpB,QAAO,aAAa,KAAK,GAAG,IAAI,yBAAyB;QAEzD,QAAO,UAAU,KAAK,GAAG,IAAI,yBAAyB;MAG1D,MAAM,YAAY,SACnB;AACD;;EAMF,MAAM,EAAE,QAAQ,iBAAiB,MAAM,OAAO,OAC5C,SACA,UACA,UACD;AACD,MAAI,gBAAgB,MAAM,SAAS;AAOjC,SACE,KACA,OARc,MAAM,OAAO,OAC3B,SACA,UACA,WACA,MAAM,QACP,EAIS,cACF,OAAO,aAAa,KAAK,IAAI,EACnC,kBACD;AACD;;AAEF,QAAM,KAAK,MAAM,cAAc;AAC7B,OAAI,aAAc,QAAO,UAAU,KAAK,IAAI;OACvC,QAAO;IACZ;;AAMJ,KAAI,MAAM,SAAS;AACjB,MAAI,cAAc,MAAM,MAAM,EAAE,iBAAiB,CAC/C,OAAM,kBACJ,WACA,QACA,kBAAkB,wBAAwB,MAAM,SAAS,KAAK,CAC/D;AAEH,QAAM,iBAAiB,iBAAiB;AACxC,OAAK,MAAM,EAAE,KAAK,MAAM,SAAS,YAAY,cAC3C,KAAI,SAAS,KAAK,MAAM,QAAQ,CAAE,SAAQ;;AAI9C,KAAI,YAAa,MAAM,OAAO,SAAS,CACrC,MAAK,MAAM,QAAQ,UAAU,OAAO,EAAE;AACpC,MACE,WAAW,IAAI,KAAK,aAAa,IACjC,oBAAoB,IAAI,KAAK,aAAa,CAE1C;AAIF,MAAI,gBAAgB,KAAK,aAAa,CAAE;EAExC,MAAM,UAAU,MAAM,OAAO,WAAW,KAAK,aAAa;AAC1D,MAAI,CAAC,QAAS;EACd,MAAM,WAAW,aAAa,KAAK,aAAa;AAChD,MAAI,CAAC,SAAU;AACf,MAAI,CAAC,SAAS,OAAO,QAAQ,CAAE;AAE/B,MAAI;AACF,OAAI,MAAM,gBACR,OAAM,gBAAgB,OAAO,CAAC,KAAK,aAAa,QAC9C,WAAW,KAAK,aAAa,CAC9B;OACE,YAAW,KAAK,aAAa;AAClC,UAAO;WACA,OAAO;AACd,OAAI,iBAAiB,2BAA4B,OAAM;;;AAsB7D,KAAI;EACF,MAAM,gBAAsD,EAAE;AAC9D,OAAK,MAAM,CAAC,KAAK,QAAQ,eAAe;AACtC,OAAI,cAAc,IAAI,IAAI,CAAE;AAC5B,iBAAc,KAAK;IAAE,MAAM;IAAK,KAAK,MAAM,OAAO,UAAU,IAAI;IAAE,CAAC;;AAErE,OAAK,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,YAAY,GAAG,oBAAoB,CAAC,EAAE;AAClE,OAAI,MAAM,gBAAgB,IAAI,IAAI,IAAI,CAAC,oBAAoB,IAAI,IAAI,CACjE;AAEF,OAAI,cAAc,IAAI,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAE;GACvD,MAAM,WAAW,MAAM,OAAO,WAAW,IAAI;AAC7C,OAAI,YAAY,KAAM;AACtB,iBAAc,KAAK;IAAE,MAAM;IAAK,KAAK,MAAM,OAAO,UAAU,SAAS;IAAE,CAAC;;EAE1E,MAAM,gBAAgB,IAAI,mBAAmB,UAAU,KAAK;EAC5D,IAAI;AACJ,OAAK,MAAM,OAAO,MAAM,kBAAkB,EAAE,EAAE;AAI5C,OAAI,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAE;AACrD,+BAA4B,MAAM,OAAO,UACvC,8BACD;AACD,iBAAc,KAAK;IACjB,MAAM;IACN,KAAK;IACN,CAAC;;AASJ,MACE,cAAc,SAAS,KACtB,WAAW,SAAS,KAAM,MAAM,OAAO,SAAS,EACjD;AACA,SAAM,iBAAiB,iBAAiB;AACxC,SAAM,OAAO,YACX,eACA,kBAAkB,QAAQ,MAAM,SAAS,KAAK,CAC/C;;UAEI,OAAO;AACd,MAAI,iBAAiB,2BAA4B,OAAM;AACvD,SAAO,oBAAoB;AAC3B,SAAO,OAAO,KACZ,2HAA2H,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,+GACnL;;AAGH,QAAO;;AAGT,SAAS,4BAA4B,SAA0B;CAC7D,MAAM,OAAO,QAAQ,SAAS,OAAO;AACrC,QACE,sBAAsB,KAAK,KAAK,IAChC,gBAAgB,KAAK,KAAK,IAC1B,uBAAuB,KAAK,KAAK;;AAIrC,eAAe,YACb,UACA,aACwB;AACxB,KAAI,SAAS,kBAAkB,kBAAkB,SAAS,IACxD,QAAO,YAAY,SAAS,IAAI;AAElC,KAAI,SAAS,WAAW,KAAM,QAAO;CACrC,MAAM,OACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,QAAQ;AACtC,QAAO,OAAO,KAAK,KAAK;;AAG1B,SAAS,OAAO,GAAoB;AAClC,QAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;;;;AC9cnD,eAAe,sBACb,KACiB;CAEjB,MAAM,aADM,MAAM,IAAI,IAAe,+BAA+B,EAC9C,MAAM,SAAS;AACrC,KAAI,CAAC,WAAW;AACd,UAAQ,MACN,wEACD;AACD,UAAQ,KAAK,EAAE;;AAEjB,QAAO;;AAGT,SAAgB,oBAA6B;AAC3C,QAAO,IAAI,QAAQ,OAAO,CACvB,YAAY,8CAA8C,CAC1D,OAAO,4BAA4B,2BAA2B,CAC9D,OAAO,kBAAkB,8CAA8C,CACvE,OAAO,iBAAiB,uBAAuB,CAC/C,OAAO,aAAa,2BAA2B,CAC/C,OACC,mBACA,gEACD,CACA,OACC,eACA,0DACD,CACA,OACC,oBACA,uKAGD,CACA,OACC,OAAO,SAQD;EACJ,IAAI;AACJ,MAAI;AACF,iBAAc;AAEd,OACE,KAAK,YAAY,KAAA,KACjB,KAAK,YAAY,WACjB,KAAK,YAAY,UACjB;AACA,YAAQ,MACN,4BAA4B,KAAK,QAAQ,8BAC1C;AACD,YAAQ,KAAK,EAAE;;GAEjB,MAAM,cAAc,KAAK;GAEzB,MAAM,MAAM,iBAAiB;GAC7B,MAAM,YAAY,eAAe;GAEjC,MAAM,QAAQ,KAAK,QACf,MAAM,UAAU,KAAK,KAAK,MAAM,GAChC,MAAM,YAAY,KAAK,yBAAyB;GAEpD,MAAM,YAAY,MAAM,sBAAsB,IAAI;GAClD,IAAI;AACJ,OAAI,KAAK,KACP,QAAO,KAAK;YACH,UACT,QACE,wBAAwB,UAAU,IAClC,KAAK,UAAU,MAAM,SAAS,UAAU;OAE1C,QAAO;GAGT,MAAM,eAAe,QAAQ,KAAK;GAClC,MAAM,iBAAiB,gBAAgB,aAAa;AACpD,OAAI,kBAAkB,CAAC,aAAa,gBAAgB,MAAM,EAAE;AAG1D,QAAI,KAAK,aAAa;AACpB,aAAQ,IACN,uCAAuC,eAAe,UAAU,KAAK,eAAe,QAAQ,4BAA4B,MAAM,GAAG,GAClI;AACD,aAAQ,KAAK,EAAE;;AAEjB,YAAQ,MAAM,wBAAwB,gBAAgB,MAAM,CAAC;AAC7D,YAAQ,KAAK,EAAE;;GAEjB,MAAM,kBAAkB,KAAK,cACzB,IAAI,oBAAoB,aAAa,GACrC,KAAA;AAEJ,WAAQ,KAAK;AACb,WAAQ,IAAI,cAAc,MAAM,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM,GAAG,GAAG;AAClE,WAAQ,IAAI,cAAc,MAAM,KAAK,UAAU,GAAG;AAClD,WAAQ,IAAI,cAAc,MAAM,KAAK,aAAa,GAAG;AACrD,WAAQ,KAAK;AAEb,OAAI,CAAC,KAAK,KAAK;IACb,MAAM,EAAE,cAAc,MAAM,QAC1B;KACE,MAAM;KACN,MAAM;KACN,SAAS;KACT,SAAS;KACV,EACD,EAAE,gBAAgB,QAAQ,KAAK,IAAI,EAAE,CACtC;AACD,QAAI,CAAC,WAAW;AACd,aAAQ,IAAI,WAAW;AACvB,aAAQ,KAAK,EAAE;;;AAInB,OACE,KAAK,eACL,CAAE,MAAM,yBAAyB,cAAc,MAAM,GAAG,EACxD;AACA,YAAQ,IAAI,sDAAsD;AAClE,YAAQ,KAAK,EAAE;;AAEjB,oBAAiB,iBAAiB;GAClC,MAAM,YAAY,IAAI,UAAU,KAAK;GACrC,MAAM,SAAS,MAAM,WAAW,KAAK,cAAc,MAAM,GAAG;AAC5D,SAAM,iCAAiC;IACrC;IACA;IACA;IACA,SAAS,MAAM;IAChB,CAAC;GACF,MAAM,SAAS,IAAI,OAAO,KAAK,MAAM,IAAI,UAAU;GAInD,MAAM,eAAe,eAAe,IAAI;AAExC,aAAU,IAAI,WAAW,MAAM,KAAK,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO;GAC9D,MAAM,YAAY,MAAM,OACrB,aAAa,CACb,OAAO,UAAmB;AACzB,QAAI,EAAE,iBAAiB,oBAAqB,OAAM;AAClD,QAAI,QAAS,SAAQ,KAAK,MAAM,QAAQ;AACxC,YAAQ,KAAK,EAAE;KACf;AAEJ,OACE,KAAK,eACL,CAAE,MAAM,yBAAyB,cAAc,MAAM,GAAG,EACxD;AACA,YAAQ,MAAM;AACd,YAAQ,IAAI,kDAAkD;AAC9D,YAAQ,KAAK,EAAE;;GAEjB,MAAM,qBAAqB,MAAM,OAAO,wBACtC,WACA;IACE,QAAQ,CAAC,KAAK;IACd;IACD,CACF;GAED,MAAM,SAAS,MAAM,UAAU;IAC7B;IACA;IACA;IACA,QAAQ;IACR,cAAc,QAAQ,OAAO,oBAAoB,IAAI;IACrD,QAAQ,CAAC,KAAK;IACd,OAAO,KAAK,SAAS;IACrB,gBAAgB,mBAAmB;IACnC,SAAS;IACT,OAAO,MAAM;IACb,aAAa,MAAM,UAAU;AAC3B,SAAI,QAAS,SAAQ,OAAO,eAAe,KAAK,GAAG,MAAM;;IAE5D,CAAC;AACF,UAAO,OAAO,KAAK,GAAG,mBAAmB,OAAO;GAEhD,MAAM,QAAkB,EAAE;AAC1B,OAAI,OAAO,UAAU,EAAG,OAAM,KAAK,SAAS,OAAO,QAAQ,UAAU;AACrE,OAAI,OAAO,SAAS,EAClB,OAAM,KAAK,UAAU,OAAO,OAAO,kBAAkB;AACvD,OAAI,mBAAmB,SAAS,EAC9B,OAAM,KACJ,QAAQ,mBAAmB,OAAO,yBACnC;AAEH,OAAI,OAAO,aAAa,SAAS,EAC/B,OAAM,KACJ,iBAAiB,OAAO,aAAa,OAAO,qBAAqB,YAAY,GAC9E;AACH,OAAI,OAAO,UAAU,EACnB,OAAM,KAAK,WAAW,OAAO,QAAQ,gBAAgB;AACvD,OAAI,OAAO,UAAU,EACnB,OAAM,KAAK,GAAG,OAAO,QAAQ,kBAAkB;AAEjD,OAAI,OAAO,OAAO,QAAQ;AACxB,YAAQ,KACN,eAAe,OAAO,OAAO,OAAO,aAAa,MAAM,KAAK,KAAK,CAAC,GACnE;AACD,SAAK,MAAM,KAAK,OAAO,OAAQ,SAAQ,MAAM,KAAK,IAAI;AActD,YAAQ,WAAW;;AAIrB,OAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,YAAQ,KACN,GAAG,OAAO,UAAU,OAAO,iDAAiD,MAAM,KAAK,KAAK,CAAC,GAC9F;AACD,YAAQ,KAAK;AACb,SAAK,MAAM,KAAK,OAAO,UACrB,SAAQ,IAAI,KAAK,MAAM,OAAO,WAAW,CAAC,GAAG,IAAI;AAEnD,YAAQ,KAAK;AACb,YAAQ,IACN,2CAA2C,MAAM,KAAK,UAAU,CAAC,KAAK,MAAM,KAAK,UAAU,CAAC,WAC7F;AACD,YAAQ,IACN,cAAc,MAAM,KAAK,mBAAmB,CAAC,oCAC9C;AACD,YAAQ,KAAK;cACJ,OAAO,OAAO,WAAW,EAClC,SAAQ,QAAQ,MAAM,KAAK,KAAK,IAAI,sBAAsB;AAI5D,OAAI,OAAO,kBAAmB;GAK9B,MAAM,YAAY,OAAO,WAAW;AACpC,oBAAiB,iBAAiB;GAClC,MAAM,qBACJ,iBAAiB,cAAc;IAC7B,SAAS,MAAM;IACf,WAAW,MAAM;IACjB,SAAS;IACT,SAAS,aAAa,gBAAgB;IACtC,kBAAkB,IAAI,mBACpB,aACD,CAAC,YAAY,EACZ,gBAAgB,MACjB,CAAC;IACH,CAAC;AAEJ,OAAI,gBACF,iBAAgB,OAAO,CAAC,oBAAoB,EAAE,aAAa;OACxD,eAAc;AACnB,OAAI,OAAO,UAAU,SAAS,EAAG,SAAQ,KAAK,EAAE;WACzC,OAAO;AACd,OAAI,EAAE,iBAAiB,4BAA6B,OAAM;AAC1D,YAAS,MAAM;AACf,WAAQ,IAAI,MAAM,QAAQ;AAC1B,WAAQ,WAAW;;GAGxB;;;;AC7RL,SAAS,cAAc,cAAqC;CAC1D,MAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,KAAI,MAAM,OAAO,cAAc,MAAM,UAAU,EAC7C,QAAO,MAAM,GAAI,QAAQ,aAAa,GAAG;AAE3C,QAAO;;AAGT,SAAgB,oBAA6B;AAC3C,QAAO,IAAI,QAAQ,OAAO,CACvB,YAAY,uDAAuD,CACnE,OAAO,iBAAiB,wBAAwB,IAAI,CACpD,OAAO,UAAU,iCAAiC,CAClD,OAAO,OAAO,SAA2C;EAGxD,IAAI,WAAW,KAAK;AACpB,MAAI,aAAa,KAAK;GACpB,MAAM,YAAY,eAAe;AACjC,OAAI,UACF,YAAW,wBAAwB,UAAU,IAAI;;EAIrD,MAAM,YAAY,IAAI,UAAU,SAAS;AACzC,MAAI,CAAC,UAAU,SAAS,EAAE;GACxB,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAI,KAAK,KACP,SAAQ,IAAI,KAAK,UAAU;IAAE,IAAI;IAAO,OAAO;IAAS,CAAC,CAAC;OAE1D,SAAQ,MAAM,QAAQ;AAExB,WAAQ,KAAK,EAAE;;EAMjB,MAAM,cAHQ,UAAU,OAAO,CAI5B,QAAQ,MAAM,EAAE,SAAS,CACzB,KAAK,OAAO;GAAE,MAAM;GAAG,SAAS,EAAE,MAAM;GAAE,EAAE;EAE/C,MAAM,yBAAS,IAAI,KAA2B;EAC9C,MAAM,UAAU,MAAc,eAAiC;GAC7D,MAAM,WAAW,OAAO,IAAI,KAAK;AACjC,OAAI,SAAU,UAAS,KAAK,WAAW;OAClC,QAAO,IAAI,MAAM,CAAC,WAAW,CAAC;;AAMrC,OAAK,MAAM,EAAE,MAAM,aAAa,aAAa;GAC3C,MAAM,mBAAqC,KAAK,aAC5C,WACA;AACJ,QAAK,MAAM,cAAc,mBAAmB,SAAS,EACnD,kBACD,CAAC,CACA,QAAO,KAAK,cAAc,WAAW;AAEvC,QAAK,MAAM,cAAc,8BAA8B,QAAQ,CAC7D,QAAO,KAAK,cAAc,WAAW;;EAOzC,MAAM,uCAAuB,IAAI,KAAa;AAC9C,OAAK,MAAM,EAAE,UAAU,aAAa;GAClC,MAAM,OAAO,cAAc,KAAK,aAAa;AAC7C,OAAI,KAAM,sBAAqB,IAAI,KAAK;;EAE1C,MAAM,YAA6B,YAChC,QAAQ,EAAE,WAAW,cAAc,KAAK,aAAa,KAAK,KAAK,CAC/D,KAAK,EAAE,MAAM,eAAe;GAAE,MAAM,KAAK;GAAc;GAAS,EAAE;AACrE,OAAK,MAAM,WAAW,6BACpB,WACA,qBACD,CACC,QAAO,QAAQ,cAAc,QAAQ,WAAW;EAGlD,MAAM,UAA6B,CAAC,GAAG,OAAO,SAAS,CAAC,CACrD,KAAK,CAAC,MAAM,kBAAkB;GAAE;GAAM;GAAa,EAAE,CACrD,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;EAE/C,IAAI,SAAS;EACb,IAAI,WAAW;AACf,OAAK,MAAM,EAAE,iBAAiB,QAC5B,MAAK,MAAM,KAAK,YACd,KAAI,EAAE,aAAa,QAAS;MACvB;EAOT,MAAM,wBAAwB,QAAQ,MAAM,EAAE,kBAC5C,YAAY,MACT,MACC,EAAE,QAAQ,SAAS,aACnB,EAAE,OAAO,UAAU,UACnB,EAAE,OAAO,gBAAgB,KAAA,EAC5B,CACF;AAED,MAAI,KAAK,KACP,SAAQ,IACN,KAAK,UAAU;GACb,IAAI,WAAW;GACf;GACA;GACA,cAAc,YAAY;GAC1B,GAAI,wBACA,EAAE,mBAAmB,qBAAqB,GAC1C,EAAE;GACN,OAAO;GACR,CAAC,CACH;MAED,WAAU,SAAS,QAAQ,UAAU,YAAY,OAAO;AAG1D,UAAQ,KAAK,SAAS,IAAI,IAAI,EAAE;GAChC;;AAGN,SAAS,OAAO,OAAe,MAAsB;AACnD,QAAO,GAAG,MAAM,GAAG,OAAO,UAAU,IAAI,KAAK;;AAG/C,SAAS,UACP,SACA,QACA,UACA,cACM;AACN,MAAK,MAAM,EAAE,MAAM,iBAAiB,SAAS;AAC3C,UAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;AAC7B,OAAK,MAAM,KAAK,aAAa;GAC3B,MAAM,QACJ,EAAE,aAAa,UACX,MAAM,IAAI,QAAQ,OAAO,EAAE,CAAC,GAC5B,MAAM,OAAO,UAAU,OAAO,EAAE,CAAC;GAGvC,MAAM,UAAU,EAAE,QAAQ,MAAM,KAAK,CAAC;AACtC,WAAQ,IAAI,KAAK,MAAM,GAAG,UAAU;;;CAIxC,MAAM,SAAS,IAAI,OAAO,cAAc,OAAO,CAAC;AAChD,KAAI,SAAS,EACX,SAAQ,IACN,KAAK,MAAM,IAAI,KAAK,OAAO,QAAQ,QAAQ,CAAC,IAAI,OAAO,UAAU,UAAU,GAAG,CAAC,GAAG,SACnF;UACQ,WAAW,EACpB,SAAQ,IACN,KAAK,MAAM,OAAO,KAAK,OAAO,UAAU,UAAU,GAAG,CAAC,GAAG,SAC1D;KAED,SAAQ,IAAI,GAAG,MAAM,MAAM,sBAAsB,CAAC,GAAG,SAAS;;;;ACrLlE,MAAM,oBAAoB;AAE1B,MAAM,eAAe;AAErB,SAAgB,oBAA6B;AAC3C,QAAO,IAAI,QAAQ,OAAO,CACvB,YAAY,mDAAmD,CAC/D,SAAS,UAAU,mCAAmC,CACtD,OAAO,yBAAyB,yBAAyB,kBAAkB,CAC3E,OAAO,OAAO,MAA0B,SAA+B;AACtE,MAAI,CAAC,MAAM;AAST,WARY,MAAM,QAChB;IACE,MAAM;IACN,MAAM;IACN,SAAS;IACV,EACD,EAAE,gBAAgB,QAAQ,KAAK,IAAI,EAAE,CACtC,EACU;AACX,OAAI,CAAC,MAAM;AACT,YAAQ,MAAM,oBAAoB;AAClC,YAAQ,KAAK,EAAE;;;AAInB,MAAI,CAAC,aAAa,KAAK,KAAK,EAAE;AAC5B,WAAQ,MACN,wBAAwB,KAAK,+DAC9B;AACD,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,IAAI,sBAAsB,KAAK,SAAS,QAAQ,KAAK,GAAG;AAChE,eAAa,OAAO;GAAC;GAAS,KAAK;GAAU;GAAK,EAAE,EAAE,OAAO,WAAW,CAAC;AAEzE,OAAK,MAAM,OAAO,CAAC,QAAQ,UAAU,EAAE;GACrC,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,OAAI,WAAW,KAAK,CAAE,QAAO,MAAM;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;;AAGtE,UAAQ,IAAI,4BAA4B,OAAO;AAC/C,UAAQ,IAAI,qBAAqB,KAAK,sBAAsB;GAC5D;;;;ACrCN,SAAgB,eAAe,WAA6B;AAC1D,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO,EAAE;AACrC,QAAO,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC,CACnD,QAAQ,UAAU,MAAM,aAAa,CAAC,CACtC,KAAK,UAAU,MAAM,KAAK,CAC1B,QAAQ,SAAS,WAAW,KAAK,WAAW,MAAM,WAAW,CAAC,CAAC,CAC/D,MAAM;;AA+BX,eAAsB,cACpB,SAC8B;CAC9B,MAAM,EAAE,WAAW,YAAY,OAAO,kBAAkB,eACtD;CAEF,MAAM,YAAsB,EAAE;CAC9B,MAAM,UAAoB,EAAE;AAE5B,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAE1C,MAAK,MAAM,QAAQ,eAAe,UAAU,EAAE;EAC5C,MAAM,OAAO,KAAK,WAAW,KAAK;EAClC,MAAM,KAAK,KAAK,YAAY,KAAK;AAGjC,MAFe,WAAW,GAAG,IAEf,CAAC,SAAS,CAAE,MAAM,iBAAiB,KAAK,EAAG;AACvD,WAAQ,KAAK,KAAK;AAClB;;EAOF,MAAM,WAAW,iBAAiB,MAAM,GAAG;AAC3C,MAAI,aAAa,KAAM,YAAW,SAAS;AAC3C,YAAU,KAAK,KAAK;;AAGtB,QAAO;EAAE;EAAW;EAAS;;;;;;;;;;;;;;;;;;;;;;;;;AA0B/B,SAAgB,iBACd,QACA,QACe;CACf,MAAM,UAAU,mBAAmB,QAAQ,UAAU;AACrD,KAAI;AACF,SAAO,QAAQ,SAAS,EAAE,WAAW,MAAM,CAAC;UACrC,OAAO;AACd,gBAAc,QAAQ;AACtB,QAAM;;AAIR,KAAI,CAAC,WAAW,OAAO,CACrB,QAAO,cAAc,SAAS,QAAQ,KAAK;CAI7C,MAAM,SAAS,mBAAmB,QAAQ,SAAS;AACnD,KAAI;AACF,aAAW,QAAQ,OAAO;UACnB,OAAO;AACd,gBAAc,QAAQ;AACtB,QAAM;;AAER,QAAO,cAAc,SAAS,QAAQ,OAAO;;AAS/C,SAAS,cACP,SACA,QACA,QACe;AACf,KAAI;AACF,aAAW,SAAS,OAAO;UACpB,OAAO;AACd,MAAI,WAAW,KAAM,eAAc,QAAQ,QAAQ,MAAM;AACzD,gBAAc,QAAQ;AACtB,QAAM;;AAER,QAAO,cAAc,OAAO;;AAK9B,SAAS,cAAc,QAAgB,QAAgB,OAAsB;AAC3E,KAAI;AACF,aAAW,QAAQ,OAAO;SACpB;AACN,QAAM,IAAI,MACR,qBAAqB,OAAO,2CAA2C,OAAO,IAC9E,EAAE,OAAO,CACV;;;AAQL,SAAS,cAAc,MAAoC;AACzD,KAAI,SAAS,KAAM,QAAO;AAC1B,KAAI;AACF,SAAO,MAAM;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAC9C,SAAO;SACD;AACN,SAAO;;;AAOX,SAAS,mBAAmB,UAAkB,OAAuB;CACnE,IAAI,YAAY,GAAG,SAAS,GAAG;AAC/B,MAAK,IAAI,IAAI,GAAG,WAAW,UAAU,EAAE,KAAK,EAC1C,aAAY,GAAG,SAAS,GAAG,MAAM,GAAG;AAEtC,QAAO;;;;ACnLT,MAAM,qBAAqB;AAK3B,SAAS,0BAAkC;CACzC,MAAM,OAAO,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CACpD,MAAM,aAAa;EACjB,KAAK,MAAM,SAAS;EACpB,KAAK,MAAM,MAAM,SAAS;EAC1B,KAAK,MAAM,MAAM,MAAM,SAAS;EACjC;AACD,MAAK,MAAM,OAAO,WAChB,KAAI,eAAe,IAAI,CAAC,SAAS,EAAG,QAAO;CAG7C,IAAI,MAAM;AACV,MAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAAS;EACtC,MAAM,YAAY,KAAK,KAAK,SAAS;AACrC,MAAI,eAAe,UAAU,CAAC,SAAS,EAAG,QAAO;AACjD,QAAM,QAAQ,IAAI;;AAEpB,OAAM,IAAI,MACR,uEACD;;AAOH,SAAS,yBAAyB,KAAa,YAA6B;AAC1E,KAAI,CAAC,IAAI,UAAU,IAAI,CAAC,SAAS,CAAE,QAAO;CAC1C,MAAM,MAAM,SAAS,KAAK,WAAW;AACrC,KAAI,IAAI,WAAW,KAAK,CAAE,QAAO;AACjC,QAAO,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,YAAY,QAAQ,WAAW,IAAI,CAAC;;AAGnE,SAAgB,sBAA+B;CAC7C,MAAM,SAAS,IAAI,QAAQ,SAAS,CAAC,YACnC,2CACD;AAED,QACG,QAAQ,UAAU,CAClB,YACC,sFACD,CACA,OAAO,oBAAoB,6BAA6B,mBAAmB,CAC3E,OAAO,eAAe,8CAA8C,CACpE,OAAO,OAAO,SAA2C;EACxD,MAAM,YAAY,yBAAyB;AAC3C,MAAI,eAAe,UAAU,CAAC,WAAW,GAAG;AAC1C,WAAQ,MAAM,sCAAsC;AACpD,WAAQ,KAAK,EAAE;;EAGjB,MAAM,aAAa,QAAQ,QAAQ,KAAK,EAAE,KAAK,IAAI;AACnD,MAAI,yBAAyB,QAAQ,KAAK,EAAE,WAAW,CACrD,SAAQ,IACN,GAAG,MAAM,OAAO,IAAI,CAAC,GAAG,MAAM,KAAK,KAAK,IAAI,CAAC,6HAEN,MAAM,KAAK,mBAAmB,CAAC,sBACvE;EAGH,MAAM,EAAE,WAAW,YAAY,MAAM,cAAc;GACjD;GACA;GACA,OAAO,QAAQ,KAAK,MAAM;GAC1B,kBAAkB,OAAO,SAAS;IAChC,MAAM,MAAM,MAAM,QAChB;KACE,MAAM;KACN,MAAM;KACN,SAAS,GAAG,MAAM,OAAO,KAAK,CAAC,qBAAqB,KAAK,IAAI;KAC7D,SAAS;KACV,EACD,EAAE,gBAAgB,QAAQ,KAAK,IAAI,EAAE,CACtC;AACD,WAAO,QAAQ,IAAI,UAAU;;GAI/B,aAAa,SAAS;AACpB,YAAQ,IACN,GAAG,MAAM,OAAO,IAAI,CAAC,6BAA6B,KAAK,4CACxD;;GAEJ,CAAC;AAEF,OAAK,MAAM,QAAQ,UACjB,SAAQ,IAAI,GAAG,MAAM,MAAM,IAAI,CAAC,GAAG,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAEtE,OAAK,MAAM,QAAQ,QACjB,SAAQ,IAAI,GAAG,MAAM,IAAI,aAAa,KAAK,kBAAkB,GAAG;EAGlE,MAAM,QAAQ,CACZ,UAAU,SAAS,IAAI,GAAG,UAAU,OAAO,cAAc,MACzD,QAAQ,SAAS,IAAI,GAAG,QAAQ,OAAO,YAAY,KACpD,CAAC,OAAO,QAAQ;AACjB,UAAQ,IACN,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,IAAI,gBAAgB,CAAC,MAAM,aAC5D;AACD,MAAI,UAAU,SAAS,EACrB,SAAQ,IACN,MAAM,IAAI,wDAAwD,CACnE;GAEH;AAEJ,QAAO;;;;AClHT,SAAgB,mBACd,WACgB;AAChB,UAAS,WAAW,SAAS,aAAa;EACxC,MAAM,kBACJ,OAAO,YAAY,WAAW,UAAW,QAAQ,UAAU;EAC7D,MAAM,WACJ,oBAAoB,KAAK,oBAAoB,IACzC,UAAU,QAAQ,EAAE,aAAa,WAAW,gBAAgB,GAC5D;EACN,MAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,UAAU;AAEX,YAMA,OAAO,uBAAO,IAAI,MAAM,0CAA0C,EAAE,EAClE,MAAM,aACP,CAAC,CACH;AACD;;AAEF,MAAI,OAAO,YAAY,YAAY,QAAQ,KAAK;AAE5C,YAIA,MAAM,SAAS;AACjB;;AAGA,WAKA,MAAM,SAAS,SAAS,SAAS,OAAO;;;;;;;;AAS9C,eAAsB,kBACpB,KACA,WACA,SAKmE;CACnE,MAAM,aAAa,IAAI,MAAM,EAC3B,SAAS,EACP,QAAQ,mBAAmB,UAAU,EACtC,EACF,CAAC;AACF,KAAI;AAQF,SAAO;GACL,UARe,MAAMC,QAAY,KAAK;IACtC;IACA,SAAS,QAAQ;IACjB,QAAQ,QAAQ,UAAU;IAC1B,UAAU;IACV,QAAQ,QAAQ;IACjB,CAAC;GAGA,OAAO,YAAY;AACjB,UAAM,WAAW,OAAO;;GAE3B;UACM,OAAO;AACd,QAAM,WAAW,OAAO;AACxB,QAAM;;;;;ACpFV,MAAM,gBAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACD;AAeD,SAAS,kBAAkB,UAA0B;AACnD,QAAO,SACJ,aAAa,CACb,QAAQ,YAAY,GAAG,CACvB,QAAQ,OAAO,GAAG;;AAGvB,SAAS,WAAW,OAAgC;CAClD,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EAAG,QAAO;CAC/B,MAAM,SAAS,MAAM,KAAK,SAAS,OAAO,KAAK,CAAC;AAChD,KACE,OAAO,MACJ,OAAO,UACN,CAAC,OAAO,UAAU,MAAM,IACxB,QAAQ,KACR,QAAQ,OACR,OAAO,MAAM,KAAK,MAAM,OAC3B,CAED,QAAO;AAET,QAAO;;AAGT,SAAS,cAAc,OAAwB;CAC7C,MAAM,SAAS,WAAW,MAAM;AAChC,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,KAAK;AACrC,QACE,MAAM,KACN,MAAM,MACN,MAAM,OACL,MAAM,OAAO,KAAK,MAAM,KAAK,OAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,KAAK,MAAM,KAAK,MAC7B,MAAM,OAAO,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,MACpD,MAAM,OAAO,MAAM,KAAK,MAAM,KAC9B,MAAM,OAAO,MAAM,MAAM,MAAM,MAC/B,MAAM,OAAO,MAAM,OACnB,MAAM,QAAQ,MAAM,MAAM,MAAM,OAChC,MAAM,OAAO,MAAM,MAAM,MAAM,OAC/B,MAAM,OAAO,MAAM,KAAK,MAAM,OAC/B,KAAK;;AAIT,SAAS,cAAc,OAAwB;CAC7C,MAAM,aAAa,MAAM,aAAa,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;CAC3D,MAAM,cAAc,OAAO,SAAS,WAAW,MAAM,IAAI,CAAC,MAAM,IAAI,GAAG;AAKvE,KACE,CAAC,OAAO,SAAS,YAAY,IAC7B,cAAc,QACd,cAAc,MAEd,QAAO;AAIT,QACE,WAAW,WAAW,SAAS,IAC/B,gBAAgB,KAAK,WAAW,IAChC,WAAW,WAAW,YAAY,IAClC,eAAe,gBACf,WAAW,WAAW,QAAQ,IAC9B,WAAW,WAAW,QAAQ;;AAIlC,SAAgB,oBAAoB,UAA2B;CAC7D,MAAM,aAAa,kBAAkB,SAAS;AAC9C,KACE,CAAC,cACD,cAAc,IAAI,WAAW,IAC7B,sBAAsB,MAAM,WAAW,WAAW,SAAS,OAAO,CAAC,CAEnE,QAAO;AAGT,KADe,KAAK,WAAW,KAChB,EAAG,QAAO,uBAAuB,WAAW;AAC3D,QAAO;;AAGT,SAAgB,uBAAuB,SAA0B;CAC/D,MAAM,aAAa,kBAAkB,QAAQ;CAC7C,MAAM,SAAS,KAAK,WAAW;AAC/B,KAAI,WAAW,EAAG,QAAO,cAAc,WAAW;AAClD,KAAI,WAAW,EAAG,QAAO,cAAc,WAAW;AAClD,QAAO;;AAGT,MAAM,yBAA0C,OAAO,aACrDC,OAAU,UAAU;CAAE,KAAK;CAAM,UAAU;CAAM,CAAC;AAEpD,eAAsB,sBACpB,QACA,kBAAmC,wBACL;CAC9B,IAAI;AACJ,KAAI;AACF,QAAM,kBAAkB,MAAM,SAAS,IAAI,IAAI,OAAO;SAChD;AACN,SAAO;GAAE,IAAI;GAAO,QAAQ;GAAmB;;AAEjD,KAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,QAAO;EACL,IAAI;EACJ,QAAQ,sCAAsC,IAAI,SAAS;EAC5D;AAEH,KAAI,IAAI,YAAY,IAAI,SACtB,QAAO;EACL,IAAI;EACJ,QAAQ;EACT;CAGH,MAAM,WAAW,kBAAkB,IAAI,SAAS;AAChD,KAAI,oBAAoB,SAAS,CAC/B,QAAO;EACL,IAAI;EACJ,QAAQ,YAAY,SAAS;EAC9B;CAGH,IAAI;CACJ,MAAM,gBAAgB,KAAK,SAAS;AACpC,KAAI,kBAAkB,GAAG;EACvB,IAAI;AACJ,MAAI;AACF,cAAW,MAAM,gBAAgB,SAAS;WACnC,OAAO;AACd,UAAO;IACL,IAAI;IACJ,QAAQ,YAAY,SAAS,oBAC3B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACvD;IACF;;AAEH,MAAI,SAAS,WAAW,EACtB,QAAO;GACL,IAAI;GACJ,QAAQ,YAAY,SAAS;GAC9B;EAEH,MAAM,UAAU,SAAS,MAAM,EAAE,cAC/B,uBAAuB,QAAQ,CAChC;AACD,MAAI,QACF,QAAO;GACL,IAAI;GACJ,QAAQ,YAAY,SAAS,qCAAqC,QAAQ,QAAQ;GACnF;AAEH,cAAY;OAEZ,aAAY,CAAC;EAAE,SAAS;EAAU,QAAQ;EAAe,CAAC;AAG5D,QAAO;EAAE,IAAI;EAAM;EAAK;EAAW;;AC9JrC,MAAa,sBAAsB,IAAI,OAAO;AAC9C,MAAa,uBAAuB,OAAO;;;AAG3C,MAAa,4BAA4B,IAAI,OAAO;AACpD,MAAM,mBAAmB;;;;AAIzB,MAAM,aAAa;;;AAGnB,MAAa,iBAAiB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAI,CAAC;AAC1E,MAAM,iBAAiB;AAwCvB,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;AAGD,SAAgB,mBACd,MACA,KACA,UACkB;CAClB,MAAM,OAAO,IAAI,MAAM,GAAG,IAAK,CAAC,aAAa;AAC7C,KAAI,yBAAyB,MAAM,MAAM,KAAK,SAAS,EAAE,CAAC,CACxD,QAAO;CAET,MAAM,YAAY,KAAK,aAAa;AACpC,KAAI,eAAe,MAAM,MAAM,UAAU,SAAS,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC,CACvE,QAAO;AAET,KAAI,UAAU,WAAW,OAAO,CAC9B,KAAI;EACF,MAAM,OAAO,IAAI,IAAI,KAAK,CAAC,SAAS,aAAa;EACjD,MAAM,QAAQ,MAAc,EAAE,QAAQ,UAAU,GAAG;AAEnD,MACE,CAAC,KAAK,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,IACpC,CAAC,KAAK,SAAS,CAAC,SAAS,KAAK,KAAK,CAAC,CAEpC,QAAO;SAEH;AAIV,QAAO;;;;;;;;;;;;;;;;;AAgDT,SAAgB,oBAAoB,MAAc,aAA6B;AAI7E,MAAK,MAAM,SAAS,KAAK,SAAS,kBAAkB,EAAE;EACpD,MAAM,OAAO,cAAc,MAAM,IAAI,OAAO;AAC5C,MAAI,SAAS,KAAA,KAAa,KAAK,MAAM,CAAC,WAAW,EAAG;AACpD,MAAI;AACF,UAAO,IAAI,IAAI,MAAM,YAAY,CAAC,UAAU;UACtC;AACN,UAAO;;;AAGX,QAAO;;;;;;;;;;AAWT,SAAgB,cAAc,KAAa,MAAkC;CAK3E,MAAM,QAJU,IAAI,OAClB,GAAG,KAAK,oDACR,IACD,CACqB,KAAK,IAAI;AAC/B,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,QAAO,MAAM,MAAM,MAAM,MAAM,MAAM;;;AAIvC,SAAS,eAAe,KAA6C;CACnE,MAAM,QAAQ,KAAK,MAAM;AACzB,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,QAAO,SAAS,KAAK,MAAM,GAAG,KAAA,IAAY;;AAG5C,SAAgB,sBACd,MACA,aACmD;CACnD,MAAM,UAAU,oBAAoB,MAAM,YAAY;CACtD,MAAM,eAAyB,EAAE;CACjC,MAAM,OAAwB,EAAE;AAOhC,MAAK,MAAM,SAAS,KAAK,SADT,oDAC0B,EAAE;EAC1C,MAAM,CAAC,KAAK,cAAc;AAC1B,MAAI,eAAe,KAAA,GAAW;AAC5B,OAAI,WAAW,MAAM,CAAC,WAAW,EAAG;GACpC,MAAM,QAAQ,eAAe,cAAc,KAAK,QAAQ,CAAC;AACzD,QAAK,KAAK;IACR,MAAM;IACN,MAAM,UAAU,aAAa;IAC7B,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;IAC3B,CAAC;AACF,gBAAa,KAAK,WAAW;AAC7B;;EAEF,MAAM,MAAM,cAAc,KAAK,MAAM;AACrC,MAAI,CAAC,OAAO,CAAC,0BAA0B,KAAK,IAAI,CAAE;EAClD,MAAM,OAAO,cAAc,KAAK,OAAO;AACvC,MAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,WAAW,EAAG;EACvC,IAAI;AACJ,MAAI;AACF,cAAW,IAAI,IAAI,MAAM,QAAQ,CAAC,UAAU;UACtC;AACN;;AAEF,MAAI,CAAC,aAAa,KAAK,IAAI,IAAI,SAAS,CAAC,SAAS,CAAE;EAOpD,MAAM,YAAY,eAAe,cAAc,KAAK,QAAQ,CAAC;AAC7D,OAAK,KAAK;GACR,MAAM;GACN,MAAM;GACN,GAAI,YAAY,EAAE,OAAO,WAAW,GAAG,EAAE;GAC1C,CAAC;;AAGJ,QAAO;EAAE;EAAM;EAAc;;AAG/B,SAAS,aAAa,QAAgB,SAAyB;AAC7D,QAAO,OAAO,MAAM,QAAQ,EAAE,UAAU;;AAG1C,SAAgB,aAAa,KAA4C;AACvE,QAAO;EACL,qBAAqB,aACnB,KACA,qEACD;EACD,cAAc,aAAa,KAAK,aAAa;EAC7C,WAAW,aAAa,KAAK,iBAAiB;EAC9C,WAAW,aAAa,KAAK,iBAAiB;EAC9C,kBAAkB,aAAa,KAAK,iCAAiC;EACtE;;AAmBH,MAAMC,sBAAoB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAI,CAAC;AAC5D,MAAM,gBAAgB;;;;;;;;;;;;AAatB,eAAsB,eACpB,UACA,UAC+C;CAC/C,MAAM,WAAW,OAAO,SAAS,QAAQ,IAAI,iBAAiB,CAAC;AAC/D,KAAI,OAAO,SAAS,SAAS,IAAI,WAAW,SAC1C,QAAO;EAAE,MAAM;EAAI,WAAW;EAAM;CAEtC,MAAM,SAAS,SAAS;AACxB,KACE,CAAC,UACD,OAAQ,OAAqC,OAAO,mBAClD,WAEF,QAAO;EAAE,MAAM;EAAI,WAAW;EAAO;CAEvC,MAAM,SAAuB,EAAE;CAC/B,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ;AAChC,WAAS,MAAM;AACf,MAAI,QAAQ,SAAU,QAAO;GAAE,MAAM;GAAI,WAAW;GAAM;AAC1D,SAAO,KAAK,MAAM;;AAEpB,QAAO;EAAE,MAAM,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO;EAAE,WAAW;EAAO;;;;;;;;AAS3E,eAAsB,gBACpB,QACA,YAAY,kBACZ,WAAW,sBACX,UAAU,GACgB;CAC1B,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,UAAU;CAC7D,IAAI,aAAa;AACjB,KAAI;AACF,OAAK,IAAI,MAAM,GAAG,OAAO,eAAe,OAAO,GAAG;GAChD,MAAM,aAAa,MAAM,sBAAsB,WAAW;AAC1D,OAAI,CAAC,WAAW,GACd,QAAO;IACL,IAAI;IACJ,QAAQ;IACR,MAAM;IACN,UAAU;IACV,WAAW;IACX,OAAO,0BAA0B,WAAW;IAC7C;GAEH,MAAM,SAAS,MAAM,kBACnB,WAAW,KACX,WAAW,WACX;IACE,QAAQ,WAAW;IACnB,SAAS;KACP,QAAQ;KACR,cAAc;KACf;IACF,CACF;AACD,OAAI;IACF,MAAM,EAAE,aAAa;AACrB,QAAIA,oBAAkB,IAAI,SAAS,OAAO,EAAE;KAC1C,MAAM,WAAW,SAAS,QAAQ,IAAI,WAAW;AACjD,SAAI,CAAC,SACH,QAAO;MACL,IAAI;MACJ,QAAQ,SAAS;MACjB,MAAM;MACN,UAAU;MACV,WAAW;MACZ;AAEH,kBAAa,IAAI,IAAI,UAAU,WAAW,CAAC,UAAU;AACrD;;AAEF,QAAI,CAAC,SAAS,IAAI;AAGhB,SAAI,eAAe,IAAI,SAAS,OAAO,IAAI,YAAY,GAAG;AACxD,YAAM,IAAI,SAAS,YAAY,WAAW,SAAS,eAAe,CAAC;AACnE,aAAO,gBAAgB,QAAQ,WAAW,UAAU,EAAE;;AAExD,YAAO;MACL,IAAI;MACJ,QAAQ,SAAS;MACjB,MAAM;MACN,UAAU;MACV,WAAW;MACX,OAAO,QAAQ,SAAS;MACzB;;IAEH,MAAM,OAAO,MAAM,eAAe,UAAU,SAAS;AACrD,WAAO;KACL,IAAI,CAAC,KAAK;KACV,QAAQ,SAAS;KACjB,MAAM,KAAK;KACX,UAAU;KACV,WAAW,KAAK;KACjB;aACO;AACR,UAAM,OAAO,OAAO;;;AAGxB,SAAO;GACL,IAAI;GACJ,QAAQ;GACR,MAAM;GACN,UAAU;GACV,WAAW;GACZ;UACM,OAAO;AACd,SAAO;GACL,IAAI;GACJ,QAAQ;GACR,MAAM;GACN,UAAU;GACV,WAAW;GACX,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC9D;WACO;AACR,eAAa,MAAM;;;AA0FvB,eAAsB,yBACpB,WACA,aAAwD,QACtD,gBAAgB,IAAI,EACS;CAC/B,MAAM,WAAqB,EAAE;CAC7B,MAAM,UAAiC,EAAE;CACzC,MAAM,SAAmB,EAAE;CAC3B,IAAI,QAAQ;CACZ,IAAI,WAAW;CACf,MAAM,0BAAU,IAAI,KAAqB;;;;CAIzC,MAAM,sCAAsB,IAAI,KAAa;;;;;CAK7C,MAAM,4CAA4B,IAAI,KAAa;CAGnD,MAAM,UAAU,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MACtC,EAAE,QAAQ,WAAW,KAAK,EAAE,QAAQ,WAAW,IAAI,EACpD;CAED,IAAI,WAAW;AACf,KAAI;AACF,aAAW,IAAI,IAAI,QAAQ,IAAI,WAAW,0BAA0B,CACjE;SACG;AACN,aAAW;;AAGb,MAAK,MAAM,YAAY,SAAS;EAC9B,MAAM,EAAE,MAAM,iBAAiB,sBAC7B,SAAS,MACT,SAAS,QACV;EACD,IAAI,cAAc;EAClB,MAAM,oCAAoB,IAAI,KAAa;AAE3C,OAAK,MAAM,OAAO,MAAM;AACtB,OAAI,IAAI,SAAS,UAAU,0BAA0B,IAAI,IAAI,KAAK,CAChE;GAEF,MAAM,eAAe,IAAI,SAAS,UAAU,CAAC,QAAQ,IAAI,IAAI,KAAK;AAClE,OAAI,gBAAgB,YAAA,IAAqC;AACvD,aAAS,KACP,wEACD;AACD;;AAEF,OAAI,aAAc,aAAY;AAC9B,OAAI,QAAQ,UAAA,IAA2B;AACrC,aAAS,KACP,6DACD;AACD;;GAGF,IAAI;AACJ,OAAI,IAAI,SAAS,UAAU;AACzB,WAAO,aAAa,gBAAgB;AACpC,mBAAe;cACN,QAAQ,IAAI,IAAI,KAAK,CAC9B,QAAO,QAAQ,IAAI,IAAI,KAAK;QACvB;IACL,MAAM,MAAM,MAAM,UAAU,IAAI,KAAK;AACrC,QAAI,CAAC,IAAI,IAAI;AACX,cAAS,KACP,IAAI,YACA,GAAG,IAAI,KAAK,iBAAiB,qBAAqB,8CAClD,GAAG,IAAI,KAAK,IAAI,IAAI,SAAS,QAAQ,IAAI,UAAU,mBACxD;AACD;;AAEF,WAAO,IAAI;AACX,YAAQ,IAAI,IAAI,MAAM,KAAK;;AAG7B,OAAI,IAAI,SAAS,UAAU;IACzB,MAAM,cAAc,GAAG,IAAI,SAAS,GAAG,QAAQ;AAC/C,QAAI,oBAAoB,IAAI,YAAY,CAAE;AAC1C,wBAAoB,IAAI,YAAY;;GAGtC,MAAM,aAAa,OAAO,WAAW,MAAM,OAAO;AAClD,OAAI,aAAA,SAAmC;AACrC,aAAS,KACP,GAAG,IAAI,KAAK,IAAI,WAAW,qBAAqB,qBAAqB,iCACtE;AACD;;AAEF,OAAI,QAAQ,aAAA,SAAkC;AAC5C,aAAS,KACP,0BAA0B,oBAAoB,WAAW,IAAI,KAAK,oCACnE;AACD;;GAGF,MAAM,SAAS,mBAAmB,IAAI,MAAM,MAAM,SAAS;GAC3D,MAAM,mBAAmB,QAAQ,QAC9B,aAAa,SAAS,SAAS,UAAU,SAAS,SAAS,IAAI,KACjE,CAAC;AAEF,OAAI,IAAI,SAAS,OAAQ,mBAAkB,IAAI,IAAI,KAAK;AACxD,YAAS;AACT,WAAQ,KAAK;IAAE,GAAG;IAAK;IAAY,MAAM,SAAS;IAAK;IAAQ,CAAC;GAEhE,MAAM,aAAa,YAAY,IAAI,KAAK,IAAI,IAAI,OAC9C,mBAAmB,IACf,2BAA2B,mBAAmB,EAAE,KAChD,KACH,IAAI,QAAQ,YAAY,IAAI,MAAM,KAAK,GAAG,IAAI,OAAO,SAAS,SAAS,IAAI;AAC9E,UAAO,KACL,IAAI,QACA,GAAG,WAAW,WAAW,IAAI,MAAM,MAAM,KAAK,OAC9C,GAAG,WAAW,IAAI,OACvB;;AAEH,OAAK,MAAM,QAAQ,kBAAmB,2BAA0B,IAAI,KAAK;;CAG3E,MAAM,MAAM,OAAO,KAAK,OAAO;CAC/B,MAAM,WAAW;EACf,MAAM,QAAQ,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;EACjD,kBAAkB,QAAQ,QAAQ,MAAM,EAAE,WAAW,qBAAqB,CACvE;EACH,iBAAiB,QAAQ,QAAQ,MAAM,EAAE,WAAW,mBAAmB,CACpE;EACJ;AACD,QAAO;EACL;EACA;EACA;EACA,OAAO,aAAa,IAAI;EACxB;EACA,eAAe,QAAQ,KAAK,MAAM,EAAE,IAAI;EACzC;;;;;;;;;;;;;;;;;;AC/pBH,SAAgB,oBACd,OACA,OACA,OACA,OACe;CACf,MAAM,MAAM,KAAK,UAAU,MAAM;CACjC,MAAM,WAAW,MAAM,IAAI,IAAI;AAC/B,KAAI,aAAa,KAAA,EAAW,QAAO;AACnC,KAAI,MAAM,UAAU,MAAO,QAAO;CAClC,MAAM,OAAO,MAAM;AACnB,OAAM,KAAK,MAAM;AACjB,OAAM,IAAI,KAAK,KAAK;AACpB,QAAO;;AAGT,SAAgB,sBACd,OACA,UACQ;CACR,MAAM,aAAa,MAAM,KAAK,MAAM,IAAI;AACxC,KAAI,WAAW,UAAU,IAAO,QAAO;CAEvC,IAAI,OAAO;AACX,MAAK,IAAI,QAAQ,GAAG,QAAQ,WAAW,QAAQ,SAAS;AACtD,UAAQ,WAAW,WAAW,MAAM;AACpC,SAAO,KAAK,KAAK,MAAM,SAAW;;CAEpC,MAAM,SAAS,eAAe,SAAS,GAAG,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC;AACxE,QAAO,SAAS,WAAW,MAAM,EAAE,MAAQ,OAAO,QAAQ;;AAW5D,SAAgB,0BACd,WACQ;CACR,IAAI,SAAS;CACb,IAAI;AACJ,MAAK,MAAM,YAAY,WAAW;AAChC,MAAI,UAAU;GACZ,MAAM,gBAAgB,SAAS,SAAS,IAAI,SAAS,SAAS;GAC9D,MAAM,iBAAiB,SAAS,SAAS,IAAI,SAAS,SAAS;GAC/D,MAAM,eAAe,SAAS,UAAU,IAAI,SAAS,UAAU;GAC/D,MAAM,gBAAgB,SAAS,UAAU,IAAI,SAAS,UAAU;GAChE,MAAM,sBACJ,SAAS,UAAU,KAAK,iBAAiB,KACzC,SAAS,SAAS,KAAK,gBAAgB;GACzC,MAAM,wBACJ,CAAC,wBACA,SAAS,UAAU,IAAI,gBAAgB,KACtC,SAAS,SAAS,IAAI,eAAe;AACzC,OACE,SAAS,sBACT,SAAS,qBACT,uBACA,sBAEA,WAAU;;AAGd,YAAU,SAAS;AACnB,aAAW;;AAEb,QAAO;;AAIT,MAAa,sCAAsC;AACnD,MAAa,qCAAqC;AAIlD,MAAM,aAAa,EAAE,OAAO;CAC1B,GAAG,EAAE,QAAQ,CAAC,QAAQ;CACtB,GAAG,EAAE,QAAQ,CAAC,QAAQ;CACtB,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa;CACxC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa;CAC1C,CAAC;AAEF,MAAM,gBAAgB,EACnB,QAAQ,CACR,KAAK,CACL,IAAI,KAAO,CACX,QAAQ,UAAU,gBAAgB,KAAK,MAAM,EAAE,EAC9C,SAAS,kDACV,CAAC;AAEJ,MAAM,2BAA2B,EAAE,OAAO;CACxC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG;CAC9B,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU;CACpC,MAAM,EAAE,SAAS,CAAC,UAAU,CAAC,UAAU;CACvC,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM;CAC3B,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM,CAAC,UAAU;CACtC,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAM,CAAC,UAAU;CAC3C,MAAM;CAEN,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,UAAU;CAC/D,CAAC;AAEF,MAAM,2BAA2B,EAAE,QAAQ,CAAC,IAAI,IAAM;AAEtD,MAAM,uCAAuC,EAAE,OAAO;CACpD,MAAM,EAAE,QAAQ,kBAAkB;CAClC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAM;CACpC,MAAM,EAAE,KAAK,CAAC,UAAU,cAAc,CAAC;CACvC,YAAY,EAAE,QAAQ,KAAK;CAC3B,MAAM;CACN,OAAO,EAAE,OAAO;EACd,SAAS;EACT,YAAY;EACZ,UAAU;EACV,SAAS;EACT,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAM;EACjC,CAAC;CACH,CAAC;AAEF,MAAM,sCAAsC,EAAE,OAAO;CACnD,MAAM,EAAE,QAAQ,wBAAwB;CACxC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAM;CACpC,UAAU,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;CACpC,eAAe,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK;CAClE,QAAQ,EAAE,KAAK;EAAC;EAAc;EAAoB;EAAqB,CAAC;CACxE,MAAM;CACN,OAAO,EAAE,OAAO;EACd,SAAS;EACT,YAAY;EACZ,UAAU;EACV,SAAS;EACT,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAM;EACjC,CAAC;CACH,CAAC;AACF,MAAM,sCAAsC,EAAE,OAAO;CACnD,MAAM,EAAE,QAAQ,gBAAgB;CAChC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAM;CACpC,kBAAkB,EAAE,KAAK,CAAC,SAAS,SAAS,CAAC;CAC7C,oBAAoB,EAAE,KAAK,CAAC,YAAY,WAAW,CAAC;CACpD,MAAM;CACN,OAAO,EAAE,OAAO;EACd,SAAS;EACT,YAAY;EACZ,SAAS;EACT,KAAK;EACL,OAAO;EACP,QAAQ;EACR,MAAM;EACN,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAM;EACjC,CAAC;CACH,CAAC;AAEF,MAAM,+BAA+B,EAAE,OAAO;CAC5C,SAAS;CACT,UAAU;CACV,iBAAiB;CACjB,iBAAiB,EAAE,QAAQ,CAAC,IAAI,IAAM;CACtC,OAAO;CACP,SAAS;CACT,QAAQ;CACR,KAAK;CACL,qBAAqB,EAAE,QAAQ,CAAC,IAAI,IAAM;CAC1C,eAAe;CACf,gBAAgB;CAChB,YAAY;CACZ,cAAc;CACf,CAAC;AAEF,MAAM,4BAA4B,EAAE,OAAO;CACzC,OAAO;CACP,YAAY,EAAE,QAAQ,CAAC,IAAI,IAAM;CACjC,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,eAAe;CACf,WAAW;CACX,eAAe;CAChB,CAAC;AAEF,MAAM,6BAA6B,EAAE,OAAO;CAC1C,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG;CAC9B,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM;CAC3B,MAAM;CACN,OAAO;CACR,CAAC;AAEF,MAAM,0BAA0B,EAAE,OAAO;CACvC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG;CAC9B,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAM;CACpC,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU;CACpC,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM;CAC3B,MAAM;CACN,OAAO;CACP,cAAc,EAAE,SAAS,CAAC,QAAQ,MAAM;CACxC,SAAS,2BAA2B,UAAU;CAC9C,MAAM,2BAA2B,UAAU;CAC3C,QAAQ,2BAA2B,UAAU;CAC9C,CAAC;AAEF,MAAM,wBAAwB,EAAE,OAAO;CACrC,MAAM,EAAE,KAAK;EAAC;EAAS;EAAS;EAAa,CAAC;CAC9C,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG;CAC9B,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAM,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC7D,UAAU,cAAc,UAAU,CAAC,QAAQ,KAAK;CAChD,aAAa,EAAE,QAAQ,CAAC,IAAI,IAAM,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC3D,YAAY,EAAE,QAAQ,CAAC,IAAI,KAAO,CAAC,UAAU;CAC7C,KAAK,EAAE,QAAQ,CAAC,IAAI,KAAO,CAAC,UAAU;CACtC,QAAQ,EAAE,QAAQ,CAAC,IAAI,KAAO,CAAC,UAAU;CACzC,KAAK,EAAE,QAAQ,CAAC,IAAI,IAAM,CAAC,UAAU;CACrC,kBAAkB,EACf,MACC,EAAE,OAAO;EACP,KAAK,EAAE,QAAQ,CAAC,IAAI,KAAO;EAC3B,QAAQ,EAAE,QAAQ,CAAC,IAAI,KAAO;EAC9B,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAM;EAC5B,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM;EAC5B,CAAC,CACH,CACA,IAAI,IAAI;CACX,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,UAAU;CACrD,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACvD,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACxD,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACrD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU;CACtD,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC9C,MAAM,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC1C,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC3C,aAAa,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CACjD,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC9C,MAAM;CACP,CAAC;AAEF,MAAa,mCAAmC,EAAE,OAAO;CACvD,SAAS,EAAE,QAAA,EAAqC;CAChD,KAAK;CACL,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAM;CAC5B,UAAU,EAAE,OAAO;EACjB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAM;EAC7C,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAM;EAC/C,CAAC;CACF,UAAU,EAAE,OAAO;EACjB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,IAAQ;EAClD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,IAAU;EACtD,CAAC;CACF,aAAa,EAAE,QAAQ,CAAC,IAAI,oCAAoC;CAChE,sBAAsB,EAAE,SAAS;CAIjC,aAAa,EAAE,QAAQ,CAAC,IAAI,oCAAoC,CAAC,UAAU;CAC3E,sBAAsB,EAAE,SAAS,CAAC,UAAU;CAC5C,WAAW,EACR,MAAM,yBAAyB,CAC/B,IAAI,mCAAmC;CAC1C,oBAAoB,EAAE,SAAS;CAa/B,gBAAgB,EACb,MACC,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ;EACjB,iBAAiB,EAAE,QAAQ;EAC3B,YAAY,EAAE,QAAQ;EACtB,UAAU,EAAE,QAAQ;EACpB,YAAY,EAAE,QAAQ;EACtB,YAAY,EAAE,QAAQ;EACtB,eAAe,EAAE,QAAQ;EACzB,WAAW,EAAE,QAAQ;EACrB,eAAe,EAAE,QAAQ;EACzB,QAAQ,EAAE,QAAQ;EAClB,SAAS,EAAE,QAAQ;EACnB,SAAS,EAAE,QAAQ;EACpB,CAAC,CACH,CACA,IAAI,IAAI,CACR,UAAU;CACb,yBAAyB,EAAE,SAAS,CAAC,UAAU;CAC/C,UAAU,EACP,MAAM,wBAAwB,CAC9B,IAAA,IAAsC,CACtC,QAAQ,EAAE,CAAC;CACd,mBAAmB,EAAE,SAAS,CAAC,QAAQ,MAAM;CAC7C,wBAAwB,EAAE,SAAS,CAAC,QAAQ,MAAM;CAClD,uBAAuB,EACpB,MAAM,qCAAqC,CAC3C,IAAI,IAAI,CACR,QAAQ,EAAE,CAAC;CACd,gCAAgC,EAAE,SAAS,CAAC,QAAQ,MAAM;CAC1D,sBAAsB,EACnB,MAAM,oCAAoC,CAC1C,IAAI,EAAE,CACN,QAAQ,EAAE,CAAC;CACd,sBAAsB,EACnB,MAAM,oCAAoC,CAC1C,IAAI,IAAI,CACR,QAAQ,EAAE,CAAC;CACd,+BAA+B,EAAE,SAAS,CAAC,QAAQ,MAAM;CAIzD,sBAAsB,EAAE,SAAS,CAAC,UAAU;CAC5C,OAAO,EAAE,MAAM,sBAAsB,CAAC,IAAA,IAAmC;CACzE,gBAAgB,EAAE,SAAS;CAC5B,CAAC;;;;;;;AAYF,SAAgB,gCACd,UACU;AACV,QAAO,SAAS,SACb,QACE,EAAE,cAAc,YACf,gBACA,CAAC,SAAS,SAAS,CAAC,SAAS,MAAM,SAAS,aAAa,CAAC,CAC7D,CACA,KAAK,EAAE,aAAa,OAAO;;AAGhC,MAAM,qCAAqC,EAAE,OAAO;CAClD,MAAM,EACH,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,KAAO,CACX,QAAQ,UACP,MAAM,WAAW,MAAM,IAAI,CAAC,WAAW,kCAAkC,CAC1E;CACH,QAAQ,EAAE,QAAQ,CAAC,MAAM,iBAAiB;CAC1C,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;CACxC,CAAC;AAEF,MAAM,qCAAqC,EAAE,OAAO;CAClD,MAAM,EAAE,KAAK,CAAC,YAAY,YAAY,CAAC;CACvC,QAAQ,EAAE,KAAK,CAAC,SAAS,iBAAiB,CAAC;CAC3C,kBAAkB,EAAE,OAAO;EACzB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa;EACrC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,aAAa;EACvC,CAAC;CACF,YAAY,EAAE,OAAO;EACnB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;EAClC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;EACpC,CAAC;CACF,aAAa,EAAE,QAAQ,CAAC,KAAK;CAC9B,CAAC;AAEF,MAAM,2CAA2C;AACjD,MAAM,oDAAoD;;;;;;;AAQ1D,SAAgB,qCAAqC,EACnD,kBACA,wBACA,kBAKU;CACV,MAAM,YAAY,yBAAyB;AAC3C,KAAI,aAAa,EAAG,QAAO;AAS3B,QAAO,aARc,KAAK,IACxB,KAAK,KACH,yBAAyB,yCAC1B,EACD,KAAK,KACH,iBAAiB,kDAClB,CACF;;AAIH,MAAa,kCAAkC,EAAE,OAAO;CACtD,SAAS,EAAE,QAAA,EAAqC;CAChD,YAAY,EAAE,QAAQ,CAAC,UAAU;CACjC,cAAc;CACd,UAAU;CACV,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI;CAC9C,mBAAmB,EAAE,OAAO;EAC1B,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAM;EAC7C,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAM;EAC/C,CAAC;CACF,YAAY,EAAE,OAAO;EACnB,MAAM,EACH,QAAQ,CACR,IAAI,EAAE,CACN,IAAI,KAAO,CACX,QAAQ,UACP,MACG,WAAW,MAAM,IAAI,CACrB,WAAW,kCAAkC,CACjD;EACH,QAAQ,EAAE,QAAQ,CAAC,MAAM,iBAAiB;EAC1C,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;EACvC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;EAClC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;EACpC,CAAC;CACF,WAAW,EACR,OAAO;EACN,MAAM,mCAAmC,UAAU;EACnD,UAAU,mCAAmC,UAAU;EAUvD,YAAY,mCAAmC,UAAU;EAQzD,SAAS,mCAAmC,UAAU;EACvD,CAAC,CACD,QAAQ,EAAE,CAAC;CAOd,iBAAiB,mCAAmC,UAAU;CAC9D,UAAU;CACV,mBAAmB,EAAE,QAAQ,CAAC,MAAM,iBAAiB;CACtD,CAAC;AAwDF,SAAS,8BACP,OACS;AACT,QAAO,QACL,MAAM,cACN,MAAM,OACN,MAAM,iBAAiB,MACpB,cAAc,UAAU,OAAO,UAAU,OAC3C,CACF;;AAGH,SAAS,oBAAoB,OAAyB;CACpD,MAAM,SAAmB,EAAE;CAC3B,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,IAAI,QAA0B;CAC9B,IAAI,UAAU;AAEd,MAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,YAAY,MAAM;AACxB,MAAI,SAAS;AACX,aAAU;AACV;;AAEF,MAAI,cAAc,MAAM;AACtB,aAAU;AACV;;AAEF,MAAI,OAAO;AACT,OAAI,cAAc,MAAO,SAAQ;AACjC;;AAEF,MAAI,cAAc,OAAO,cAAc,MAAK;AAC1C,WAAQ;AACR;;AAEF,MAAI,cAAc,KAAK;AACrB,YAAS;AACT;;AAEF,MAAI,cAAc,KAAK;AACrB,WAAQ,KAAK,IAAI,GAAG,QAAQ,EAAE;AAC9B;;AAEF,MAAI,cAAc,OAAO,UAAU,GAAG;AACpC,UAAO,KAAK,MAAM,MAAM,OAAO,MAAM,CAAC,MAAM,CAAC;AAC7C,WAAQ,QAAQ;;;AAGpB,QAAO,KAAK,MAAM,MAAM,MAAM,CAAC,MAAM,CAAC;AACtC,QAAO,OAAO,OAAO,QAAQ;;AAG/B,SAAS,wBAAwB,OAAwB;CACvD,MAAM,OAAO,MAAM,KACjB,MAAM,SAAS,iDAAiD,CACjE;AACD,KAAI,KAAK,SAAS,EAChB,QAAO,KAAK,MAAM,UAChB,SAAS,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,MAAM,CAAC,CACzD;CAEH,MAAM,WAAW,MAAM,MAAM,yCAAyC;AACtE,QAAO,QAAQ,WAAW,IAAI,MAAM,CAAC;;AASvC,SAAS,6BAA6B,OAAqC;AACzE,KAAI,CAAC,MAAO,QAAO;AAKnB,QAJmB,0BAA0B,MAAM,CAChD,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,MAAM,IACY;;AAGvB,SAAS,cAAc,OAA8B;CACnD,MAAM,UAAU,MAAM,MAAM,CAAC,QAAQ,gBAAgB,GAAG;AACxD,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI;EACF,MAAM,OAAO,IAAI,IAAI,QAAQ,CAAC;AAM9B,SALiB,mBAAmB,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAC9D,QAAQ,iBAAiB,GAAG,CAC5B,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,IACP;SACb;AACN,SAAO;;;AAIX,SAAS,kBAAkB,OAAyB;AAClD,QAAO,MAAM,KACX,MAAM,SAAS,iDAAiD,CACjE,CACE,KAAK,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,MAAM,CAAC,CAC/D,OAAO,QAAQ;;AAGpB,SAAS,gBACP,OACU;AAWV,QAVmB;EACjB,MAAM;EACN,MAAM;EACN,GAAG,MAAM,iBAAiB,SAAS,EAAE,KAAK,aAAa,CACrD,KACA,GAAG,OACA,MAAM,IAAI,CACV,KAAK,cAAc,UAAU,MAAM,CAAC,MAAM,MAAM,CAAC,MAAM,GAAG,CAC9D,CAAC;EACH,CACiB,QAAQ,cACxB,QAAQ,UAAU,CACnB;;AAGH,SAAS,yBACP,OAC2B;AAC3B,QAAO,MAAM,SAAS,SAAS;EAC7B,MAAM,gBACJ,6BAA6B,KAAK,IAAI,IACtC,6BAA6B,KAAK,YAAY;AAChD,MAAI,KAAK,SAAS,QAChB,QAAO,CACL;GACE,YAAY,8BAA8B,KAAK;GAC/C;GACA,WAAW,gBAAgB,KAAK,CAC7B,IAAI,cAAc,CAClB,QAAQ,QAAuB,QAAQ,IAAI,CAAC;GAChD,CACF;AAEH,MAAI,KAAK,SAAS,gBAAgB,CAAC,KAAK,WAAY,QAAO,EAAE;AAE7D,SAAO,oBAAoB,KAAK,WAAW,CACxC,QAAQ,UAAU,uCAAuC,KAAK,MAAM,CAAC,CACrE,KAAK,WAAW;GACf,YAAY,wBAAwB,MAAM;GAC1C;GACA,WAAW,kBAAkB,MAAM,CAChC,IAAI,cAAc,CAClB,QAAQ,QAAuB,QAAQ,IAAI,CAAC;GAChD,EAAE;GACL;;AAGJ,SAAS,yBACP,YACA,aACS;AACT,QAAO,WAAW,MAAM,cACtB,YAAY,MACT,eACC,cAAc,cACd,UAAU,WAAW,GAAG,WAAW,GAAG,IACtC,WAAW,WAAW,GAAG,UAAU,GAAG,CACzC,CACF;;AAGH,SAAS,2BACP,QACA,SACS;AACT,QAAO,QACJ,OAAO,iBACN,QAAQ,iBACR,OAAO,kBAAkB,QAAQ,iBACnC,yBAAyB,OAAO,WAAW,QAAQ,UAAU,CAC9D;;AAGH,SAAS,wBAAwB,YAA6C;AAC5E,QACE,WAAW,iBACX,WAAW,UAAU,MACrB;;;;;;;;;;AAYJ,SAAgB,0BACd,QACA,SAC6B;CAC7B,MAAM,eAAe,yBAAyB,OAAO,MAAM;CAC3D,MAAM,gBAAgB,yBAAyB,QAAQ,MAAM;CAC7D,MAAM,mBAAmB,aAAa,QACnC,EAAE,iBAAiB,WACrB,CAAC;CACF,MAAM,oBAAoB,cAAc,QACrC,EAAE,iBAAiB,WACrB,CAAC;CACF,MAAM,aAA0C,EAAE;AAElD,KAAI,aAAa,WAAW,cAAc,OACxC,YAAW,KAAK;EACd,OAAO,KAAK,IAAI,aAAa,QAAQ,cAAc,OAAO,GAAG;EAC7D,SAAS,CACP,gCAAgC,aAAa,OAAO,aAAa,cAAc,SAChF;EACF,CAAC;AAEJ,KAAI,aAAa,WAAW,cAAc,QAAQ;EAChD,MAAM,0BAA0B,IAAI,IAAI,cAAc,MAAM,CAAC;AAC7D,eAAa,SAAS,aAAa,gBAAgB;GACjD,MAAM,eAAe,MAAM,KAAK,wBAAwB,CAAC,MAAM,UAC7D,2BAA2B,aAAa,cAAc,OAAQ,CAC/D;AACD,OAAI,iBAAiB,KAAA,GAAW;AAC9B,eAAW,KAAK;KACd,OAAO,cAAc;KACrB,SAAS,CACP,6CAA6C,wBAAwB,YAAY,CAAC,GACnF;KACF,CAAC;AACF;;AAEF,2BAAwB,OAAO,aAAa;IAC5C;;AAEJ,KAAI,qBAAqB,aAAa,OACpC,YAAW,KAAK;EACd,OAAO,mBAAmB;EAC1B,SAAS,CACP,GAAG,aAAa,SAAS,iBAAiB,0EAC3C;EACF,CAAC;AAEJ,KAAI,sBAAsB,cAAc,OACtC,YAAW,KAAK;EACd,OAAO,oBAAoB;EAC3B,SAAS,CACP,GAAG,cAAc,SAAS,kBAAkB,2EAC7C;EACF,CAAC;AAGJ,QAAO;EACL,OACE,CAAC,OAAO,kBACR,CAAC,QAAQ,kBACT,qBAAqB,aAAa,UAClC,sBAAsB,cAAc,UACpC,WAAW,WAAW;EACxB,aAAa,aAAa;EAC1B,cAAc,cAAc;EAC5B;EACA;EACA,iBAAiB,OAAO;EACxB,kBAAkB,QAAQ;EAC1B;EACD;;AAGH,SAAS,8BACP,OACS;AACT,QAAO,QACL,MAAM,cACN,MAAM,OACN,MAAM,iBAAiB,MACpB,cAAc,UAAU,OAAO,UAAU,OAC3C,CACF;;AAGH,SAAS,yBACP,OAC8B;CAC9B,MAAM,QACJ,MAAM,cAAc,MAAM,aAAa,IACnC,MAAM,aACN,MAAM,KAAK;CACjB,MAAM,SACJ,MAAM,eAAe,MAAM,cAAc,IACrC,MAAM,cACN,MAAM,KAAK;CACjB,MAAM,QAAQ,SAAS,IAAI,QAAQ,SAAS;AAC5C,KAAI,QAAQ,IAAK,QAAO;AACxB,KAAI,QAAQ,GAAK,QAAO;AACxB,QAAO;;;;;;;;AAST,SAAgB,0BACd,QACA,SAC6B;CAC7B,MAAM,eAAe,OAAO,MAAM,QAAQ,UAAU,MAAM,SAAS,QAAQ;CAC3E,MAAM,gBAAgB,QAAQ,MAAM,QAAQ,UAAU,MAAM,SAAS,QAAQ;CAC7E,MAAM,mBAAmB,aAAa,OACpC,8BACD,CAAC;CACF,MAAM,oBAAoB,cAAc,OACtC,8BACD,CAAC;CACF,MAAM,aAA0C,EAAE;CAClD,MAAM,YAAY,KAAK,IAAI,aAAa,QAAQ,cAAc,OAAO;CACrE,MAAM,eAAe;EACnB;EACA;EACA;EACA;EACA;EACD;AAED,MAAK,IAAI,QAAQ,GAAG,QAAQ,WAAW,SAAS,GAAG;EACjD,MAAM,cAAc,aAAa;EACjC,MAAM,eAAe,cAAc;EACnC,MAAM,UAAoB,EAAE;EAC5B,MAAM,oBAAoB,yBAAyB,YAAY;EAC/D,MAAM,qBAAqB,yBAAyB,aAAa;AACjE,MAAI,sBAAsB,mBACxB,SAAQ,KACN,eAAe,kBAAkB,UAAU,qBAC5C;AAEH,MAAI,CAAC,8BAA8B,YAAY,CAC7C,SAAQ,KAAK,2DAA2D;AAE1E,MAAI,CAAC,8BAA8B,aAAa,CAC9C,SAAQ,KAAK,oDAAoD;AAEnE,OAAK,MAAM,OAAO,aAChB,KAAI,YAAY,SAAS,QAAQ,YAAY,SAAS,aAAa,KACjE,SAAQ,KACN,GAAG,IAAI,YAAY,OAAO,YAAY,KAAK,CAAC,aAAa,OAAO,aAAa,KAAK,GACnF;AAGL,MAAI,QAAQ,SAAS,EAAG,YAAW,KAAK;GAAE,OAAO,QAAQ;GAAG;GAAS,CAAC;;AAGxE,KAAI,aAAa,WAAW,cAAc,OACxC,YAAW,KAAK;EACd,OAAO,YAAY;EACnB,SAAS,CACP,wBAAwB,aAAa,OAAO,aAAa,cAAc,SACxE;EACF,CAAC;AAGJ,QAAO;EACL,OACE,CAAC,OAAO,kBACR,CAAC,QAAQ,kBACT,qBAAqB,aAAa,UAClC,sBAAsB,cAAc,UACpC,WAAW,WAAW;EACxB,aAAa,aAAa;EAC1B,cAAc,cAAc;EAC5B;EACA;EACA,iBAAiB,OAAO;EACxB,kBAAkB,QAAQ;EAC1B;EACD;;AAGH,SAAgB,0BAA0B,OAAuB;AAC/D,QAAO,MACJ,WAAW,QAAU,IAAI,CACzB,QAAQ,UAAU,KAAK,CACvB,MAAM,KAAK,CACX,KAAK,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAAC,CAC/C,OAAO,QAAQ,CACf,KAAK,KAAK;;AAGf,SAAgB,uBAAuB,OAAuB;AAC5D,QAAO,WAAW,SAAS,CACxB,OAAO,0BAA0B,MAAM,CAAC,CACxC,OAAO,MAAM;;AAGlB,SAAgB,uBACd,QACA,SAC2B;AAC3B,KACE,OAAO,gBAAgB,KAAA,KACvB,OAAO,yBAAyB,KAAA,KAChC,QAAQ,gBAAgB,KAAA,KACxB,QAAQ,yBAAyB,KAAA,EAEjC,QAAO;EACL,YAAY,OAAO;EACnB,aAAa,QAAQ;EACrB,aAAa;EACb,iBAAiB,OAAO;EACxB,kBAAkB,QAAQ;EAC3B;AAEH,QAAO;EACL,YAAY,OAAO;EACnB,aAAa,QAAQ;EACrB,aAAa;EACb,iBAAiB,OAAO;EACxB,kBAAkB,QAAQ;EAC3B;;AAGH,SAAgB,wBACd,aACA,cAC4B;CAC5B,MAAM,SAAS,0BAA0B,YAAY;CACrD,MAAM,UAAU,0BAA0B,aAAa;CACvD,MAAM,cAAc,SAAS,OAAO,MAAM,KAAK,GAAG,EAAE;CACpD,MAAM,eAAe,UAAU,QAAQ,MAAM,KAAK,GAAG,EAAE;CACvD,MAAM,mBAAmB,UACvB,MAAM,SAAS,MAAM,cACnB,KAAK,MAAM,IAAI,CAAC,KAAK,WAAW;EAAE;EAAW;EAAO,EAAE,CACvD;CACH,MAAM,eAAe,gBAAgB,YAAY;CACjD,MAAM,gBAAgB,gBAAgB,aAAa;CACnD,MAAM,mBAAmB,aAAa,KAAK,EAAE,YAAY,MAAM,CAAC,KAAK,IAAI;CACzE,MAAM,oBAAoB,cAAc,KAAK,EAAE,YAAY,MAAM,CAAC,KAAK,IAAI;CAC3E,MAAM,aAAa,KAAK,IAAI,aAAa,QAAQ,cAAc,OAAO;CACtE,IAAI,0BAAyC;AAC7C,MAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,SAAS,EAC/C,KAAI,aAAa,QAAQ,UAAU,cAAc,QAAQ,OAAO;AAC9D,4BAA0B;AAC1B;;CAGJ,MAAM,qBACJ,4BAA4B,OACxB,OACC,aAAa,0BAA0B,aACxC,KAAK,IAAI,YAAY,SAAS,GAAG,EAAE;CACzC,MAAM,sBACJ,4BAA4B,OACxB,OACC,cAAc,0BAA0B,aACzC,KAAK,IAAI,aAAa,SAAS,GAAG,EAAE;CAC1C,MAAM,oBAAoB,UACxB,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;AAClD,QAAO;EAIL,OAAO,qBAAqB;EAC5B,kBAAkB,iBAAiB,iBAAiB;EACpD,mBAAmB,iBAAiB,kBAAkB;EACtD,kBAAkB,OAAO;EACzB,mBAAmB,QAAQ;EAC3B,aAAa,YAAY;EACzB,cAAc,aAAa;EAC3B,mBACE,uBAAuB,OAAO,OAAO,qBAAqB;EAC5D,gBACE,uBAAuB,OACnB,OACC,YAAY,uBAAuB;EAC1C,iBACE,wBAAwB,OACpB,OACC,aAAa,wBAAwB;EAC7C;;AAGH,SAAgB,gCACd,OAC4B;CAC5B,MAAM,YACJ,OAAO,UAAU,WAAY,KAAK,MAAM,MAAM,GAAe;CAC/D,MAAM,SAAS,iCAAiC,MAAM,UAAU;AAChE,QAAO;EACL,GAAG;EACH,aAAa,0BAA0B,OAAO,YAAY;EAC1D,GAAI,OAAO,gBAAgB,KAAA,IACvB,EAAE,GACF,EAAE,aAAa,0BAA0B,OAAO,YAAY,EAAE;EACnE;;;;;;;;AASH,SAAgB,yCACd,OACA,mBAC4B;CAC5B,MAAM,YACJ,iCAAiC,MAAM,SAAS,MAAM,kBAAkB;CAC1E,MAAM,YACJ,OAAO,UAAU,WAAY,KAAK,MAAM,MAAM,GAAe;CAC/D,MAAM,oBAAoB,EACvB,OAAO,EACN,UAAU,EAAE,OAAO;EACjB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAQ;EAC/C,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAU;EACnD,CAAC,EACH,CAAC,CACD,aAAa,CACb,MAAM,UAAU;AAEnB,KACE,kBAAkB,SAAS,UAAU,UAAU,SAC/C,kBAAkB,SAAS,WAAW,UAAU,OAEhD,OAAM,IAAI,MACR,sCAAsC,kBAAkB,SAAS,MAAM,GAAG,kBAAkB,SAAS,OAAO,gCAAgC,UAAU,MAAM,GAAG,UAAU,OAAO,YACjL;AAGH,QAAO,gCAAgC,kBAAkB;;AAG3D,SAAgB,+BACd,OAC2B;CAC3B,MAAM,YACJ,OAAO,UAAU,WAAY,KAAK,MAAM,MAAM,GAAe;CAC/D,MAAM,SAAS,gCAAgC,MAAM,UAAU;CAC/D,MAAM,WAAW,gCAAgC,OAAO,SAAS;CACjE,MAAM,oBAAoB,uBAAuB,SAAS,YAAY;AACtE,KAAI,sBAAsB,OAAO,kBAC/B,OAAM,IAAI,MACR,yEACD;AAEH,KACE,SAAS,QAAQ,OAAO,YACxB,SAAS,SAAS,UAAU,OAAO,kBAAkB,SACrD,SAAS,SAAS,WAAW,OAAO,kBAAkB,OAEtD,OAAM,IAAI,MACR,0FACD;AAEH,KAAI,OAAO,iBAAiB;EAC1B,MAAM,WAAW,OAAO;EACxB,MAAM,2BACJ,SAAS,SAAS,aACd,OAAO,kBAAkB,SACzB,SAAS,iBAAiB;EAChC,MAAM,iBACJ,SAAS,SAAS,cAClB,SAAS,WAAW,WAAW,SAAS,iBAAiB,SACrD,UACA;AACN,MACE,SAAS,WAAW,UAAU,OAAO,WAAW,SAChD,SAAS,WAAW,WAAW,OAAO,WAAW,UACjD,SAAS,WAAW,UAAU,OAAO,kBAAkB,SACtD,SAAS,SAAS,cACjB,SAAS,WAAW,WAAW,OAAO,kBAAkB,UACzD,SAAS,SAAS,eACjB,CAAC,qCAAqC;GACpC,kBAAkB,SAAS,WAAW;GACtC,wBAAwB,SAAS,iBAAiB;GAClD,gBAAgB,OAAO,kBAAkB;GAC1C,CAAC,IACJ,SAAS,iBAAiB,UAAU,SAAS,SAAS,SACtD,SAAS,iBAAiB,WAAW,SAAS,SAAS,UACvD,SAAS,gBACP,SAAS,WAAW,SAAS,4BAC/B,SAAS,WAAW,eAEpB,OAAM,IAAI,MACR,4GACD;;AAGL,QAAO;EAAE,GAAG;EAAQ;EAAU;EAAmB;;;;;;;AAQnD,MAAa,+CAA+C,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA6FrC,0BAA0B,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAyH9C,oBAAoB,UAAU,CAAC;;;;;;;;wBAQ/B,sBAAsB,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkdzD,MAAa,uCAAuC,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAujB5B,sBAAsB,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAgmBpD,6CAA6C;;;;;;;;;;;;;;;;;;;eAmB7C,6CAA6C;;;;;;;;;;;;;;;;;;;;;ACr7F5D,MAAa,oBAAoB;;AAEjC,MAAa,mBAAmB;;AAEhC,MAAa,mBAAmB,MAAM;;AAEtC,MAAa,4BAA4B,KAAK;;AAE9C,MAAa,2BAA2B,KAAK,OAAO;;;AAGpD,MAAa,qBAAqB;;;;;;AASlC,SAAgB,4BACd,kBACA,MACA,qBACU;CACV,IAAI,YAAY,sBACZ,4BACA;CACJ,MAAM,QAAkB,EAAE;CAC1B,MAAM,qBACJ,uBAAuB,iBAAiB,SAAS,WAAW,GACxD,CACE,YACA,GAAG,iBAAiB,QAAQ,WAAW,WAAW,WAAW,CAC9D,GACD;AAEN,MAAK,MAAM,UAAU,oBAAoB;EACvC,MAAM,UAAU,KAAK;AACrB,MAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,EAAG;AAEzD,MAAI,QAAQ,UAAU,WAAW;AAC/B,SAAM,KAAK,QAAQ,OAAO,QAAQ,UAAU;AAC5C,gBAAa,QAAQ;AACrB,OAAI,aAAa,EAAG;AACpB;;EAGF,MAAM,SAAS,sBACX,wCAAwC,OAAO,mDAAmD,QAAQ,OAAO,gBACjH,2BAA2B,QAAQ,OAAO;EAC9C,MAAM,gBAAgB,KAAK,IAAI,GAAG,YAAY,OAAO,OAAO;AAC5D,QAAM,KACJ,QAAQ,OAAO,QAAQ,QAAQ,MAAM,GAAG,cAAc,GAAG,SAC1D;AACD;;AAGF,QAAO;;AAGT,SAAgB,iCACd,kBACA,MACA,WACS;AAKT,QAJ2B,iBAAiB,QACzC,WACC,WAAW,UAAU,WAAW,WACnC,CACyB,OAAO,WAAW;EAC1C,MAAM,UAAU,KAAK;AACrB,SACE,OAAO,YAAY,YACnB,QAAQ,SAAS,KACjB,YAAY,YAAY,KAAA;GAE1B;;AAGJ,SAAgB,uBAAuB,EACrC,cACA,UACA,sBACA,0BAMC;CACD,MAAM,mBACJ,OAAO,SAAS,iBAAiB,WAC7B,SAAS,eACT,OAAO,SAAS,WAAW,WACzB,SAAS,SACT;CACR,MAAM,mBAAmB,sBAAsB;AAE/C,QAAO;EACL,eAAe;EACf,WAAW,oBAAoB;EAC/B,GAAI,oBAAoB,qBAAqB,mBACzC,EAAE,6BAA6B,kBAAkB,GACjD,EAAE;EACN,aAAa,0BAA0B,SAAS,iBAAiB;EACjE,OAAO,SAAS,YAAY;EAC5B,aAAa,SAAS,kBAAkB;EACzC;;AAGH,SAAgB,yBAAyB,WAA2B;AAClE,SAAQ,UAAU,aAAa,EAA/B;EACE,KAAK,aACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;;;;;;;;AAWb,SAAgB,wBAAwB,EACtC,WACA,mBACA,WACA,YACA,QAOS;CACT,MAAM,SAAS,IAAI,IAAI,UAAU;CACjC,MAAM,YAAY,GAAG,OAAO,WAAW,OAAO,WAC3C,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,UAAU,GAAG,CACrB,MAAM,GAAG,GAAG;CACf,MAAM,oBAAoB,WAAW,SAAS,CAC3C,OAAO,OAAO,KAAK,CACnB,OAAO,MAAM,CACb,MAAM,GAAG,GAAG;CACf,MAAM,WAAW,mBAAmB;CACpC,MAAM,eAAe,WACjB,GAAG,SAAS,MAAM,GAAG,SAAS,WAC9B;CACJ,MAAM,WAAW,mBAAmB,WAChC,GAAG,aAAa,SAChB;CACJ,MAAM,YAAY,WACf,QAAQ,SAAS,IAAI,CACrB,QAAQ,KAAK,IAAI,CACjB,QAAQ,KAAK,GAAG;AACnB,QAAO,UAAU,aAAa,OAAO,GAAG,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,yBAAyB,UAAU;;;AAInI,SAAgB,0BACd,wBACA,QACQ;CACR,MAAM,YACJ,WAAW,aACP,OACA,WAAW,eACT,QACA,WAAW,YACT,aACA,WAAW,kBACT,uBACA;AACZ,QAAO,GAAG,uBAAuB,QAAQ,YAAY,GAAG,CAAC,GAAG;;;;;;;AA6B9D,SAAgB,0BACd,OAC2B;CAC3B,MAAM,WAAW,MAAM,mBAAmB;AAC1C,KAAI,CAAC,SACH,OAAM,IAAI,MACR,iGACD;CAEH,MAAM,aAAa,MAAM;CACzB,MAAM,WAAW,MAAM,mBAAmB,aAAa;CACvD,MAAM,sBAAsB,WACxB,MAAM,SAAS,SAAS,SACxB,SAAS;AACb,KACE,WAAW,UAAU,SAAS,SAC7B,CAAC,YAAY,WAAW,WAAW,uBACnC,YACC,CAAC,qCAAqC;EACpC,kBAAkB,WAAW;EAC7B,wBAAwB;EACxB,gBAAgB,SAAS;EAC1B,CAAC,EACJ;EACA,MAAM,mBAAmB,WACrB,iBAAiB,SAAS,MAAM,+BAA+B,oBAAoB,gCACnF,cAAc,SAAS,MAAM,GAAG,oBAAoB;AACxD,QAAM,IAAI,MACR,sCAAsC,WAAW,MAAM,GAAG,WAAW,OAAO,oBAAoB,iBAAiB,8EAClH;;CAEH,MAAM,kBAEF;EACF,MAAM,WAAW,cAAc;EAC/B,QACE,CAAC,YAAY,WAAW,WAAW,MAAM,SAAS,SAAS,SACvD,UACA;EACN,kBAAkB,MAAM,SAAS;EACjC,YAAY;GAAE,OAAO,WAAW;GAAO,QAAQ,WAAW;GAAQ;EAClE,aAAa,WAAW,SAAS;EAClC;AACD,QAAO,+BAA+B;EACpC,SAAS;EACT,YAAY,MAAM;EAClB,cAAc,MAAM;EACpB,UAAU,MAAM,SAAS;EACzB,YAAY,MAAM;EAClB,mBAAmB;EACnB,YAAY;GACV,MAAM,WAAW;GACjB,QAAQ,WAAW;GACnB,YAAY,WAAW;GACvB,OAAO,WAAW;GAClB,QAAQ,WAAW;GACpB;EACD,WAAW,OAAO,YAChB,MAAM,UAAU,KAAK,aAAa,CAChC,SAAS,QACT;GACE,MAAM,SAAS;GACf,QAAQ,SAAS;GACjB,YAAY,SAAS;GACtB,CACF,CAAC,CACH;EACD;EACA,UAAU,MAAM;EAChB,mBAAmB,uBAAuB,MAAM,SAAS,YAAY;EACtE,CAAC;;;;;;;;;;AClSJ,MAAa,uBAAuB;AACpC,MAAa,yBAAyB;AA0BtC,IAAa,sBAAb,cAAyC,MAAM;CAC7C,YACE,MACA,SACA,UAA4C,EAAE,EAC9C;AACA,QAAM,QAAQ;AAJL,OAAA,OAAA;AAEA,OAAA,UAAA;AAGT,OAAK,OAAO;;;AAyBhB,SAAS,sBAAsB,OAA8C;AAC3E,QACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,OAAQ,MAA8B,WAAW,YACjD,UAAU;;AAId,SAAS,UAAU,MAAe,OAAwB;AACxD,QAAO,OAAO,SAAS,YAAY,SAAS,OACvC,KAAiC,SAClC,KAAA;;AAGN,SAAS,YAAY,MAAmC;CACtD,MAAM,QAAQ,UAAU,MAAM,QAAQ;AACtC,KAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;CAC1D,MAAM,UAAU,UAAU,MAAM,gBAAgB,IAAI,UAAU,MAAM,UAAU;AAC9E,QAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IACnD,UACA,KAAA;;AAGN,MAAM,oBAAoB,wCAAwC,uBAAuB;;AAGzF,SAAgB,gBAAgB,OAAiD;CAC/E,MAAM,SAAS,YAAY,MAAM,KAAK;CACtC,MAAM,SAAS,SAAS,KAAK,WAAW;AACxC,SAAQ,MAAM,QAAd;EACE,KAAK;EACL,KAAK,IACH,QAAO,IAAI,oBACT,qBACA,iCAAiC,MAAM,OAAO,GAAG,OAAO,4EACxD,EAAE,QAAQ,MAAM,QAAQ,CACzB;EACH,KAAK,IACH,QAAO,IAAI,oBACT,8BACA,4EAA4E,qBAC5E,EAAE,QAAQ,KAAK,CAChB;EACH,KAAK,IACH,QAAO,IAAI,oBACT,yBACA,8CAA8C,OAAO,IACrD;GACE,QAAQ;GACR,GAAI,UAAU,MAAM,MAAM,SAAS,KAAK,KAAA,IACpC,EAAE,GACF,EAAE,QAAQ,UAAU,MAAM,MAAM,SAAS,EAAE;GAChD,CACF;EACH,KAAK,IACH,QAAO,IAAI,oBACT,sBACA,oDAAoD,MAAM,aAAa,iBAAiB,MAAM,aAAa,QAAQ,KAAK,MAAM,WAAW,GAAG,MAAM,OAAO,gBAAgB,IACzK;GAAE,QAAQ;GAAK,aAAa,MAAM;GAAY,CAC/C;EACH,KAAK,IACH,QAAO,IAAI,oBACT,yBACA,+DAA+D,OAAO,IACtE;GACE,QAAQ;GACR,iBAAiB,UAAU,MAAM,MAAM,kBAAkB,IAAI;GAC9D,CACF;EACH,KAAK,IACH,QAAO,IAAI,oBACT,wBACA,yEAAyE,qBACzE,EAAE,QAAQ,KAAK,CAChB;EACH,KAAK,IACH,QAAO,IAAI,oBACT,iBACA,gFACA,EAAE,QAAQ,KAAK,CAChB;EACH,QACE,QAAO,IAAI,oBACT,gBACA,uCAAuC,MAAM,OAAO,GAAG,OAAO,IAC9D,EAAE,QAAQ,MAAM,QAAQ,CACzB;;;AAIP,SAAS,aAAa,OAAgB,QAAqC;AACzE,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,oBACR,0BACA,GAAG,OAAO,iCACX;AAEH,QAAO;;AAGT,eAAe,uBACb,SACA,QACA,QACA,WAC8B;CAC9B,MAAM,WAAW,MAAM,UAAU,mBAAmB;EAClD,QAAQ;EACR,SAAS;GACP,eAAe,UAAU;GACzB,gBAAgB;GAChB,QAAQ;GACT;EACD,MAAM,KAAK,UAAU,QAAQ;EAC7B;EACD,CAAC;AACF,KAAI,CAAC,SAAS,IAAI;EAChB,IAAI,SAAS;AACb,MAAI;AACF,YAAS,MAAM,SAAS,MAAM;UACxB;AAGR,QAAM,IAAI,oBACR,oBACA,0BAA0B,SAAS,OAAO,GAAG,SAAS,KAAK,OAAO,MAAM,GAAG,IAAI,KAAK,MACpF,EAAE,QAAQ,SAAS,QAAQ,CAC5B;;CAEH,MAAM,OAAQ,MAAM,SAAS,MAAM;AAKnC,KAAI,CAAC,KAAK,WAAW,CAAC,KAAK,KACzB,OAAM,IAAI,oBACR,oBACA,4BAA4B,KAAK,SAAS,qEAC3C;AAEH,QAAO,aAAa,KAAK,MAAM,YAAY;;;;;;AAO7C,eAAsB,cACpB,SACA,SACA,MACuB;CACvB,MAAM,SAAS,KAAK,iBAAiB,MAAM,IAAI,KAAA;CAC/C,MAAM,SAAS,OAAO,mBAAmD;AACvE,MAAI,CAAC,OACH,OAAM,IAAI,oBACR,yBACA,wEAAwE,uBAAuB,4CAChG;AAQH,SAAO;GACL,MAPW,MAAM,uBACjB,SACA,QACA,QAAQ,QACR,KAAK,MACN;GAGC,KAAK;GACL,GAAI,iBAAiB,EAAE,gBAAgB,GAAG,EAAE;GAC7C;;AAGH,KAAI,QAAQ,OAAQ,QAAO,QAAQ;AAEnC,KAAI,CAAC,KAAK,gBAAgB;AACxB,MAAI,OAAQ,QAAO,OAAO,yBAAyB;AACnD,QAAM,IAAI,oBACR,iBACA,sGAAsG,uBAAuB,+BAC9H;;CAGH,IAAI;AACJ,KAAI;AACF,SAAO,MAAM,KAAK,eAAe,SAAS,QAAQ,OAAO;UAClD,OAAO;AACd,MAAI,CAAC,sBAAsB,MAAM,CAAE,OAAM;AACzC,OAAK,MAAM,WAAW,OAAO,MAAM,WAAW,QAAQ,OACpD,QAAO,OACL,MAAM,WAAW,MACb,iDACA,iDACL;AAEH,QAAM,gBAAgB,MAAM;;AAE9B,QAAO;EAAE,MAAM,aAAa,UAAU,MAAM,OAAO,EAAE,QAAQ;EAAE,KAAK;EAAS;;;;;;;AAQ/E,SAAgB,uBACd,SACA,OACA,YAA0B,OACY;AACtC,KAAI,CAAC,MAAO,QAAO;AACnB,QAAO,OAAO,SAAS,WAAW;EAChC,MAAM,WAAW,MAAM,UACrB,GAAG,QAAQ,QAAQ,QAAQ,GAAG,GAAG,wBACjC;GACE,QAAQ;GACR,SAAS;IACP,eAAe,UAAU;IACzB,gBAAgB;IAChB,QAAQ;IACT;GACD,MAAM,KAAK,UAAU,QAAQ;GAC7B;GACD,CACF;EACD,IAAI,OAAgB;AACpB,MAAI;AACF,UAAO,MAAM,SAAS,MAAM;UACtB;AAGR,MAAI,CAAC,SAAS,GAMZ,OALqC;GACnC,QAAQ,SAAS;GACjB;GACA,YAAY,SAAS,QAAQ,IAAI,cAAc;GAChD;AAGH,SAAO;;;;;ACxOX,SAAgB,2CAA2C,EACzD,qBACA,mBACA,oBAKU;AACV,QACE,wBAAwB,QACxB,mBAAmB,aAAa,QAChC,iBAAiB,MAAM,WAAW,WAAW,aAAa,KACzD,kBAAkB,UAAU,SAAS,OAAO,qBAAqB;;;;;;;;AAiBtE,SAAgB,qCACd,MACA,wBACoB;AACpB,KAAI,wBAAwB;EAC1B,MAAM,oBAAoB,KAAK,SAAS,eAAe,EAAE;AACzD,OAAK,IAAI,QAAQ,kBAAkB,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;GACrE,MAAM,YAAY,kBAAkB;AACpC,OAAI,OAAO,cAAc,YAAY,UAAU,SAAS,EACtD,QAAO;;;AAIb,QAAO,OAAO,KAAK,eAAe,YAAY,KAAK,WAAW,SAAS,IACnE,KAAK,aACL,KAAA;;AAGN,MAAM,mCAAmC,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;AAsBnD,MAAM,mCAAmC,OAAO,GAAG;;;;;;;AAQnD,MAAM,qDAAqD,OAAO,GAAG;;;;AAKrE,SAAS,0CACP,SACoB;CACpB,MAAM,UAAU,QAAQ,SAAS,WAC/B,OAAO,SAAS,WAAW,OAAO,SAAS,YACvC,CACE;EACE,UAAU,OAAO;EACjB,kBAAkB,OAAO,SAAS;EAClC,eACE,OAAO,SAAS,YAAY,OAAO,iBAAiB;EACvD,CACF,GACD,EAAE,CACP;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CACjC,MAAM,oBAAoB,KAAK,UAAU,QAAQ,CAC9C,WAAW,KAAK,UAAU,CAC1B,WAAW,UAAU,UAAU,CAC/B,WAAW,UAAU,UAAU;AAClC,QAAO,OAAO,GAAG;;;;;mBAKA,kBAAkB;;;;;;;;;AAUrC,MAAM,4CAA4C,OAAO,GAAG;;;;;;;;;;;;AAa5D,SAAgB,4BAA4B,EAC1C,KACA,SACA,iBACA,mBACA,SACA,qBACA,uBAC0D;AAC1D,KACE,uBACA,oBAAoB,SAAS,KAC7B,EAAE,uBAAuB,mBAAmB,UAE5C,OAAM,IAAI,MACR,gFACD;CAEH,MAAM,mBACJ,WAAW,QAAQ,SAAS,IAAI,CAAC,GAAG,QAAQ,GAAG,CAAC,WAAoB;AACtE,KAAI,qBAAqB,CAAC,iBAAiB,SAAS,aAAa,CAC/D,kBAAiB,KAAK,aAAa;AAOrC,KACE,uBACA,iBAAiB,SAAS,OAAO,IACjC,CAAC,iBAAiB,SAAS,UAAU,CAErC,kBAAiB,KAAK,UAAU;CAGlC,MAAM,+BACJ,2CAA2C;EACzC;EACA;EACA;EACD,CAAC;CACJ,MAAM,oBAAuD,iBAC1D,QACE,WAAW,WAAW,gBAAgB,CAAC,6BACzC,CACA,KAAK,WAAW;AACf,MAAI,WAAW,aAAc,QAAO;AACpC,SAAO;GACL,MAAM;GACN,GAAI,mBAAmB,aAAa,KAAA,IAChC,EAAE,GACF,EAAE,UAAU,kBAAkB,UAAU;GAC5C,GAAI,mBAAmB,YAAY,KAAA,IAC/B,EAAE,GACF,EAAE,SAAS,kBAAkB,SAAS;GAC1C,GAAI,mBAAmB,WACnB,EAAE,UAAU,kBAAkB,UAAU,GACxC,EAAE;GACP;GACD;CAEJ,MAAM,oBAAuC,EAAE;AAC/C,KAAI,uBAAuB,mBAAmB,UAAU;AAStD,oBAAkB,KAAK;GACrB,MAAM;GACN,UAAU;GACV,SAAS;GACT,UAAU,kBAAkB;GAC7B,CAAC;AACF,oBAAkB,KAAK;GACrB,MAAM;GACN,QAAQ;GACT,CAAC;;AAEJ,mBAAkB,KAAK,GAAI,WAAW,EAAE,CAAE;AAC1C,KAAI,qBAAqB;AACvB,MAAI,mBAAmB,UAAU;AAC/B,qBAAkB,KAAK;IACrB,MAAM;IACN,QAAQ;IACT,CAAC;AACF,OAAI,uBAAuB,oBAAoB,SAAS,EAKtD,mBAAkB,KAAK;IACrB,MAAM;IACN,QAAQ;IACT,CAAC;;AAGN,oBAAkB,KAAK;GACrB,MAAM;GACN,QAAQ,mBAAmB,WACvB,uCACA;GACL,CAAC;AACF,MAAI,uBAAuB,oBAAoB,SAAS,GAAG;GACzD,MAAM,gBACJ,0CAA0C,oBAAoB;AAChE,OAAI,cACF,mBAAkB,KAAK;IACrB,MAAM;IACN,QAAQ;IACT,CAAC;AAEJ,qBAAkB,KAChB,GAAG,oBAAoB,KAAK,WAC1B,OAAO,SAAS,YACZ;IAAE,MAAM;IAAkB,UAAU,OAAO;IAAU,GACrD,OACL,CACF;AAOD,qBAAkB,KAAK;IACrB,MAAM;IACN,QAAQ;IACT,CAAC;;AAEJ,MAAI,mBAAmB,YAAY,8BAA8B;AAG/D,qBAAkB,KAAK;IACrB,MAAM;IACN,UAAU;IACV,GAAI,kBAAkB,YAAY,KAAA,IAC9B,EAAE,GACF,EAAE,SAAS,kBAAkB,SAAS;IAC1C,GAAI,kBAAkB,WAClB,EAAE,UAAU,kBAAkB,UAAU,GACxC,EAAE;IACP,CAAC;AACF,qBAAkB,KAAK;IACrB,MAAM;IACN,QAAQ;IACT,CAAC;;;AAIN,QAAO;EACL;EACA,SAAS;EACT,iBAAiB,mBAAmB;EAMpC,GAAI,mBAAmB,UAAU,UAAU,KAAA,KAC3C,kBAAkB,SAAS,SAAS,OACpC,EAAE,uBAAuB,kBAAkB,YACvC,EAAE,QAAQ,MAAM,GAChB,EAAE;EACN,GAAI,kBAAkB,SAAS,IAAI,EAAE,SAAS,mBAAmB,GAAG,EAAE;EACvE;;;;;AC9VH,SAAgB,oBACd,OAC+B;AAC/B,KACE,MAAM,UAAU,KAChB,MAAM,OAAO,OACb,MAAM,OAAO,MACb,MAAM,OAAO,MACb,MAAM,OAAO,GAEb,QAAO;AAET,KAAI,MAAM,UAAU,KAAK,MAAM,OAAO,OAAQ,MAAM,OAAO,IACzD,QAAO;AAET,KACE,MAAM,UAAU,MAChB,MAAM,OAAO,GAAG,EAAE,KAAK,UACvB,MAAM,OAAO,GAAG,EAAE,KAAK,OAEvB,QAAO;AAET,KAAI,MAAM,UAAU,KAAK,MAAM,OAAO,GAAG,EAAE,KAAK,MAAO,QAAO;AAC9D,QAAO;;AAGT,SAAS,MAAM,OAAmB,OAAe,QAAwB;AACvE,QAAO,OAAO,aAAa,GAAG,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC;;AAGtE,SAAS,aAAa,OAAmB,QAAwB;AAC/D,QAAQ,MAAM,WAAY,IAAK,MAAM,SAAS;;AAGhD,SAAS,aAAa,OAAmB,QAAwB;AAC/D,SACI,MAAM,WAAY,OAAQ,MAC3B,MAAM,SAAS,MAAO,OACtB,MAAM,SAAS,MAAO,KACvB,MAAM,SAAS;;AAInB,SAAS,aAAa,OAAmB,QAAwB;AAC/D,QACE,MAAM,UAAY,MAAM,SAAS,MAAO,IAAM,MAAM,SAAS,MAAO;;;;;;AAQxE,SAAgB,cAAc,OAAqC;CACjE,MAAM,OAAO,oBAAoB,MAAM;AACvC,KAAI;AACF,MAAI,SAAS,eAAe,MAAM,UAAU,GAC1C,QAAO;GAAE,OAAO,aAAa,OAAO,GAAG;GAAE,QAAQ,aAAa,OAAO,GAAG;GAAE;AAE5E,MAAI,SAAS,eAAe,MAAM,UAAU,GAC1C,QAAO;GACL,OAAO,MAAM,KAAO,MAAM,MAAO;GACjC,QAAQ,MAAM,KAAO,MAAM,MAAO;GACnC;AAEH,MAAI,SAAS,aAAc,QAAO,aAAa,MAAM;AACrD,MAAI,SAAS,aAAc,QAAO,aAAa,MAAM;SAC/C;AACN,SAAO;;AAET,QAAO;;AAGT,SAAS,aAAa,OAAqC;CACzD,IAAI,SAAS;AACb,QAAO,SAAS,IAAI,MAAM,QAAQ;AAChC,MAAI,MAAM,YAAY,IAAM,QAAO;EACnC,MAAM,SAAS,MAAM,SAAS;AAE9B,MAAI,WAAW,OAAS,UAAU,OAAQ,UAAU,KAAO;AACzD,aAAU;AACV;;EAEF,MAAM,SAAS,aAAa,OAAO,SAAS,EAAE;AAO9C,MALE,UAAU,OACV,UAAU,OACV,WAAW,OACX,WAAW,OACX,WAAW,IAEX,QAAO;GACL,QAAQ,aAAa,OAAO,SAAS,EAAE;GACvC,OAAO,aAAa,OAAO,SAAS,EAAE;GACvC;AAEH,YAAU,IAAI;;AAEhB,QAAO;;AAGT,SAAS,aAAa,OAAqC;CACzD,MAAM,QAAQ,MAAM,OAAO,IAAI,EAAE;AACjC,KAAI,UAAU,UAAU,MAAM,UAAU,GACtC,QAAO;EACL,OAAO,aAAa,OAAO,GAAG,GAAG;EACjC,QAAQ,aAAa,OAAO,GAAG,GAAG;EACnC;AAEH,KAAI,UAAU,UAAU,MAAM,UAAU,GACtC,QAAO;EACL,QAAQ,MAAM,MAAQ,MAAM,OAAQ,KAAM;EAC1C,SAAS,MAAM,MAAQ,MAAM,OAAQ,KAAM;EAC5C;AAEH,KAAI,UAAU,UAAU,MAAM,UAAU,IAAI;EAC1C,MAAM,OACJ,MAAM,MACL,MAAM,OAAQ,IACd,MAAM,OAAQ,KACb,MAAM,OAAQ,OAAQ;AAC1B,SAAO;GACL,QAAQ,OAAO,SAAU;GACzB,SAAU,SAAS,KAAM,SAAU;GACpC;;AAEH,QAAO;;;AAIT,SAAgB,YAAY,OAAmB,OAA6B;CAC1E,MAAM,OAAO,oBAAoB,MAAM;AACvC,KAAI,SAAS,aAAa;EACxB,MAAM,MAAM,IAAI,KAAK,KAAK,OAAO,KAAK,MAAM,CAAC;AAC7C,SAAO;GAAE,OAAO,IAAI;GAAO,QAAQ,IAAI;GAAQ,MAAM,IAAI;GAAM;;AAEjE,KAAI,SAAS,cAAc;EACzB,MAAM,UAAU,KAAK,OAAO,OAAO;GACjC,WAAW;GACX,cAAc;GAEd,mBAAmB;GACnB,oBAAoB;GACrB,CAAC;AACF,SAAO;GACL,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,MAAM,QAAQ;GACf;;AAEH,OAAM,IAAI,MACR,GAAG,MAAM,MAAM,QAAQ,yBAAyB,6IACjD;;;AAIH,SAAgB,SACd,OACA,GACA,QACY;CACZ,MAAM,WAAW,MAAM,QAAQ;AAC/B,KAAI,IAAI,KAAK,UAAU,KAAK,IAAI,SAAS,MAAM,OAC7C,OAAM,IAAI,MACR,aAAa,EAAE,GAAG,IAAI,OAAO,oBAAoB,MAAM,MAAM,GAAG,MAAM,OAAO,SAC9E;AAEH,QAAO,MAAM,KAAK,SAAS,IAAI,WAAW,IAAI,UAAU,SAAS;;;;;;;AAQnE,SAAgB,WACd,OACA,OACA,YACA,cACA,eACY;CACZ,MAAM,MAAM,IAAI,WAAW,eAAe,gBAAgB,EAAE;AAC5D,MAAK,IAAI,IAAI,GAAG,IAAI,eAAe,KAAK,GAAG;EACzC,MAAM,UACJ,QAAQ,KAAK,IAAI,aAAa,GAAG,KAAK,MAAO,IAAI,aAAc,cAAc,CAAC;AAChF,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK,GAAG;GACxC,MAAM,UAAU,KAAK,IACnB,MAAM,QAAQ,GACd,KAAK,MAAO,IAAI,MAAM,QAAS,aAAa,CAC7C;GACD,MAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW;GACjD,MAAM,MAAM,IAAI,eAAe,KAAK;AACpC,OAAI,MAAM,MAAM,KAAK;AACrB,OAAI,KAAK,KAAK,MAAM,KAAK,OAAO;AAChC,OAAI,KAAK,KAAK,MAAM,KAAK,OAAO;AAChC,OAAI,KAAK,KAAK,MAAM,KAAK,OAAO;;;AAGpC,QAAO;;;;;;ACxLT,MAAa,8BAA8B;;AAG3C,MAAa,6BAA6B,KAAK,OAAO;AACtD,MAAM,8BAA8B;AA6FpC,SAAS,KACP,MACA,SACA,QAAiC,EAAE,EACtB;AACb,QAAO;EAAE,IAAI;EAAO,OAAO;GAAE;GAAM;GAAS,GAAG;GAAO;EAAE;;AAG1D,SAASC,SAAO,OAAuB;AACrC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;;;AAKzD,SAAS,YAAY,UAAgD;CACnE,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAQ,YACZ,GAAG,QAAQ,MAAM,GAAG,GAAG,CAAC,KAAK,KAAK,GAAG,QAAQ,SAAS,KAAK,QAAQ;CACrE,MAAM,gBAAgB,gCAAgC,SAAS;AAC/D,KAAI,SAAS,qBAAqB,SAAS,GAAG;EAC5C,MAAM,UAAU,SAAS,qBAAqB,KAAK,EAAE,aAAa,OAAO;AACzE,QAAM,KACJ,UAAU,QAAQ,OAAO,gCAAgC,QAAQ,WAAW,IAAI,KAAK,IAAI,0EAA0E,KAAK,QAAQ,CAAC,iHAClL;;AAEH,KACE,cAAc,SAAS,MACtB,SAAS,qBAAqB,WAAW,KACxC,SAAS,+BAEX,OAAM,KACJ,2BAA2B,cAAc,OAAO,uBAAuB,cAAc,WAAW,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc,CAAC,gMACtI;AAEH,KAAI,SAAS,8BACX,OAAM,KACJ,6LACD;AAEH,KAAI,SAAS,uBACX,OAAM,KACJ,mJACD;AAEH,KAAI,SAAS,sBAAsB,SAAS,GAAG;EAC7C,MAAM,UAAU,SAAS,sBAAsB,KAAK,EAAE,aAAa,OAAO;AAC1E,QAAM,KACJ,YAAY,QAAQ,OAAO,4BAA4B,QAAQ,WAAW,IAAI,KAAK,IAAI,kCAAkC,KAAK,QAAQ,CAAC,8IACxI;;AAEH,KAAI,SAAS,+BACX,OAAM,KACJ,sJACD;AAEH,KAAI,SAAS,qBAAqB,SAAS,GAAG;EAC5C,MAAM,UAAU,SAAS,qBAAqB,KAAK,EAAE,aAAa,OAAO;AACzE,QAAM,KACJ,YAAY,QAAQ,OAAO,uBAAuB,QAAQ,WAAW,IAAI,KAAK,IAAI,8CAA8C,KAAK,QAAQ,CAAC,yNAC/I;;AAEH,QAAO;;AAGT,SAASC,WAAS,MAAc,WAA4B;CAC1D,MAAM,iBAAiB,KAAK,SAAS,IAAI,GAAG,OAAO,GAAG,OAAO;AAC7D,QAAO,UAAU,WAAW,eAAe;;;;;;;;AAS7C,eAAe,mBAAmB,MAU+B;CAC/D,MAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,KAAI,KAAK,MAAM,SAAA,SACb,OAAM,IAAI,MACR,yBAAyB,KAAK,MAAM,UAAU,OAAO,OAAO,QAAQ,EAAE,CAAC,gBAAgB,8BAA8B,OAAO,MAAM,oBACnI;CAEH,MAAM,aAAa,cAAc,KAAK,MAAM;AAC5C,KAAI,CAAC,WACH,OAAM,IAAI,MACR,yGACD;CASH,MAAM,eAAe,GAAG,mBAAmB,GAP1B,wBAAwB;EACvC,WAAW,KAAK;EAChB,mBAAmB,KAAK;EACxB,WAAW,KAAK;EAChB,YAAY,KAAK;EACjB,MAAM,KAAK;EACZ,CAAC;CAEF,MAAM,YAAY,aAAqB;EACrC,MAAM,OAAO,QAAQ,MAAM,GAAG,SAAS,MAAM,IAAI,CAAC;AAClD,MAAI,CAACA,WAAS,MAAM,KAAK,CACvB,OAAM,IAAI,MAAM,iBAAiB,SAAS,WAAW,KAAK,GAAG;AAE/D,SAAO;;CAET,MAAM,mBAAmBD,SAAO,KAAK,MAAM;CAE3C,MAAM,WAAqB,EAAE;CAC7B,MAAM,YAAY,KAAK,UAAU,SAAS,aAAa;EACrD,MAAM,QAAQ,OAAO,KAAK,SAAS,SAAS,OAAO;AACnD,MAAI,MAAM,WAAW,GAAG;AACtB,YAAS,KACP,aAAa,SAAS,OAAO,oDAC9B;AACD,UAAO,EAAE;;AAEX,MAAI,MAAM,SAAA,UAAmC;AAC3C,YAAS,KACP,aAAa,SAAS,OAAO,uBAAuB,MAAM,UAAU,OAAO,OAAO,QAAQ,EAAE,CAAC,gBAAgB,4BAA4B,OAAO,MAAM,iCACvJ;AACD,UAAO,EAAE;;EAEX,MAAM,uBAAuB,0BAC3B,cACA,SAAS,OACV;AACD,SAAO,CACL;GACE,QAAQ,SAAS;GACjB;GACA,cAAc;GACd,UAAU,SAAS,qBAAqB;GACxC,QAAQA,SAAO,MAAM;GACtB,CACF;GACD;CAEF,IAAI;AAQJ,KAAI,KAAK,cAAc;EACrB,MAAM,WAAW,0BAA0B;GACzC,YAAY,KAAK;GACjB,WAAW,KAAK;GAChB,mBAAmB,KAAK;GACxB,YAAY;IACV;IACA,QAAQ;IACR,YAAY,KAAK,MAAM;IACvB,OAAO,WAAW;IAClB,QAAQ,WAAW;IACpB;GACD,WAAW,UAAU,KAAK,cAAc;IACtC,QAAQ,SAAS;IACjB,cAAc,SAAS;IACvB,QAAQ,SAAS;IACjB,YAAY,SAAS,MAAM;IAC5B,EAAE;GACH,UAAU,KAAK,aAAa;GAC5B,YAAY,KAAK,aAAa;GAC/B,CAAC;EACF,MAAM,sBAAsB,0BAC1B,cACA,gBACD;AACD,YAAU;GACR,cAAc;GACd,UAAU,SAAS,oBAAoB;GACvC,OAAO,OAAO,KAAK,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;GACrD;GACD;;CAGH,MAAM,qBAAqB,SAAS,aAAa;CACjD,MAAM,cAAc,KAAK,MAAM,GAAG,mBAAmB,MAAM,IAAI,CAAC;AAChE,OAAM,MAAM,aAAa,EAAE,WAAW,MAAM,CAAC;CAG7C,MAAM,CAAC,UAAU,mBAAmB,MAAM,QAAQ,IAAI,CACpD,SAAS,KAAK,EACd,SAAS,YAAY,CACtB,CAAC;AACF,KAAI,oBAAoB,KAAK,UAAU,GAAA,iCAAsB,MAAM,IAAI,CAAC,CACtE,OAAM,IAAI,MACR,GAAG,YAAY,eAAe,gBAAgB,YAAY,SAAS,8DACpE;CAEH,MAAM,UAAoB,EAAE;AAC5B,KAAI;AACF,QAAM,UAAU,oBAAoB,KAAK,MAAM;AAC/C,UAAQ,KAAK,mBAAmB;AAChC,MAAI,SAAS;AACX,SAAM,UAAU,QAAQ,UAAU,QAAQ,MAAM;AAChD,WAAQ,KAAK,QAAQ,SAAS;;AAEhC,OAAK,MAAM,YAAY,WAAW;AAChC,SAAM,UAAU,SAAS,UAAU,SAAS,MAAM;AAClD,WAAQ,KAAK,SAAS,SAAS;;UAE1B,OAAO;AACd,QAAM,QAAQ,IACZ,QAAQ,KAAK,SAAS,OAAO,KAAK,CAAC,YAAY,KAAA,EAAU,CAAC,CAC3D;AACD,QAAM;;CAGR,MAAM,QAAgC,EAAE,YAAY,oBAAoB;AACxE,KAAI,QAAS,OAAM,mBAAmB,QAAQ;AAC9C,MAAK,MAAM,YAAY,UAAW,OAAM,SAAS,UAAU,SAAS;AAEpE,QAAO;EACL;EACA,QAAQ;GACN;GACA,MAAM;GACN,aAAa,KAAK;GAClB,QAAQ;GACR,OAAO,KAAK,MAAM;GAClB,OAAO,WAAW;GAClB,QAAQ,WAAW;GACnB,GAAI,KAAK,kBAAkB,WACvB,EAAE,oBAAoB,KAAK,kBAAkB,UAAU,GACvD,EAAE;GACN,GAAI,UACA,EACE,eAAe;IACb,MAAM,QAAQ;IACd,QAAQA,SAAO,QAAQ,MAAM;IAC7B,OAAO,QAAQ,MAAM;IACrB,qBAAqB,QAAQ,SAAS;IACtC,WAAW,QAAQ,SAAS,SAAS,UAAU;IAC/C,UAAU,QAAQ,SAAS,SAAS,SAAS;IAC7C,OAAO,QAAQ,SAAS,SAAS,MAAM;IACvC,GAAI,QAAQ,SAAS,kBACjB,EAAE,kBAAkB,QAAQ,SAAS,iBAAiB,GACtD,EAAE;IACP,EACF,GACD,EAAE;GACN,GAAI,UAAU,SAAS,IACnB,EACE,WAAW,OAAO,YAChB,UAAU,KAAK,aAAa,CAC1B,SAAS,QACT;IACE,MAAM,SAAS;IACf,QAAQ,SAAS;IACjB,OAAO,SAAS,MAAM;IACvB,CACF,CAAC,CACH,EACF,GACD,EAAE;GACN;GACD;EACF;;AAGH,SAAS,eAAe,UAAuD;CAC7E,MAAM,MAAM,WAAW;CACvB,MAAM,aACJ,OAAO,QAAQ,WACX,MACA,OAAO,QAAQ,WACb,OAAO,IAAI,GACX;AACR,KAAI,CAAC,OAAO,UAAU,WAAW,IAAI,aAAa,OAAO,aAAa,IACpE,OAAM,IAAI,MACR,4DACD;AAEH,QAAO;;AAGT,eAAe,mBACb,SAC6C;CAC7C,MAAM,aAAa,IAAI,iBAAiB;CACxC,IAAI;CACJ,MAAM,UAAU,IAAI,SAA8B,YAAY;AAC5D,UAAQ,iBAAiB;AAGvB,cAAW,OAAO;AAClB,WAAQ,EACN,SAAS,uCAAuC,8BAA8B,IAAK,IACpF,CAAC;KACD,4BAA4B;GAC/B;AACF,KAAI;AACF,SAAO,MAAM,QAAQ,KAAK,CAAC,QAAQ,WAAW,OAAO,EAAE,QAAQ,CAAC;WACxD;AACR,eAAa,MAAM;;;;;;;;AASvB,eAAsB,SACpB,SACA,MACsB;AACtB,KAAI,CAAC,gBAAgB,KAAK,QAAQ,IAAI,CACpC,QAAO,KACL,eACA,qBAAqB,QAAQ,IAAI,kCAClC;CAEH,MAAM,sBAAsB,QAAQ,iBAAiB,KAAA;CACrD,MAAM,mBACJ,QAAQ,WAAW,QAAQ,QAAQ,SAAS,IACxC,CAAC,GAAG,IAAI,IAAI,QAAQ,QAAQ,CAAC,GAC7B,sBACE;EAAC;EAAY;EAAQ;EAAa,GAClC,CAAC,WAAW;CACpB,MAAM,oBACJ,QAAQ,YAAY,QAAQ,YAAY,QAAQ,YAAY,KAAA,IACxD;EACE,GAAI,QAAQ,WAAW,EAAE,UAAU,MAAM,GAAG,EAAE;EAC9C,GAAI,QAAQ,YAAY,KAAA,IACpB,EAAE,GACF,EAAE,SAAS,QAAQ,SAAS;EAChC,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;EAC3D,GACD,KAAA;AACN,KAAI,qBAAqB,CAAC,iBAAiB,SAAS,aAAa,CAC/D,kBAAiB,KAAK,aAAa;AAErC,KACE,wBACC,CAAC,iBAAiB,SAAS,aAAa,IAAI,CAAC,mBAAmB,UAEjE,QAAO,KACL,2BACA,0KACD;AAGH,KACE,QAAQ,uBACR,QAAQ,oBAAoB,SAAS,KACrC,EAAE,uBAAuB,mBAAmB,UAE5C,QAAO,KACL,uCACA,+IACD;CAGH,MAAM,UAAU,4BAA4B;EAC1C,KAAK,QAAQ;EACb,SAAS;EACT,iBAAiB,QAAQ;EACzB;EACA;EACA,GAAI,QAAQ,SAAS,SAAS,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EAC/D,GAAI,QAAQ,qBAAqB,SAC7B,EAAE,qBAAqB,QAAQ,qBAAqB,GACpD,EAAE;EACP,CAAC;CAEF,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,iBAAiB;CACpE,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,KAAK,OAAO,SAAS;GAClC,QAAQ,QAAQ,WAAW;GAC3B,QAAQ,WAAW;GACpB,CAAC;UACK,OAAO;AACd,MAAI,iBAAiB,oBACnB,QAAO,KAAK,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ;AAEvD,MAAI,iBAAiB,SAAS,MAAM,SAAS,aAC3C,QAAO,KACL,iBACA,yBAAyB,mBAAmB,IAAK,IAClD;AAEH,SAAO,KACL,gBACA,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACvE;WACO;AACR,eAAa,MAAM;;CAGrB,MAAM,OAA4B,OAAO;CACzC,MAAM,QAAkB,EAAE;CAC1B,MAAM,WAAqB,EAAE;CAC7B,IAAI;CACJ,IAAI;CACJ,IAAI;AACJ,KAAI,oBACF,KAAI;AACF,aAAW,yCACT,KAAK,SAAS,mBAAmB,GAAG,GAAG,EAAE,OACzC,kBAAmB,SACpB;AACD,eAAa,eAAe,KAAK,SAAS;UACnC,OAAO;AACd,kBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;;CAI1E,MAAM,SAAsB;EAC1B,IAAI;EACJ,KAAK,OAAO;EACZ,GAAI,OAAO,iBACP,EAAE,iBAAiB,OAAO,gBAAgB,GAC1C,EAAE;EACP;AACD,KAAI,KAAK,SACP,QAAO,WAAW,uBAAuB;EACvC,cAAc,QAAQ;EACtB,UAAU,KAAK;EACf,GAAI,WAAW,EAAE,sBAAsB,UAAU,GAAG,EAAE;EACtD,GAAI,eAAe,KAAA,IACf,EAAE,GACF,EAAE,wBAAwB,YAAY;EAC3C,CAAC;AAEJ,KAAI,mBAAmB,YAAY,SACjC,OAAM,KAAK,GAAG,YAAY,SAAS,CAAC;CAEtC,MAAM,gBAAgB,qCACpB,MACA,2CAA2C;EACzC;EACA;EACA;EACD,CAAC,CACH;AACD,KAAI,cAAe,QAAO,aAAa,EAAE,KAAK,eAAe;CAE7D,IAAI,2BAA2B;AAC/B,KACE,uBACA,iBAAiB,SAAS,aAAa,IACvC,eACA;EAGA,MAAM,MAAM,MAAM,oBAAoB,WACpC,KAAK,mBACH,QAAQ,KACR,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,IAClD,OACD,CACF;EACD,MAAM,UAAU,MAAM,KAAK,gBAAgB,cAAc;EACzD,IAAI,cAAc;AAClB,MAAI,WAAW,QACb,KAAI;GACF,MAAM,YAGD;IACH,GAAG,iBAAiB,SAAS,WAAW;AACtC,SAAI,WAAW,UAAU,WAAW,WAAY,QAAO,EAAE;KACzD,MAAM,UAAU,KAAK;AACrB,YAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IACnD,CAAC;MAAE;MAAQ;MAAS,CAAC,GACrB,EAAE;MACN;IACF,GAAI,IAAI,aAAa,IAAI,UAAU,IAAI,SAAS,IAC5C,CAAC;KAAE,QAAQ;KAAuB,SAAS,IAAI,UAAU;KAAK,CAAC,GAC/D,EAAE;IACN,GAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,IAC1D,CAAC;KAAE,QAAQ;KAAoB,SAAS,KAAK;KAAS,CAAC,GACvD,EAAE;IACP;GACD,MAAM,QAAQ,MAAM,mBAAmB;IACrC,MAAM,QAAQ;IACd,WAAW,QAAQ;IACA;IACnB,OAAO,QAAQ;IACf,WAAW,QAAQ;IACnB,YAAY,KAAK,KAAK,CAAC,aAAa;IACpC,MAAM,KAAK,MAAM;IACjB;IACA,GAAI,YAAY,eAAe,KAAA,KAAa,CAAC,gBACzC,EAAE,cAAc;KAAE;KAAU;KAAY,EAAE,GAC1C,EAAE;IACP,CAAC;AACF,UAAO,WAAW,MAAM;AACxB,YAAS,KAAK,GAAG,MAAM,SAAS;AAChC,iBAAc,QAAQ,MAAM,OAAO,WAAW,WAAW;AACzD,8BAA2B,iCACzB,kBACA,MACA,MAAM,OAAO,UACd;AACD,OAAI,CAAC,4BAA4B,CAAC,cAChC,iBACE,MAAM,SAAS,KAAK,IAAI,IACxB;GAEJ,MAAM,WAAW,MAAM,OAAO,eAAe;AAC7C,OAAI,UAAU,WAAW,iBACvB,OAAM,KACJ,oHAAoH,SAAS,iBAAiB,MAAM,GAAG,SAAS,iBAAiB,OAAO,eAAe,SAAS,WAAW,MAAM,GAAG,SAAS,WAAW,OAAO,IAAI,SAAS,eAAe,IAAI,MAAM,KAAK,SAAS,YAAY,iGAChU;WAEI,OAAO;AACd,qBACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;;MAG1D,UAAS,KAAK,QAAQ,KAAK;AAE7B,SAAO,MAAM,IAAI,YACb;GACE,UAAU;GACV,SAAS,IAAI,UAAU,QAAQ;GAC/B,GAAI,IAAI,UAAU,WACd,EAAE,WAAW,IAAI,UAAU,UAAU,GACrC,EAAE;GACN,OAAO,IAAI,UAAU;GACrB,GAAI,IAAI,UAAU,SAAS,SAAS,IAChC,EAAE,UAAU,IAAI,UAAU,UAAU,GACpC,EAAE;GACP,GACD;GACE,UAAU;GACV,GAAI,IAAI,UAAU,EAAE,SAAS,IAAI,SAAS,GAAG,EAAE;GAChD;;CAGP,MAAM,UAAU,4BACd,kBACA,MACA,uBAAuB,yBACxB,CAAC,KAAK,OAAO;AACd,KAAI,QAAQ,SAAS,EAAG,QAAO,UAAU;AAEzC,KACE,uBACA,CAAC,OAAO,UAAU,iBAClB,CAAC,cAED,iBACE;AAEJ,KAAI,MAAM,SAAS,EAAG,QAAO,QAAQ;AACrC,KAAI,SAAS,SAAS,EAAG,QAAO,WAAW;AAC3C,KAAI,uBAAuB,eAAe;AACxC,SAAO,KAAK;AACZ,SAAO,QAAQ;GACb,MAAM;GACN,SAAS,2EAA2E;GACrF;;AAEH,QAAO;;AAQT,MAAM,2BAA2B;AACjC,MAAM,oBAAoB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAI,CAAC;;;AAI5D,eAAsB,eAAe,UAOjB;AAClB,KAAI,CAAC,SAAS,KAAM,QAAO,OAAO,MAAM,EAAE;CAC1C,MAAM,SAAS,SAAS,KAAK,WAAW;CACxC,MAAM,SAAuB,EAAE;CAC/B,IAAI,QAAQ;AACZ,KAAI;AACF,WAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,OAAI,QAAQ,CAAC,MAAO;AACpB,YAAS,MAAM;AACf,OAAI,QAAA,SACF,OAAM,IAAI,MACR,0BAA0B,8BAA8B,OAAO,MAAM,mBACtE;AAEH,UAAO,KAAK,MAAM;;WAEZ;AACR,QAAM,OAAO,QAAQ,CAAC,YAAY,KAAA,EAAU;;AAE9C,QAAO,OAAO,OAAO,OAAO;;;;;;;;AAS9B,eAAsB,iBACpB,KACA,QAC4B;CAC5B,IAAI,UAAU;AACd,MAAK,IAAI,MAAM,GAAG,OAAO,0BAA0B,OAAO,GAAG;EAC3D,MAAM,aAAa,MAAM,sBAAsB,QAAQ;AACvD,MAAI,CAAC,WAAW,GACd,OAAM,IAAI,MAAM,0BAA0B,WAAW,SAAS;EAEhE,MAAM,SAAS,MAAM,kBACnB,WAAW,KACX,WAAW,WACX;GACE;GACA,SAAS,EAAE,QAAQ,WAAW;GAC/B,CACF;AACD,MAAI;GACF,MAAM,EAAE,aAAa;AACrB,OAAI,kBAAkB,IAAI,SAAS,OAAO,EAAE;IAC1C,MAAM,WAAW,SAAS,QAAQ,IAAI,WAAW;AACjD,QAAI,CAAC,SACH,QAAO;KAAE,QAAQ,SAAS;KAAQ,OAAO,OAAO,MAAM,EAAE;KAAE;AAC5D,cAAU,IAAI,IAAI,UAAU,QAAQ,CAAC,UAAU;AAC/C;;GAEF,MAAM,WAAW,OAAO,SAAS,QAAQ,IAAI,iBAAiB,IAAI,IAAI;AACtE,OAAI,WAAA,SACF,OAAM,IAAI,MACR,kBAAkB,YAAY,OAAO,OAAO,QAAQ,EAAE,CAAC,gBAAgB,8BAA8B,OAAO,MAAM,mBACnH;AAEH,UAAO;IAAE,QAAQ,SAAS;IAAQ,OAAO,MAAM,eAAe,SAAS;IAAE;YACjE;AACR,SAAM,OAAO,OAAO;;;AAGxB,OAAM,IAAI,MAAM,aAAa,yBAAyB,YAAY;;;AAIpE,eAAsB,qBACpB,KACA,aAGkC,kBACgC;AAClE,KAAI,CAAC,gBAAgB,KAAK,IAAI,CAC5B,QAAO,EAAE,MAAM,mCAAmC,IAAI,MAAM,GAAG,IAAI,CAAC,IAAI;CAE1E,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBACN,WAAW,OAAO,EACxB,4BACD;AACD,KAAI;EACF,MAAM,WAAW,MAAM,WAAW,KAAK,WAAW,OAAO;AACzD,MAAI,SAAS,SAAS,OAAO,SAAS,UAAU,IAC9C,QAAO,EAAE,MAAM,iCAAiC,SAAS,OAAO,IAAI;EAEtE,MAAM,QAAQ,SAAS;AACvB,MAAI,MAAM,WAAW,EACnB,QAAO,EAAE,MAAM,2CAA2C;AAC5D,MAAI,MAAM,SAAA,SACR,QAAO,EACL,MAAM,kBAAkB,MAAM,UAAU,OAAO,OAAO,QAAQ,EAAE,CAAC,gBAAgB,8BAA8B,OAAO,MAAM,oBAC7H;EAEH,MAAM,UAAU,oBAAoB,MAAM;AAC1C,MAAI,CAAC,QACH,QAAO,EACL,MAAM,iEACP;AAEH,SAAO;GAAE;GAAO,WAAW;GAAS;UAC7B,OAAO;AACd,SAAO,EACL,MACE,iBAAiB,SAAS,MAAM,SAAS,eACrC,oCAAoC,8BAA8B,IAAK,KACvE,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACxF;WACO;AACR,eAAa,MAAM;;;;;ACjzBvB,MAAM,UAAyC;CAC7C;CACA;CACA;CACD;AAED,SAAgB,cAAc,OAG5B;CACA,MAAM,QAAQ,iBAAiB,KAAK,MAAM,MAAM,CAAC;AACjD,KAAI,CAAC,MACH,OAAM,IAAI,qBAAqB,iCAAiC;CAElE,MAAM,QAAQ,OAAO,MAAM,GAAG;CAC9B,MAAM,SAAS,OAAO,MAAM,GAAG;AAC/B,KAAI,QAAQ,OAAO,QAAQ,QAAQ,SAAS,OAAO,SAAS,KAC1D,OAAM,IAAI,qBACR,oDACD;AAEH,QAAO;EAAE;EAAO;EAAQ;;AAG1B,SAAS,cACP,OACA,UACsB;CACtB,MAAM,UAAU,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,KAAK,MAAM,CAAC,aAAa,CAAC;AACzE,MAAK,MAAM,UAAU,QACnB,KAAI,CAAC,QAAQ,SAAS,OAA6B,CACjD,OAAM,IAAI,qBACR,mBAAmB,OAAO,uCAC3B;AAGL,QAAO,CAAC,GAAI,YAAY,EAAE,EAAG,GAAI,QAAiC;;AAKpE,MAAM,sBAAsB;AAC5B,MAAM,cAAc;AAEpB,SAAS,WAAW,OAAgB,OAAuB;AACzD,KACE,OAAO,UAAU,YACjB,MAAM,MAAM,CAAC,WAAW,KACxB,MAAM,SAAS,oBAEf,OAAM,IAAI,qBACR,GAAG,MAAM,+BAA+B,oBAAoB,cAC7D;AAEH,QAAO;;;;;AAMT,SAAgB,iBACd,KACA,cACyB;CACzB,IAAI;AACJ,KAAI;AACF,UAAQ,KAAK,MAAM,IAAI;SACjB;AACN,QAAM,IAAI,qBACR,aAAa,IAAI,0DAClB;;AAEH,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,qBAAqB,kCAAkC;CAEnE,MAAM,SAAS;AACf,SAAQ,OAAO,SAAf;EACE,KAAK,QAAQ;GACX,MAAM,KAAK,OAAO;AAClB,OACE,OAAO,OAAO,YACd,CAAC,OAAO,UAAU,GAAG,IACrB,KAAK,KACL,KAAK,YAEL,OAAM,IAAI,qBACR,kDAAkD,YAAY,GAC/D;AAEH,UAAO;IAAE,MAAM;IAAQ,cAAc;IAAI;;EAE3C,KAAK,QACH,QAAO;GACL,MAAM;GACN,UAAU,WAAW,OAAO,aAAa,iBAAiB;GAC3D;EACH,KAAK,UAAU;GACb,MAAM,YAAY,OAAO;AACzB,OAAI,cAAc,QAAQ,cAAc,OACtC,OAAM,IAAI,qBACR,+CACD;AAEH,UAAO,OAAO,gBAAgB,KAAA,IAC1B;IAAE,MAAM;IAAU;IAAW,GAC7B;IACE,MAAM;IACN;IACA,UAAU,WAAW,OAAO,aAAa,kBAAkB;IAC5D;;EAEP,KAAK;AACH,OAAI,CAAC,aACH,OAAM,IAAI,qBACR,sDACD;AAEH,UAAO;IACL,MAAM;IACN,UAAU,WAAW,OAAO,aAAa,mBAAmB;IAC5D,gBAAgB,WACd,OAAO,mBACP,yBACD;IACF;EACH,QACE,OAAM,IAAI,qBACR,uBAAuB,KAAK,UAAU,OAAO,QAAQ,CAAC,8BAA8B,eAAe,iBAAiB,GAAG,GACxH;;;AAIP,SAAS,cAAc,cAAuB;AAC5C,SACE,OACA,aAC8B;EAC9B,MAAM,UAAU,CACd,GAAI,YAAY,EAAE,EAClB,iBAAiB,OAAO,aAAa,CACtC;AACD,MAAI,QAAQ,SAAA,EACV,OAAM,IAAI,qBACR,+BACD;AAEH,SAAO;;;AAIX,SAAS,aAAa,OAAuB;CAC3C,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,CAAC,OAAO,UAAU,QAAQ,IAAI,UAAU,KAAK,UAAU,IACzD,OAAM,IAAI,qBAAqB,4CAA4C;AAE7E,QAAO;;;;AAKT,eAAe,mBACb,KACA,iBACA,QAC6C;AAC7C,KAAI;EACF,MAAM,YAA8B,EAAE;EACtC,MAAM,SAAS,MAAM,gBACnB,KACA,KACA,0BACD;AACD,MAAI,OAAO,MAAM,OAAO,KAAK,SAAS,EACpC,WAAU,KAAK;GACb,MAAM,OAAO;GACb,SAAS,OAAO;GAChB,KAAK;GACN,CAAC;AAEJ,MAAI,gBAAgB,SAAS,EAC3B,WAAU,KAAK;GACb,MAAM;GACN,SAAS;GACT,KAAK;GACN,CAAC;AAEJ,MAAI,UAAU,WAAW,EACvB,QAAO,EACL,SAAS,OAAO,SAAS,QAAQ,OAAO,UAAU,kBACnD;EAEH,MAAM,YAAY,MAAM,yBAAyB,YAAY,aAC3D,OAAO,UACH,QAAQ,QAAQ;GACd,IAAI;GACJ,QAAQ;GACR,MAAM;GACN,UAAU;GACV,WAAW;GACX,OAAO;GACR,CAAC,GACF,gBAAgB,SAAS,CAC9B;AACD,MAAI,CAAC,OAAO,GACV,WAAU,SAAS,KACjB,gCAAgC,OAAO,SAAS,QAAQ,OAAO,SAAS,qDACzE;AAEH,SAAO,EAAE,WAAW;UACb,OAAO;AACd,SAAO,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAAE;;;AAI9E,SAAgB,iBACd,MAAyB,QAAQ,KACtB;AACX,QAAO;EACL,SAAS,SAAS,YAChB,cAAc,SAAS,SAAS;GAC9B,gBAAgB,uBAAuB,YAAY,EAAE,cAAc,CAAC;GACpE,iBAAiB,IAAI;GACrB;GACD,CAAC;EACJ,kBAAkB,QAAQ,qBAAqB,IAAI;EACnD;EACA,2BAAW,IAAI,MAAM;EACrB,YAAY,YAAY;EACzB;;AAeH,SAAgB,mBACd,aAA8B,kBAAkB,EACvC;AACT,QAAO,IAAI,QAAQ,QAAQ,CACxB,YACC,uJACD,CACA,SAAS,SAAS,iCAAiC,CACnD,OACC,qBACA,iHACA,cACD,CACA,OACC,oBACA,2EACA,cACD,CACA,OAAO,eAAe,uCAAuC,CAC7D,OAAO,qBAAqB,sBAAsB,aAAa,CAC/D,OACC,0BACA,4EACD,CACA,OACC,oBACA,0FACD,CACA,OACC,mBACA,yLACA,cAAc,MAAM,CACrB,CACA,OACC,iCACA,wLACA,cAAc,KAAK,CACpB,CACA,OACC,YACA,wEACD,CACA,OAAO,OAAO,KAAa,SAA8B;EACxD,MAAM,SAAS,MAAM,SACnB;GACE;GACA,GAAI,KAAK,SAAS,EAAE,SAAS,KAAK,QAAQ,GAAG,EAAE;GAC/C,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,UAAU,GAAG,EAAE;GACpD,GAAI,KAAK,WAAW,EAAE,UAAU,MAAM,GAAG,EAAE;GAC3C,GAAI,KAAK,YAAY,KAAA,IAAY,EAAE,GAAG,EAAE,SAAS,KAAK,SAAS;GAC/D,iBAAiB,KAAK;GACtB,GAAI,KAAK,WAAW,EAAE,cAAc,KAAK,UAAU,GAAG,EAAE;GACxD,GAAI,KAAK,SAAS,EAAE,QAAQ,MAAM,GAAG,EAAE;GACvC,GAAI,KAAK,SAAS,EAAE,SAAS,KAAK,QAAQ,GAAG,EAAE;GAC/C,GAAI,KAAK,qBACL,EAAE,qBAAqB,KAAK,oBAAoB,GAChD,EAAE;GACP,EACD,MAAM,CACP;AACD,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C,MAAI,CAAC,OAAO,GAAI,SAAQ,WAAW;GACnC;;;;;;;;;AChUN,MAAa,6BACX;AA8CF,IAAa,6BAAb,cAAgD,MAAM;CACpD,iBAA0B;CAC1B,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAIhB,SAAS,aAAa,OAAyC;CAC7D,MAAM,YACJ,OAAO,UAAU,YAAY,UAAU,QAAQ,aAAa,QACtD,MAA+B,WAAW,QAC5C;AACN,QACE,OAAO,cAAc,YACrB,cAAc,QACd,cAAc,aACd,OAAQ,UAAkD,UACtD,WAAW;;AAInB,SAAS,OAAO,OAAgC;AAC9C,KACE,OAAO,UAAU,YACjB,UAAU,QACV,cAAc,MAEd,QAAO;AAET,QAAQ,MAAsC;;AAGhD,SAAS,oBAAmC;AAC1C,KAAI;AACF,SAAO,aAAa,OAAO,CAAC,QAAQ,KAAK,EAAE;GACzC,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACnC,SAAS;GACV,CAAC,CAAC,MAAM;SACH;AACN,SAAO;;;;;;;AAQX,eAAsB,eACpB,YAAqD,cACnD,OAAO,YACgB;CACzB,MAAM,WAAgC;QAC9B;QACA,cAAc,KAAK,QAAQ,KAAK,EAAE,UAAU,CAAC,CAAC,QAAQ,aAAa;QACnE;GACJ,MAAM,OAAO,mBAAmB;AAChC,OAAI,CAAC,KAAM,OAAM,IAAI,MAAM,qBAAqB;AAChD,UAAO,cAAc,KAAK,MAAM,UAAU,CAAC,CAAC,QAAQ,aAAa;;EAEpE;AACD,MAAK,MAAM,WAAW,SACpB,KAAI;EACF,MAAM,YAAY,SAAS;EAC3B,MAAM,SAAS,MAAM,SACnB,cAAc,eAAe,YAAY,cAAc,UAAU,CAAC,KACnE;AACD,MAAI,aAAa,OAAO,CAAE,QAAO,OAAO,OAAO;SACzC;AAIV,OAAM,IAAI,2BACR,iHAAiH,6BAClH;;;AA6BH,MAAa,+BAA+B;AAE5C,MAAM,wBAAwB;AAC9B,MAAM,0BAA0B;;;AAIhC,MAAM,wBAAwB,OAAO,GAAG;;;;;;;;;;;;;;;;;AAkBxC,SAAS,qBAAqB,UAA2B;AACvD,QAAO,OAAO,GAAG;sBACG,WAAW,SAAS,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwClD,MAAM,iBAAiB,OAAO,GAAG;;;;;;;;;;;;;;AAejC,eAAsB,eACpB,YACA,MAOyB;CACzB,MAAM,UAAU,MAAM,WAAW,SAAS,OAAO,EAAE,UAAU,MAAM,CAAC;AACpE,KAAI;EAOF,MAAM,OAAO,OANG,MAAM,QAAQ,WAAW;GACvC,UAAU;IAAE,OAAO,KAAK;IAAO,QAAQ,KAAK;IAAQ;GACpD,mBAAmB;GACnB,UAAU,KAAK,SAAS;GACxB,UAAU,KAAK,SAAS;GACzB,CAAC,EACyB,SAAS;EACpC,MAAM,WAAW,MAAM,KAAK,KAAK,KAAK,KAAK;GACzC,WAAW;GACX,SAAS;GACV,CAAC;AACF,QAAM,KACH,iBAAiB,eAAe,EAAE,SAAS,yBAAyB,CAAC,CACrE,YAAY,KAAA,EAAU;AACzB,MAAI,KAAK,SAAS,OAAQ,OAAM,KAAK,SAAS,sBAAsB;EACpE,MAAM,iBAAkB,MAAM,KAAK,SACjC,qBAAqB,KAAK,SAAS,OAAO,CAC3C;EACD,MAAM,YAAY,MAAM,KAAK,SAAS,KAAK,eAAe;EAC1D,MAAM,WAAY,MAAM,KAAK,SAAS,eAAe;EAKrD,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,YAAY;AAChB,MAAI,KAAK,SAAS,QAAQ;AACxB,iBAAc,KAAK,IAAI,KAAK,QAAQ,SAAS,eAAe;GAC5D,MAAM,YAAY,KAAK,IACrB,8BAAA,KACyB,KAAK,OAC/B;AACD,eAAY,cAAc;AAC1B,oBAAiB,KAAK,IAAI,aAAa,UAAU;AACjD,SAAM,MAAM,KAAK,WAAW;IAC1B,MAAM;IACN,UAAU;IACV,GAAI,YACA,EACE,MAAM;KACJ,GAAG;KACH,GAAG;KACH,OAAO,SAAS;KAChB,QAAQ;KACT,EACF,GACD,EAAE;IACP,CAAC;SACG;AACL,iBAAc,KAAK;AACnB,oBAAiB,KAAK;AACtB,SAAM,MAAM,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;;AAE9C,SAAO;GACL,YAAY,WAAW,SAAS,QAAQ,GAAG;GAC3C,gBAAgB,WAAW,SAAS,YAAY,GAAG;GACnD,UAAU,KAAK,KAAK;GACpB;GACA,WAAW,SAAS;GACpB,eAAe,SAAS;GACxB;GACA;GACA;GACA;GACA;GACD;WACO;AACR,QAAM,QAAQ,OAAO,CAAC,YAAY,KAAA,EAAU;;;;;ACpUhD,MAAM,6BAA6B;AACnC,MAAM,wCAAwC;AAC9C,MAAM,yBAAyB;AAC/B,MAAM,6BAA6B;AACnC,MAAM,kCAAkC;AAExC,MAAa,mCAAmC;;AA0GhD,SAAgB,aACd,SACA,YAAA,IACsB;AACtB,KAAI,aAAa,KAAK,QAAQ,WAAW,EAAG,QAAO,KAAA;CACnD,MAAM,UAAU,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,QAAQ,CAAC;CACpE,MAAM,OAAO,KAAK,IAChB,GAAG,QAAQ,KAAK,WAAW,OAAO,UAAU,OAAO,OAAO,CAC3D;CACD,MAAM,iBAAiB,OAAO;AAC9B,KAAI,kBAAkB,EAAG,QAAO,KAAA;AAChC,QAAO;EACL;EACA,YAAY,KAAK,KAAK,iBAAiB,UAAU;EACjD;EACA,cAAc;EACd,eAAe,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,SAAS,EAAE,CAAC;EACvE;;AAuBH,SAAS,cACP,cACA,YACA,SACA,cAAc,GAKd,SAAS,OAAO,mBAChB,SAAS,OAAO,mBACO;CACvB,MAAM,QAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,WAAW,gBAAgB,aAAa,SAAS,EAAE;AAI7D,MAAI,YAAY,kBAAkB,EAAG;AACrC,QAAM,KAAK;GACT;GACA,cAAc,UAAU,YAAY;GACpC,YAAY,KAAK,IAAI,WAAW,YAAY,KAAK,YAAY,OAAO;GACpE,eAAe,YAAY;GAC3B,iBAAiB,YACf,YAAY,cAAc,YAAY,cACvC;GACD,uBAAuB,YACrB,YAAY,oBAAoB,YAAY,cAC7C;GACD,0BAA0B,YACxB,YAAY,gBAAgB,YAAY,gBAAgB,GACzD;GACD,qBAAqB,YAAY;GACjC,SAAS,YAAY,QAAQ,SAAS,QAAQ,gBAC5C,OAAO,kBAAkB,IACrB,EAAE,GACF,CACE;IACE;IACA,QAAQ,cAAc;IACtB,MAAM,KAAK,KAAK,cAAc,KAAK,aAAa,OAAO;IACvD,eAAe,OAAO;IACtB,uBAAuB,YACrB,OAAO,oBAAoB,OAAO,cACnC;IACD,qBAAqB,OAAO;IAC7B,CACF,CACN;GACF,CAAC;;AAEJ,QAAO;;AAGT,SAAgB,wBACd,cACA,eACQ;AACR,KACE,CAAC,OAAO,UAAU,aAAa,IAC/B,CAAC,OAAO,UAAU,cAAc,IAChC,gBAAgB,KAChB,iBAAiB,EAEjB,OAAM,IAAI,MAAM,gDAAgD;AAElE,QAAO,KAAK,IAAI,gBAAgB,aAAa,GAAG;;AAGlD,SAAS,sBACP,OACA,QACA,UACQ;AACR,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;;AAG7E,SAAS,YACP,UACA,UACQ;CACR,IAAI,eAAe;CACnB,IAAI,gBAAgB;AACpB,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,aAAa;EAC/C,MAAM,MAAM,KAAK,IAAI,UAAU,QAAQ,eAAe,QAAQ,OAAO;AACrE,MAAI,OAAO,MAAO;EAClB,MAAM,iBAAiB,KAAK,IAAI,OAAO,aAAa;AACpD,MAAI,MAAM,eAAgB,kBAAiB,MAAM;AACjD,iBAAe,KAAK,IAAI,cAAc,IAAI;;AAE5C,QAAO;;;;;;;;;;AAWT,SAAgB,sBACd,aACA,cACA,eACA,UACuB;AACvB,KACE,CAAC,OAAO,UAAU,YAAY,IAC9B,CAAC,OAAO,UAAU,aAAa,IAC/B,CAAC,OAAO,UAAU,cAAc,IAChC,eAAe,KACf,gBAAgB,KAChB,iBAAiB,EAEjB,OAAM,IAAI,MAAM,mDAAmD;CAGrE,MAAM,SAAS,SACZ,KAAK,SAAS,kBAAkB;EAAE,GAAG;EAAS;EAAc,EAAE,CAC9D,MACE,MAAM,UACL,KAAK,eAAe,MAAM,gBAC1B,KAAK,eAAe,MAAM,aAC7B;AACH,MAAK,MAAM,WAAW,QAAQ;AAC5B,MACE,CAAC,OAAO,UAAU,QAAQ,MAAM,IAChC,CAAC,OAAO,UAAU,QAAQ,OAAO,IACjC,CAAC,OAAO,UAAU,QAAQ,aAAa,IACvC,QAAQ,SAAS,KACjB,QAAQ,UAAU,KAClB,QAAQ,eAAe,EAEvB,OAAM,IAAI,MAAM,6CAA6C;AAE/D,MAAI,QAAQ,UAAU,YACpB,OAAM,IAAI,MACR,iDAAiD,YAAY,eAAe,QAAQ,MAAM,cAC3F;;CAIL,MAAM,qBAAqB,KAAK,IAAI,cAAc,cAAc;CAChE,MAAM,UAAqC,EAAE;CAC7C,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,QAAQ;EAC5B,MAAM,UAAU,KAAK,IAAI,QAAQ,cAAc,aAAa;EAC5D,MAAM,kBAAkB,sBACtB,SACA,QAAQ,eAAe,QAAQ,SAAS,SACxC,mBACD;AACD,MAAI,mBAAmB,EAAG;AAC1B,UAAQ,KAAK;GACX,cAAc,QAAQ;GACtB;GACA,UAAU,UAAU,QAAQ;GAC5B,OAAO;GACP,QAAQ;GACT,CAAC;AACF,iBAAe,UAAU;;AAG3B,QAAO;EACL;EACA,uBAAuB,YAAY,QAAQ,cAAc;EACzD,gBAAgB,QAAQ,QACrB,QAAQ,WAAW,SAAS,OAAO,QACpC,EACD;EACF;;AAGH,SAAS,kBACP,MACA,QACA,OACA,QACM;CACN,MAAM,gBAAgB,QAAQ,SAAS;AACvC,KAAI,OAAO,aAAa,cACtB,OAAM,IAAI,MACR,GAAG,KAAK,mBAAmB,OAAO,WAAW,4BAA4B,cAAc,GACxF;;AAIL,SAAS,iBACP,SACA,QACQ;AACR,QAAO,QAAQ,QAAQ,OAAO,WAAW;EACvC,MAAM,UAAU,SAAU,OAAO,UAAU,SAAU,UAAU;AAC/D,MAAI,UAAU,OAAO,OAAQ,QAAO;AAGpC,SAAO,QAFM,KAAK,MAAM,OAAO,SAAS,UAAU,OAAO,GACzC,KAAK,KAAK,OAAO,QAAQ,OAAO;IAE/C,EAAE;;AAGP,SAAS,YAAY,OAAuB;AAC1C,QAAO,OAAO,MAAM,QAAQ,EAAE,CAAC;;;;;;;;AAsBjC,SAAgB,mCACd,OACA,QACA,eACA,oBAKA,mBAAmB,uCACY;AAC/B,KACE,CAAC,OAAO,UAAU,MAAM,IACxB,CAAC,OAAO,UAAU,OAAO,IACzB,CAAC,OAAO,UAAU,cAAc,IAChC,CAAC,OAAO,UAAU,iBAAiB,IACnC,SAAS,KACT,UAAU,KACV,gBAAgB,KAChB,oBAAoB,EAEpB,OAAM,IAAI,MACR,mHACD;CAEH,MAAM,wBAAwB,KAAK,IAAI,eAAe,uBAAuB;CAC7E,IAAI,SAAS,KAAK,IAChB,GACA,KAAK,KAAK,KAAK,KAAM,QAAQ,SAAU,iBAAiB,CAAC,CAC1D;AACD,QACE,KAAK,KAAK,QAAQ,OAAO,GAAG,KAAK,KAAK,SAAS,OAAO,GACtD,iBAEA,WAAU;CAEZ,MAAM,eAAe,KAAK,KAAK,QAAQ,OAAO;CAC9C,MAAM,gBAAgB,KAAK,KAAK,SAAS,OAAO;CAChD,MAAM,cAAc,eAAe;CACnC,MAAM,kBAAkB,IAAI,WAAW,YAAY;CACnD,IAAI,WAAW,mBAAmB,GAAG,cAAc,cAAc;AACjE,mBACE,uBACA,UACA,cACA,cACD;AAED,MACE,IAAI,gBAAgB,GACpB,gBAAgB,uBAChB,iBAAiB,GACjB;EACA,MAAM,UAAU,mBACd,eACA,cACA,cACD;AACD,oBACE,qBAAqB,gBAAgB,KACrC,SACA,cACA,cACD;EACD,IAAI,cAAc;AAClB,OAAK,IAAI,IAAI,GAAG,IAAI,eAAe,KAAK,EACtC,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK,GAAG;GACxC,MAAM,UAAU,IAAI,eAAe,KAAK;AACxC,OACE,SAAS,YAAY,QAAQ,WAC7B,SAAS,SAAS,OAAO,QAAQ,SAAS,MAC1C,SAAS,SAAS,OAAO,QAAQ,SAAS,GAE1C,iBAAgB,eAAe,gBAAgB,eAAgB;AAEjE,kBAAe;;AAGnB,aAAW;;CAGb,MAAM,YAAY,wBAAwB;CAC1C,MAAM,qBAAqB,KAAK,KAAK,YAAY,GAAI;CACrD,IAAI,iBAAiB;CACrB,MAAM,oBAAoB,IAAI,WAAW,YAAY;AACrD,MACE,IAAI,cAAc,GAClB,cAAc,gBAAgB,QAC9B,eAAe,GACf;AACA,MAAI,gBAAgB,eAAgB,mBAAoB;AACxD,oBAAkB,eAAe;AACjC,oBAAkB;;CAGpB,MAAM,UAAU,IAAI,WAAW,YAAY;CAC3C,MAAM,UAAyC,EAAE;CACjD,MAAM,kBAAkB;EAAC;EAAI;EAAG,CAAC;EAAc;EAAa;AAC5D,MAAK,IAAI,QAAQ,GAAG,QAAQ,kBAAkB,QAAQ,SAAS,GAAG;AAChE,MAAI,kBAAkB,WAAW,KAAK,QAAQ,WAAW,EAAG;EAC5D,MAAM,UAAU,CAAC,MAAM;AACvB,UAAQ,SAAS;EACjB,IAAI,aAAa;EACjB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;AACX,SAAO,QAAQ,SAAS,GAAG;GACzB,MAAM,cAAc,QAAQ,KAAK;GACjC,MAAM,IAAI,KAAK,MAAM,cAAc,aAAa;GAChD,MAAM,IAAI,cAAc,IAAI;AAC5B,iBAAc;AACd,UAAO,KAAK,IAAI,MAAM,EAAE;AACxB,UAAO,KAAK,IAAI,MAAM,EAAE;AACxB,UAAO,KAAK,IAAI,MAAM,EAAE;AACxB,UAAO,KAAK,IAAI,MAAM,EAAE;AACxB,QAAK,MAAM,kBAAkB,iBAAiB;IAC5C,MAAM,WAAW,cAAc;AAC/B,QACE,WAAW,KACX,YAAY,kBAAkB,UAC9B,kBAAkB,cAAc,KAChC,QAAQ,cAAc,EAEtB;IAEF,MAAM,YAAY,KAAK,MAAM,WAAW,aAAa;IACrD,MAAM,YAAY,WAAW,YAAY;AACzC,QAAI,KAAK,IAAI,YAAY,EAAE,GAAG,KAAK,IAAI,YAAY,EAAE,KAAK,EAAG;AAC7D,YAAQ,YAAY;AACpB,YAAQ,KAAK,SAAS;;;EAG1B,MAAM,oBAAoB,aAAa;AACvC,MAAI,oBAAoB,gCAAiC;EACzD,MAAM,IAAI,KAAK,MAAO,OAAO,QAAS,aAAa;EACnD,MAAM,IAAI,KAAK,MAAO,OAAO,SAAU,cAAc;EACrD,MAAM,QAAQ,KAAK,MAAO,OAAO,KAAK,QAAS,aAAa;EAC5D,MAAM,SAAS,KAAK,MAAO,OAAO,KAAK,SAAU,cAAc;AAC/D,UAAQ,KAAK;GACX;GACA;GACA,OAAO,QAAQ;GACf,QAAQ,SAAS;GACjB,mBAAmB,YAAY,kBAAkB;GAClD,CAAC;;AAEJ,SAAQ,MACL,MAAM,UAAU,MAAM,oBAAoB,KAAK,kBACjD;AACD,QAAO;EACL,iBAAiB,YAAY,iBAAiB,YAAY;EAC1D,SAAS,QAAQ,MAAM,GAAG,2BAA2B;EACtD;;AAuBH,SAAgB,8BACd,SACA,mBAAmB,4BACX;AACR,KAAI,CAAC,OAAO,UAAU,iBAAiB,IAAI,oBAAoB,EAC7D,OAAM,IAAI,MAAM,+CAA+C;AAEjE,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,0DAA0D;CAE5E,IAAI,SAAS,KAAK,IAChB,GACA,KAAK,KACH,KAAK,KACH,QAAQ,QACL,QAAQ,WAAW,SAAS,OAAO,QAAQ,OAAO,QACnD,EACD,GAAG,iBACL,CACF,CACF;AACD,QAAO,iBAAiB,SAAS,OAAO,GAAG,iBAAkB,WAAU;AACvE,QAAO;;;;;;;;AAST,SAAgB,qBACd,SACA,mBAAmB,4BACnB,aACA,YAAA,IACA,cAAA,GACA,UACwB;AACxB,KACE,gBAAgB,KAAA,MACf,CAAC,OAAO,UAAU,YAAY,IAAI,eAAe,GAElD,OAAM,IAAI,MAAM,0CAA0C;AAE5D,KAAI,CAAC,OAAO,UAAU,UAAU,IAAI,YAAY,EAC9C,OAAM,IAAI,MAAM,4CAA4C;AAE9D,KAAI,CAAC,OAAO,UAAU,YAAY,IAAI,cAAc,EAClD,OAAM,IAAI,MAAM,8CAA8C;CAEhE,MAAM,SACJ,eAAe,8BAA8B,SAAS,iBAAiB;CAEzE,IAAI,gBAAgB;CACpB,IAAI,cAAc;CAClB,IAAI,oBAAoB;CACxB,IAAI,eAAe;CACnB,IAAI,sBAAsB;CAI1B,MAAM,iBAAiB,QAAQ,SAC3B,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,QAAQ,CAAC,GACpD;CACJ,MAAM,eAAe,QAAQ,SACzB,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,UAAU,OAAO,OAAO,CAAC,GACpE;CACJ,MAAM,iBAAiB,KAAK,IAAI,GAAG,eAAe,eAAe;CAGjE,MAAM,cAAc,WAAW,SAAS,UAAU;CAClD,MAAM,qBAAqB,WAAW,SAAS,YAAY;CAC3D,MAAM,aAAa,WACf,SAAS,aACT,YAAY,KAAK,iBAAiB,IAChC,KAAK,KAAK,iBAAiB,UAAU,GACrC;CACN,MAAM,gBAAgB,QAAQ,SAC1B,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,MAAM,CAAC,GAClD;CACJ,MAAM,cACJ,cAAc,KAAK,gBAAgB,IAC/B,KAAK,KAAK,gBAAgB,YAAY,GACtC;CACN,MAAM,eACJ,aAAa,IACT,MAAM,KAAK,EAAE,QAAQ,oBAAoB,SAAS;EAChD,eAAe;EACf,aAAa;EACb,mBAAmB;EACnB,cAAc;EACd,qBAAqB;EACrB,SACE,cAAc,IACV,MAAM,KAAK,EAAE,QAAQ,aAAa,SAAS;GACzC,eAAe;GACf,mBAAmB;GACnB,qBAAqB;GACtB,EAAE,GACH,EAAE;EACT,EAAE,GACH,EAAE;AAER,MAAK,MAAM,UAAU,SAAS;AAC5B,oBACE,UACA,OAAO,cACP,OAAO,OACP,OAAO,OACR;AACD,oBACE,WACA,OAAO,eACP,OAAO,OACP,OAAO,OACR;EACD,MAAM,UAAU,SAAU,OAAO,UAAU,SAAU,UAAU;AAC/D,OAAK,IAAI,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,OAC3C,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,OAAO,KAAK,QAAQ;GAC7C,MAAM,UAAU,IAAI,OAAO,QAAQ,KAAK;GACxC,MAAM,WAAW,KAAK,IACpB,OAAO,aAAa,UAAW,OAAO,cAAc,QACrD;GACD,MAAM,WAAW,KAAK,IACpB,OAAO,aAAa,SAAS,KAAM,OAAO,cAAc,SAAS,GAClE;GACD,MAAM,WAAW,KAAK,IACpB,OAAO,aAAa,SAAS,KAAM,OAAO,cAAc,SAAS,GAClE;GACD,MAAM,eAAe,KAAK,IAAI,UAAU,UAAU,SAAS;AAC3D,mBAAgB,WAAW,WAAW;AACtC,yBAAsB,KAAK,IAAI,qBAAqB,aAAa;AACjE,oBAAiB;AACjB,OAAI,iBAAiB,EAAG,gBAAe;AACvC,OAAI,gBAAA,GAAyC,sBAAqB;AAElE,OAAI,aAAa,GAAG;IAKlB,MAAM,cAAc,aAJF,KAAK,IACrB,aAAa,SAAS,GACtB,KAAK,OAAO,OAAO,UAAU,IAAI,eAAe,WAAW,CAC5D;AAED,gBAAY,iBAAiB;AAC7B,gBAAY,gBAAgB,WAAW,WAAW;AAClD,QAAI,iBAAiB,EAAG,aAAY,eAAe;AACnD,QAAI,gBAAA,GACF,aAAY,qBAAqB;AAEnC,gBAAY,sBAAsB,KAAK,IACrC,YAAY,qBACZ,aACD;AACD,QAAI,cAAc,GAAG;KACnB,MAAM,SACJ,YAAY,QACV,KAAK,IACH,YAAY,QAAQ,SAAS,GAC7B,KAAK,MAAM,IAAI,YAAY,CAC5B;AAEL,YAAO,iBAAiB;AACxB,SAAI,gBAAA,GACF,QAAO,qBAAqB;AAE9B,YAAO,sBAAsB,KAAK,IAChC,OAAO,qBACP,aACD;;;;;AAOX,KAAI,kBAAkB,EACpB,OAAM,IAAI,MAAM,0DAA0D;CAE5E,MAAM,2BAA2B,gBAAgB,gBAAgB;AACjE,QAAO;EACL;EACA,cAAc;EACd,0BAA0B,YAAY,yBAAyB;EAC/D,6BAA6B,YAAY,2BAA2B,IAAI;EACxE,iBAAiB,YAAY,cAAc,cAAc;EACzD,uBAAuB,YAAY,oBAAoB,cAAc;EACrE,uBAAA;EACA;EACA,OAAO,cACL,cACA,YACA,aACA,aACA,WAAW,SAAS,eAAe,cACnC,WAAW,SAAS,gBAAgB,cACrC;EACF;;AAGH,SAAgB,2BACd,SACwB;AACxB,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,6CAA6C;CAE/D,MAAM,eAAe,QAAQ,GAAI;CACjC,MAAM,wBAAwB,QAAQ,GAAI;AAC1C,KACE,QAAQ,MACL,WACC,OAAO,iBAAiB,gBACxB,OAAO,0BAA0B,sBACpC,CAED,OAAM,IAAI,MACR,mEACD;CAEH,MAAM,gBAAgB,QAAQ,QAC3B,OAAO,WAAW,QAAQ,OAAO,eAClC,EACD;CACD,MAAM,YAAY,UAChB,QAAQ,QACL,OAAO,WAAW,QAAQ,MAAM,OAAO,GAAG,OAAO,eAClD,EACD,GAAG;CACN,MAAM,2BAA2B,UAC9B,WAAW,OAAO,yBACpB;AACD,QAAO;EACL;EACA;EACA,0BAA0B,YAAY,yBAAyB;EAC/D,6BAA6B,YAAY,2BAA2B,IAAI;EACxE,iBAAiB,YAAY,UAAU,WAAW,OAAO,gBAAgB,CAAC;EAC1E,uBAAuB,YACrB,UAAU,WAAW,OAAO,sBAAsB,CACnD;EACD;EACA,qBAAqB,KAAK,IACxB,GAAG,QAAQ,KAAK,WAAW,OAAO,oBAAoB,CACvD;EACD,OAAO,0BAA0B,QAAQ;EAC1C;;;;;;;;;;;AAYH,SAAS,aACP,MACA,OACyB;CACzB,MAAM,0BAAU,IAAI,KAAuD;AAC3E,MAAK,MAAM,UAAU,CAAC,GAAG,MAAM,GAAG,MAAM,EAAE;EACxC,MAAM,WAAW,QAAQ,IAAI,OAAO,OAAO;AAC3C,MAAI,CAAC,UAAU;AACb,WAAQ,IAAI,OAAO,QAAQ;IACzB,GAAG;IACH,MAAM,OAAO,wBAAwB,OAAO;IAC7C,CAAC;AACF;;AAEF,WAAS,iBAAiB,OAAO;AACjC,WAAS,QAAQ,OAAO,wBAAwB,OAAO;AACvD,WAAS,sBAAsB,KAAK,IAClC,SAAS,qBACT,OAAO,oBACR;;AAEH,QAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,CACzB,KAAK,EAAE,MAAM,GAAG,cAAc;EAC7B,GAAG;EACH,uBAAuB,YAAY,OAAO,OAAO,cAAc;EAChE,EAAE,CACF,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,CACnC,KAAK,QAAQ,iBAAiB;EAAE,GAAG;EAAQ;EAAa,EAAE;;AAG/D,SAAS,0BACP,SACuB;CACvB,MAAM,0BAAU,IAAI,KAQjB;AACH,MAAK,MAAM,UAAU,QACnB,MAAK,MAAM,QAAQ,OAAO,SAAS,EAAE,EAAE;EACrC,MAAM,MAAM,GAAG,KAAK,aAAa,GAAG,KAAK;EACzC,MAAM,WAAW,QAAQ,IAAI,IAAI;EACjC,MAAM,cAAc,KAAK,kBAAkB,KAAK;EAChD,MAAM,oBAAoB,KAAK,wBAAwB,KAAK;EAC5D,MAAM,eACJ,KAAK,2BAA2B,KAAK,gBAAgB;AACvD,MAAI,CAAC,UAAU;AACb,WAAQ,IAAI,KAAK;IACf,MAAM,EAAE,GAAG,MAAM;IACjB;IACA;IACA;IACD,CAAC;AACF;;AAEF,WAAS,KAAK,iBAAiB,KAAK;AACpC,WAAS,KAAK,sBAAsB,KAAK,IACvC,SAAS,KAAK,qBACd,KAAK,oBACN;AACD,WAAS,eAAe;AACxB,WAAS,qBAAqB;AAC9B,WAAS,gBAAgB;AACzB,WAAS,KAAK,UAAU,aAAa,SAAS,KAAK,SAAS,KAAK,QAAQ;;AAG7E,QAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,CACzB,KAAK,EAAE,MAAM,aAAa,mBAAmB,oBAAoB;EAChE,GAAG;EACH,iBAAiB,YAAY,cAAc,KAAK,cAAc;EAC9D,uBAAuB,YACrB,oBAAoB,KAAK,cAC1B;EACD,0BAA0B,YACxB,gBAAgB,KAAK,gBAAgB,GACtC;EACF,EAAE,CACF,MAAM,MAAM,UAAU,KAAK,eAAe,MAAM,aAAa,CAC7D,KAAK,MAAM,WAAW;EAAE,GAAG;EAAM,WAAW;EAAO,EAAE;;;;;;;;;;AC72B1D,MAAa,+BAA+B;AAC5C,MAAM,8BAA8B;AAKpC,MAAM,2CAA2C;AACjD,MAAM,mCAAmC;AACzC,MAAM,8CAA8C;AAOpD,MAAM,yCAAyC;AAC/C,MAAM,+CAA+C;AACrD,MAAM,gDAAgD;AACtD,MAAM,yCAAyC;AAC/C,MAAM,4CAA4C;AAmBlD,SAAS,oBAAoB,MAAiD;AAC5E,QAAO,aAAa,QAAQ,WAAW;;AAGzC,SAAS,qBAAqB,SAAiD;AAC7E,QAAO,IAAI,IACT,QAAQ,MAAM,KAAK,EAAE,MAAM,YAAY,CAAC,KAAK,aAAa,EAAE,MAAM,CAAC,CACpE;;AAGH,SAAS,4BACP,SACA,YAC+B;AAC/B,KAAI,QAAQ,QAAQ,aAAa,KAAK,WAAY,QAAO;CACzD,MAAM,OAAO,WAAW,IAAI,iBAAiB,EAAE,aAAa;AAC5D,QAAO,SAAS,UAAU,SAAS;;AAGrC,SAAS,wBAAwB,OAAwB;CACvD,MAAM,eAAe,IAAI,IACvB,MACG,MAAM,IAAI,CACV,KAAK,gBAAgB,YAAY,MAAM,KAAK,EAAE,CAAC,CAC/C,QACE,UACC,MAAM,WAAW,KACjB,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,KAAA,EAChB,CACA,KAAK,CAAC,UAAU,WAAW,CAC1B,SAAS,MAAM,CAAC,aAAa,EAC7B,MACG,QAAQ,uBAAuB,GAAG,CAClC,MAAM,CACN,aAAa,CACjB,CAAC,CACL;AACD,QACE,aAAa,IAAI,UAAU,KAAK,UAChC,CAAC,UAAU,WAAW,CAAC,SAAS,aAAa,IAAI,aAAa,IAAI,GAAG,IACrE,aAAa,IAAI,qBAAqB,KAAK,YAC3C,OAAO,WAAW,aAAa,IAAI,UAAU,IAAI,IAAI,KAAK;;AAI9D,SAAgB,2BAA2B,MAAwC;CACjF,MAAM,yBAAS,IAAI,KAAa;CAChC,MAAM,OAAO,MAAM,KAAK;CACxB,MAAM,SAAS,MAAsB,kBAAiC;AACpE,MAAI,OAAO,QAAQ,GAAI;EACvB,IAAI,QAAQ;AACZ,MAAI,oBAAoB,KAAK,EAAE;GAC7B,MAAM,MAAM,KAAK,QAAQ,aAAa;GACtC,MAAM,aAAa,qBAAqB,KAAK;AAC7C,OAAI,QAAQ,YAAY;AACtB,QAAI,CAAC,4BAA4B,MAAM,WAAW,IAAI,MAAO;AAC7D,SAAK,MAAM,SAAS,KAAK,QAAQ,YAAY;AAC3C,WAAM,OAAO,MAAM;AACnB,SAAI,OAAO,QAAQ,GAAI;;AAEzB;;AAEF,OAAI;IAAC;IAAU;IAAS;IAAW,CAAC,SAAS,IAAI,CAAE;AACnD,WACE,SACA,WAAW,IAAI,SAAS,IACxB,wBAAwB,WAAW,IAAI,QAAQ,IAAI,GAAG;AACxD,OAAI,QAAQ,YAAY,WAAW,IAAI,OAAO,IAAI,CAAC,OAAO;IACxD,MAAM,SAAS,WAAW,IAAI,aAAa,IAAI,IAC5C,QAAQ,SAAS,IAAI,CACrB,MAAM;AACT,WAAO,IAAI,SAAS,cAAc;;;AAGtC,MAAI,MAAO;AACX,OAAK,MAAM,SAAS,gBAAgB,OAAO,KAAK,aAAa,EAAE,EAAE;AAC/D,SAAM,OAAO,MAAM;AACnB,OAAI,OAAO,QAAQ,GAAI;;;AAG3B,OAAM,MAAM,MAAM;AAClB,QAAO,CAAC,GAAG,OAAO,CAAC,KAAK,WAAW,EAAE,OAAO,EAAE;;AAGhD,SAAgB,gBAAgB,OAAe,MAAuB;CACpE,MAAM,MAAM,IAAI,IAAI,OAAO,QAAQ,oBAAoB;AACvD,QAAO,GAAG,IAAI,WAAW,IAAI;;;;;;;AAQ/B,SAAgB,sCACd,SACA,QACA,MACA,YAAY,gOACN;AAIN,KAHuB,QAAQ,WAAW,KACvC,WAAW,MAAM,IAAI,CACrB,QAAQ,SAAS,GAAG,KAEF,OAAO,gBAC1B,QAAQ,WAAW,WAAW,OAAO,OAErC,OAAM,IAAI,MACR,4FACD;AAEH,KACE,QAAQ,kBAAkB,UAAU,KAAK,SACzC,QAAQ,kBAAkB,WAAW,KAAK,UAC1C,QAAQ,WAAW,UAAU,KAAK,MAElC,OAAM,IAAI,MACR,4CAA4C,QAAQ,kBAAkB,MAAM,GAAG,QAAQ,kBAAkB,OAAO,UAAU,QAAQ,WAAW,MAAM,oDAAoD,KAAK,MAAM,GAAG,KAAK,OAAO,GAClO;CAEH,MAAM,sBAAsB,KAAK,eAAe,KAAK;AACrD,KACE,gBAAgB,QAAQ,SAAS,KAC/B,gBAAgB,qBAAqB,QAAQ,SAAS,IACxD,QAAQ,cAAc,IAEtB,OAAM,IAAI,MACR,mCAAmC,gBAAgB,QAAQ,SAAS,CAAC,SAAS,QAAQ,WAAW,+CAA+C,gBAAgB,qBAAqB,QAAQ,SAAS,CAAC,GAAG,KAAK,gBAAgB,KAAA,IAAY,YAAY,KACxP;;AAIL,SAAgB,oBAAoB,OAA8B;AAChE,KAAI,UAAU,KAAM,QAAO;CAC3B,MAAM,UAAU,MAAM,MAAM,GAAG,4BAA4B;AAC3D,QAAO,KAAK,UACV,QAAQ,WAAW,MAAM,SAAS,UAAU,GAAG,QAAQ,GACxD;;AAGH,SAAgB,gCACd,UACA,UACQ;CACR,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,KAAK,EAAE;CACzC,MAAM,MAAM,KAAK,IAAI,GAAG,SAAS,KAAK,EAAE;CACxC,MAAM,QAAQ,KAAK,IAAI,SAAS,OAAO,SAAS,KAAK,IAAI,SAAS,KAAK,MAAM;CAC7E,MAAM,SAAS,KAAK,IAClB,SAAS,QACT,SAAS,KAAK,IAAI,SAAS,KAAK,OACjC;AAGD,QAFqB,KAAK,IAAI,GAAG,QAAQ,KAAK,GACxB,KAAK,IAAI,GAAG,SAAS,IAAI,IACN,SAAS,QAAQ,SAAS;;AAGrE,SAAgB,oBACd,UACQ;AAGR,SAFc,SAAS,aAAa,SAAS,QAAQ,SAAS,KACrC,QAAQ,QAAQ,IAAI,CAAC,MAAM,CAClC,MAAM,GAAG,IAAI,IAAI;;AAGrC,SAAgB,kCACd,UACA,mBACA,uBACA,+BAC6B;CAC7B,MAAM,WAAW,SAAS;CAC1B,MAAM,4BACJ,SAAS,WAAW,SAAS,SAAS;CACxC,MAAM,iCACJ,KAAK,IAAI,SAAS,WAAW,SAAS,SAAS,SAAS,SAAS,OAAO,GACxE,SAAS;CACX,MAAM,+BAA+B,IAAI,IACvC,sBAAsB,KAAK,EAAE,YAAY,MAAM,aAAa,CAAC,CAC9D;CACD,MAAM,qCACJ,+BAA+B,mBAAmB;CACpD,MAAM,kCACJ,+BAA+B,WAAW,EAAE;CAC9C,MAAM,2BAA2B,gCAAgC,QAC9D,WAAW,8BAA8B,QAAQ,SAAS,CAC5D;CACD,MAAM,iCAAiC,yBAAyB;CAChE,MAAM,gCAAgC,SAAS,SAAS,UAAU,QAC/D,aAAa,SAAS,IAAI,aAAa,KAAK,SAC9C;CACD,MAAM,2BAA2B,SAAS,SAAS,UAChD,QAAQ,aAAa;EACpB,MAAM,MAAM,SAAS,IAAI,aAAa;EACtC,MAAM,OAAO,SAAS,MAAM,aAAa;AACzC,MAAI,QAAQ,UAAU;AACpB,OAAI,SAAS,SAAS,MAAO,QAAO;AACpC,OAAI,SAAS,SAAS,KAAM,QAAO;AACnC,OAAI,kBACF,QACE,6BAA6B,IAC3B,oBAAoB,SAAS,CAAC,aAAa,CAC5C,IACD,yBAAyB,MAAM,WAC7B,8BAA8B,QAAQ,SAAS,KAAK,CACrD;AAGL,UAAO;;AAET,SAAO,SAAS,YAAY,SAAS;GACrC,CACD,KAAK,cAAc;EAClB,OAAO,oBAAoB,SAAS;EACpC,WAAW,gCAAgC,UAAU,SAAS;EAC9D,QAAQ;EACT,EAAE,CACF,QACE,EAAE,gBAAgB,aAAa,yCACjC;CACH,MAAM,oCAAoC,sBACvC,QAAQ,EAAE,YAAY;EACrB,MAAM,kBAAkB,MAAM,aAAa;AAK3C,SACE,CALkC,8BAA8B,MAC/D,aACC,oBAAoB,SAAS,CAAC,aAAa,KAAK,gBACnD,IAEiC,yBAAyB,SAAS;GAEpE,CACD,KAAK,EAAE,YAAY,MAAM;CAC5B,MAAM,mBAAmB;CACzB,MAAM,yBAAyB,yBAAyB,MACrD,EAAE,gBAAgB,aAAa,iCACjC;CACD,MAAM,kCACJ,iBAAiB,SAAS,KAAK,4BAA4B;CAC7D,MAAM,oCACJ,iBAAiB,SAAS,KAC1B,kCACE;CACJ,MAAM,eACJ,0BACA,mCACA;CACF,MAAM,oBACJ,CAAC,gBAAgB,kCAAkC,SAAS;CAC9D,MAAM,SAAS,eACX,iBACA,oBACE,uBACA;CACN,MAAM,UAAU,CACd,GAAI,eACA;EACE,GAAG,iBAAiB,KACjB,EAAE,OAAO,gBACR,oBAAoB,MAAM,YAAY,YAAY,KAAK,QAAQ,EAAE,CAAC,8BACrE;EACD,GAAI,kCACA,CACE,8BAA8B,0BAA0B,QAAQ,EAAE,CAAC,gGACpE,GACD,EAAE;EACN,GAAI,oCACA,CACE,sEAAsE,+BAA+B,QAAQ,EAAE,CAAC,gGACjH,GACD,EAAE;EACP,GACD,EAAE,EACN,GAAI,oBACA,CACE,mCAAmC,kCAAkC,KAAK,UAAU,IAAI,MAAM,GAAG,CAAC,KAAK,KAAK,CAAC,kEAAkE,gCAAgC,qBAAqB,KAAK,KAAK,QAAQ,EAAE,CAAC,yBAAyB,gCAAgC,KAAK,EAAE,MAAM,gCAAgC,KAAK,EAAE,UAAU,gCAAgC,SAAS,EAAE,WAAW,gCAAgC,UAAU,EAAE,8HACnd,GACD,EAAE,CACP;CACD,MAAM,gBACJ,iBAAiB,IAAI,SAAS,kCAAkC;AAClE,QAAO;EACL;EACA,uBAAuB,WAAW;EAClC,sBAAsB,iBAAiB;EACvC,wCACE,kCAAkC;EACpC;EACA;EACA;EACA;EACA;EACA,UACE,WAAW,WACP,OACA,6CAA6C,gBAAgB,IAAI,cAAc,KAAK,uBAAuB;EAClH;;AAGH,SAAgB,8BACd,QACA,UACS;AACT,KAAI,OAAO,qBAAqB,uCAC9B,QAAO;CAET,MAAM,aAAa,OAAO,QAAQ,SAAS;CAC3C,MAAM,gBACJ,SAAS,SAAS;CACpB,MAAM,sBACJ,OAAO,KAAK,iBACZ,OAAO,IAAI,OAAO,UAAU,SAAS,SAAS;AAChD,QACE,OAAO,qBAAqB,gDAC5B,cAAc,iDACd;;AAIJ,SAAgB,8BACd,QACA,UACS;CACT,MAAM,OAAO,KAAK,IAAI,OAAO,GAAG,SAAS,EAAE;CAC3C,MAAM,MAAM,KAAK,IAAI,OAAO,GAAG,SAAS,EAAE;CAC1C,MAAM,QAAQ,KAAK,IAAI,OAAO,IAAI,OAAO,OAAO,SAAS,IAAI,SAAS,MAAM;CAC5E,MAAM,SAAS,KAAK,IAClB,OAAO,IAAI,OAAO,QAClB,SAAS,IAAI,SAAS,OACvB;CACD,MAAM,eAAe,KAAK,IAAI,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI,GAAG,SAAS,IAAI;CAG1E,MAAM,QAFa,OAAO,QAAQ,OAAO,SACpB,SAAS,QAAQ,SAAS,SACL;AAC1C,QACE,QAAQ,KACR,eAAe,SAAS;;;;ACtV5B,SAAgB,0BACd,OACmB;CACnB,MAAM,EACJ,MACA,UACA,cACA,WACA,aACA,aACA,qBACE;CACJ,MAAM,sBACJ,SAAS,UAAU,mBAAA;AAmErB,QAAO;EAAE,qBAlEmB;GAC1B,GAAI,MAAM,oBAAoB,WAAW,iBACrC,CAAC,yCAAyC,GAC1C,EAAE;GACN,GAAI,MAAM,+BAA+B,KAAA,IACrC,CAAC,wDAAwD,GACzD,MAAM,6BACJ,CAAC,gEAAgE,GACjE,EAAE;GACR,GAAI,MAAM,gCAAgC,KAAA,IACtC,CAAC,uDAAuD,GACxD,MAAM,8BACJ,CAAC,+DAA+D,GAChE,EAAE;GACR,GAAI,MAAM,eAAe,QAAQ,MAAM,cAAc,MACjD,CAAC,6BAA6B,MAAM,aAAa,GACjD,EAAE;GACN,GAAI,MAAM,YAAY,CAAC,kCAAkC,GAAG,EAAE;GAC9D,GAAI,MAAM,qBAAqB,CAAC,qCAAqC,GAAG,EAAE;GAC1E,GAAI,MAAM,mBAAmB,CAAC,6BAA6B,GAAG,EAAE;GAChE,GAAI,cAAc,YAAY,eAAe,CAAC,YAAY,QACtD,CACE,qCAAqC,YAAY,YAAY,WAAW,YAAY,aAAa,SAClG,GACD,EAAE;GACN,GAAI,cAAc,YAAY,CAAC,YAAY,QACvC,CACE,qCAAqC,YAAY,YAAY,WAAW,YAAY,aAAa,SAClG,GACD,EAAE;GACN,GAAI,MAAM,gBACN,CAAC,4DAA4D,GAC7D,EAAE;GACN,GAAI,aAAa,WAAW,CAAC,MAAM,KAAK,QACpC,CACE,iCAAiC,MAAM,KAAK,qBAAqB,YAClE,GACD,EAAE;GACN,GAAI,iBAAiB,YAAY,sBAC7B,CACE,iDAAiD,mBAAmB,KAAK,QAAQ,EAAE,CAAC,aAAa,mCAAmC,IAAI,IACzI,GACD,EAAE;GACP;EAuB6B,uBAtBA;GAC5B,GAAI,MAAM,oBAAoB,WAAW,uBACrC,CACE,oNACD,GACD,EAAE;GACN,GAAI,cAAc,gBAAgB,eAAe,CAAC,YAAY,QAC1D,CACE,sCAAsC,YAAY,YAAY,WAAW,YAAY,aAAa,SACnG,GACD,EAAE;GACN,GAAI,cAAc,gBAAgB,CAAC,YAAY,QAC3C,CACE,sCAAsC,YAAY,YAAY,WAAW,YAAY,aAAa,SACnG,GACD,EAAE;GACN,GAAI,iBAAiB,gBAAgB,sBACjC,CACE,iDAAiD,mBAAmB,KAAK,QAAQ,EAAE,CAAC,GACrF,GACD,EAAE;GACP;EACoD;;;AAIvD,SAAgB,oBACd,OACuB;AACvB,QAAO,CAAC,GAAG,MAAM,CACd,QAAQ,SAAS,KAAK,wBAAwB,EAAE,CAChD,MACE,MAAM,UAAU,MAAM,wBAAwB,KAAK,sBACrD;;;;;;;;;AAUL,SAAgB,wBACd,OACQ;CACR,MAAM,cAAc,oBAAoB,MAAM;AAC9C,KAAI,YAAY,WAAW,EACzB,QAAO,MAAM,SACT,6DACA;AAEN,QAAO,kDAAkD,YACtD,MAAM,GAAG,EAAE,CACX,KAAK,SAAS;EAGb,MAAM,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,MAC7B,MAAM,UACL,MAAM,wBAAwB,KAAK,sBACtC,CAAC;EACF,MAAM,WACJ,SAAS,MAAM,wBAAwB,KAAK,wBAAwB;AACtE,SAAO,GAAG,KAAK,aAAa,GAAG,KAAK,WAAW,MAC7C,KAAK,wBAAwB,KAC7B,QAAQ,EAAE,CAAC,GACX,WACI,aAAa,MAAM,OAAO,GAAG,MAAM,KAAK,SACtC,MAAM,wBAAwB,KAC9B,QAAQ,EAAE,CAAC,MACb;GAEN,CACD,KAAK,KAAK,GACX,YAAY,SAAS,IACjB,MAAM,YAAY,SAAS,EAAE,4BAC7B,GACL;;;;;ACtKH,MAAM,yBAAyB,KAAK,OAAO;AAC3C,MAAM,iCAAiC,KAAK,OAAO;AACnD,MAAM,wBAAwB,KAAK,OAAO;AAC1C,MAAM,iBAAiB;AACvB,MAAM,oBAAoB,mBAAmB,MAAM,IAAI;AAEvD,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YACE,SACA,YACA;AACA,QAAM,QAAQ;AAFL,OAAA,aAAA;AAGT,OAAK,OAAO;;;AA2BhB,SAAS,SAAS,WAAmB,WAA4B;CAC/D,MAAM,MAAM,SAAS,WAAW,UAAU;AAC1C,QAAO,IAAI,SAAS,KAAK,CAAC,IAAI,WAAW,KAAK,IAAI,CAAC,IAAI,WAAW,IAAI;;AAGxE,eAAe,YAAY,MAAgC;AACzD,KAAI;AACF,UAAQ,MAAM,KAAK,KAAK,EAAE,aAAa;SACjC;AACN,SAAO;;;;;;;;;;AAWX,eAAsB,mBACpB,QACA,QACiB;CACjB,MAAM,QAAQ,QAAQ,OAAO;AAC7B,KAAI,CAAE,MAAM,YAAY,MAAM,EAAG;AAC/B,MAAI,MAAM,SAAS,eAAe,CAAE,QAAO;EAC3C,MAAM,UAAU,GAAG,MAAM,QAAQ,eAAe,GAAG,GAAG;AACtD,MAAI;AACF,SAAM,KAAK,QAAQ;AACnB,UAAO;UACD;AACN,SAAM,IAAI,kBACR,GAAG,OAAO,2IACX;;;CAGL,MAAM,cAAe,MAAM,YAAY,KAAK,OAAO,GAAG,kBAAkB,CAAC,GACrE,KAAK,OAAO,GAAG,kBAAkB,GACjC;CACJ,MAAM,SAAS,MAAM,QAAQ,YAAY,EAAE,QAAQ,SACjD,KAAK,SAAS,eAAe,CAC9B;AACD,KAAI,MAAM,WAAW,EACnB,OAAM,IAAI,kBACR,2CAA2C,YAAY,6FACxD;CAEH,MAAM,aAMD,EAAE;AACP,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,aAAa,KAAK;AACpC,MAAI;GACF,MAAM,MAAM,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC;GAKpD,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;GACnE,MAAM,QAAQ,IAAI,mBAAmB;GACrC,MAAM,SAAS,IAAI,mBAAmB;GACtC,MAAM,QAAQ,WAAW,gBAAgB,SAAS,GAAG;GACrD,MAAM,eACJ,UAAU,MACV,UAAU,gBAAgB,OAAO,aAAa,SAAS;GACzD,MAAM,kBACJ,CAAC,OAAO,YACP,UAAU,OAAO,SAAS,SAAS,WAAW,OAAO,SAAS;AACjE,cAAW,KAAK;IACd;IACA,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;IAClE;IACA,UAAU,GAAG,OAAO,MAAM,CAAC,GAAG,OAAO,OAAO;IAC5C,SAAS,gBAAgB;IAC1B,CAAC;UACI;;CAKV,MAAM,WAAW,WACd,QAAQ,cAAc,UAAU,QAAQ,CACxC,MAAM,MAAM,UAAU,MAAM,WAAW,cAAc,KAAK,WAAW,CAAC;AACzE,KAAI,SAAS,WAAW,EACtB,OAAM,IAAI,kBACR,iBAAiB,YAAY,wBAAwB,OAAO,cAAc,OAAO,WAAW,OAAO,OAAO,SAAS,MAAM,GAAG,OAAO,SAAS,WAAW,GAAG,4IAC1J,WAAW,KAAK,EAAE,MAAM,YAAY,OAAO,gBAAgB;EACzD,SAAS;EACT,aAAa;EACb;EACA;EACD,EAAE,CACJ;AAEH,QAAO,SAAS,GAAI;;AAGtB,eAAe,YACb,MACA,UACA,OACiB;CACjB,MAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,KAAI,CAAC,KAAK,QAAQ,CAAE,OAAM,IAAI,kBAAkB,GAAG,MAAM,iBAAiB;AAC1E,KAAI,KAAK,OAAO,SACd,OAAM,IAAI,kBACR,GAAG,MAAM,OAAO,KAAK,QAAQ,OAAO,OAAO,QAAQ,EAAE,CAAC,8BAA8B,YAAY,OAAO,MAAM,KAC9G;AAEH,QAAO,SAAS,KAAK;;AAGvB,SAAS,OAAO,OAAuB;AACrC,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;;;;;;;AASzD,eAAsB,iBAAiB,aAA4C;CACjF,MAAM,mBAAmB,MAAM,SAAS,YAAY;CACpD,MAAM,cAAc,QAAQ,iBAAiB;AAE7C,KADa,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,kBAAkB,OAAO,CAC3D,KAAK,IAAI,KAAA,iCAChB,OAAM,IAAI,kBACR,8BAA8B,mBAAmB,0EAA0E,YAAY,YACxI;CAEH,MAAM,OAAO,QAAQ,aAAa,GAAG,kBAAkB,UAAU,KAAK,CAAC;CACvE,MAAM,eAAe,MAAM,YACzB,kBACA,gCACA,2BACD;CACD,IAAI;AACJ,KAAI;AACF,YAAU,+BAA+B,aAAa,SAAS,OAAO,CAAC;UAChE,OAAO;AACd,QAAM,IAAI,kBACR,wCAAwC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAC/F;;CAGH,MAAM,iBAAiB,OAAO,aAAqB,UAAkB;EACnE,MAAM,eAAe,YAAY,WAAW,MAAM,IAAI,CAAC,QAAQ,SAAS,GAAG;EAE3E,MAAM,YAAY,MAAM,SADX,QAAQ,MAAM,GAAG,aAAa,MAAM,IAAI,CAAC,CAChB,CAAC,YAAY;AACjD,SAAM,IAAI,kBAAkB,GAAG,MAAM,GAAG,aAAa,cAAc;IACnE;AACF,MAAI,CAAC,SAAS,aAAa,UAAU,CACnC,OAAM,IAAI,kBACR,GAAG,MAAM,GAAG,aAAa,wBAAwB,mBAAmB,cACrE;AAEH,SAAO;GAAE;GAAc,UAAU;GAAW;;CAG9C,MAAM,oBAAoB,MAAM,eAC9B,QAAQ,WAAW,MACnB,oBACD;CACD,MAAM,kBAAkB,MAAM,YAC5B,kBAAkB,UAClB,wBACA,oBACD;CAED,IAAI,wBAAkD,EAAE;CACxD,IAAI,oBAAoB;CACxB,MAAM,cAAc,QAAQ,UAAU;AACtC,KAAI,aAAa;EAEf,MAAM,YAAY,MAAM,aADX,MAAM,eAAe,YAAY,MAAM,qBAAqB,EAElE,UACL,uBACA,8BACD;AACD,MACE,UAAU,WAAW,YAAY,cACjC,OAAO,UAAU,KAAK,YAAY,OAElC,OAAM,IAAI,kBACR,6EACD;AAEH,sBAAoB;AACpB,0BAAwB,2BAA2B,UAAU,SAAS,OAAO,CAAC;;AAGhF,QAAO;EACL;EACA,aAAa;EACb,qBAAqB,GAAG,mBAAmB,GAAG,SAAS,iBAAiB;EACxE,eAAe,OAAO,aAAa;EACnC;EACA,YAAY;GACV,UAAU,kBAAkB;GAC5B,cAAc,kBAAkB;GAChC,OAAO;GACP,QAAQ,OAAO,gBAAgB;GAChC;EACD;EACA;EACD;;;;ACtNH,MAAa,sBAAsB;;AAEnC,MAAa,qBAAqB;AAqGlC,SAAS,OACP,MACA,SACA,QAA0D,EAAE,EAC7C;CACf,MAAM,EAAE,OAAO,UAAU,GAAG,SAAS;AACrC,QAAO;EACL,IAAI;EACJ,SAAS;EACT,SAAS;EACT,UAAU,CAAC,QAAQ;EACnB,aAAa,EAAE;EACf,GAAG;EACH,OAAO;GAAE;GAAM;GAAS;EACzB;;AAGH,SAAS,MAAM,WAAmB,aAA6B;AAC7D,KAAI,eAAe,EAAG,QAAO;AAC7B,QAAO,QAAQ,YAAY,aAAa,QAAQ,EAAE,CAAC;;AAGrD,SAAS,YAAe,WAAmC;AACzD,QAAO,IAAI,SAAY,SAAS,WAAW;EACzC,MAAM,QAAQ,iBAEV,uBACE,IAAI,MACF,sCAAsC,qBAAqB,IAAK,mFACjE,CACF,EACH,mBACD;AACD,YAAU,MACP,UAAU;AACT,gBAAa,MAAM;AACnB,WAAQ,MAAM;MAEf,UAAmB;AAClB,gBAAa,MAAM;AACnB,UAAO,MAAM;IAEhB;GACD;;AAGJ,SAAS,QAAQ,MAAc,OAAuB;AACpD,QAAO,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,CAAC,UAAU;;;;AAK1E,SAAS,sCACP,aACA,UACA,uBACsC;AACtC,KAAI,sBAAsB,WAAW,EAAG,QAAO;CAC/C,MAAM,iBAAiB,SAAS,kBAAkB;CAClD,MAAM,oBAAoB,KAAK,MAAM,YAAY,SAAS,eAAe;AACzE,KAAI,oBAAoB,EAAG,QAAO;AAClC,QAAO,mCACL,YAAY,OACZ,gBACA,oBACC,OAAO,cAAc,kBACpB,WACE,aACA,QAAQ,gBACR,gBACA,cACA,cACD,CACJ;;AAGH,SAAS,eAAe,WAGtB;AAMA,KAJE,UAAU,OAAO,SACjB,UAAU,OAAO,UACjB,UAAU,OAAO,SACjB,UAAU,OAAO,YACA,EACjB,QAAO;EACL,MAAM,qBAAqB,UAAU,OAAO,MAAM,GAAG,UAAU,OAAO,MAAM,cAAc,UAAU,OAAO,MAAM,GAAG,UAAU,OAAO,MAAM;EAC3I,aAAa;EACd;AAEH,QAAO;EACL,MAAM,qBAAqB,UAAU,OAAO,OAAO,cAAc,UAAU,OAAO,QAAQ,yBAAyB,UAAU,OAAO,OAAO,cAAc,UAAU,OAAO,QAAQ,uBAAuB,UAAU,WAAW,sCAAsC,GAAG;EACvQ,aAAa;EACd;;;;;;;AAQH,eAAsB,WACpB,SACA,MACwB;CACxB,IAAI;AACJ,KAAI;AAKF,WAAS,MAAM,iBAJK,MAAM,mBAAmB,QAAQ,QAAQ;GAC3D,aAAa,QAAQ,eAAe,QAAQ;GAC5C,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;GAC3D,CAAC,CAC0C;UACrC,OAAO;AACd,MAAI,iBAAiB,kBACnB,QAAO,OAAO,2BAA2B,MAAM,SAAS,EACtD,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,YAAY,GAAG,EAAE,EAC7D,CAAC;AAEJ,SAAO,OACL,2BACA,6CAA6C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACpG;;CAGH,MAAM,UAAU,OAAO;CACvB,MAAM,EAAE,OAAO,WAAW,QAAQ;CAClC,MAAM,gBAAsD;EAC1D,MAAM,OAAO;EACb,YAAY,OAAO,WAAW;EAC9B,eAAe,OAAO;EACtB,sBAAsB,OAAO;EAC7B,WAAW,QAAQ;EACnB,aAAa,QAAQ;EACrB,UAAU;GAAE;GAAO;GAAQ;EAC5B;AACD,KAAI;AACF,wCACE,SACA,OAAO,YACP;GACE;GACA;GACA,MAAM,QAAQ;GACd,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;GACpE,EACD,oOACD;UACM,OAAO;AACd,SAAO,OACL,iBACA,6CAA6C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACnG,EAAE,QAAQ,eAAe,CAC1B;;CAGH,IAAI;AACJ,KAAI;AACF,eAAa,MAAM,KAAK,gBAAgB;UACjC,OAAO;AACd,MAAI,iBAAiB,2BACnB,QAAO;GACL,GAAG,OAAO,sBAAsB,MAAM,SAAS,EAAE,QAAQ,eAAe,CAAC;GACzE,OAAO;IACL,MAAM;IACN,SAAS,MAAM;IACf,SAAS,MAAM;IAChB;GACF;AAEH,QAAM;;CAGR,MAAM,WAAW,QAAQ,QAAQ,KAAK,QAAQ,MAAM;CACpD,MAAM,QAA6C,EAAE,KAAK,UAAU;CACpE,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,YACd,KAAK,QAAQ,YAAY;GACvB,KAAK;GACL;GACA;GACA,MAAM,QAAQ;GACd,gBAAgB;GACjB,CAAC,CACH;UACM,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,SAAO,OACL,0BACA,SAAS,MAAM,GAAG,OAAO,GAAG,QAAQ,SAAS,SAAS,cAAc,WAAW,cAAc,SAAS,WAAW,QAAQ,+DAA+D,oBAAoB,IAC5M;GAAE,QAAQ;GAAe;GAAO,CACjC;;AAEH,OAAM,YAAY,QAAQ;AAC1B,OAAM,cAAc,QAAQ;AAC5B,KAAI,QAAQ,eAAe,KACzB,QAAO,OACL,wBACA,qCAAqC,KAAK,UAAU,QAAQ,MAAM,CAAC,wHACnE;EAAE,QAAQ;EAAe;EAAO,CACjC;AAEH,KAAI,IAAI,IAAI,QAAQ,SAAS,CAAC,WAAW,IAAI,IAAI,SAAS,CAAC,OACzD,QAAO,OACL,iBACA,qBAAqB,QAAQ,MAAM,sCAAsC,QAAQ,SAAS,IAC1F;EAAE,QAAQ;EAAe;EAAO,CACjC;AAGH,KAAI;EACF,MAAM,mBAAmB,gCAAgC,QAAQ,UAAU;EAC3E,MAAM,cAAc,YAAY,OAAO,WAAW,OAAO,kBAAkB;AAC3E,MAAI,YAAY,QAAQ,YAAY,SAAA,KAClC,OAAM,IAAI,MACR,6BAA6B,YAAY,QAAQ,YAAY,QAAQ,gBAAgB,CAAC,+CAA+C,6BAA6B,gBAAgB,CAAC,sEACpL;AAEH,MAAI,YAAY,UAAU,MACxB,OAAM,IAAI,MACR,sBAAsB,YAAY,MAAM,yCAAyC,MAAM,kFACxF;AAEH,MAAI,QAAQ,cAAc,MACxB,OAAM,IAAI,MACR,6BAA6B,QAAQ,UAAU,8BAA8B,MAAM,KACpF;AAEH,MACE,QAAQ,WAAW,UAAU,YAAY,SACzC,QAAQ,WAAW,WAAW,YAAY,UAC1C,QAAQ,WAAW,eAAe,OAAO,WAAW,MAAM,OAE1D,OAAM,IAAI,MACR,0GACD;AAEH,MACE,iBAAiB,SAAS,UAAU,SACpC,iBAAiB,SAAS,WAAW,OAErC,OAAM,IAAI,MACR,qCAAqC,iBAAiB,SAAS,MAAM,GAAG,iBAAiB,SAAS,OAAO,sBAAsB,MAAM,GAAG,OAAO,YAChJ;AAEH,MAAI,gBAAgB,iBAAiB,IAAI,KAAK,gBAAgB,QAAQ,SAAS,CAC7E,OAAM,IAAI,MACR,0CAA0C,gBAAgB,iBAAiB,IAAI,CAAC,mCAAmC,gBAAgB,QAAQ,SAAS,CAAC,GACtJ;EAEH,MAAM,eAAe,YAAY,QAAQ,KAAK,wBAAwB;AACtE,MACE,aAAa,UAAU,QAAQ,aAC/B,aAAa,WAAW,QAAQ,eAEhC,OAAM,IAAI,MACR,4BAA4B,aAAa,MAAM,GAAG,aAAa,OAAO,qBAAqB,QAAQ,UAAU,GAAG,QAAQ,eAAe,4CACxI;EAGH,MAAM,oBACJ,QAAQ,SAAS,SACb,YAAY,SACZ,KAAK,IAAI,YAAY,QAAQ,OAAO;EAC1C,MAAM,uBACJ,QAAQ,SAAS,SAAS,QAAQ,SAAS,SAAS,SAAS;EAC/D,MAAM,OAAO,sBACX,YAAY,OACZ,mBACA,QAAQ,aACR,CAAC;GAAE,OAAO,aAAa;GAAO,QAAQ,aAAa;GAAQ,cAAc;GAAG,CAAC,CAC9E;EACD,MAAM,eAAe,8BAA8B,KAAK,QAAQ;EAChE,MAAM,WAAW,aAAa,KAAK,QAAQ;EAC3C,MAAM,eAAe,2BACnB,KAAK,QAAQ,KAAK,WAChB,qBACE,CACE;GACE,GAAG;GACH,cAAc,SAAS,aAAa,OAAO,SAAS,OAAO,OAAO;GAClE,eAAe,SAAS,cAAc,OAAO,UAAU,OAAO,OAAO;GACtE,CACF,EACD,OAAO,kBACP,cAAA,IAAA,GAGA,SACD,CACF,CACF;EAED,MAAM,gBAAgB,WAAW,SAAS,CAAC,OAAO,QAAQ,IAAI,CAAC,OAAO,MAAM;EAC5E,MAAM,qBAAqB,QAAQ,gBAAgB,QAAQ;EAC3D,MAAM,cAAc,QAAQ,cAAc;EAC1C,MAAM,mBAAmB,wBACvB,sBACA,QAAQ,YACT;EACD,MAAM,eAAe,uBAAuB,QAAQ,UAAU,iBAAiB;EAC/E,MAAM,OAAO,wBACX,aAAa,YACb,aAAa,YACd;EACD,MAAM,cACJ,QAAQ,SAAS,SACb,0BAA0B,QAAQ,UAAU,iBAAiB,GAC7D;EACN,MAAM,cAAc,0BAA0B,QAAQ,UAAU,iBAAiB;EACjF,MAAM,gCAAgC,sCACpC,aACA,SACA,OAAO,sBACR;EACD,MAAM,sBAAsB,kCAC1B,SACA,OAAO,mBACP,OAAO,uBACP,8BACD;EACD,MAAM,eAAe,eAAe,QAAQ,eAAe;EAC3D,MAAM,gBACJ,aAAa,mBAAmB,aAAa;EAC/C,MAAM,EAAE,qBAAqB,0BAC3B,0BAA0B;GACxB,MAAM,QAAQ;GACd,UAAU,QAAQ;GAClB,cAAc,QAAQ;GACtB,WAAW,QAAQ;GACnB;GACA,4BAA4B,QAAQ,SAAS;GAC7C,6BAA6B,iBAAiB;GAC9C,YAAY,QAAQ;GACpB,WAAW,QAAQ;GACnB;GACA,kBAAkB,aAAa;GAC/B;GACA;GACA;GACA;GACA;GACD,CAAC;EAEJ,MAAM,aAAsC;GAC1C,SAAS;GACT,MAAM;GACN,MAAM,QAAQ,SAAS,SAAS,cAAc;GAC9C,QAAQ;IAAE,UAAU,QAAQ;IAAc,OAAO,QAAQ;IAAW;GACpE,QAAQ;IACN,QAAQ,OAAO,WAAW;IAC1B,OAAO,YAAY;IACnB,QAAQ,YAAY;IACpB,eAAe;IAChB;GACD,SAAS;IACP,QAAQ;IACR,OAAO,QAAQ;IACf,QAAQ,QAAQ;IAChB,eAAe,QAAQ;IACvB,gBAAgB,KAAK;IACrB,UAAU;IACV,WAAW,QAAQ;IACnB;IACD;GACD,YAAY,QAAQ;GACpB,gBAAgB,KAAK;GACrB;GACA;GACA,qBAAqB,MAAM,KAAK,gBAAgB,kBAAkB;GAClE,sBAAsB,MAAM,KAAK,uBAAuB,QAAQ,YAAY;GAC5E,GAAG;GACH,OAAO;IACL,cAAc,QAAQ,eAAe,OAAO;IAC5C,eAAe,QAAQ,eAAe,OAAO;IAC7C,cAAc,QAAQ,eAAe,OAAO;IAC5C,eAAe,QAAQ,eAAe,OAAO;IAC7C,UAAU,QAAQ,eAAe;IACjC,GAAI,cACA,EACE,aAAa;KACX,OAAO,YAAY;KACnB,cAAc,YAAY;KAC1B,eAAe,YAAY;KAC3B,kBAAkB,YAAY;KAC9B,mBAAmB,YAAY;KAC/B,iBAAiB,YAAY;KAC7B,kBAAkB,YAAY;KAC9B,oBAAoB,YAAY,WAAW,IAAI,SAAS;KACzD,EACF,GACD,EAAE;IACN,aAAa;KACX,OAAO,YAAY;KACnB,cAAc,YAAY;KAC1B,eAAe,YAAY;KAC3B,kBAAkB,YAAY;KAC9B,mBAAmB,YAAY;KAC/B,iBAAiB,YAAY;KAC7B,kBAAkB,YAAY;KAC9B,oBAAoB,YAAY,WAAW,IAAI,SAAS;KACzD;IACF;GACD,MAAM;IACJ,MAAM,QAAQ;IACd,aAAa,aAAa;IAC1B,sBAAsB,OAAO;IAC7B,kBAAkB,KAAK;IACvB,mBAAmB,KAAK;IACxB,OAAO,KAAK;IACZ,kBAAkB,KAAK;IACvB,mBAAmB,KAAK;IACxB,aAAa,KAAK;IAClB,cAAc,KAAK;IACnB,mBAAmB,KAAK;IACxB,iBAAiB,aAAa;IAC9B,kBAAkB,aAAa;IAChC;GACF;EAID,MAAM,YACJ,QAAQ,MACL,MAAM,QAAQ,EAAE,CAAC,IAChB,QAAQ,kBAAkB,IAAI,CAC/B,QAAQ,UAAU,GAAG,CACrB,MAAM,GAAG,GAAG,IAAI;EACrB,MAAM,QAAQ,KACX,KAAK,CACL,aAAa,CACb,QAAQ,SAAS,IAAI,CACrB,QAAQ,KAAK,IAAI,CACjB,MAAM,GAAG,GAAG;EACf,MAAM,iBAAiB,KAAK,OAAO,MAAM,iBAAiB,cAAc;EACxE,MAAM,kBAAkB,KACtB,gBACA,WAAW,UAAU,GAAG,MAAM,GAAG,KAAK,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC,MAC1D;AACD,QAAM,MAAM,gBAAgB,EAAE,WAAW,MAAM,CAAC;AAChD,QAAM,UAAU,iBAAiB,QAAQ,IAAI;AAC7C,QAAM,aAAa;EAEnB,MAAM,cAAc,wBAAwB,aAAa,SAAS,EAAE,CAAC;EACrE,MAAM,qBAAqB,aAAa,WAAW;EACnD,MAAM,qBAAqB,YAAY,WAAW;EAClD,MAAM,WAAW,KAAK,QAClB,4BAA4B,KAAK,YAAY,UAAU,KAAK,iBAAiB,gBAAgB,CAAC,uBAAuB,KAAK,iBAAiB,MAC3I,mCAAmC,KAAK,qBAAqB,UAAU,WAAW,oBAAoB,KAAK,eAAe,CAAC,UAAU,oBAAoB,KAAK,gBAAgB,CAAC,GAAG,QAAQ,aAAa,eAAe,qEAAqE;EAC/R,MAAM,oBACJ,oBAAoB,WAAW,WAC3B,sEAAsE,oBAAoB,+BAA+B,QAAQ,EAAE,CAAC,0EACpI,mCAAmC,oBAAoB,OAAO,IAAI,oBAAoB,QAAQ,KAAK,IAAI,CAAC,GAAG,oBAAoB,YAAY,KAAK,SAAS;EAC/J,MAAM,UAAU,wDAAwD,MAAM,GAAG,OAAO,IAAI,QAAQ,KAAK,kBAAkB,QAAQ,SAAS,SAAS,QAAQ,aAAa,QAAQ,iBAAiB,IAAI,QAAQ,mBAAmB,GAAG,uBAAuB,YAAY,MAAM,GAAG,YAAY,OAAO,2BAA2B,QAAQ,SAAS,SAAS,MAAM,GAAG,qBAAqB,mBAAmB,QAAQ,cAAc,GAAG,QAAQ,YAAY,iBAAiB,eAAe,IAAI,MAAM,KAAK,YAAY,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC,eAAe,KAAK,eAAe,KAAK,kBAAkB,qBAAqB,oBAAoB,SAAS,IAAI,WAAW,mFAAmF,cAAc,aAAa,cAAc,gBAAgB,CAAC,oBAAoB,aAAa,aAAa,mCAAmC,aAAa,4BAA4B,QAAQ,EAAE,CAAC,6BAA6B,aAAa,wBAAwB,KAAK,QAAQ,EAAE,CAAC,qBAAqB,aAAa,sBAAsB,wBAAwB,aAAa,kBAAkB,KAAK,QAAQ,EAAE,CAAC,IAAI,cAAc,WAAW,aAAa,OAAO,oBAAoB,SAAS,IAAI,+BAA+B,oBAAoB,KAAK,KAAK,CAAC,KAAK,KAAK,sBAAsB,SAAS,IAAI,gCAAgC,sBAAsB,KAAK,KAAK,CAAC,KAAK,GAAG;AAEh4C,SAAO;GACL,IAAI,oBAAoB,WAAW;GACnC,SAAS,oBAAoB,WAAW,IAAI,cAAc;GAC1D,SAAS,YAAY,MAAM;GAC3B,UAAU;GACV,aAAa;GACb;GACA,GAAI,KAAK,QACL,EAAE,GACF,EACE,eAAe;IACb,MAAM,KAAK;IACX,QAAQ,oBAAoB,KAAK,eAAe;IAChD,OAAO,oBAAoB,KAAK,gBAAgB;IACjD,EACF;GACL,GAAI,eAAe,CAAC,YAAY,QAC5B,EACE,gBAAgB;IACd,OAAO,oBAAoB,SAAS;IACpC,SAAS,oBAAoB,WAAW,CACtC,oDACD;IACF,EACF,GACD,EAAE;GACN,GAAI,YAAY,QACZ,EAAE,GACF,EACE,gBAAgB;IACd,OAAO,oBAAoB,SAAS;IACpC,SAAS,oBAAoB,WAAW,CACtC,oDACD;IACF,EACF;GACL,QAAQ;GACR;GACA;GACD;UACM,OAAO;AACd,SAAO,OACL,sBACA,yDAAyD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC/G;GAAE,QAAQ;GAAe;GAAO,CACjC;;;;;ACnoBL,SAAgB,qBAAkC;AAChD,QAAO;EACL,sBAAsB,gBAAgB;EACtC,SAAS;EACT,2BAAW,IAAI,MAAM;EACrB,YAAY,YAAY;EACzB;;AAGH,SAAS,WAAW,OAAuB;AACzC,KAAI,CAAC,MAAM,WAAW,IAAI,CACxB,OAAM,IAAI,qBAAqB,sEAAgE;AAEjG,QAAO;;AAGT,SAAS,aAAa,OAAuB;AAC3C,KAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAAU,OAAM,IAAI,OAAO;AAC5E,SAAO,IAAI,UAAU;SACf;AACN,QAAM,IAAI,qBAAqB,yDAAyD;;;AAgB5F,SAAgB,qBACd,OAA0B,oBACjB;AACT,QAAO,IAAI,QAAQ,UAAU,CAC1B,YACC,oKACD,CACA,eACC,mBACA,oHACD,CACA,OAAO,kBAAkB,0BAA0B,YAAY,IAAI,CACnE,OACC,yBACA,2EACA,WACD,CACA,OAAO,eAAe,mCAAmC,cAAc,oBAAoB,CAC3F,UACC,IAAI,OAAO,iBAAiB,6CAA6C,CACtE,QAAQ,CAAC,QAAQ,WAAW,CAAC,CAC7B,QAAQ,OAAO,CACnB,CACA,UACC,IAAI,OAAO,iBAAiB,oDAAoD,CAC7E,QAAQ,CAAC,SAAS,aAAa,CAAC,CAChC,QAAQ,QAAQ,CACpB,CACA,UACC,IAAI,OAAO,qBAAqB,4DAA4D,CACzF,QAAQ,CAAC,UAAU,aAAa,CAAC,CACjC,QAAQ,SAAS,CACrB,CACA,UACC,IAAI,OAAO,kBAAkB,iDAAiD,CAC3E,QAAQ,CAAC,UAAU,aAAa,CAAC,CACjC,QAAQ,SAAS,CACrB,CACA,OACC,oBACA,mEACA,cACD,CACA,OAAO,OAAO,SAAgC;EAC7C,MAAM,SAAS,MAAM,WACnB;GACE,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE;GAC7D,KAAK,KAAK;GACV,MAAM,KAAK;GACX,UAAU,KAAK;GACf,cAAc,KAAK;GACnB,WAAW,KAAK;GAChB,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,UAAU,GAAG,EAAE;GACrD,EACD,MAAM,CACP;AACD,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C,MAAI,CAAC,OAAO,GAAI,SAAQ,WAAW;GACnC;;;;ACjGN,SAAgB,qBAAqB,KAA0B;CAC7D,MAAM,MAAM,IAAI,QAAQ,QAAQ,CAAC,YAC/B,wFACD;AAED,KAAI,WAAW,kBAAkB,CAAC;AAClC,KAAI,WAAW,mBAAmB,CAAC;AACnC,KAAI,WAAW,mBAAmB,CAAC;AACnC,KAAI,WAAW,mBAAmB,CAAC;AACnC,KAAI,WAAW,mBAAmB,CAAC;AACnC,KAAI,WAAW,uBAAuB,CAAC;AACvC,KAAI,WAAW,qBAAqB,CAAC;AACrC,KAAI,WAAW,oBAAoB,CAAC;AACpC,KAAI,WAAW,sBAAsB,CAAC;AAEtC,KAAI,QAAQ,WAAW,IAAI;;;;ACxB7B,MAAM,SAAsB;CAC1B,MAAM;CACN,SAAS;CACT,SAAS,KAAoB;AAC3B,uBAAqB,IAAI;;CAE5B"}
|