@open-mercato/enterprise 0.6.8-develop.6998.1.d2bc46c56c → 0.6.8-develop.7001.1.03608ab252

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.
@@ -170,6 +170,13 @@ function resolveRecordLockSaveErrorDecision(args) {
170
170
  }
171
171
  return { action: "apply-conflict", payload };
172
172
  }
173
+ function resolveLockAfterValidate(currentLock, payloadLock) {
174
+ if (!payloadLock) return currentLock ?? null;
175
+ if (payloadLock.token == null && payloadLock.status === "active" && currentLock && currentLock.id === payloadLock.id) {
176
+ return { ...payloadLock, token: currentLock.token };
177
+ }
178
+ return payloadLock;
179
+ }
173
180
  function clearIncomingChangesQueryFlag() {
174
181
  if (typeof window === "undefined") return;
175
182
  try {
@@ -1166,7 +1173,7 @@ async function validateBeforeSave(data, context) {
1166
1173
  resourceKind,
1167
1174
  resourceId,
1168
1175
  latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,
1169
- lock: payload.lock ?? state?.lock ?? null,
1176
+ lock: resolveLockAfterValidate(state?.lock, payload.lock),
1170
1177
  conflict: preserveConflictUntilSuccessfulSave ? state?.conflict ?? null : null,
1171
1178
  pendingConflictId: nextResolution === "normal" ? null : conflictId ?? state?.pendingConflictId ?? null,
1172
1179
  pendingResolution: nextResolution,
@@ -1177,7 +1184,7 @@ async function validateBeforeSave(data, context) {
1177
1184
  setRecordLockFormState(formId, {
1178
1185
  resourceKind,
1179
1186
  resourceId,
1180
- lock: payload.lock ?? state?.lock ?? null,
1187
+ lock: resolveLockAfterValidate(state?.lock, payload.lock),
1181
1188
  conflict: payload.conflict ?? state?.conflict ?? null,
1182
1189
  pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,
1183
1190
  pendingResolution: resolution === "normal" ? "normal" : resolution,
@@ -1187,6 +1194,7 @@ async function validateBeforeSave(data, context) {
1187
1194
  }
1188
1195
  export {
1189
1196
  RecordLockingWidget as default,
1197
+ resolveLockAfterValidate,
1190
1198
  resolveRecordLockSaveErrorDecision,
1191
1199
  validateBeforeSave
1192
1200
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { BACKEND_MUTATION_ERROR_EVENT } from '@open-mercato/ui/backend/injection/mutationEvents'\nimport { registerRecordLockConflictHandler } from '@open-mercato/ui/backend/conflicts'\nimport { useSearchParams } from 'next/navigation'\nimport { Mail } from 'lucide-react'\nimport {\n RECORD_LOCKS_FORCE_RELEASED_EVENT,\n RECORD_LOCKS_INCOMING_CHANGES_EVENT,\n RECORD_LOCKS_LOCK_CONTENDED_EVENT,\n RECORD_LOCKS_RECORD_DELETED_EVENT,\n} from '@open-mercato/enterprise/modules/record_locks/notifications.handlers'\nimport {\n ChangedFieldsTable,\n type ChangeRow,\n} from '@open-mercato/core/modules/audit_logs/lib/display-helpers'\nimport {\n clearRecordLockFormState,\n getRecordLockFormState,\n setRecordLockFormState,\n subscribeRecordLockFormState,\n type RecordLockFormState,\n type RecordLockUiConflict,\n type RecordLockUiView,\n} from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'\nimport { isUuid, resolveConflictId, runAcceptIncoming } from './conflictResolution'\nimport { isOptimisticLockFloorConflict } from '@open-mercato/enterprise/modules/record_locks/lib/optimisticLockFloor'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('record_locks').child({ component: 'record-locking-widget' })\n\ntype CrudInjectionContext = {\n formId?: string\n entityId?: string\n resourceKind?: string\n resourceId?: string\n recordId?: string\n path?: string\n query?: string\n kind?: string\n personId?: string\n companyId?: string\n dealId?: string\n retryLastMutation?: () => Promise<boolean | void> | boolean | void\n}\n\ntype RecordLockWidgetOwner = {\n instanceId: string\n priority: number\n}\n\nconst GLOBAL_RECORD_LOCK_OWNERS_KEY = '__openMercatoRecordLockWidgetOwners__'\n\nfunction getRecordLockOwnerMap(): Map<string, RecordLockWidgetOwner> {\n const store = globalThis as Record<string, unknown>\n const existing = store[GLOBAL_RECORD_LOCK_OWNERS_KEY]\n if (existing instanceof Map) return existing as Map<string, RecordLockWidgetOwner>\n const next = new Map<string, RecordLockWidgetOwner>()\n store[GLOBAL_RECORD_LOCK_OWNERS_KEY] = next\n return next\n}\n\ntype AcquireResponse = {\n ok?: boolean\n acquired?: boolean\n allowForceUnlock?: boolean\n heartbeatSeconds?: number\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n currentUserId?: string\n error?: string\n code?: string\n}\n\ntype ValidateResponse = {\n ok: boolean\n status?: number\n code?: string\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n conflict?: RecordLockUiConflict | null\n}\n\ntype CrudSaveErrorEventDetail = {\n contextId?: string\n formId?: string\n error?: unknown\n}\n\ntype RecordLockContendedEventDetail = {\n sourceEntityId?: string | null\n}\n\ntype RecordDeletedEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\ntype RecordLockIncomingChangesEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\ntype RecordLockForceReleasedEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object'\n}\n\nfunction readStringOrNull(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction extractErrorStatus(error: unknown): number | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = current.status\n if (typeof status === 'number' && Number.isFinite(status)) return status\n if (typeof status === 'string') {\n const parsed = Number(status)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const statusCode = current.statusCode\n if (typeof statusCode === 'number' && Number.isFinite(statusCode)) return statusCode\n if (typeof statusCode === 'string') {\n const parsed = Number(statusCode)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return null\n}\n\nfunction extractRecordLockConflictPayload(error: unknown): {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n} | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const nested = ['body', 'response', 'data', 'details', 'error']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n\n const code = typeof current.code === 'string' ? current.code : null\n const status = extractErrorStatus(current)\n const hasLockMarkers = (\n isObjectRecord(current.lock)\n || isObjectRecord(current.conflict)\n || typeof current.conflictId === 'string'\n || typeof current.resourceKind === 'string'\n || typeof current.resourceId === 'string'\n || Array.isArray(current.resolutionOptions)\n || typeof current.allowIncomingOverride === 'boolean'\n || typeof current.canOverrideIncoming === 'boolean'\n )\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n const looksLikeLockConflictMessage = (\n message.includes('record conflict')\n || message.includes('record_lock_conflict')\n || message.includes('conflict detected')\n )\n const isRecordLockConflict = (\n code === 'record_lock_conflict'\n || (status === 409 && (hasLockMarkers || looksLikeLockConflictMessage))\n )\n if (!isRecordLockConflict) continue\n if (!isObjectRecord(current.conflict)) {\n const lock = isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined\n const fallbackConflictId = typeof current.conflictId === 'string' && isUuid(current.conflictId)\n ? current.conflictId\n : 'unresolved'\n const fallbackConflict: RecordLockUiConflict = {\n id: fallbackConflictId,\n resourceKind:\n (typeof current.resourceKind === 'string' && current.resourceKind.trim().length > 0\n ? current.resourceKind\n : lock?.resourceKind) ?? '',\n resourceId:\n (typeof current.resourceId === 'string' && current.resourceId.trim().length > 0\n ? current.resourceId\n : lock?.resourceId) ?? '',\n baseActionLogId:\n typeof current.baseActionLogId === 'string' || current.baseActionLogId === null\n ? current.baseActionLogId\n : lock?.baseActionLogId ?? null,\n incomingActionLogId:\n typeof current.incomingActionLogId === 'string' || current.incomingActionLogId === null\n ? current.incomingActionLogId\n : null,\n allowIncomingOverride: Boolean(current.allowIncomingOverride),\n canOverrideIncoming: Boolean(current.canOverrideIncoming),\n resolutionOptions: Array.isArray(current.resolutionOptions) && current.resolutionOptions.includes('accept_mine')\n ? ['accept_mine']\n : [],\n changes: [],\n }\n return {\n conflict: fallbackConflict,\n lock,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n return {\n conflict: current.conflict as RecordLockUiConflict,\n lock: isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n\n return null\n}\n\nfunction isRecordDeletedError(error: unknown): boolean {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = extractErrorStatus(current)\n const code = typeof current.code === 'string' ? current.code.toLowerCase() : ''\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n\n const matchesCode = (\n code === 'record_not_found'\n || code === 'not_found'\n || code === 'record_deleted'\n )\n const matchesMessage = (\n message.includes('not found')\n || message.includes('was deleted')\n || message.includes('record deleted')\n )\n\n if (status === 404 || matchesCode || matchesMessage) {\n return true\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return false\n}\n\n/**\n * Action the record-lock widget takes when a CrudForm / guarded-mutation save\n * error event (`om:crud-save-error` / backend-mutation-error) fires. Extracted\n * as a pure function so the single-conflict-surface (S3) decision is unit-testable\n * without rendering the widget:\n *\n * - `ignore` \u2014 event is for a different form/record, there is no\n * actionable mounted record, OR the error is a plain OSS\n * `optimistic_lock_conflict` 409 already owned by the OSS\n * conflict bar via `surfaceRecordConflict` (opening the\n * dialog too would render TWO surfaces \u2014 #3504/#3505).\n * - `record-deleted` \u2014 the record was deleted by another user.\n * - `apply-conflict` \u2014 a genuine enterprise `record_lock_conflict` payload;\n * open the field-level merge dialog with it.\n * - `fallback-dialog` \u2014 an unrecognized 409 (neither record-lock nor OSS\n * optimistic-lock) for the mounted record; open the\n * degraded fallback dialog (legacy behavior).\n */\nexport type RecordLockSaveErrorDecision =\n | { action: 'ignore' }\n | { action: 'record-deleted' }\n | {\n action: 'apply-conflict'\n payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }\n }\n | { action: 'fallback-dialog' }\n\nexport function resolveRecordLockSaveErrorDecision(args: {\n error: unknown\n eventContextId: string | null | undefined\n formId: string\n currentState: { resourceKind?: string | null; resourceId?: string | null } | null | undefined\n}): RecordLockSaveErrorDecision {\n const { error, eventContextId, formId, currentState } = args\n const payload = extractRecordLockConflictPayload(error)\n const eventTargetsCurrentForm = !eventContextId || eventContextId === formId\n if (!eventTargetsCurrentForm) {\n if (!payload || !currentState?.resourceKind || !currentState?.resourceId) return { action: 'ignore' }\n const payloadResourceKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadResourceId = payload.conflict.resourceId?.trim() ?? ''\n if (!payloadResourceKind || !payloadResourceId) return { action: 'ignore' }\n if (payloadResourceKind !== currentState.resourceKind || payloadResourceId !== currentState.resourceId) {\n return { action: 'ignore' }\n }\n }\n if (!payload) {\n if (!currentState?.resourceKind || !currentState?.resourceId) return { action: 'ignore' }\n if (isRecordDeletedError(error)) return { action: 'record-deleted' }\n // A plain OSS `optimistic_lock_conflict` 409 (the shape `makeCrudRoute` /\n // `CrudForm` returns on a stale write) is already surfaced by the OSS conflict\n // bar via `surfaceRecordConflict`. Defer to it instead of opening the degraded\n // fallback merge dialog \u2014 otherwise both surfaces render at once (#3504) and the\n // dialog has no field diff and a no-op \"Accept incoming\" (#3505). The rich merge\n // dialog stays reserved for genuine `record_lock_conflict` payloads (handled\n // above) whose conflict carries field changes + resolution options. Delegates to\n // the shared `optimisticLockFloor` detector so this arbitration stays identical to\n // the conflict bar's ownership decision (single source of truth).\n if (isOptimisticLockFloorConflict(error)) return { action: 'ignore' }\n if (extractErrorStatus(error) === 409) return { action: 'fallback-dialog' }\n return { action: 'ignore' }\n }\n return { action: 'apply-conflict', payload }\n}\n\nfunction clearIncomingChangesQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showIncomingChanges') !== '1') return\n url.searchParams.delete('showIncomingChanges')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction clearLockContentionQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showLockContention') !== '1') return\n url.searchParams.delete('showLockContention')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction submitCrudForm(formId: string): boolean {\n if (typeof document === 'undefined') return false\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return false\n form.requestSubmit()\n return true\n}\n\nfunction resolveResourceKind(context: CrudInjectionContext): string | null {\n if (context.resourceKind && context.resourceKind.trim()) return context.resourceKind\n if (context.kind === 'order') return 'sales.order'\n if (context.kind === 'quote') return 'sales.quote'\n if (context.personId) return 'customers.person'\n if (context.companyId) return 'customers.company'\n if (context.dealId) return 'customers.deal'\n const entityId = context.entityId\n if (entityId && entityId.includes(':')) {\n const [moduleId, rawEntity] = entityId.split(':')\n const entity = rawEntity ?? ''\n const normalizedModuleId = moduleId.trim()\n const normalizedEntity = entity.trim()\n if (normalizedModuleId && normalizedEntity) {\n const singularModuleId = normalizedModuleId.endsWith('s')\n ? normalizedModuleId.slice(0, -1)\n : normalizedModuleId\n\n const stripPrefixes = [\n `${normalizedModuleId}_`,\n `${singularModuleId}_`,\n ]\n\n let finalEntity = normalizedEntity\n for (const prefix of stripPrefixes) {\n if (finalEntity.startsWith(prefix)) {\n finalEntity = finalEntity.slice(prefix.length)\n break\n }\n }\n\n if (finalEntity) return `${normalizedModuleId}.${finalEntity}`\n }\n }\n\n const path = context.path ?? ''\n if (path.startsWith('/backend/customers/people/')) return 'customers.person'\n if (path.startsWith('/backend/customers/companies/')) return 'customers.company'\n if (path.startsWith('/backend/customers/deals/')) return 'customers.deal'\n if (path.startsWith('/backend/sales/orders/')) return 'sales.order'\n if (path.startsWith('/backend/sales/quotes/')) return 'sales.quote'\n if (path.startsWith('/backend/sales/documents/')) {\n const query = context.query ?? ''\n const params = new URLSearchParams(query)\n const kind = params.get('kind')\n if (kind === 'order') return 'sales.order'\n if (kind === 'quote') return 'sales.quote'\n }\n\n return null\n}\n\nfunction resolveResourceId(context: CrudInjectionContext, data: unknown): string | null {\n if (context.resourceId && context.resourceId.trim()) return context.resourceId\n if (context.recordId && context.recordId.trim()) return context.recordId\n if (context.personId && context.personId.trim()) return context.personId\n if (context.companyId && context.companyId.trim()) return context.companyId\n if (context.dealId && context.dealId.trim()) return context.dealId\n if (data && typeof data === 'object' && 'id' in data) {\n const id = (data as { id?: unknown }).id\n if (typeof id === 'string' && id.trim()) return id\n }\n if (data && typeof data === 'object') {\n const nestedPersonId = (data as { person?: { id?: unknown } }).person?.id\n if (typeof nestedPersonId === 'string' && nestedPersonId.trim()) return nestedPersonId\n const nestedCompanyId = (data as { company?: { id?: unknown } }).company?.id\n if (typeof nestedCompanyId === 'string' && nestedCompanyId.trim()) return nestedCompanyId\n const nestedDealId = (data as { deal?: { id?: unknown } }).deal?.id\n if (typeof nestedDealId === 'string' && nestedDealId.trim()) return nestedDealId\n }\n const path = context.path ?? ''\n const parts = path.split('/').filter((part) => part.length > 0)\n const candidates = [\n ['backend', 'customers', 'people'],\n ['backend', 'customers', 'companies'],\n ['backend', 'customers', 'deals'],\n ['backend', 'sales', 'orders'],\n ['backend', 'sales', 'quotes'],\n ['backend', 'sales', 'documents'],\n ] as const\n for (const prefix of candidates) {\n const matchesPrefix = prefix.every((segment, index) => parts[index] === segment)\n if (!matchesPrefix || parts.length <= prefix.length) continue\n const rawId = parts[prefix.length] ?? ''\n if (!rawId) continue\n try {\n const decoded = decodeURIComponent(rawId).trim()\n if (decoded.length > 0) return decoded\n } catch {\n const trimmed = rawId.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n}\n\nfunction resolveFormId(\n context: CrudInjectionContext,\n resourceKind: string | null,\n resourceId: string | null,\n): string {\n if (context.formId && context.formId.trim().length > 0) return context.formId\n if (resourceKind && resourceId) return `record-lock:${resourceKind}:${resourceId}`\n if (context.path && context.path.trim().length > 0) {\n const query = context.query?.trim()\n return `record-lock:${context.path}${query ? `?${query}` : ''}`\n }\n return 'record-lock:global'\n}\n\nasync function releaseLock(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n await apiCall('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'cancelled',\n }),\n })\n}\n\nfunction releaseLockWithKeepalive(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n const payload = JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'unmount',\n })\n\n try {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([payload], { type: 'application/json' })\n const sent = navigator.sendBeacon('/api/record_locks/release', blob)\n if (sent) return\n }\n } catch {\n // ignore and fallback to fetch\n }\n\n void fetch('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: payload,\n keepalive: true,\n credentials: 'include',\n }).catch((error) => {\n logger.warn('Failed to release lock with keepalive fallback', { err: error })\n })\n}\n\nexport default function RecordLockingWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<CrudInjectionContext, Record<string, unknown>>) {\n const t = useT()\n const searchParams = useSearchParams()\n const resourceKind = React.useMemo(() => resolveResourceKind(context), [context])\n const resourceId = React.useMemo(() => resolveResourceId(context, data), [context, data])\n const formId = React.useMemo(\n () => resolveFormId(context, resourceKind, resourceId),\n [context, resourceId, resourceKind],\n )\n const [, forceRender] = React.useReducer((value) => value + 1, 0)\n const state = getRecordLockFormState(formId)\n const [mounted, setMounted] = React.useState(false)\n const [showIncomingChangesRequested, setShowIncomingChangesRequested] = React.useState(false)\n const [showLockContentionBanner, setShowLockContentionBanner] = React.useState(false)\n const [isConflictDialogOpen, setIsConflictDialogOpen] = React.useState(false)\n const instanceId = React.useMemo(\n () =>\n `record-lock-widget:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`,\n [],\n )\n const ownerPriority = context.formId ? 2 : 1\n const ownerKey = resourceKind && resourceId ? `${resourceKind}:${resourceId}` : null\n const [isPrimaryInstance, setIsPrimaryInstance] = React.useState(true)\n const releasePayloadRef = React.useRef<{\n resourceKind: string\n resourceId: string\n token: string\n } | null>(null)\n const keepMineRetryVersionRef = React.useRef(0)\n\n React.useEffect(() => {\n if (!ownerKey) {\n setIsPrimaryInstance(true)\n return\n }\n\n const owners = getRecordLockOwnerMap()\n const notifyOwnersChanged = () => {\n if (typeof window === 'undefined') return\n window.dispatchEvent(\n new CustomEvent('om:record-lock-owner-changed', {\n detail: { ownerKey },\n }),\n )\n }\n\n const claimOwnership = () => {\n const current = owners.get(ownerKey)\n if (!current) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n if (current.instanceId === instanceId) {\n setIsPrimaryInstance(true)\n return\n }\n if (ownerPriority > current.priority) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n setIsPrimaryInstance(false)\n }\n\n claimOwnership()\n const onOwnersChanged = () => claimOwnership()\n if (typeof window !== 'undefined') {\n window.addEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n\n return () => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n const current = owners.get(ownerKey)\n if (current?.instanceId === instanceId) {\n owners.delete(ownerKey)\n notifyOwnersChanged()\n }\n }\n }, [instanceId, ownerKey, ownerPriority])\n\n React.useEffect(() => {\n if (isPrimaryInstance) return\n const current = getRecordLockFormState(formId)\n if (!current?.lock?.token || !current.resourceKind || !current.resourceId) {\n clearRecordLockFormState(formId)\n return\n }\n void releaseLock({\n resourceKind: current.resourceKind,\n resourceId: current.resourceId,\n token: current.lock.token,\n reason: 'cancelled',\n }).catch((error) => {\n logger.warn('Failed to release lock while demoting owner', { err: error })\n })\n clearRecordLockFormState(formId)\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n React.useEffect(() => {\n const showIncomingChanges = searchParams?.get('showIncomingChanges') === '1'\n const showLockContention = searchParams?.get('showLockContention') === '1'\n\n if (showIncomingChanges) {\n setShowIncomingChangesRequested(true)\n clearIncomingChangesQueryFlag()\n }\n\n if (showLockContention) {\n setShowLockContentionBanner(true)\n clearLockContentionQueryFlag()\n }\n }, [searchParams])\n\n React.useEffect(() => subscribeRecordLockFormState(formId, () => forceRender()), [formId])\n\n React.useEffect(() => {\n if (!state?.conflict) {\n setIsConflictDialogOpen(false)\n return\n }\n setIsConflictDialogOpen(true)\n }, [\n state?.conflict?.id,\n state?.conflict?.incomingActionLogId,\n state?.conflict?.baseActionLogId,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n setRecordLockFormState(formId, { formId, resourceKind, resourceId })\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n let active = true\n const acquire = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind, resourceId }),\n })\n const payload = call.result ?? {}\n if (!active) return\n if (!call.ok) {\n const defaultMessage = call.status === 403\n ? t('api.errors.forbidden', 'Forbidden')\n : t('record_locks.errors.acquire_failed', 'Failed to load record lock status.')\n const message = typeof payload.error === 'string' && payload.error.trim().length\n ? payload.error\n : defaultMessage\n flash(message, 'error')\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n void acquire()\n return () => {\n active = false\n }\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n const mine = Boolean(state?.lock?.token)\n const participants = React.useMemo(() => {\n if (!state?.lock) return []\n const fromPayload = Array.isArray(state.lock.participants) ? state.lock.participants : []\n if (fromPayload.length) return fromPayload\n return [{\n userId: state.lock.lockedByUserId,\n lockedByName: state.lock.lockedByName,\n lockedByEmail: state.lock.lockedByEmail,\n lockedByIp: state.lock.lockedByIp,\n lockedAt: state.lock.lockedAt,\n lastHeartbeatAt: state.lock.lastHeartbeatAt,\n expiresAt: state.lock.expiresAt,\n }]\n }, [state?.lock])\n const activeParticipantCount = state?.lock?.activeParticipantCount ?? participants.length\n const otherParticipants = React.useMemo(() => {\n if (!state?.currentUserId) return participants\n return participants.filter((participant) => participant.userId !== state.currentUserId)\n }, [participants, state?.currentUserId])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!mine || !state?.lock?.id) return\n\n const onContention = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockContendedEventDetail)\n : null\n if (!detail) return\n if (detail.sourceEntityId !== state.lock?.id) return\n setShowLockContentionBanner(true)\n }\n\n window.addEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n }\n }, [isPrimaryInstance, mine, state?.lock?.id])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.resourceKind || !state?.resourceId) return\n if (state.recordDeleted === true) return\n const onRecordDeleted = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordDeletedEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }\n\n window.addEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n }\n }, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.lock?.token || !state.resourceKind || !state.resourceId) return\n const heartbeatSeconds = (\n typeof state.heartbeatSeconds === 'number'\n && Number.isFinite(state.heartbeatSeconds)\n && state.heartbeatSeconds > 0\n )\n ? state.heartbeatSeconds\n : 10\n const intervalMs = Math.max(1_000, Math.round(heartbeatSeconds * 1000))\n const interval = window.setInterval(() => {\n void apiCall('/api/record_locks/heartbeat', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token,\n }),\n })\n }, intervalMs)\n return () => window.clearInterval(interval)\n }, [isPrimaryInstance, state?.heartbeatSeconds, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const hasUnresolvedConflict = Boolean(state?.conflict)\n && !(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal'\n )\n if (hasUnresolvedConflict) return\n if (!state?.resourceKind || !state?.resourceId) return\n const refreshPresence = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n const payload = call.result ?? {}\n if (!call.ok) {\n const currentState = getRecordLockFormState(formId)\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? currentState?.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? currentState?.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? currentState?.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n const currentState = getRecordLockFormState(formId)\n const previousToken = currentState?.lock?.token ?? null\n const nextToken = payload.lock?.token ?? null\n const isSameSession = Boolean(previousToken && nextToken && previousToken === nextToken)\n const nextLatestActionLogId = isSameSession\n ? (currentState?.latestActionLogId ?? null)\n : (payload.latestActionLogId ?? currentState?.latestActionLogId ?? null)\n\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: nextLatestActionLogId,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n const onIncomingChanges = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockIncomingChangesEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n setShowIncomingChangesRequested(true)\n void refreshPresence()\n }\n const onForceReleased = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockForceReleasedEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n void refreshPresence()\n }\n const onBridgeReconnected = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? (event as CustomEvent<Record<string, unknown>>).detail\n : null\n if (detail?.id !== 'om:bridge:reconnected') return\n void refreshPresence()\n }\n const onFocus = () => {\n void refreshPresence()\n }\n const onVisibilityChange = () => {\n if (!document.hidden) {\n void refreshPresence()\n }\n }\n\n window.addEventListener(RECORD_LOCKS_INCOMING_CHANGES_EVENT, onIncomingChanges)\n window.addEventListener(RECORD_LOCKS_FORCE_RELEASED_EVENT, onForceReleased)\n window.addEventListener('om:event', onBridgeReconnected)\n window.addEventListener('focus', onFocus)\n document.addEventListener('visibilitychange', onVisibilityChange)\n const heartbeatInterval = window.setInterval(() => {\n void refreshPresence()\n }, 30_000)\n void refreshPresence()\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_INCOMING_CHANGES_EVENT, onIncomingChanges)\n window.removeEventListener(RECORD_LOCKS_FORCE_RELEASED_EVENT, onForceReleased)\n window.removeEventListener('om:event', onBridgeReconnected)\n window.removeEventListener('focus', onFocus)\n document.removeEventListener('visibilitychange', onVisibilityChange)\n window.clearInterval(heartbeatInterval)\n }\n }, [\n formId,\n isPrimaryInstance,\n state?.conflict,\n state?.pendingResolution,\n state?.pendingResolutionArmed,\n state?.resourceId,\n state?.resourceKind,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!showIncomingChangesRequested) return\n if (!state?.resourceKind || !state?.resourceId || !state?.lock) return\n let cancelled = false\n const openIncomingChangesDialog = async () => {\n clearIncomingChangesQueryFlag()\n setShowIncomingChangesRequested(false)\n await validateBeforeSave({}, context)\n if (cancelled) return\n }\n void openIncomingChangesDialog()\n return () => {\n cancelled = true\n }\n }, [context, isPrimaryInstance, showIncomingChangesRequested, state?.lock, state?.resourceId, state?.resourceKind, t])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (\n state?.resourceKind\n && state?.resourceId\n && typeof state.lock?.token === 'string'\n && state.lock.token.trim().length > 0\n ) {\n releasePayloadRef.current = {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock.token,\n }\n return\n }\n releasePayloadRef.current = null\n }, [isPrimaryInstance, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onPageHide = () => {\n const payload = releasePayloadRef.current\n if (!payload) return\n releaseLockWithKeepalive({\n ...payload,\n reason: 'unmount',\n })\n }\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n }\n }, [isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n return () => {\n const payload = releasePayloadRef.current\n if (payload) {\n void releaseLock({\n ...payload,\n reason: 'unmount',\n })\n }\n clearRecordLockFormState(formId)\n }\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onCrudSaveError = (event: Event) => {\n const applyConflictPayload = (payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }) => {\n setIsConflictDialogOpen(true)\n const nextPatch: Partial<RecordLockFormState> = {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n }\n if (payload.lock !== undefined) {\n nextPatch.lock = payload.lock\n }\n if (payload.latestActionLogId !== undefined) {\n nextPatch.latestActionLogId = payload.latestActionLogId\n }\n setRecordLockFormState(formId, {\n ...nextPatch,\n })\n }\n\n const buildFallbackConflict = (currentState: RecordLockFormState) => {\n const preferredConflictId = isUuid(currentState.pendingConflictId)\n ? currentState.pendingConflictId\n : isUuid(currentState.conflict?.id)\n ? currentState.conflict.id\n : 'unresolved'\n return ({\n conflict: {\n id: preferredConflictId,\n resourceKind: currentState.resourceKind ?? '',\n resourceId: currentState.resourceId ?? '',\n baseActionLogId: currentState.latestActionLogId ?? null,\n incomingActionLogId: null,\n allowIncomingOverride: false,\n canOverrideIncoming: false,\n resolutionOptions: [],\n changes: [],\n } as RecordLockUiConflict,\n lock: currentState.lock ?? undefined,\n latestActionLogId: currentState.latestActionLogId ?? null,\n })\n }\n\n const detail = (event as CustomEvent<CrudSaveErrorEventDetail>).detail\n if (!detail) return\n const eventContextId = detail.contextId ?? detail.formId\n const currentState = getRecordLockFormState(formId)\n const decision = resolveRecordLockSaveErrorDecision({\n error: detail.error,\n eventContextId,\n formId,\n currentState,\n })\n if (decision.action === 'ignore') return\n if (decision.action === 'record-deleted') {\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return\n }\n if (decision.action === 'fallback-dialog') {\n if (currentState) applyConflictPayload(buildFallbackConflict(currentState))\n return\n }\n\n applyConflictPayload(decision.payload)\n }\n\n window.addEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.addEventListener('om:crud-save-error', onCrudSaveError)\n return () => {\n window.removeEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.removeEventListener('om:crud-save-error', onCrudSaveError)\n }\n }, [formId, isPrimaryInstance])\n\n // Single conflict surface (S3): own the surface for record_lock_conflict 409s\n // routed through `surfaceRecordConflict` (command/raw-write paths that don't\n // emit the crud-save-error event). Opening the dialog here is idempotent with\n // the listener above; returning true tells the core helper to suppress the OSS\n // bar (so we never render both). Decline (false) for a different record so the\n // bar still renders there \u2014 a conflict is never swallowed.\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n const unregister = registerRecordLockConflictHandler((_conflict, error) => {\n const payload = extractRecordLockConflictPayload(error)\n if (!payload) return false\n const payloadKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadId = payload.conflict.resourceId?.trim() ?? ''\n if (payloadKind && payloadId && (payloadKind !== resourceKind || payloadId !== resourceId)) return false\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return true\n })\n return unregister\n }, [formId, isPrimaryInstance, resourceKind, resourceId])\n\n const handleTakeOver = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.resourceKind || !state?.resourceId) return\n const call = await apiCall<AcquireResponse>('/api/record_locks/force-release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n if (!call.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const acquire = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind: state.resourceKind, resourceId: state.resourceId }),\n })\n if (!acquire.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const payload = acquire.result ?? {}\n setRecordLockFormState(formId, {\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n latestActionLogId: payload.latestActionLogId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }, [formId, state?.resourceId, state?.resourceKind, t])\n\n const handleAcceptIncoming = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n await runAcceptIncoming({\n conflict: state?.conflict,\n resourceKind: state?.resourceKind,\n resourceId: state?.resourceId,\n revalidateConflictId: async () => {\n const validation = await validateBeforeSave({}, context)\n return resolveConflictId(validation.conflict)\n },\n releaseIncoming: async (conflictId) => {\n await apiCallOrThrow('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state?.resourceKind,\n resourceId: state?.resourceId,\n token: state?.lock?.token ?? undefined,\n reason: 'conflict_resolved',\n conflictId,\n resolution: 'accept_incoming',\n }),\n })\n },\n clearConflictState: () => {\n setRecordLockFormState(formId, {\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n },\n reload: () => {\n window.location.reload()\n },\n })\n }, [context, formId, state?.conflict, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n const handleKeepMine = React.useCallback(() => {\n if (!state?.conflict) return\n const applyAcceptMine = async () => {\n const keepMineRetryVersion = keepMineRetryVersionRef.current + 1\n keepMineRetryVersionRef.current = keepMineRetryVersion\n let conflictId: string | null = isUuid(state.conflict?.id) ? state.conflict.id : null\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : null\n }\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n setRecordLockFormState(formId, {\n pendingResolution: 'accept_mine',\n pendingConflictId: conflictId,\n pendingResolutionArmed: true,\n })\n window.setTimeout(async () => {\n if (keepMineRetryVersionRef.current !== keepMineRetryVersion) return\n const currentState = getRecordLockFormState(formId)\n if (currentState?.pendingResolution !== 'accept_mine') return\n const submitted = submitCrudForm(formId)\n if (submitted) return\n const retried = await Promise.resolve(context.retryLastMutation?.()).catch(() => false)\n if (!retried) {\n flash(\n t(\n 'record_locks.conflict.retry_save_after_accept_mine',\n 'Click save again to apply your changes.',\n ),\n 'info',\n )\n }\n }, 0)\n }\n void applyAcceptMine()\n }, [context, context.retryLastMutation, formId, state?.conflict, t])\n\n const handleKeepEditing = React.useCallback(() => {\n keepMineRetryVersionRef.current += 1\n setIsConflictDialogOpen(false)\n }, [])\n\n const noneLabel = t('audit_logs.common.none')\n const conflictChangeRows = React.useMemo<ChangeRow[]>(\n () => (state?.conflict?.changes ?? []).map((change) => ({\n field: change.field,\n from: change.incomingValue,\n to: change.mineValue,\n })),\n [state?.conflict?.changes],\n )\n const canKeepMyChanges = Boolean(\n state?.conflict?.allowIncomingOverride\n && state?.conflict?.canOverrideIncoming === true\n && state?.conflict?.resolutionOptions?.includes('accept_mine'),\n )\n const isRecordDeleted = state?.recordDeleted === true\n const showOverrideBlockedNotice = Boolean(\n state?.conflict?.allowIncomingOverride\n && !state?.conflict?.canOverrideIncoming,\n )\n const conflictDialog = (\n <Dialog open={Boolean(state?.conflict || isRecordDeleted) && isConflictDialogOpen} onOpenChange={(open) => {\n if (open) {\n setIsConflictDialogOpen(true)\n return\n }\n if (isRecordDeleted) {\n setIsConflictDialogOpen(true)\n return\n }\n setIsConflictDialogOpen(false)\n }}>\n <DialogContent data-testid=\"record-lock-conflict-dialog\">\n <DialogHeader>\n <DialogTitle>\n {isRecordDeleted\n ? t('record_locks.conflict.record_deleted_title', 'Record was deleted')\n : t('record_locks.conflict.title', 'Conflict detected')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-3 text-sm\">\n <p className=\"text-muted-foreground\">\n {isRecordDeleted\n ? t(\n 'record_locks.conflict.record_deleted_description',\n 'This record was deleted by another user while you were editing. Saving is blocked to avoid unexpected results.',\n )\n : t('record_locks.conflict.description', 'The record was changed by another user after you started editing.')}\n </p>\n {!isRecordDeleted ? (\n <ChangedFieldsTable\n changeRows={conflictChangeRows}\n noneLabel={noneLabel}\n t={t}\n beforeLabel={t('record_locks.conflict.incoming_label', 'Incoming')}\n afterLabel={t('record_locks.conflict.current_label', 'Current')}\n />\n ) : null}\n {(state?.conflict?.changes?.length ?? 0) === 0 ? (\n !isRecordDeleted ? (\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'record_locks.conflict.no_field_details',\n 'Field-level conflict details are unavailable for this record. Choose a resolution to continue.'\n )}\n </AlertDescription>\n </Alert>\n ) : null\n ) : null}\n {showOverrideBlockedNotice ? (\n <Alert status=\"warning\">\n <AlertDescription>\n {t(\n 'record_locks.conflict.override_blocked_notice',\n 'You cannot keep your version because you do not have permission to override incoming changes.',\n )}\n </AlertDescription>\n </Alert>\n ) : null}\n <div className=\"-mx-6 -mb-6 mt-4 border-t bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80\">\n <div className=\"flex flex-wrap justify-end gap-2\">\n {isRecordDeleted ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleAcceptIncoming()\n }}\n >\n {t('record_locks.conflict.accept_incoming', 'Accept incoming')}\n </Button>\n {canKeepMyChanges ? (\n <Button\n type=\"button\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleKeepMine()\n }}\n >\n {t('record_locks.conflict.accept_mine', 'Keep my changes')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n handleKeepEditing()\n }}\n >\n {t('record_locks.conflict.keep_editing', 'Keep editing')}\n </Button>\n </>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n\n const participantEmails = React.useMemo(() => {\n return otherParticipants\n .map((participant) => participant.lockedByEmail?.trim() ?? '')\n .filter((email, index, all) => email.length > 0 && all.indexOf(email) === index)\n .slice(0, 4)\n }, [otherParticipants])\n\n React.useEffect(() => {\n if (!showLockContentionBanner) return\n if (!mine) return\n if (activeParticipantCount > 1) return\n setShowLockContentionBanner(false)\n }, [activeParticipantCount, mine, showLockContentionBanner])\n\n const topBannerTarget = mounted ? document.getElementById('om-top-banners') : null\n\n if (!isPrimaryInstance) return null\n if (!state?.lock) return conflictDialog\n\n const defaultPresenceMessage = activeParticipantCount > 1\n ? `${activeParticipantCount} users are currently on this record.`\n : 'This record is currently locked by another user.'\n const bannerMessageRaw = !mine\n ? t('record_locks.banner.locked_by_other', 'This record is currently locked by another user.')\n : showLockContentionBanner\n ? t('record_locks.banner.contention_notice', 'Another user opened this record while you are editing it. Conflicts may occur on save.')\n : t('record_locks.banner.participants_notice', 'Multiple users are currently on this record.')\n const bannerMessage = typeof bannerMessageRaw === 'string' && bannerMessageRaw.trim().length > 0\n ? bannerMessageRaw\n : defaultPresenceMessage\n const shouldShowPresenceBanner = Boolean(\n showLockContentionBanner\n || activeParticipantCount > 1\n || !mine,\n )\n\n const lockBanner = shouldShowPresenceBanner ? (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 shadow-sm\">\n <div className=\"font-medium\">\n {bannerMessage}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-amber-900/90\">\n <span>{`${t('record_locks.banner.active_users_label', 'Active users')}: ${activeParticipantCount}`}</span>\n {participantEmails.map((email) => (\n <span\n key={email}\n className=\"inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-overline font-medium text-amber-900\"\n >\n <Mail className=\"h-3 w-3\" />\n <span>{email}</span>\n </span>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {state.allowForceUnlock && !mine ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={handleTakeOver}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('record_locks.banner.take_over', 'Take over editing')}\n </Button>\n ) : null}\n {showLockContentionBanner ? (\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowLockContentionBanner(false)}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('common.close', 'Close')}\n </Button>\n </div>\n ) : null}\n </div>\n </div>\n ) : null\n\n return (\n <>\n {lockBanner ? (topBannerTarget ? createPortal(lockBanner, topBannerTarget) : lockBanner) : null}\n {conflictDialog}\n </>\n )\n}\n\nexport async function validateBeforeSave(\n data: Record<string, unknown>,\n context: CrudInjectionContext,\n): Promise<ValidateResponse> {\n const contextResourceKind = resolveResourceKind(context)\n const contextResourceId = resolveResourceId(context, data)\n const formId = resolveFormId(context, contextResourceKind, contextResourceId)\n const state = getRecordLockFormState(formId)\n const resourceKind = state?.resourceKind || contextResourceKind\n const resourceId = state?.resourceId || contextResourceId\n if (!resourceKind || !resourceId) {\n return { ok: true }\n }\n const hasSelectedConflictResolution = Boolean(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal',\n )\n if (state?.conflict && !hasSelectedConflictResolution) {\n return {\n ok: false,\n status: 409,\n code: 'record_lock_conflict',\n lock: state.lock ?? null,\n conflict: state.conflict,\n latestActionLogId: state.latestActionLogId ?? null,\n }\n }\n const hasResolvableConflict = Boolean(state?.conflict?.id && isUuid(state.conflict.id))\n const requestedResolution = state?.pendingResolution ?? 'normal'\n const resolution = requestedResolution !== 'normal' && !hasResolvableConflict\n ? 'normal'\n : requestedResolution\n const rawConflictId = resolution === 'normal' || !hasResolvableConflict\n ? undefined\n : (state?.pendingConflictId ?? state?.conflict?.id ?? undefined)\n const conflictId = isUuid(rawConflictId) ? rawConflictId : undefined\n const call = await apiCall<ValidateResponse>('/api/record_locks/validate', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind,\n resourceId,\n method: 'PUT',\n token: state?.lock?.token ?? undefined,\n baseLogId: state?.latestActionLogId ?? state?.lock?.baseActionLogId ?? undefined,\n conflictId,\n resolution,\n mutationPayload: data,\n }),\n })\n const payload = call.result ?? { ok: false }\n if (payload.ok) {\n const nextResolution = resolution === 'normal' ? 'normal' : resolution\n const preserveConflictUntilSuccessfulSave = nextResolution !== 'normal'\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: preserveConflictUntilSuccessfulSave ? (state?.conflict ?? null) : null,\n pendingConflictId: nextResolution === 'normal' ? null : (conflictId ?? state?.pendingConflictId ?? null),\n pendingResolution: nextResolution,\n pendingResolutionArmed: nextResolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n }\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n lock: payload.lock ?? state?.lock ?? null,\n conflict: payload.conflict ?? state?.conflict ?? null,\n pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,\n pendingResolution: resolution === 'normal' ? 'normal' : resolution,\n pendingResolutionArmed: resolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n}\n"],
5
- "mappings": ";AAsyCU,SAiDI,UAjDJ,KAiDI,YAjDJ;AApyCV,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,SAAS,sBAAsB;AACxC,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,OAAO,wBAAwB;AACxC,SAAS,YAAY;AAErB,SAAS,oCAAoC;AAC7C,SAAS,yCAAyC;AAClD,SAAS,uBAAuB;AAChC,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,QAAQ,mBAAmB,yBAAyB;AAC7D,SAAS,qCAAqC;AAC9C,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,cAAc,EAAE,MAAM,EAAE,WAAW,wBAAwB,CAAC;AAsBxF,MAAM,gCAAgC;AAEtC,SAAS,wBAA4D;AACnE,QAAM,QAAQ;AACd,QAAM,WAAW,MAAM,6BAA6B;AACpD,MAAI,oBAAoB,IAAK,QAAO;AACpC,QAAM,OAAO,oBAAI,IAAmC;AACpD,QAAM,6BAA6B,IAAI;AACvC,SAAO;AACT;AAgDA,SAAS,eAAe,OAAkD;AACxE,SAAO,QAAQ,KAAK,KAAK,OAAO,UAAU;AAC5C;AAEA,SAAS,iBAAiB,OAA+B;AACvD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEA,SAAS,mBAAmB,OAA+B;AACzD,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,QAAQ;AACvB,QAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,EAAG,QAAO;AAClE,QAAI,OAAO,WAAW,UAAU;AAC9B,YAAM,SAAS,OAAO,MAAM;AAC5B,UAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,IACtC;AAEA,UAAM,aAAa,QAAQ;AAC3B,QAAI,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,EAAG,QAAO;AAC1E,QAAI,OAAO,eAAe,UAAU;AAClC,YAAM,SAAS,OAAO,UAAU;AAChC,UAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,IACtC;AAEA,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,SAAS,OAAO;AACvE,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iCAAiC,OAIjC;AACP,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,OAAO;AAC9D,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAEA,UAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAC/D,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,iBACJ,eAAe,QAAQ,IAAI,KACxB,eAAe,QAAQ,QAAQ,KAC/B,OAAO,QAAQ,eAAe,YAC9B,OAAO,QAAQ,iBAAiB,YAChC,OAAO,QAAQ,eAAe,YAC9B,MAAM,QAAQ,QAAQ,iBAAiB,KACvC,OAAO,QAAQ,0BAA0B,aACzC,OAAO,QAAQ,wBAAwB;AAE5C,UAAM,UAAU,OAAO,QAAQ,YAAY,WACvC,QAAQ,QAAQ,YAAY,IAC5B,OAAO,QAAQ,UAAU,WACvB,QAAQ,MAAM,YAAY,IAC1B;AACN,UAAM,+BACJ,QAAQ,SAAS,iBAAiB,KAC/B,QAAQ,SAAS,sBAAsB,KACvC,QAAQ,SAAS,mBAAmB;AAEzC,UAAM,uBACJ,SAAS,0BACL,WAAW,QAAQ,kBAAkB;AAE3C,QAAI,CAAC,qBAAsB;AAC3B,QAAI,CAAC,eAAe,QAAQ,QAAQ,GAAG;AACrC,YAAM,OAAO,eAAe,QAAQ,IAAI,IAAK,QAAQ,OAA4B;AACjF,YAAM,qBAAqB,OAAO,QAAQ,eAAe,YAAY,OAAO,QAAQ,UAAU,IAC1F,QAAQ,aACR;AACJ,YAAM,mBAAyC;AAAA,QAC7C,IAAI;AAAA,QACJ,eACG,OAAO,QAAQ,iBAAiB,YAAY,QAAQ,aAAa,KAAK,EAAE,SAAS,IAC9E,QAAQ,eACR,MAAM,iBAAiB;AAAA,QAC7B,aACG,OAAO,QAAQ,eAAe,YAAY,QAAQ,WAAW,KAAK,EAAE,SAAS,IAC1E,QAAQ,aACR,MAAM,eAAe;AAAA,QAC3B,iBACE,OAAO,QAAQ,oBAAoB,YAAY,QAAQ,oBAAoB,OACvE,QAAQ,kBACR,MAAM,mBAAmB;AAAA,QAC/B,qBACE,OAAO,QAAQ,wBAAwB,YAAY,QAAQ,wBAAwB,OAC/E,QAAQ,sBACR;AAAA,QACN,uBAAuB,QAAQ,QAAQ,qBAAqB;AAAA,QAC5D,qBAAqB,QAAQ,QAAQ,mBAAmB;AAAA,QACxD,mBAAmB,MAAM,QAAQ,QAAQ,iBAAiB,KAAK,QAAQ,kBAAkB,SAAS,aAAa,IAC3G,CAAC,aAAa,IACd,CAAC;AAAA,QACL,SAAS,CAAC;AAAA,MACZ;AACA,aAAO;AAAA,QACL,UAAU;AAAA,QACV;AAAA,QACA,mBAAmB,OAAO,QAAQ,sBAAsB,YAAY,QAAQ,sBAAsB,OAC9F,QAAQ,oBACR;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU,QAAQ;AAAA,MAClB,MAAM,eAAe,QAAQ,IAAI,IAAK,QAAQ,OAA4B;AAAA,MAC1E,mBAAmB,OAAO,QAAQ,sBAAsB,YAAY,QAAQ,sBAAsB,OAC9F,QAAQ,oBACR;AAAA,IACN;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAyB;AACrD,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,UAAU,oBAAI,IAAa;AAEjC,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,WAAW,QAAQ,IAAI,OAAO,EAAG;AACtC,YAAQ,IAAI,OAAO;AACnB,QAAI,CAAC,eAAe,OAAO,EAAG;AAE9B,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,KAAK,YAAY,IAAI;AAC7E,UAAM,UAAU,OAAO,QAAQ,YAAY,WACvC,QAAQ,QAAQ,YAAY,IAC5B,OAAO,QAAQ,UAAU,WACvB,QAAQ,MAAM,YAAY,IAC1B;AAEN,UAAM,cACJ,SAAS,sBACN,SAAS,eACT,SAAS;AAEd,UAAM,iBACJ,QAAQ,SAAS,WAAW,KACzB,QAAQ,SAAS,aAAa,KAC9B,QAAQ,SAAS,gBAAgB;AAGtC,QAAI,WAAW,OAAO,eAAe,gBAAgB;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,CAAC,QAAQ,YAAY,QAAQ,WAAW,SAAS,OAAO;AACvE,eAAW,OAAO,QAAQ;AACxB,YAAM,OAAO,QAAQ,GAAG;AACxB,UAAI,QAAQ,CAAC,QAAQ,IAAI,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AACT;AAiCO,SAAS,mCAAmC,MAKnB;AAC9B,QAAM,EAAE,OAAO,gBAAgB,QAAQ,aAAa,IAAI;AACxD,QAAM,UAAU,iCAAiC,KAAK;AACtD,QAAM,0BAA0B,CAAC,kBAAkB,mBAAmB;AACtE,MAAI,CAAC,yBAAyB;AAC5B,QAAI,CAAC,WAAW,CAAC,cAAc,gBAAgB,CAAC,cAAc,WAAY,QAAO,EAAE,QAAQ,SAAS;AACpG,UAAM,sBAAsB,QAAQ,SAAS,cAAc,KAAK,KAAK;AACrE,UAAM,oBAAoB,QAAQ,SAAS,YAAY,KAAK,KAAK;AACjE,QAAI,CAAC,uBAAuB,CAAC,kBAAmB,QAAO,EAAE,QAAQ,SAAS;AAC1E,QAAI,wBAAwB,aAAa,gBAAgB,sBAAsB,aAAa,YAAY;AACtG,aAAO,EAAE,QAAQ,SAAS;AAAA,IAC5B;AAAA,EACF;AACA,MAAI,CAAC,SAAS;AACZ,QAAI,CAAC,cAAc,gBAAgB,CAAC,cAAc,WAAY,QAAO,EAAE,QAAQ,SAAS;AACxF,QAAI,qBAAqB,KAAK,EAAG,QAAO,EAAE,QAAQ,iBAAiB;AAUnE,QAAI,8BAA8B,KAAK,EAAG,QAAO,EAAE,QAAQ,SAAS;AACpE,QAAI,mBAAmB,KAAK,MAAM,IAAK,QAAO,EAAE,QAAQ,kBAAkB;AAC1E,WAAO,EAAE,QAAQ,SAAS;AAAA,EAC5B;AACA,SAAO,EAAE,QAAQ,kBAAkB,QAAQ;AAC7C;AAEA,SAAS,gCAAgC;AACvC,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,IAAI,aAAa,IAAI,qBAAqB,MAAM,IAAK;AACzD,QAAI,aAAa,OAAO,qBAAqB;AAC7C,UAAM,UAAU,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AACvD,WAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,EAC/D,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,+BAA+B;AACtC,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,QAAI,IAAI,aAAa,IAAI,oBAAoB,MAAM,IAAK;AACxD,QAAI,aAAa,OAAO,oBAAoB;AAC5C,UAAM,UAAU,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AACvD,WAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,OAAO;AAAA,EAC/D,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,eAAe,QAAyB;AAC/C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,OAAO,SAAS,eAAe,MAAM;AAC3C,MAAI,EAAE,gBAAgB,iBAAkB,QAAO;AAC/C,OAAK,cAAc;AACnB,SAAO;AACT;AAEA,SAAS,oBAAoB,SAA8C;AACzE,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,KAAK,EAAG,QAAO,QAAQ;AACxE,MAAI,QAAQ,SAAS,QAAS,QAAO;AACrC,MAAI,QAAQ,SAAS,QAAS,QAAO;AACrC,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,QAAQ,UAAW,QAAO;AAC9B,MAAI,QAAQ,OAAQ,QAAO;AAC3B,QAAM,WAAW,QAAQ;AACzB,MAAI,YAAY,SAAS,SAAS,GAAG,GAAG;AACtC,UAAM,CAAC,UAAU,SAAS,IAAI,SAAS,MAAM,GAAG;AAChD,UAAM,SAAS,aAAa;AAC5B,UAAM,qBAAqB,SAAS,KAAK;AACzC,UAAM,mBAAmB,OAAO,KAAK;AACrC,QAAI,sBAAsB,kBAAkB;AAC1C,YAAM,mBAAmB,mBAAmB,SAAS,GAAG,IACpD,mBAAmB,MAAM,GAAG,EAAE,IAC9B;AAEJ,YAAM,gBAAgB;AAAA,QACpB,GAAG,kBAAkB;AAAA,QACrB,GAAG,gBAAgB;AAAA,MACrB;AAEA,UAAI,cAAc;AAClB,iBAAW,UAAU,eAAe;AAClC,YAAI,YAAY,WAAW,MAAM,GAAG;AAClC,wBAAc,YAAY,MAAM,OAAO,MAAM;AAC7C;AAAA,QACF;AAAA,MACF;AAEA,UAAI,YAAa,QAAO,GAAG,kBAAkB,IAAI,WAAW;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,MAAI,KAAK,WAAW,4BAA4B,EAAG,QAAO;AAC1D,MAAI,KAAK,WAAW,+BAA+B,EAAG,QAAO;AAC7D,MAAI,KAAK,WAAW,2BAA2B,EAAG,QAAO;AACzD,MAAI,KAAK,WAAW,wBAAwB,EAAG,QAAO;AACtD,MAAI,KAAK,WAAW,wBAAwB,EAAG,QAAO;AACtD,MAAI,KAAK,WAAW,2BAA2B,GAAG;AAChD,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,SAAS,IAAI,gBAAgB,KAAK;AACxC,UAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,QAAS,QAAO;AAAA,EAC/B;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAA+B,MAA8B;AACtF,MAAI,QAAQ,cAAc,QAAQ,WAAW,KAAK,EAAG,QAAO,QAAQ;AACpE,MAAI,QAAQ,YAAY,QAAQ,SAAS,KAAK,EAAG,QAAO,QAAQ;AAChE,MAAI,QAAQ,YAAY,QAAQ,SAAS,KAAK,EAAG,QAAO,QAAQ;AAChE,MAAI,QAAQ,aAAa,QAAQ,UAAU,KAAK,EAAG,QAAO,QAAQ;AAClE,MAAI,QAAQ,UAAU,QAAQ,OAAO,KAAK,EAAG,QAAO,QAAQ;AAC5D,MAAI,QAAQ,OAAO,SAAS,YAAY,QAAQ,MAAM;AACpD,UAAM,KAAM,KAA0B;AACtC,QAAI,OAAO,OAAO,YAAY,GAAG,KAAK,EAAG,QAAO;AAAA,EAClD;AACA,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,iBAAkB,KAAuC,QAAQ;AACvE,QAAI,OAAO,mBAAmB,YAAY,eAAe,KAAK,EAAG,QAAO;AACxE,UAAM,kBAAmB,KAAwC,SAAS;AAC1E,QAAI,OAAO,oBAAoB,YAAY,gBAAgB,KAAK,EAAG,QAAO;AAC1E,UAAM,eAAgB,KAAqC,MAAM;AACjE,QAAI,OAAO,iBAAiB,YAAY,aAAa,KAAK,EAAG,QAAO;AAAA,EACtE;AACA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AAC9D,QAAM,aAAa;AAAA,IACjB,CAAC,WAAW,aAAa,QAAQ;AAAA,IACjC,CAAC,WAAW,aAAa,WAAW;AAAA,IACpC,CAAC,WAAW,aAAa,OAAO;AAAA,IAChC,CAAC,WAAW,SAAS,QAAQ;AAAA,IAC7B,CAAC,WAAW,SAAS,QAAQ;AAAA,IAC7B,CAAC,WAAW,SAAS,WAAW;AAAA,EAClC;AACA,aAAW,UAAU,YAAY;AAC/B,UAAM,gBAAgB,OAAO,MAAM,CAAC,SAAS,UAAU,MAAM,KAAK,MAAM,OAAO;AAC/E,QAAI,CAAC,iBAAiB,MAAM,UAAU,OAAO,OAAQ;AACrD,UAAM,QAAQ,MAAM,OAAO,MAAM,KAAK;AACtC,QAAI,CAAC,MAAO;AACZ,QAAI;AACF,YAAM,UAAU,mBAAmB,KAAK,EAAE,KAAK;AAC/C,UAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,IACjC,QAAQ;AACN,YAAM,UAAU,MAAM,KAAK;AAC3B,UAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cACP,SACA,cACA,YACQ;AACR,MAAI,QAAQ,UAAU,QAAQ,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,QAAQ;AACvE,MAAI,gBAAgB,WAAY,QAAO,eAAe,YAAY,IAAI,UAAU;AAChF,MAAI,QAAQ,QAAQ,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AAClD,UAAM,QAAQ,QAAQ,OAAO,KAAK;AAClC,WAAO,eAAe,QAAQ,IAAI,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,EAC/D;AACA,SAAO;AACT;AAEA,eAAe,YAAY,OAKxB;AACD,QAAM,QAAQ,6BAA6B;AAAA,IACzC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,OAAO,MAAM,SAAS;AAAA,MACtB,QAAQ,MAAM,UAAU;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,yBAAyB,OAK/B;AACD,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B,cAAc,MAAM;AAAA,IACpB,YAAY,MAAM;AAAA,IAClB,OAAO,MAAM,SAAS;AAAA,IACtB,QAAQ,MAAM,UAAU;AAAA,EAC1B,CAAC;AAED,MAAI;AACF,QAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,YAAY;AAClF,YAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC7D,YAAM,OAAO,UAAU,WAAW,6BAA6B,IAAI;AACnE,UAAI,KAAM;AAAA,IACZ;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,OAAK,MAAM,6BAA6B;AAAA,IACtC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,EACf,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,WAAO,KAAK,kDAAkD,EAAE,KAAK,MAAM,CAAC;AAAA,EAC9E,CAAC;AACH;AAEe,SAAR,oBAAqC;AAAA,EAC1C;AAAA,EACA;AACF,GAAiF;AAC/E,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,gBAAgB;AACrC,QAAM,eAAe,MAAM,QAAQ,MAAM,oBAAoB,OAAO,GAAG,CAAC,OAAO,CAAC;AAChF,QAAM,aAAa,MAAM,QAAQ,MAAM,kBAAkB,SAAS,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC;AACxF,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,cAAc,SAAS,cAAc,UAAU;AAAA,IACrD,CAAC,SAAS,YAAY,YAAY;AAAA,EACpC;AACA,QAAM,CAAC,EAAE,WAAW,IAAI,MAAM,WAAW,CAAC,UAAU,QAAQ,GAAG,CAAC;AAChE,QAAM,QAAQ,uBAAuB,MAAM;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,8BAA8B,+BAA+B,IAAI,MAAM,SAAS,KAAK;AAC5F,QAAM,CAAC,0BAA0B,2BAA2B,IAAI,MAAM,SAAS,KAAK;AACpF,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,MAAM,SAAS,KAAK;AAC5E,QAAM,aAAa,MAAM;AAAA,IACvB,MACE,sBAAsB,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IAC1F,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,QAAQ,SAAS,IAAI;AAC3C,QAAM,WAAW,gBAAgB,aAAa,GAAG,YAAY,IAAI,UAAU,KAAK;AAChF,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,IAAI;AACrE,QAAM,oBAAoB,MAAM,OAItB,IAAI;AACd,QAAM,0BAA0B,MAAM,OAAO,CAAC;AAE9C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU;AACb,2BAAqB,IAAI;AACzB;AAAA,IACF;AAEA,UAAM,SAAS,sBAAsB;AACrC,UAAM,sBAAsB,MAAM;AAChC,UAAI,OAAO,WAAW,YAAa;AACnC,aAAO;AAAA,QACL,IAAI,YAAY,gCAAgC;AAAA,UAC9C,QAAQ,EAAE,SAAS;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,iBAAiB,MAAM;AAC3B,YAAM,UAAU,OAAO,IAAI,QAAQ;AACnC,UAAI,CAAC,SAAS;AACZ,eAAO,IAAI,UAAU,EAAE,YAAY,UAAU,cAAc,CAAC;AAC5D,6BAAqB,IAAI;AACzB,4BAAoB;AACpB;AAAA,MACF;AACA,UAAI,QAAQ,eAAe,YAAY;AACrC,6BAAqB,IAAI;AACzB;AAAA,MACF;AACA,UAAI,gBAAgB,QAAQ,UAAU;AACpC,eAAO,IAAI,UAAU,EAAE,YAAY,UAAU,cAAc,CAAC;AAC5D,6BAAqB,IAAI;AACzB,4BAAoB;AACpB;AAAA,MACF;AACA,2BAAqB,KAAK;AAAA,IAC5B;AAEA,mBAAe;AACf,UAAM,kBAAkB,MAAM,eAAe;AAC7C,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,gCAAgC,eAAe;AAAA,IACzE;AAEA,WAAO,MAAM;AACX,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,oBAAoB,gCAAgC,eAAe;AAAA,MAC5E;AACA,YAAM,UAAU,OAAO,IAAI,QAAQ;AACnC,UAAI,SAAS,eAAe,YAAY;AACtC,eAAO,OAAO,QAAQ;AACtB,4BAAoB;AAAA,MACtB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,UAAU,aAAa,CAAC;AAExC,QAAM,UAAU,MAAM;AACpB,QAAI,kBAAmB;AACvB,UAAM,UAAU,uBAAuB,MAAM;AAC7C,QAAI,CAAC,SAAS,MAAM,SAAS,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,YAAY;AACzE,+BAAyB,MAAM;AAC/B;AAAA,IACF;AACA,SAAK,YAAY;AAAA,MACf,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,MACpB,OAAO,QAAQ,KAAK;AAAA,MACpB,QAAQ;AAAA,IACV,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,aAAO,KAAK,+CAA+C,EAAE,KAAK,MAAM,CAAC;AAAA,IAC3E,CAAC;AACD,6BAAyB,MAAM;AAAA,EACjC,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,QAAM,UAAU,MAAM;AACpB,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,UAAM,sBAAsB,cAAc,IAAI,qBAAqB,MAAM;AACzE,UAAM,qBAAqB,cAAc,IAAI,oBAAoB,MAAM;AAEvE,QAAI,qBAAqB;AACvB,sCAAgC,IAAI;AACpC,oCAA8B;AAAA,IAChC;AAEA,QAAI,oBAAoB;AACtB,kCAA4B,IAAI;AAChC,mCAA6B;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,UAAU,MAAM,6BAA6B,QAAQ,MAAM,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;AAEzF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,OAAO,UAAU;AACpB,8BAAwB,KAAK;AAC7B;AAAA,IACF;AACA,4BAAwB,IAAI;AAAA,EAC9B,GAAG;AAAA,IACD,OAAO,UAAU;AAAA,IACjB,OAAO,UAAU;AAAA,IACjB,OAAO,UAAU;AAAA,EACnB,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,gBAAgB,CAAC,WAAY;AAClC,2BAAuB,QAAQ,EAAE,QAAQ,cAAc,WAAW,CAAC;AAAA,EACrE,GAAG,CAAC,QAAQ,mBAAmB,YAAY,YAAY,CAAC;AAExD,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,gBAAgB,CAAC,WAAY;AAClC,QAAI,SAAS;AACb,UAAM,UAAU,YAAY;AAC1B,YAAM,OAAO,MAAM,QAAyB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,cAAc,WAAW,CAAC;AAAA,MACnD,CAAC;AACD,YAAM,UAAU,KAAK,UAAU,CAAC;AAChC,UAAI,CAAC,OAAQ;AACb,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,iBAAiB,KAAK,WAAW,MACnC,EAAE,wBAAwB,WAAW,IACrC,EAAE,sCAAsC,oCAAoC;AAChF,cAAM,UAAU,OAAO,QAAQ,UAAU,YAAY,QAAQ,MAAM,KAAK,EAAE,SACtE,QAAQ,QACR;AACJ,cAAM,SAAS,OAAO;AACtB,+BAAuB,QAAQ;AAAA,UAC7B;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,MAAM,QAAQ,QAAQ;AAAA,UACtB,eAAe,QAAQ,iBAAiB;AAAA,UACxC,kBAAkB,QAAQ,oBAAoB;AAAA,UAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,UAChD,kBAAkB,QAAQ,oBAAoB;AAAA,QAChD,CAAC;AACD;AAAA,MACF;AACA,6BAAuB,QAAQ;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,QAAQ,YAAY;AAAA,QAC9B,MAAM,QAAQ,QAAQ;AAAA,QACtB,eAAe,QAAQ,iBAAiB;AAAA,QACxC,kBAAkB,QAAQ,oBAAoB;AAAA,QAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,QAChD,kBAAkB,QAAQ,oBAAoB;AAAA,MAChD,CAAC;AAAA,IACH;AACA,SAAK,QAAQ;AACb,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,YAAY,YAAY,CAAC;AAExD,QAAM,OAAO,QAAQ,OAAO,MAAM,KAAK;AACvC,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,QAAI,CAAC,OAAO,KAAM,QAAO,CAAC;AAC1B,UAAM,cAAc,MAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,eAAe,CAAC;AACxF,QAAI,YAAY,OAAQ,QAAO;AAC/B,WAAO,CAAC;AAAA,MACN,QAAQ,MAAM,KAAK;AAAA,MACnB,cAAc,MAAM,KAAK;AAAA,MACzB,eAAe,MAAM,KAAK;AAAA,MAC1B,YAAY,MAAM,KAAK;AAAA,MACvB,UAAU,MAAM,KAAK;AAAA,MACrB,iBAAiB,MAAM,KAAK;AAAA,MAC5B,WAAW,MAAM,KAAK;AAAA,IACxB,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,IAAI,CAAC;AAChB,QAAM,yBAAyB,OAAO,MAAM,0BAA0B,aAAa;AACnF,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,QAAI,CAAC,OAAO,cAAe,QAAO;AAClC,WAAO,aAAa,OAAO,CAAC,gBAAgB,YAAY,WAAW,MAAM,aAAa;AAAA,EACxF,GAAG,CAAC,cAAc,OAAO,aAAa,CAAC;AAEvC,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,QAAQ,CAAC,OAAO,MAAM,GAAI;AAE/B,UAAM,eAAe,CAAC,UAAiB;AACrC,YAAM,SAAS,eAAgB,MAA+B,MAAM,IAC9D,MAA+B,SACjC;AACJ,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,mBAAmB,MAAM,MAAM,GAAI;AAC9C,kCAA4B,IAAI;AAAA,IAClC;AAEA,WAAO,iBAAiB,mCAAmC,YAAY;AAEvE,WAAO,MAAM;AACX,aAAO,oBAAoB,mCAAmC,YAAY;AAAA,IAC5E;AAAA,EACF,GAAG,CAAC,mBAAmB,MAAM,OAAO,MAAM,EAAE,CAAC;AAE7C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,QAAI,MAAM,kBAAkB,KAAM;AAClC,UAAM,kBAAkB,CAAC,UAAiB;AACxC,YAAM,SAAS,eAAgB,MAA+B,MAAM,IAC9D,MAA+B,SACjC;AACJ,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,eAAe,MAAM,WAAY;AAC5C,YAAM,OAAO,iBAAiB,OAAO,YAAY;AACjD,UAAI,QAAQ,SAAS,MAAM,aAAc;AACzC,8BAAwB,IAAI;AAC5B,6BAAuB,QAAQ;AAAA,QAC7B,eAAe;AAAA,QACf,UAAU;AAAA,QACV,MAAM;AAAA,QACN,UAAU;AAAA,QACV,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,QACnB,wBAAwB;AAAA,MAC1B,CAAC;AAAA,IACH;AAEA,WAAO,iBAAiB,mCAAmC,eAAe;AAE1E,WAAO,MAAM;AACX,aAAO,oBAAoB,mCAAmC,eAAe;AAAA,IAC/E;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,OAAO,eAAe,OAAO,YAAY,OAAO,YAAY,CAAC;AAE5F,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,OAAO,MAAM,SAAS,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAY;AACrE,UAAM,mBACJ,OAAO,MAAM,qBAAqB,YAC/B,OAAO,SAAS,MAAM,gBAAgB,KACtC,MAAM,mBAAmB,IAE1B,MAAM,mBACN;AACJ,UAAM,aAAa,KAAK,IAAI,KAAO,KAAK,MAAM,mBAAmB,GAAI,CAAC;AACtE,UAAM,WAAW,OAAO,YAAY,MAAM;AACxC,WAAK,QAAQ,+BAA+B;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,UAClB,OAAO,MAAM,MAAM;AAAA,QACrB,CAAC;AAAA,MACH,CAAC;AAAA,IACH,GAAG,UAAU;AACb,WAAO,MAAM,OAAO,cAAc,QAAQ;AAAA,EAC5C,GAAG,CAAC,mBAAmB,OAAO,kBAAkB,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,YAAY,CAAC;AAE3G,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,wBAAwB,QAAQ,OAAO,QAAQ,KAChD,EACD,OAAO,2BAA2B,QAC/B,OAAO,OAAO,sBAAsB,YACpC,MAAM,sBAAsB;AAEnC,QAAI,sBAAuB;AAC3B,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,UAAM,kBAAkB,YAAY;AAClC,YAAM,OAAO,MAAM,QAAyB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,QACpB,CAAC;AAAA,MACH,CAAC;AACD,YAAM,UAAU,KAAK,UAAU,CAAC;AAChC,UAAI,CAAC,KAAK,IAAI;AACZ,cAAMA,gBAAe,uBAAuB,MAAM;AAClD,+BAAuB,QAAQ;AAAA,UAC7B,cAAc,MAAM;AAAA,UACpB,YAAY,MAAM;AAAA,UAClB,UAAU;AAAA,UACV,MAAM,QAAQ,QAAQ;AAAA,UACtB,eAAe,QAAQ,iBAAiBA,eAAc,iBAAiB;AAAA,UACvE,kBAAkB,QAAQ,oBAAoBA,eAAc,oBAAoB;AAAA,UAChF,mBAAmB,QAAQ,qBAAqBA,eAAc,qBAAqB;AAAA,UACnF,kBAAkB,QAAQ,oBAAoB;AAAA,QAChD,CAAC;AACD;AAAA,MACF;AACA,YAAM,eAAe,uBAAuB,MAAM;AAClD,YAAM,gBAAgB,cAAc,MAAM,SAAS;AACnD,YAAM,YAAY,QAAQ,MAAM,SAAS;AACzC,YAAM,gBAAgB,QAAQ,iBAAiB,aAAa,kBAAkB,SAAS;AACvF,YAAM,wBAAwB,gBACzB,cAAc,qBAAqB,OACnC,QAAQ,qBAAqB,cAAc,qBAAqB;AAErE,6BAAuB,QAAQ;AAAA,QAC7B,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,UAAU,QAAQ,YAAY;AAAA,QAC9B,MAAM,QAAQ,QAAQ;AAAA,QACtB,eAAe,QAAQ,iBAAiB;AAAA,QACxC,kBAAkB,QAAQ,oBAAoB;AAAA,QAC9C,mBAAmB;AAAA,QACnB,kBAAkB,QAAQ,oBAAoB;AAAA,MAChD,CAAC;AAAA,IACH;AACA,UAAM,oBAAoB,CAAC,UAAiB;AAC1C,YAAM,SAAS,eAAgB,MAA+B,MAAM,IAC9D,MAA+B,SACjC;AACJ,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,eAAe,MAAM,WAAY;AAC5C,YAAM,OAAO,iBAAiB,OAAO,YAAY;AACjD,UAAI,QAAQ,SAAS,MAAM,aAAc;AACzC,sCAAgC,IAAI;AACpC,WAAK,gBAAgB;AAAA,IACvB;AACA,UAAM,kBAAkB,CAAC,UAAiB;AACxC,YAAM,SAAS,eAAgB,MAA+B,MAAM,IAC9D,MAA+B,SACjC;AACJ,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,eAAe,MAAM,WAAY;AAC5C,YAAM,OAAO,iBAAiB,OAAO,YAAY;AACjD,UAAI,QAAQ,SAAS,MAAM,aAAc;AACzC,WAAK,gBAAgB;AAAA,IACvB;AACA,UAAM,sBAAsB,CAAC,UAAiB;AAC5C,YAAM,SAAS,eAAgB,MAA+B,MAAM,IAC/D,MAA+C,SAChD;AACJ,UAAI,QAAQ,OAAO,wBAAyB;AAC5C,WAAK,gBAAgB;AAAA,IACvB;AACA,UAAM,UAAU,MAAM;AACpB,WAAK,gBAAgB;AAAA,IACvB;AACA,UAAM,qBAAqB,MAAM;AAC/B,UAAI,CAAC,SAAS,QAAQ;AACpB,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF;AAEA,WAAO,iBAAiB,qCAAqC,iBAAiB;AAC9E,WAAO,iBAAiB,mCAAmC,eAAe;AAC1E,WAAO,iBAAiB,YAAY,mBAAmB;AACvD,WAAO,iBAAiB,SAAS,OAAO;AACxC,aAAS,iBAAiB,oBAAoB,kBAAkB;AAChE,UAAM,oBAAoB,OAAO,YAAY,MAAM;AACjD,WAAK,gBAAgB;AAAA,IACvB,GAAG,GAAM;AACT,SAAK,gBAAgB;AAErB,WAAO,MAAM;AACX,aAAO,oBAAoB,qCAAqC,iBAAiB;AACjF,aAAO,oBAAoB,mCAAmC,eAAe;AAC7E,aAAO,oBAAoB,YAAY,mBAAmB;AAC1D,aAAO,oBAAoB,SAAS,OAAO;AAC3C,eAAS,oBAAoB,oBAAoB,kBAAkB;AACnE,aAAO,cAAc,iBAAiB;AAAA,IACxC;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,6BAA8B;AACnC,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,cAAc,CAAC,OAAO,KAAM;AAChE,QAAI,YAAY;AAChB,UAAM,4BAA4B,YAAY;AAC5C,oCAA8B;AAC9B,sCAAgC,KAAK;AACrC,YAAM,mBAAmB,CAAC,GAAG,OAAO;AACpC,UAAI,UAAW;AAAA,IACjB;AACA,SAAK,0BAA0B;AAC/B,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,SAAS,mBAAmB,8BAA8B,OAAO,MAAM,OAAO,YAAY,OAAO,cAAc,CAAC,CAAC;AAErH,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QACE,OAAO,gBACJ,OAAO,cACP,OAAO,MAAM,MAAM,UAAU,YAC7B,MAAM,KAAK,MAAM,KAAK,EAAE,SAAS,GACpC;AACA,wBAAkB,UAAU;AAAA,QAC1B,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,QAClB,OAAO,MAAM,KAAK;AAAA,MACpB;AACA;AAAA,IACF;AACA,sBAAkB,UAAU;AAAA,EAC9B,GAAG,CAAC,mBAAmB,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,YAAY,CAAC;AAElF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,aAAa,MAAM;AACvB,YAAM,UAAU,kBAAkB;AAClC,UAAI,CAAC,QAAS;AACd,+BAAyB;AAAA,QACvB,GAAG;AAAA,QACH,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,WAAO,iBAAiB,YAAY,UAAU;AAC9C,WAAO,MAAM;AACX,aAAO,oBAAoB,YAAY,UAAU;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,WAAO,MAAM;AACX,YAAM,UAAU,kBAAkB;AAClC,UAAI,SAAS;AACX,aAAK,YAAY;AAAA,UACf,GAAG;AAAA,UACH,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AACA,+BAAyB,MAAM;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,UAAM,kBAAkB,CAAC,UAAiB;AACxC,YAAM,uBAAuB,CAAC,YAIxB;AACJ,gCAAwB,IAAI;AAC5B,cAAM,YAA0C;AAAA,UAC9C,UAAU,QAAQ;AAAA,UAClB,mBAAmB,QAAQ,SAAS;AAAA,UACpC,mBAAmB;AAAA,UACnB,wBAAwB;AAAA,QAC1B;AACA,YAAI,QAAQ,SAAS,QAAW;AAC9B,oBAAU,OAAO,QAAQ;AAAA,QAC3B;AACA,YAAI,QAAQ,sBAAsB,QAAW;AAC3C,oBAAU,oBAAoB,QAAQ;AAAA,QACxC;AACA,+BAAuB,QAAQ;AAAA,UAC7B,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAEA,YAAM,wBAAwB,CAACA,kBAAsC;AACnE,cAAM,sBAAsB,OAAOA,cAAa,iBAAiB,IAC7DA,cAAa,oBACb,OAAOA,cAAa,UAAU,EAAE,IAC9BA,cAAa,SAAS,KACtB;AACN,eAAQ;AAAA,UACN,UAAU;AAAA,YACR,IAAI;AAAA,YACJ,cAAcA,cAAa,gBAAgB;AAAA,YAC3C,YAAYA,cAAa,cAAc;AAAA,YACzC,iBAAiBA,cAAa,qBAAqB;AAAA,YACnD,qBAAqB;AAAA,YACrB,uBAAuB;AAAA,YACvB,qBAAqB;AAAA,YACrB,mBAAmB,CAAC;AAAA,YACpB,SAAS,CAAC;AAAA,UACZ;AAAA,UACA,MAAMA,cAAa,QAAQ;AAAA,UAC3B,mBAAmBA,cAAa,qBAAqB;AAAA,QACvD;AAAA,MACA;AAEA,YAAM,SAAU,MAAgD;AAChE,UAAI,CAAC,OAAQ;AACb,YAAM,iBAAiB,OAAO,aAAa,OAAO;AAClD,YAAM,eAAe,uBAAuB,MAAM;AAClD,YAAM,WAAW,mCAAmC;AAAA,QAClD,OAAO,OAAO;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,SAAS,WAAW,SAAU;AAClC,UAAI,SAAS,WAAW,kBAAkB;AACxC,gCAAwB,IAAI;AAC5B,+BAAuB,QAAQ;AAAA,UAC7B,eAAe;AAAA,UACf,UAAU;AAAA,UACV,MAAM;AAAA,UACN,UAAU;AAAA,UACV,mBAAmB;AAAA,UACnB,mBAAmB;AAAA,UACnB,wBAAwB;AAAA,QAC1B,CAAC;AACD;AAAA,MACF;AACA,UAAI,SAAS,WAAW,mBAAmB;AACzC,YAAI,aAAc,sBAAqB,sBAAsB,YAAY,CAAC;AAC1E;AAAA,MACF;AAEA,2BAAqB,SAAS,OAAO;AAAA,IACvC;AAEA,WAAO,iBAAiB,8BAA8B,eAAe;AACrE,WAAO,iBAAiB,sBAAsB,eAAe;AAC7D,WAAO,MAAM;AACX,aAAO,oBAAoB,8BAA8B,eAAe;AACxE,aAAO,oBAAoB,sBAAsB,eAAe;AAAA,IAClE;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAQ9B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,kBAAmB;AACxB,QAAI,CAAC,gBAAgB,CAAC,WAAY;AAClC,UAAM,aAAa,kCAAkC,CAAC,WAAW,UAAU;AACzE,YAAM,UAAU,iCAAiC,KAAK;AACtD,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,cAAc,QAAQ,SAAS,cAAc,KAAK,KAAK;AAC7D,YAAM,YAAY,QAAQ,SAAS,YAAY,KAAK,KAAK;AACzD,UAAI,eAAe,cAAc,gBAAgB,gBAAgB,cAAc,YAAa,QAAO;AACnG,8BAAwB,IAAI;AAC5B,6BAAuB,QAAQ;AAAA,QAC7B,UAAU,QAAQ;AAAA,QAClB,mBAAmB,QAAQ,SAAS;AAAA,QACpC,mBAAmB;AAAA,QACnB,wBAAwB;AAAA,MAC1B,CAAC;AACD,aAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,mBAAmB,cAAc,UAAU,CAAC;AAExD,QAAM,iBAAiB,MAAM,YAAY,YAAY;AACnD,4BAAwB,WAAW;AACnC,QAAI,CAAC,OAAO,gBAAgB,CAAC,OAAO,WAAY;AAChD,UAAM,OAAO,MAAM,QAAyB,mCAAmC;AAAA,MAC7E,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,cAAc,MAAM;AAAA,QACpB,YAAY,MAAM;AAAA,MACpB,CAAC;AAAA,IACH,CAAC;AACD,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,EAAE,4CAA4C,8BAA8B,GAAG,OAAO;AAC5F;AAAA,IACF;AACA,UAAM,UAAU,MAAM,QAAyB,6BAA6B;AAAA,MAC1E,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,cAAc,MAAM,cAAc,YAAY,MAAM,WAAW,CAAC;AAAA,IACzF,CAAC;AACD,QAAI,CAAC,QAAQ,IAAI;AACf,YAAM,EAAE,4CAA4C,8BAA8B,GAAG,OAAO;AAC5F;AAAA,IACF;AACA,UAAM,UAAU,QAAQ,UAAU,CAAC;AACnC,2BAAuB,QAAQ;AAAA,MAC7B,UAAU,QAAQ,YAAY;AAAA,MAC9B,MAAM,QAAQ,QAAQ;AAAA,MACtB,eAAe,QAAQ,iBAAiB;AAAA,MACxC,kBAAkB,QAAQ,oBAAoB;AAAA,MAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,MAChD,kBAAkB,QAAQ,oBAAoB;AAAA,MAC9C,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,mBAAmB;AAAA,MACnB,wBAAwB;AAAA,IAC1B,CAAC;AAAA,EACH,GAAG,CAAC,QAAQ,OAAO,YAAY,OAAO,cAAc,CAAC,CAAC;AAEtD,QAAM,uBAAuB,MAAM,YAAY,YAAY;AACzD,4BAAwB,WAAW;AACnC,UAAM,kBAAkB;AAAA,MACtB,UAAU,OAAO;AAAA,MACjB,cAAc,OAAO;AAAA,MACrB,YAAY,OAAO;AAAA,MACnB,sBAAsB,YAAY;AAChC,cAAM,aAAa,MAAM,mBAAmB,CAAC,GAAG,OAAO;AACvD,eAAO,kBAAkB,WAAW,QAAQ;AAAA,MAC9C;AAAA,MACA,iBAAiB,OAAO,eAAe;AACrC,cAAM,eAAe,6BAA6B;AAAA,UAChD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,cAAc,OAAO;AAAA,YACrB,YAAY,OAAO;AAAA,YACnB,OAAO,OAAO,MAAM,SAAS;AAAA,YAC7B,QAAQ;AAAA,YACR;AAAA,YACA,YAAY;AAAA,UACd,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,MACA,oBAAoB,MAAM;AACxB,+BAAuB,QAAQ;AAAA,UAC7B,UAAU;AAAA,UACV,mBAAmB;AAAA,UACnB,mBAAmB;AAAA,UACnB,wBAAwB;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,MACA,QAAQ,MAAM;AACZ,eAAO,SAAS,OAAO;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,SAAS,QAAQ,OAAO,UAAU,OAAO,MAAM,OAAO,OAAO,YAAY,OAAO,YAAY,CAAC;AAEjG,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,OAAO,SAAU;AACtB,UAAM,kBAAkB,YAAY;AAClC,YAAM,uBAAuB,wBAAwB,UAAU;AAC/D,8BAAwB,UAAU;AAClC,UAAI,aAA4B,OAAO,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS,KAAK;AACjF,UAAI,CAAC,YAAY;AACf,cAAM,aAAa,MAAM,mBAAmB,CAAC,GAAG,OAAO;AACvD,qBAAa,OAAO,WAAW,UAAU,EAAE,IAAI,WAAW,SAAS,KAAK;AAAA,MAC1E;AACA,UAAI,CAAC,YAAY;AACf;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AACA,6BAAuB,QAAQ;AAAA,QAC7B,mBAAmB;AAAA,QACnB,mBAAmB;AAAA,QACnB,wBAAwB;AAAA,MAC1B,CAAC;AACD,aAAO,WAAW,YAAY;AAC5B,YAAI,wBAAwB,YAAY,qBAAsB;AAC9D,cAAM,eAAe,uBAAuB,MAAM;AAClD,YAAI,cAAc,sBAAsB,cAAe;AACvD,cAAM,YAAY,eAAe,MAAM;AACvC,YAAI,UAAW;AACf,cAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,oBAAoB,CAAC,EAAE,MAAM,MAAM,KAAK;AACtF,YAAI,CAAC,SAAS;AACZ;AAAA,YACE;AAAA,cACE;AAAA,cACA;AAAA,YACF;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,GAAG,CAAC;AAAA,IACN;AACA,SAAK,gBAAgB;AAAA,EACvB,GAAG,CAAC,SAAS,QAAQ,mBAAmB,QAAQ,OAAO,UAAU,CAAC,CAAC;AAEnE,QAAM,oBAAoB,MAAM,YAAY,MAAM;AAChD,4BAAwB,WAAW;AACnC,4BAAwB,KAAK;AAAA,EAC/B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,EAAE,wBAAwB;AAC5C,QAAM,qBAAqB,MAAM;AAAA,IAC/B,OAAO,OAAO,UAAU,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,MACtD,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,MACb,IAAI,OAAO;AAAA,IACb,EAAE;AAAA,IACF,CAAC,OAAO,UAAU,OAAO;AAAA,EAC3B;AACA,QAAM,mBAAmB;AAAA,IACvB,OAAO,UAAU,yBACd,OAAO,UAAU,wBAAwB,QACzC,OAAO,UAAU,mBAAmB,SAAS,aAAa;AAAA,EAC/D;AACA,QAAM,kBAAkB,OAAO,kBAAkB;AACjD,QAAM,4BAA4B;AAAA,IAChC,OAAO,UAAU,yBACd,CAAC,OAAO,UAAU;AAAA,EACvB;AACA,QAAM,iBACJ,oBAAC,UAAO,MAAM,QAAQ,OAAO,YAAY,eAAe,KAAK,sBAAsB,cAAc,CAAC,SAAS;AACzG,QAAI,MAAM;AACR,8BAAwB,IAAI;AAC5B;AAAA,IACF;AACA,QAAI,iBAAiB;AACnB,8BAAwB,IAAI;AAC5B;AAAA,IACF;AACA,4BAAwB,KAAK;AAAA,EAC/B,GACE,+BAAC,iBAAc,eAAY,+BACzB;AAAA,wBAAC,gBACC,8BAAC,eACE,4BACG,EAAE,8CAA8C,oBAAoB,IACpE,EAAE,+BAA+B,mBAAmB,GAC1D,GACF;AAAA,IACA,qBAAC,SAAI,WAAU,qBACb;AAAA,0BAAC,OAAE,WAAU,yBACV,4BACG;AAAA,QACA;AAAA,QACA;AAAA,MACF,IACE,EAAE,qCAAqC,mEAAmE,GAChH;AAAA,MACC,CAAC,kBACF;AAAA,QAAC;AAAA;AAAA,UACC,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,UACA,aAAa,EAAE,wCAAwC,UAAU;AAAA,UACjE,YAAY,EAAE,uCAAuC,SAAS;AAAA;AAAA,MAChE,IACI;AAAA,OACF,OAAO,UAAU,SAAS,UAAU,OAAO,IAC3C,CAAC,kBACD,oBAAC,SAAM,QAAO,eACZ,8BAAC,oBACE;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF,GACF,IACI,OACF;AAAA,MACH,4BACC,oBAAC,SAAM,QAAO,WACZ,8BAAC,oBACE;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF,GACF,IACE;AAAA,MACJ,oBAAC,SAAI,WAAU,kHACb,8BAAC,SAAI,WAAU,oCACd,4BAAkB,OACjB,iCACF;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,mBAAK,qBAAqB;AAAA,YAC5B;AAAA,YAEC,YAAE,yCAAyC,iBAAiB;AAAA;AAAA,QAC/D;AAAA,QACC,mBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,mBAAK,eAAe;AAAA,YACtB;AAAA,YAEC,YAAE,qCAAqC,iBAAiB;AAAA;AAAA,QAC3D,IACE;AAAA,QACJ;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,SAAS,CAAC,UAAU;AAClB,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,gCAAkB;AAAA,YACpB;AAAA,YAEC,YAAE,sCAAsC,cAAc;AAAA;AAAA,QACzD;AAAA,SACE,GAEF,GACF;AAAA,OACF;AAAA,KACF,GACF;AAGF,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAC5C,WAAO,kBACJ,IAAI,CAAC,gBAAgB,YAAY,eAAe,KAAK,KAAK,EAAE,EAC5D,OAAO,CAAC,OAAO,OAAO,QAAQ,MAAM,SAAS,KAAK,IAAI,QAAQ,KAAK,MAAM,KAAK,EAC9E,MAAM,GAAG,CAAC;AAAA,EACf,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,yBAA0B;AAC/B,QAAI,CAAC,KAAM;AACX,QAAI,yBAAyB,EAAG;AAChC,gCAA4B,KAAK;AAAA,EACnC,GAAG,CAAC,wBAAwB,MAAM,wBAAwB,CAAC;AAE3D,QAAM,kBAAkB,UAAU,SAAS,eAAe,gBAAgB,IAAI;AAE9E,MAAI,CAAC,kBAAmB,QAAO;AAC/B,MAAI,CAAC,OAAO,KAAM,QAAO;AAEzB,QAAM,yBAAyB,yBAAyB,IACpD,GAAG,sBAAsB,yCACzB;AACJ,QAAM,mBAAmB,CAAC,OACtB,EAAE,uCAAuC,kDAAkD,IAC3F,2BACE,EAAE,yCAAyC,wFAAwF,IACnI,EAAE,2CAA2C,8CAA8C;AACjG,QAAM,gBAAgB,OAAO,qBAAqB,YAAY,iBAAiB,KAAK,EAAE,SAAS,IAC3F,mBACA;AACJ,QAAM,2BAA2B;AAAA,IAC/B,4BACG,yBAAyB,KACzB,CAAC;AAAA,EACN;AAEA,QAAM,aAAa,2BACjB,qBAAC,SAAI,WAAU,6FACb;AAAA,wBAAC,SAAI,WAAU,eACZ,yBACH;AAAA,IACA,qBAAC,SAAI,WAAU,oEACb;AAAA,0BAAC,UAAM,aAAG,EAAE,0CAA0C,cAAc,CAAC,KAAK,sBAAsB,IAAG;AAAA,MAClG,kBAAkB,IAAI,CAAC,UACtB;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,gCAAC,QAAK,WAAU,WAAU;AAAA,YAC1B,oBAAC,UAAM,iBAAM;AAAA;AAAA;AAAA,QAJR;AAAA,MAKP,CACD;AAAA,OACH;AAAA,IACA,qBAAC,SAAI,WAAU,mBACd;AAAA,YAAM,oBAAoB,CAAC,OAC1B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS;AAAA,UACT,WAAU;AAAA,UAET,YAAE,iCAAiC,mBAAmB;AAAA;AAAA,MACzD,IACE;AAAA,MACH,2BACC,oBAAC,SAAI,WAAU,QACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,SAAS,MAAM,4BAA4B,KAAK;AAAA,UAChD,WAAU;AAAA,UAET,YAAE,gBAAgB,OAAO;AAAA;AAAA,MAC5B,GACF,IACE;AAAA,OACJ;AAAA,KACF,IACE;AAEJ,SACE,iCACG;AAAA,iBAAc,kBAAkB,aAAa,YAAY,eAAe,IAAI,aAAc;AAAA,IAC1F;AAAA,KACH;AAEJ;AAEA,eAAsB,mBACpB,MACA,SAC2B;AAC3B,QAAM,sBAAsB,oBAAoB,OAAO;AACvD,QAAM,oBAAoB,kBAAkB,SAAS,IAAI;AACzD,QAAM,SAAS,cAAc,SAAS,qBAAqB,iBAAiB;AAC5E,QAAM,QAAQ,uBAAuB,MAAM;AAC3C,QAAM,eAAe,OAAO,gBAAgB;AAC5C,QAAM,aAAa,OAAO,cAAc;AACxC,MAAI,CAAC,gBAAgB,CAAC,YAAY;AAChC,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACA,QAAM,gCAAgC;AAAA,IACpC,OAAO,2BAA2B,QAC/B,OAAO,OAAO,sBAAsB,YACpC,MAAM,sBAAsB;AAAA,EACjC;AACA,MAAI,OAAO,YAAY,CAAC,+BAA+B;AACrD,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM,MAAM,QAAQ;AAAA,MACpB,UAAU,MAAM;AAAA,MAChB,mBAAmB,MAAM,qBAAqB;AAAA,IAChD;AAAA,EACF;AACA,QAAM,wBAAwB,QAAQ,OAAO,UAAU,MAAM,OAAO,MAAM,SAAS,EAAE,CAAC;AACtF,QAAM,sBAAsB,OAAO,qBAAqB;AACxD,QAAM,aAAa,wBAAwB,YAAY,CAAC,wBACpD,WACA;AACJ,QAAM,gBAAgB,eAAe,YAAY,CAAC,wBAC9C,SACC,OAAO,qBAAqB,OAAO,UAAU,MAAM;AACxD,QAAM,aAAa,OAAO,aAAa,IAAI,gBAAgB;AAC3D,QAAM,OAAO,MAAM,QAA0B,8BAA8B;AAAA,IACzE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,OAAO,OAAO,MAAM,SAAS;AAAA,MAC7B,WAAW,OAAO,qBAAqB,OAAO,MAAM,mBAAmB;AAAA,MACvE;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH,CAAC;AACD,QAAM,UAAU,KAAK,UAAU,EAAE,IAAI,MAAM;AAC3C,MAAI,QAAQ,IAAI;AACd,UAAM,iBAAiB,eAAe,WAAW,WAAW;AAC5D,UAAM,sCAAsC,mBAAmB;AAC/D,2BAAuB,QAAQ;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,mBAAmB,QAAQ,qBAAqB,OAAO,qBAAqB;AAAA,MAC5E,MAAM,QAAQ,QAAQ,OAAO,QAAQ;AAAA,MACrC,UAAU,sCAAuC,OAAO,YAAY,OAAQ;AAAA,MAC5E,mBAAmB,mBAAmB,WAAW,OAAQ,cAAc,OAAO,qBAAqB;AAAA,MACnG,mBAAmB;AAAA,MACnB,wBAAwB,mBAAmB,WAAW,QAAQ,QAAQ,OAAO,sBAAsB;AAAA,IACrG,CAAC;AACD,WAAO;AAAA,EACT;AACA,yBAAuB,QAAQ;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO,QAAQ;AAAA,IACrC,UAAU,QAAQ,YAAY,OAAO,YAAY;AAAA,IACjD,mBAAmB,QAAQ,UAAU,MAAM,cAAc,OAAO,qBAAqB;AAAA,IACrF,mBAAmB,eAAe,WAAW,WAAW;AAAA,IACxD,wBAAwB,eAAe,WAAW,QAAQ,QAAQ,OAAO,sBAAsB;AAAA,EACjG,CAAC;AACD,SAAO;AACT;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { BACKEND_MUTATION_ERROR_EVENT } from '@open-mercato/ui/backend/injection/mutationEvents'\nimport { registerRecordLockConflictHandler } from '@open-mercato/ui/backend/conflicts'\nimport { useSearchParams } from 'next/navigation'\nimport { Mail } from 'lucide-react'\nimport {\n RECORD_LOCKS_FORCE_RELEASED_EVENT,\n RECORD_LOCKS_INCOMING_CHANGES_EVENT,\n RECORD_LOCKS_LOCK_CONTENDED_EVENT,\n RECORD_LOCKS_RECORD_DELETED_EVENT,\n} from '@open-mercato/enterprise/modules/record_locks/notifications.handlers'\nimport {\n ChangedFieldsTable,\n type ChangeRow,\n} from '@open-mercato/core/modules/audit_logs/lib/display-helpers'\nimport {\n clearRecordLockFormState,\n getRecordLockFormState,\n setRecordLockFormState,\n subscribeRecordLockFormState,\n type RecordLockFormState,\n type RecordLockUiConflict,\n type RecordLockUiView,\n} from '@open-mercato/enterprise/modules/record_locks/lib/clientLockStore'\nimport { isUuid, resolveConflictId, runAcceptIncoming } from './conflictResolution'\nimport { isOptimisticLockFloorConflict } from '@open-mercato/enterprise/modules/record_locks/lib/optimisticLockFloor'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('record_locks').child({ component: 'record-locking-widget' })\n\ntype CrudInjectionContext = {\n formId?: string\n entityId?: string\n resourceKind?: string\n resourceId?: string\n recordId?: string\n path?: string\n query?: string\n kind?: string\n personId?: string\n companyId?: string\n dealId?: string\n retryLastMutation?: () => Promise<boolean | void> | boolean | void\n}\n\ntype RecordLockWidgetOwner = {\n instanceId: string\n priority: number\n}\n\nconst GLOBAL_RECORD_LOCK_OWNERS_KEY = '__openMercatoRecordLockWidgetOwners__'\n\nfunction getRecordLockOwnerMap(): Map<string, RecordLockWidgetOwner> {\n const store = globalThis as Record<string, unknown>\n const existing = store[GLOBAL_RECORD_LOCK_OWNERS_KEY]\n if (existing instanceof Map) return existing as Map<string, RecordLockWidgetOwner>\n const next = new Map<string, RecordLockWidgetOwner>()\n store[GLOBAL_RECORD_LOCK_OWNERS_KEY] = next\n return next\n}\n\ntype AcquireResponse = {\n ok?: boolean\n acquired?: boolean\n allowForceUnlock?: boolean\n heartbeatSeconds?: number\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n currentUserId?: string\n error?: string\n code?: string\n}\n\ntype ValidateResponse = {\n ok: boolean\n status?: number\n code?: string\n latestActionLogId?: string | null\n lock?: RecordLockUiView | null\n conflict?: RecordLockUiConflict | null\n}\n\ntype CrudSaveErrorEventDetail = {\n contextId?: string\n formId?: string\n error?: unknown\n}\n\ntype RecordLockContendedEventDetail = {\n sourceEntityId?: string | null\n}\n\ntype RecordDeletedEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\ntype RecordLockIncomingChangesEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\ntype RecordLockForceReleasedEventDetail = {\n resourceId?: string | null\n resourceKind?: string | null\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object'\n}\n\nfunction readStringOrNull(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction extractErrorStatus(error: unknown): number | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = current.status\n if (typeof status === 'number' && Number.isFinite(status)) return status\n if (typeof status === 'string') {\n const parsed = Number(status)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const statusCode = current.statusCode\n if (typeof statusCode === 'number' && Number.isFinite(statusCode)) return statusCode\n if (typeof statusCode === 'string') {\n const parsed = Number(statusCode)\n if (Number.isFinite(parsed)) return parsed\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return null\n}\n\nfunction extractRecordLockConflictPayload(error: unknown): {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n} | null {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const nested = ['body', 'response', 'data', 'details', 'error']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n\n const code = typeof current.code === 'string' ? current.code : null\n const status = extractErrorStatus(current)\n const hasLockMarkers = (\n isObjectRecord(current.lock)\n || isObjectRecord(current.conflict)\n || typeof current.conflictId === 'string'\n || typeof current.resourceKind === 'string'\n || typeof current.resourceId === 'string'\n || Array.isArray(current.resolutionOptions)\n || typeof current.allowIncomingOverride === 'boolean'\n || typeof current.canOverrideIncoming === 'boolean'\n )\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n const looksLikeLockConflictMessage = (\n message.includes('record conflict')\n || message.includes('record_lock_conflict')\n || message.includes('conflict detected')\n )\n const isRecordLockConflict = (\n code === 'record_lock_conflict'\n || (status === 409 && (hasLockMarkers || looksLikeLockConflictMessage))\n )\n if (!isRecordLockConflict) continue\n if (!isObjectRecord(current.conflict)) {\n const lock = isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined\n const fallbackConflictId = typeof current.conflictId === 'string' && isUuid(current.conflictId)\n ? current.conflictId\n : 'unresolved'\n const fallbackConflict: RecordLockUiConflict = {\n id: fallbackConflictId,\n resourceKind:\n (typeof current.resourceKind === 'string' && current.resourceKind.trim().length > 0\n ? current.resourceKind\n : lock?.resourceKind) ?? '',\n resourceId:\n (typeof current.resourceId === 'string' && current.resourceId.trim().length > 0\n ? current.resourceId\n : lock?.resourceId) ?? '',\n baseActionLogId:\n typeof current.baseActionLogId === 'string' || current.baseActionLogId === null\n ? current.baseActionLogId\n : lock?.baseActionLogId ?? null,\n incomingActionLogId:\n typeof current.incomingActionLogId === 'string' || current.incomingActionLogId === null\n ? current.incomingActionLogId\n : null,\n allowIncomingOverride: Boolean(current.allowIncomingOverride),\n canOverrideIncoming: Boolean(current.canOverrideIncoming),\n resolutionOptions: Array.isArray(current.resolutionOptions) && current.resolutionOptions.includes('accept_mine')\n ? ['accept_mine']\n : [],\n changes: [],\n }\n return {\n conflict: fallbackConflict,\n lock,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n return {\n conflict: current.conflict as RecordLockUiConflict,\n lock: isObjectRecord(current.lock) ? (current.lock as RecordLockUiView) : undefined,\n latestActionLogId: typeof current.latestActionLogId === 'string' || current.latestActionLogId === null\n ? current.latestActionLogId\n : undefined,\n }\n }\n\n return null\n}\n\nfunction isRecordDeletedError(error: unknown): boolean {\n const queue: unknown[] = [error]\n const visited = new Set<unknown>()\n\n while (queue.length > 0) {\n const current = queue.shift()\n if (!current || visited.has(current)) continue\n visited.add(current)\n if (!isObjectRecord(current)) continue\n\n const status = extractErrorStatus(current)\n const code = typeof current.code === 'string' ? current.code.toLowerCase() : ''\n const message = typeof current.message === 'string'\n ? current.message.toLowerCase()\n : typeof current.error === 'string'\n ? current.error.toLowerCase()\n : ''\n\n const matchesCode = (\n code === 'record_not_found'\n || code === 'not_found'\n || code === 'record_deleted'\n )\n const matchesMessage = (\n message.includes('not found')\n || message.includes('was deleted')\n || message.includes('record deleted')\n )\n\n if (status === 404 || matchesCode || matchesMessage) {\n return true\n }\n\n const nested = ['body', 'response', 'data', 'details', 'error', 'cause']\n for (const key of nested) {\n const next = current[key]\n if (next && !visited.has(next)) queue.push(next)\n }\n }\n\n return false\n}\n\n/**\n * Action the record-lock widget takes when a CrudForm / guarded-mutation save\n * error event (`om:crud-save-error` / backend-mutation-error) fires. Extracted\n * as a pure function so the single-conflict-surface (S3) decision is unit-testable\n * without rendering the widget:\n *\n * - `ignore` \u2014 event is for a different form/record, there is no\n * actionable mounted record, OR the error is a plain OSS\n * `optimistic_lock_conflict` 409 already owned by the OSS\n * conflict bar via `surfaceRecordConflict` (opening the\n * dialog too would render TWO surfaces \u2014 #3504/#3505).\n * - `record-deleted` \u2014 the record was deleted by another user.\n * - `apply-conflict` \u2014 a genuine enterprise `record_lock_conflict` payload;\n * open the field-level merge dialog with it.\n * - `fallback-dialog` \u2014 an unrecognized 409 (neither record-lock nor OSS\n * optimistic-lock) for the mounted record; open the\n * degraded fallback dialog (legacy behavior).\n */\nexport type RecordLockSaveErrorDecision =\n | { action: 'ignore' }\n | { action: 'record-deleted' }\n | {\n action: 'apply-conflict'\n payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }\n }\n | { action: 'fallback-dialog' }\n\nexport function resolveRecordLockSaveErrorDecision(args: {\n error: unknown\n eventContextId: string | null | undefined\n formId: string\n currentState: { resourceKind?: string | null; resourceId?: string | null } | null | undefined\n}): RecordLockSaveErrorDecision {\n const { error, eventContextId, formId, currentState } = args\n const payload = extractRecordLockConflictPayload(error)\n const eventTargetsCurrentForm = !eventContextId || eventContextId === formId\n if (!eventTargetsCurrentForm) {\n if (!payload || !currentState?.resourceKind || !currentState?.resourceId) return { action: 'ignore' }\n const payloadResourceKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadResourceId = payload.conflict.resourceId?.trim() ?? ''\n if (!payloadResourceKind || !payloadResourceId) return { action: 'ignore' }\n if (payloadResourceKind !== currentState.resourceKind || payloadResourceId !== currentState.resourceId) {\n return { action: 'ignore' }\n }\n }\n if (!payload) {\n if (!currentState?.resourceKind || !currentState?.resourceId) return { action: 'ignore' }\n if (isRecordDeletedError(error)) return { action: 'record-deleted' }\n // A plain OSS `optimistic_lock_conflict` 409 (the shape `makeCrudRoute` /\n // `CrudForm` returns on a stale write) is already surfaced by the OSS conflict\n // bar via `surfaceRecordConflict`. Defer to it instead of opening the degraded\n // fallback merge dialog \u2014 otherwise both surfaces render at once (#3504) and the\n // dialog has no field diff and a no-op \"Accept incoming\" (#3505). The rich merge\n // dialog stays reserved for genuine `record_lock_conflict` payloads (handled\n // above) whose conflict carries field changes + resolution options. Delegates to\n // the shared `optimisticLockFloor` detector so this arbitration stays identical to\n // the conflict bar's ownership decision (single source of truth).\n if (isOptimisticLockFloorConflict(error)) return { action: 'ignore' }\n if (extractErrorStatus(error) === 409) return { action: 'fallback-dialog' }\n return { action: 'ignore' }\n }\n return { action: 'apply-conflict', payload }\n}\n\n/**\n * Merge the lock returned by `/api/record_locks/validate` into the widget's\n * current lock state. `/validate` intentionally strips the token from the\n * caller's OWN lock (`toLockView(..., includeToken=false)`), so a naive\n * `payloadLock ?? currentLock` overwrites a good token with `null`, flips\n * `mine` false, and flashes a spurious \"locked by another user\" banner for the\n * ~1s round-trip of every save (#5289). Preserve the current token when the\n * response describes the same *active* lock id; a genuinely different (foreign)\n * lock, or a same-id lock the server no longer reports as active, keeps its null\n * token so real contention banners and re-acquisition still work.\n */\nexport function resolveLockAfterValidate(\n currentLock: RecordLockUiView | null | undefined,\n payloadLock: RecordLockUiView | null | undefined,\n): RecordLockUiView | null {\n if (!payloadLock) return currentLock ?? null\n if (\n payloadLock.token == null\n && payloadLock.status === 'active'\n && currentLock\n && currentLock.id === payloadLock.id\n ) {\n return { ...payloadLock, token: currentLock.token }\n }\n return payloadLock\n}\n\nfunction clearIncomingChangesQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showIncomingChanges') !== '1') return\n url.searchParams.delete('showIncomingChanges')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction clearLockContentionQueryFlag() {\n if (typeof window === 'undefined') return\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get('showLockContention') !== '1') return\n url.searchParams.delete('showLockContention')\n const nextUrl = `${url.pathname}${url.search}${url.hash}`\n window.history.replaceState(window.history.state, '', nextUrl)\n } catch {\n // ignore URL parse failures\n }\n}\n\nfunction submitCrudForm(formId: string): boolean {\n if (typeof document === 'undefined') return false\n const form = document.getElementById(formId)\n if (!(form instanceof HTMLFormElement)) return false\n form.requestSubmit()\n return true\n}\n\nfunction resolveResourceKind(context: CrudInjectionContext): string | null {\n if (context.resourceKind && context.resourceKind.trim()) return context.resourceKind\n if (context.kind === 'order') return 'sales.order'\n if (context.kind === 'quote') return 'sales.quote'\n if (context.personId) return 'customers.person'\n if (context.companyId) return 'customers.company'\n if (context.dealId) return 'customers.deal'\n const entityId = context.entityId\n if (entityId && entityId.includes(':')) {\n const [moduleId, rawEntity] = entityId.split(':')\n const entity = rawEntity ?? ''\n const normalizedModuleId = moduleId.trim()\n const normalizedEntity = entity.trim()\n if (normalizedModuleId && normalizedEntity) {\n const singularModuleId = normalizedModuleId.endsWith('s')\n ? normalizedModuleId.slice(0, -1)\n : normalizedModuleId\n\n const stripPrefixes = [\n `${normalizedModuleId}_`,\n `${singularModuleId}_`,\n ]\n\n let finalEntity = normalizedEntity\n for (const prefix of stripPrefixes) {\n if (finalEntity.startsWith(prefix)) {\n finalEntity = finalEntity.slice(prefix.length)\n break\n }\n }\n\n if (finalEntity) return `${normalizedModuleId}.${finalEntity}`\n }\n }\n\n const path = context.path ?? ''\n if (path.startsWith('/backend/customers/people/')) return 'customers.person'\n if (path.startsWith('/backend/customers/companies/')) return 'customers.company'\n if (path.startsWith('/backend/customers/deals/')) return 'customers.deal'\n if (path.startsWith('/backend/sales/orders/')) return 'sales.order'\n if (path.startsWith('/backend/sales/quotes/')) return 'sales.quote'\n if (path.startsWith('/backend/sales/documents/')) {\n const query = context.query ?? ''\n const params = new URLSearchParams(query)\n const kind = params.get('kind')\n if (kind === 'order') return 'sales.order'\n if (kind === 'quote') return 'sales.quote'\n }\n\n return null\n}\n\nfunction resolveResourceId(context: CrudInjectionContext, data: unknown): string | null {\n if (context.resourceId && context.resourceId.trim()) return context.resourceId\n if (context.recordId && context.recordId.trim()) return context.recordId\n if (context.personId && context.personId.trim()) return context.personId\n if (context.companyId && context.companyId.trim()) return context.companyId\n if (context.dealId && context.dealId.trim()) return context.dealId\n if (data && typeof data === 'object' && 'id' in data) {\n const id = (data as { id?: unknown }).id\n if (typeof id === 'string' && id.trim()) return id\n }\n if (data && typeof data === 'object') {\n const nestedPersonId = (data as { person?: { id?: unknown } }).person?.id\n if (typeof nestedPersonId === 'string' && nestedPersonId.trim()) return nestedPersonId\n const nestedCompanyId = (data as { company?: { id?: unknown } }).company?.id\n if (typeof nestedCompanyId === 'string' && nestedCompanyId.trim()) return nestedCompanyId\n const nestedDealId = (data as { deal?: { id?: unknown } }).deal?.id\n if (typeof nestedDealId === 'string' && nestedDealId.trim()) return nestedDealId\n }\n const path = context.path ?? ''\n const parts = path.split('/').filter((part) => part.length > 0)\n const candidates = [\n ['backend', 'customers', 'people'],\n ['backend', 'customers', 'companies'],\n ['backend', 'customers', 'deals'],\n ['backend', 'sales', 'orders'],\n ['backend', 'sales', 'quotes'],\n ['backend', 'sales', 'documents'],\n ] as const\n for (const prefix of candidates) {\n const matchesPrefix = prefix.every((segment, index) => parts[index] === segment)\n if (!matchesPrefix || parts.length <= prefix.length) continue\n const rawId = parts[prefix.length] ?? ''\n if (!rawId) continue\n try {\n const decoded = decodeURIComponent(rawId).trim()\n if (decoded.length > 0) return decoded\n } catch {\n const trimmed = rawId.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n}\n\nfunction resolveFormId(\n context: CrudInjectionContext,\n resourceKind: string | null,\n resourceId: string | null,\n): string {\n if (context.formId && context.formId.trim().length > 0) return context.formId\n if (resourceKind && resourceId) return `record-lock:${resourceKind}:${resourceId}`\n if (context.path && context.path.trim().length > 0) {\n const query = context.query?.trim()\n return `record-lock:${context.path}${query ? `?${query}` : ''}`\n }\n return 'record-lock:global'\n}\n\nasync function releaseLock(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n await apiCall('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'cancelled',\n }),\n })\n}\n\nfunction releaseLockWithKeepalive(state: {\n resourceKind: string\n resourceId: string\n token?: string | null\n reason?: 'saved' | 'cancelled' | 'unmount'\n}) {\n const payload = JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.token ?? undefined,\n reason: state.reason ?? 'unmount',\n })\n\n try {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([payload], { type: 'application/json' })\n const sent = navigator.sendBeacon('/api/record_locks/release', blob)\n if (sent) return\n }\n } catch {\n // ignore and fallback to fetch\n }\n\n void fetch('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: payload,\n keepalive: true,\n credentials: 'include',\n }).catch((error) => {\n logger.warn('Failed to release lock with keepalive fallback', { err: error })\n })\n}\n\nexport default function RecordLockingWidget({\n context,\n data,\n}: InjectionWidgetComponentProps<CrudInjectionContext, Record<string, unknown>>) {\n const t = useT()\n const searchParams = useSearchParams()\n const resourceKind = React.useMemo(() => resolveResourceKind(context), [context])\n const resourceId = React.useMemo(() => resolveResourceId(context, data), [context, data])\n const formId = React.useMemo(\n () => resolveFormId(context, resourceKind, resourceId),\n [context, resourceId, resourceKind],\n )\n const [, forceRender] = React.useReducer((value) => value + 1, 0)\n const state = getRecordLockFormState(formId)\n const [mounted, setMounted] = React.useState(false)\n const [showIncomingChangesRequested, setShowIncomingChangesRequested] = React.useState(false)\n const [showLockContentionBanner, setShowLockContentionBanner] = React.useState(false)\n const [isConflictDialogOpen, setIsConflictDialogOpen] = React.useState(false)\n const instanceId = React.useMemo(\n () =>\n `record-lock-widget:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`,\n [],\n )\n const ownerPriority = context.formId ? 2 : 1\n const ownerKey = resourceKind && resourceId ? `${resourceKind}:${resourceId}` : null\n const [isPrimaryInstance, setIsPrimaryInstance] = React.useState(true)\n const releasePayloadRef = React.useRef<{\n resourceKind: string\n resourceId: string\n token: string\n } | null>(null)\n const keepMineRetryVersionRef = React.useRef(0)\n\n React.useEffect(() => {\n if (!ownerKey) {\n setIsPrimaryInstance(true)\n return\n }\n\n const owners = getRecordLockOwnerMap()\n const notifyOwnersChanged = () => {\n if (typeof window === 'undefined') return\n window.dispatchEvent(\n new CustomEvent('om:record-lock-owner-changed', {\n detail: { ownerKey },\n }),\n )\n }\n\n const claimOwnership = () => {\n const current = owners.get(ownerKey)\n if (!current) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n if (current.instanceId === instanceId) {\n setIsPrimaryInstance(true)\n return\n }\n if (ownerPriority > current.priority) {\n owners.set(ownerKey, { instanceId, priority: ownerPriority })\n setIsPrimaryInstance(true)\n notifyOwnersChanged()\n return\n }\n setIsPrimaryInstance(false)\n }\n\n claimOwnership()\n const onOwnersChanged = () => claimOwnership()\n if (typeof window !== 'undefined') {\n window.addEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n\n return () => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('om:record-lock-owner-changed', onOwnersChanged)\n }\n const current = owners.get(ownerKey)\n if (current?.instanceId === instanceId) {\n owners.delete(ownerKey)\n notifyOwnersChanged()\n }\n }\n }, [instanceId, ownerKey, ownerPriority])\n\n React.useEffect(() => {\n if (isPrimaryInstance) return\n const current = getRecordLockFormState(formId)\n if (!current?.lock?.token || !current.resourceKind || !current.resourceId) {\n clearRecordLockFormState(formId)\n return\n }\n void releaseLock({\n resourceKind: current.resourceKind,\n resourceId: current.resourceId,\n token: current.lock.token,\n reason: 'cancelled',\n }).catch((error) => {\n logger.warn('Failed to release lock while demoting owner', { err: error })\n })\n clearRecordLockFormState(formId)\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n React.useEffect(() => {\n const showIncomingChanges = searchParams?.get('showIncomingChanges') === '1'\n const showLockContention = searchParams?.get('showLockContention') === '1'\n\n if (showIncomingChanges) {\n setShowIncomingChangesRequested(true)\n clearIncomingChangesQueryFlag()\n }\n\n if (showLockContention) {\n setShowLockContentionBanner(true)\n clearLockContentionQueryFlag()\n }\n }, [searchParams])\n\n React.useEffect(() => subscribeRecordLockFormState(formId, () => forceRender()), [formId])\n\n React.useEffect(() => {\n if (!state?.conflict) {\n setIsConflictDialogOpen(false)\n return\n }\n setIsConflictDialogOpen(true)\n }, [\n state?.conflict?.id,\n state?.conflict?.incomingActionLogId,\n state?.conflict?.baseActionLogId,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n setRecordLockFormState(formId, { formId, resourceKind, resourceId })\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n let active = true\n const acquire = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind, resourceId }),\n })\n const payload = call.result ?? {}\n if (!active) return\n if (!call.ok) {\n const defaultMessage = call.status === 403\n ? t('api.errors.forbidden', 'Forbidden')\n : t('record_locks.errors.acquire_failed', 'Failed to load record lock status.')\n const message = typeof payload.error === 'string' && payload.error.trim().length\n ? payload.error\n : defaultMessage\n flash(message, 'error')\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n setRecordLockFormState(formId, {\n formId,\n resourceKind,\n resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n void acquire()\n return () => {\n active = false\n }\n }, [formId, isPrimaryInstance, resourceId, resourceKind])\n\n const mine = Boolean(state?.lock?.token)\n const participants = React.useMemo(() => {\n if (!state?.lock) return []\n const fromPayload = Array.isArray(state.lock.participants) ? state.lock.participants : []\n if (fromPayload.length) return fromPayload\n return [{\n userId: state.lock.lockedByUserId,\n lockedByName: state.lock.lockedByName,\n lockedByEmail: state.lock.lockedByEmail,\n lockedByIp: state.lock.lockedByIp,\n lockedAt: state.lock.lockedAt,\n lastHeartbeatAt: state.lock.lastHeartbeatAt,\n expiresAt: state.lock.expiresAt,\n }]\n }, [state?.lock])\n const activeParticipantCount = state?.lock?.activeParticipantCount ?? participants.length\n const otherParticipants = React.useMemo(() => {\n if (!state?.currentUserId) return participants\n return participants.filter((participant) => participant.userId !== state.currentUserId)\n }, [participants, state?.currentUserId])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!mine || !state?.lock?.id) return\n\n const onContention = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockContendedEventDetail)\n : null\n if (!detail) return\n if (detail.sourceEntityId !== state.lock?.id) return\n setShowLockContentionBanner(true)\n }\n\n window.addEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_LOCK_CONTENDED_EVENT, onContention)\n }\n }, [isPrimaryInstance, mine, state?.lock?.id])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.resourceKind || !state?.resourceId) return\n if (state.recordDeleted === true) return\n const onRecordDeleted = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordDeletedEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }\n\n window.addEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_RECORD_DELETED_EVENT, onRecordDeleted)\n }\n }, [formId, isPrimaryInstance, state?.recordDeleted, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!state?.lock?.token || !state.resourceKind || !state.resourceId) return\n const heartbeatSeconds = (\n typeof state.heartbeatSeconds === 'number'\n && Number.isFinite(state.heartbeatSeconds)\n && state.heartbeatSeconds > 0\n )\n ? state.heartbeatSeconds\n : 10\n const intervalMs = Math.max(1_000, Math.round(heartbeatSeconds * 1000))\n const interval = window.setInterval(() => {\n void apiCall('/api/record_locks/heartbeat', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock?.token,\n }),\n })\n }, intervalMs)\n return () => window.clearInterval(interval)\n }, [isPrimaryInstance, state?.heartbeatSeconds, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const hasUnresolvedConflict = Boolean(state?.conflict)\n && !(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal'\n )\n if (hasUnresolvedConflict) return\n if (!state?.resourceKind || !state?.resourceId) return\n const refreshPresence = async () => {\n const call = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n const payload = call.result ?? {}\n if (!call.ok) {\n const currentState = getRecordLockFormState(formId)\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? currentState?.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? currentState?.heartbeatSeconds ?? 15,\n latestActionLogId: payload.latestActionLogId ?? currentState?.latestActionLogId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n return\n }\n const currentState = getRecordLockFormState(formId)\n const previousToken = currentState?.lock?.token ?? null\n const nextToken = payload.lock?.token ?? null\n const isSameSession = Boolean(previousToken && nextToken && previousToken === nextToken)\n const nextLatestActionLogId = isSameSession\n ? (currentState?.latestActionLogId ?? null)\n : (payload.latestActionLogId ?? currentState?.latestActionLogId ?? null)\n\n setRecordLockFormState(formId, {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n latestActionLogId: nextLatestActionLogId,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n })\n }\n const onIncomingChanges = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockIncomingChangesEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n setShowIncomingChangesRequested(true)\n void refreshPresence()\n }\n const onForceReleased = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? ((event as CustomEvent<unknown>).detail as RecordLockForceReleasedEventDetail)\n : null\n if (!detail) return\n if (detail.resourceId !== state.resourceId) return\n const kind = readStringOrNull(detail.resourceKind)\n if (kind && kind !== state.resourceKind) return\n void refreshPresence()\n }\n const onBridgeReconnected = (event: Event) => {\n const detail = isObjectRecord((event as CustomEvent<unknown>).detail)\n ? (event as CustomEvent<Record<string, unknown>>).detail\n : null\n if (detail?.id !== 'om:bridge:reconnected') return\n void refreshPresence()\n }\n const onFocus = () => {\n void refreshPresence()\n }\n const onVisibilityChange = () => {\n if (!document.hidden) {\n void refreshPresence()\n }\n }\n\n window.addEventListener(RECORD_LOCKS_INCOMING_CHANGES_EVENT, onIncomingChanges)\n window.addEventListener(RECORD_LOCKS_FORCE_RELEASED_EVENT, onForceReleased)\n window.addEventListener('om:event', onBridgeReconnected)\n window.addEventListener('focus', onFocus)\n document.addEventListener('visibilitychange', onVisibilityChange)\n const heartbeatInterval = window.setInterval(() => {\n void refreshPresence()\n }, 30_000)\n void refreshPresence()\n\n return () => {\n window.removeEventListener(RECORD_LOCKS_INCOMING_CHANGES_EVENT, onIncomingChanges)\n window.removeEventListener(RECORD_LOCKS_FORCE_RELEASED_EVENT, onForceReleased)\n window.removeEventListener('om:event', onBridgeReconnected)\n window.removeEventListener('focus', onFocus)\n document.removeEventListener('visibilitychange', onVisibilityChange)\n window.clearInterval(heartbeatInterval)\n }\n }, [\n formId,\n isPrimaryInstance,\n state?.conflict,\n state?.pendingResolution,\n state?.pendingResolutionArmed,\n state?.resourceId,\n state?.resourceKind,\n ])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!showIncomingChangesRequested) return\n if (!state?.resourceKind || !state?.resourceId || !state?.lock) return\n let cancelled = false\n const openIncomingChangesDialog = async () => {\n clearIncomingChangesQueryFlag()\n setShowIncomingChangesRequested(false)\n await validateBeforeSave({}, context)\n if (cancelled) return\n }\n void openIncomingChangesDialog()\n return () => {\n cancelled = true\n }\n }, [context, isPrimaryInstance, showIncomingChangesRequested, state?.lock, state?.resourceId, state?.resourceKind, t])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (\n state?.resourceKind\n && state?.resourceId\n && typeof state.lock?.token === 'string'\n && state.lock.token.trim().length > 0\n ) {\n releasePayloadRef.current = {\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n token: state.lock.token,\n }\n return\n }\n releasePayloadRef.current = null\n }, [isPrimaryInstance, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onPageHide = () => {\n const payload = releasePayloadRef.current\n if (!payload) return\n releaseLockWithKeepalive({\n ...payload,\n reason: 'unmount',\n })\n }\n window.addEventListener('pagehide', onPageHide)\n return () => {\n window.removeEventListener('pagehide', onPageHide)\n }\n }, [isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n return () => {\n const payload = releasePayloadRef.current\n if (payload) {\n void releaseLock({\n ...payload,\n reason: 'unmount',\n })\n }\n clearRecordLockFormState(formId)\n }\n }, [formId, isPrimaryInstance])\n\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n const onCrudSaveError = (event: Event) => {\n const applyConflictPayload = (payload: {\n conflict: RecordLockUiConflict\n lock?: RecordLockUiView | null\n latestActionLogId?: string | null\n }) => {\n setIsConflictDialogOpen(true)\n const nextPatch: Partial<RecordLockFormState> = {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n }\n if (payload.lock !== undefined) {\n nextPatch.lock = payload.lock\n }\n if (payload.latestActionLogId !== undefined) {\n nextPatch.latestActionLogId = payload.latestActionLogId\n }\n setRecordLockFormState(formId, {\n ...nextPatch,\n })\n }\n\n const buildFallbackConflict = (currentState: RecordLockFormState) => {\n const preferredConflictId = isUuid(currentState.pendingConflictId)\n ? currentState.pendingConflictId\n : isUuid(currentState.conflict?.id)\n ? currentState.conflict.id\n : 'unresolved'\n return ({\n conflict: {\n id: preferredConflictId,\n resourceKind: currentState.resourceKind ?? '',\n resourceId: currentState.resourceId ?? '',\n baseActionLogId: currentState.latestActionLogId ?? null,\n incomingActionLogId: null,\n allowIncomingOverride: false,\n canOverrideIncoming: false,\n resolutionOptions: [],\n changes: [],\n } as RecordLockUiConflict,\n lock: currentState.lock ?? undefined,\n latestActionLogId: currentState.latestActionLogId ?? null,\n })\n }\n\n const detail = (event as CustomEvent<CrudSaveErrorEventDetail>).detail\n if (!detail) return\n const eventContextId = detail.contextId ?? detail.formId\n const currentState = getRecordLockFormState(formId)\n const decision = resolveRecordLockSaveErrorDecision({\n error: detail.error,\n eventContextId,\n formId,\n currentState,\n })\n if (decision.action === 'ignore') return\n if (decision.action === 'record-deleted') {\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n recordDeleted: true,\n acquired: false,\n lock: null,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return\n }\n if (decision.action === 'fallback-dialog') {\n if (currentState) applyConflictPayload(buildFallbackConflict(currentState))\n return\n }\n\n applyConflictPayload(decision.payload)\n }\n\n window.addEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.addEventListener('om:crud-save-error', onCrudSaveError)\n return () => {\n window.removeEventListener(BACKEND_MUTATION_ERROR_EVENT, onCrudSaveError)\n window.removeEventListener('om:crud-save-error', onCrudSaveError)\n }\n }, [formId, isPrimaryInstance])\n\n // Single conflict surface (S3): own the surface for record_lock_conflict 409s\n // routed through `surfaceRecordConflict` (command/raw-write paths that don't\n // emit the crud-save-error event). Opening the dialog here is idempotent with\n // the listener above; returning true tells the core helper to suppress the OSS\n // bar (so we never render both). Decline (false) for a different record so the\n // bar still renders there \u2014 a conflict is never swallowed.\n React.useEffect(() => {\n if (!isPrimaryInstance) return\n if (!resourceKind || !resourceId) return\n const unregister = registerRecordLockConflictHandler((_conflict, error) => {\n const payload = extractRecordLockConflictPayload(error)\n if (!payload) return false\n const payloadKind = payload.conflict.resourceKind?.trim() ?? ''\n const payloadId = payload.conflict.resourceId?.trim() ?? ''\n if (payloadKind && payloadId && (payloadKind !== resourceKind || payloadId !== resourceId)) return false\n setIsConflictDialogOpen(true)\n setRecordLockFormState(formId, {\n conflict: payload.conflict,\n pendingConflictId: payload.conflict.id,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n return true\n })\n return unregister\n }, [formId, isPrimaryInstance, resourceKind, resourceId])\n\n const handleTakeOver = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n if (!state?.resourceKind || !state?.resourceId) return\n const call = await apiCall<AcquireResponse>('/api/record_locks/force-release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state.resourceKind,\n resourceId: state.resourceId,\n }),\n })\n if (!call.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const acquire = await apiCall<AcquireResponse>('/api/record_locks/acquire', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ resourceKind: state.resourceKind, resourceId: state.resourceId }),\n })\n if (!acquire.ok) {\n flash(t('record_locks.errors.force_release_failed', 'Failed to take over editing.'), 'error')\n return\n }\n const payload = acquire.result ?? {}\n setRecordLockFormState(formId, {\n acquired: payload.acquired ?? false,\n lock: payload.lock ?? null,\n currentUserId: payload.currentUserId ?? null,\n allowForceUnlock: payload.allowForceUnlock ?? false,\n latestActionLogId: payload.latestActionLogId ?? null,\n heartbeatSeconds: payload.heartbeatSeconds ?? 15,\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n }, [formId, state?.resourceId, state?.resourceKind, t])\n\n const handleAcceptIncoming = React.useCallback(async () => {\n keepMineRetryVersionRef.current += 1\n await runAcceptIncoming({\n conflict: state?.conflict,\n resourceKind: state?.resourceKind,\n resourceId: state?.resourceId,\n revalidateConflictId: async () => {\n const validation = await validateBeforeSave({}, context)\n return resolveConflictId(validation.conflict)\n },\n releaseIncoming: async (conflictId) => {\n await apiCallOrThrow('/api/record_locks/release', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind: state?.resourceKind,\n resourceId: state?.resourceId,\n token: state?.lock?.token ?? undefined,\n reason: 'conflict_resolved',\n conflictId,\n resolution: 'accept_incoming',\n }),\n })\n },\n clearConflictState: () => {\n setRecordLockFormState(formId, {\n conflict: null,\n pendingConflictId: null,\n pendingResolution: 'normal',\n pendingResolutionArmed: false,\n })\n },\n reload: () => {\n window.location.reload()\n },\n })\n }, [context, formId, state?.conflict, state?.lock?.token, state?.resourceId, state?.resourceKind])\n\n const handleKeepMine = React.useCallback(() => {\n if (!state?.conflict) return\n const applyAcceptMine = async () => {\n const keepMineRetryVersion = keepMineRetryVersionRef.current + 1\n keepMineRetryVersionRef.current = keepMineRetryVersion\n let conflictId: string | null = isUuid(state.conflict?.id) ? state.conflict.id : null\n if (!conflictId) {\n const validation = await validateBeforeSave({}, context)\n conflictId = isUuid(validation.conflict?.id) ? validation.conflict.id : null\n }\n if (!conflictId) {\n flash(\n t(\n 'record_locks.conflict.refresh_required',\n 'Could not confirm conflict details. Save again to refresh conflict data.',\n ),\n 'error',\n )\n return\n }\n setRecordLockFormState(formId, {\n pendingResolution: 'accept_mine',\n pendingConflictId: conflictId,\n pendingResolutionArmed: true,\n })\n window.setTimeout(async () => {\n if (keepMineRetryVersionRef.current !== keepMineRetryVersion) return\n const currentState = getRecordLockFormState(formId)\n if (currentState?.pendingResolution !== 'accept_mine') return\n const submitted = submitCrudForm(formId)\n if (submitted) return\n const retried = await Promise.resolve(context.retryLastMutation?.()).catch(() => false)\n if (!retried) {\n flash(\n t(\n 'record_locks.conflict.retry_save_after_accept_mine',\n 'Click save again to apply your changes.',\n ),\n 'info',\n )\n }\n }, 0)\n }\n void applyAcceptMine()\n }, [context, context.retryLastMutation, formId, state?.conflict, t])\n\n const handleKeepEditing = React.useCallback(() => {\n keepMineRetryVersionRef.current += 1\n setIsConflictDialogOpen(false)\n }, [])\n\n const noneLabel = t('audit_logs.common.none')\n const conflictChangeRows = React.useMemo<ChangeRow[]>(\n () => (state?.conflict?.changes ?? []).map((change) => ({\n field: change.field,\n from: change.incomingValue,\n to: change.mineValue,\n })),\n [state?.conflict?.changes],\n )\n const canKeepMyChanges = Boolean(\n state?.conflict?.allowIncomingOverride\n && state?.conflict?.canOverrideIncoming === true\n && state?.conflict?.resolutionOptions?.includes('accept_mine'),\n )\n const isRecordDeleted = state?.recordDeleted === true\n const showOverrideBlockedNotice = Boolean(\n state?.conflict?.allowIncomingOverride\n && !state?.conflict?.canOverrideIncoming,\n )\n const conflictDialog = (\n <Dialog open={Boolean(state?.conflict || isRecordDeleted) && isConflictDialogOpen} onOpenChange={(open) => {\n if (open) {\n setIsConflictDialogOpen(true)\n return\n }\n if (isRecordDeleted) {\n setIsConflictDialogOpen(true)\n return\n }\n setIsConflictDialogOpen(false)\n }}>\n <DialogContent data-testid=\"record-lock-conflict-dialog\">\n <DialogHeader>\n <DialogTitle>\n {isRecordDeleted\n ? t('record_locks.conflict.record_deleted_title', 'Record was deleted')\n : t('record_locks.conflict.title', 'Conflict detected')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-3 text-sm\">\n <p className=\"text-muted-foreground\">\n {isRecordDeleted\n ? t(\n 'record_locks.conflict.record_deleted_description',\n 'This record was deleted by another user while you were editing. Saving is blocked to avoid unexpected results.',\n )\n : t('record_locks.conflict.description', 'The record was changed by another user after you started editing.')}\n </p>\n {!isRecordDeleted ? (\n <ChangedFieldsTable\n changeRows={conflictChangeRows}\n noneLabel={noneLabel}\n t={t}\n beforeLabel={t('record_locks.conflict.incoming_label', 'Incoming')}\n afterLabel={t('record_locks.conflict.current_label', 'Current')}\n />\n ) : null}\n {(state?.conflict?.changes?.length ?? 0) === 0 ? (\n !isRecordDeleted ? (\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'record_locks.conflict.no_field_details',\n 'Field-level conflict details are unavailable for this record. Choose a resolution to continue.'\n )}\n </AlertDescription>\n </Alert>\n ) : null\n ) : null}\n {showOverrideBlockedNotice ? (\n <Alert status=\"warning\">\n <AlertDescription>\n {t(\n 'record_locks.conflict.override_blocked_notice',\n 'You cannot keep your version because you do not have permission to override incoming changes.',\n )}\n </AlertDescription>\n </Alert>\n ) : null}\n <div className=\"-mx-6 -mb-6 mt-4 border-t bg-background/95 px-6 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80\">\n <div className=\"flex flex-wrap justify-end gap-2\">\n {isRecordDeleted ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleAcceptIncoming()\n }}\n >\n {t('record_locks.conflict.accept_incoming', 'Accept incoming')}\n </Button>\n {canKeepMyChanges ? (\n <Button\n type=\"button\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n void handleKeepMine()\n }}\n >\n {t('record_locks.conflict.accept_mine', 'Keep my changes')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n handleKeepEditing()\n }}\n >\n {t('record_locks.conflict.keep_editing', 'Keep editing')}\n </Button>\n </>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n\n const participantEmails = React.useMemo(() => {\n return otherParticipants\n .map((participant) => participant.lockedByEmail?.trim() ?? '')\n .filter((email, index, all) => email.length > 0 && all.indexOf(email) === index)\n .slice(0, 4)\n }, [otherParticipants])\n\n React.useEffect(() => {\n if (!showLockContentionBanner) return\n if (!mine) return\n if (activeParticipantCount > 1) return\n setShowLockContentionBanner(false)\n }, [activeParticipantCount, mine, showLockContentionBanner])\n\n const topBannerTarget = mounted ? document.getElementById('om-top-banners') : null\n\n if (!isPrimaryInstance) return null\n if (!state?.lock) return conflictDialog\n\n const defaultPresenceMessage = activeParticipantCount > 1\n ? `${activeParticipantCount} users are currently on this record.`\n : 'This record is currently locked by another user.'\n const bannerMessageRaw = !mine\n ? t('record_locks.banner.locked_by_other', 'This record is currently locked by another user.')\n : showLockContentionBanner\n ? t('record_locks.banner.contention_notice', 'Another user opened this record while you are editing it. Conflicts may occur on save.')\n : t('record_locks.banner.participants_notice', 'Multiple users are currently on this record.')\n const bannerMessage = typeof bannerMessageRaw === 'string' && bannerMessageRaw.trim().length > 0\n ? bannerMessageRaw\n : defaultPresenceMessage\n const shouldShowPresenceBanner = Boolean(\n showLockContentionBanner\n || activeParticipantCount > 1\n || !mine,\n )\n\n const lockBanner = shouldShowPresenceBanner ? (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900 shadow-sm\">\n <div className=\"font-medium\">\n {bannerMessage}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-amber-900/90\">\n <span>{`${t('record_locks.banner.active_users_label', 'Active users')}: ${activeParticipantCount}`}</span>\n {participantEmails.map((email) => (\n <span\n key={email}\n className=\"inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-overline font-medium text-amber-900\"\n >\n <Mail className=\"h-3 w-3\" />\n <span>{email}</span>\n </span>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {state.allowForceUnlock && !mine ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={handleTakeOver}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('record_locks.banner.take_over', 'Take over editing')}\n </Button>\n ) : null}\n {showLockContentionBanner ? (\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setShowLockContentionBanner(false)}\n className=\"border-amber-300 bg-amber-50 text-amber-900 hover:bg-amber-100 hover:text-amber-900\"\n >\n {t('common.close', 'Close')}\n </Button>\n </div>\n ) : null}\n </div>\n </div>\n ) : null\n\n return (\n <>\n {lockBanner ? (topBannerTarget ? createPortal(lockBanner, topBannerTarget) : lockBanner) : null}\n {conflictDialog}\n </>\n )\n}\n\nexport async function validateBeforeSave(\n data: Record<string, unknown>,\n context: CrudInjectionContext,\n): Promise<ValidateResponse> {\n const contextResourceKind = resolveResourceKind(context)\n const contextResourceId = resolveResourceId(context, data)\n const formId = resolveFormId(context, contextResourceKind, contextResourceId)\n const state = getRecordLockFormState(formId)\n const resourceKind = state?.resourceKind || contextResourceKind\n const resourceId = state?.resourceId || contextResourceId\n if (!resourceKind || !resourceId) {\n return { ok: true }\n }\n const hasSelectedConflictResolution = Boolean(\n state?.pendingResolutionArmed === true\n && typeof state?.pendingResolution === 'string'\n && state.pendingResolution !== 'normal',\n )\n if (state?.conflict && !hasSelectedConflictResolution) {\n return {\n ok: false,\n status: 409,\n code: 'record_lock_conflict',\n lock: state.lock ?? null,\n conflict: state.conflict,\n latestActionLogId: state.latestActionLogId ?? null,\n }\n }\n const hasResolvableConflict = Boolean(state?.conflict?.id && isUuid(state.conflict.id))\n const requestedResolution = state?.pendingResolution ?? 'normal'\n const resolution = requestedResolution !== 'normal' && !hasResolvableConflict\n ? 'normal'\n : requestedResolution\n const rawConflictId = resolution === 'normal' || !hasResolvableConflict\n ? undefined\n : (state?.pendingConflictId ?? state?.conflict?.id ?? undefined)\n const conflictId = isUuid(rawConflictId) ? rawConflictId : undefined\n const call = await apiCall<ValidateResponse>('/api/record_locks/validate', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n resourceKind,\n resourceId,\n method: 'PUT',\n token: state?.lock?.token ?? undefined,\n baseLogId: state?.latestActionLogId ?? state?.lock?.baseActionLogId ?? undefined,\n conflictId,\n resolution,\n mutationPayload: data,\n }),\n })\n const payload = call.result ?? { ok: false }\n if (payload.ok) {\n const nextResolution = resolution === 'normal' ? 'normal' : resolution\n const preserveConflictUntilSuccessfulSave = nextResolution !== 'normal'\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,\n lock: resolveLockAfterValidate(state?.lock, payload.lock),\n conflict: preserveConflictUntilSuccessfulSave ? (state?.conflict ?? null) : null,\n pendingConflictId: nextResolution === 'normal' ? null : (conflictId ?? state?.pendingConflictId ?? null),\n pendingResolution: nextResolution,\n pendingResolutionArmed: nextResolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n }\n setRecordLockFormState(formId, {\n resourceKind,\n resourceId,\n lock: resolveLockAfterValidate(state?.lock, payload.lock),\n conflict: payload.conflict ?? state?.conflict ?? null,\n pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,\n pendingResolution: resolution === 'normal' ? 'normal' : resolution,\n pendingResolutionArmed: resolution === 'normal' ? false : Boolean(state?.pendingResolutionArmed),\n })\n return payload\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["currentState"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.8-develop.6998.1.d2bc46c56c",
3
+ "version": "0.6.8-develop.7001.1.03608ab252",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -65,8 +65,8 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@open-mercato/core": "0.6.8-develop.6998.1.d2bc46c56c",
69
- "@open-mercato/ui": "0.6.8-develop.6998.1.d2bc46c56c",
68
+ "@open-mercato/core": "0.6.8-develop.7001.1.03608ab252",
69
+ "@open-mercato/ui": "0.6.8-develop.7001.1.03608ab252",
70
70
  "@simplewebauthn/browser": "^13.3.0",
71
71
  "@simplewebauthn/server": "^13.3.2",
72
72
  "@simplewebauthn/types": "^12.0.0",
@@ -76,12 +76,12 @@
76
76
  "qrcode": "^1.5.4"
77
77
  },
78
78
  "peerDependencies": {
79
- "@open-mercato/shared": "0.6.8-develop.6998.1.d2bc46c56c",
79
+ "@open-mercato/shared": "0.6.8-develop.7001.1.03608ab252",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.6.8-develop.6998.1.d2bc46c56c",
84
+ "@open-mercato/shared": "0.6.8-develop.7001.1.03608ab252",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.17",
87
87
  "@types/react-dom": "^19.2.3",
@@ -367,6 +367,33 @@ export function resolveRecordLockSaveErrorDecision(args: {
367
367
  return { action: 'apply-conflict', payload }
368
368
  }
369
369
 
370
+ /**
371
+ * Merge the lock returned by `/api/record_locks/validate` into the widget's
372
+ * current lock state. `/validate` intentionally strips the token from the
373
+ * caller's OWN lock (`toLockView(..., includeToken=false)`), so a naive
374
+ * `payloadLock ?? currentLock` overwrites a good token with `null`, flips
375
+ * `mine` false, and flashes a spurious "locked by another user" banner for the
376
+ * ~1s round-trip of every save (#5289). Preserve the current token when the
377
+ * response describes the same *active* lock id; a genuinely different (foreign)
378
+ * lock, or a same-id lock the server no longer reports as active, keeps its null
379
+ * token so real contention banners and re-acquisition still work.
380
+ */
381
+ export function resolveLockAfterValidate(
382
+ currentLock: RecordLockUiView | null | undefined,
383
+ payloadLock: RecordLockUiView | null | undefined,
384
+ ): RecordLockUiView | null {
385
+ if (!payloadLock) return currentLock ?? null
386
+ if (
387
+ payloadLock.token == null
388
+ && payloadLock.status === 'active'
389
+ && currentLock
390
+ && currentLock.id === payloadLock.id
391
+ ) {
392
+ return { ...payloadLock, token: currentLock.token }
393
+ }
394
+ return payloadLock
395
+ }
396
+
370
397
  function clearIncomingChangesQueryFlag() {
371
398
  if (typeof window === 'undefined') return
372
399
  try {
@@ -1558,7 +1585,7 @@ export async function validateBeforeSave(
1558
1585
  resourceKind,
1559
1586
  resourceId,
1560
1587
  latestActionLogId: payload.latestActionLogId ?? state?.latestActionLogId ?? null,
1561
- lock: payload.lock ?? state?.lock ?? null,
1588
+ lock: resolveLockAfterValidate(state?.lock, payload.lock),
1562
1589
  conflict: preserveConflictUntilSuccessfulSave ? (state?.conflict ?? null) : null,
1563
1590
  pendingConflictId: nextResolution === 'normal' ? null : (conflictId ?? state?.pendingConflictId ?? null),
1564
1591
  pendingResolution: nextResolution,
@@ -1569,7 +1596,7 @@ export async function validateBeforeSave(
1569
1596
  setRecordLockFormState(formId, {
1570
1597
  resourceKind,
1571
1598
  resourceId,
1572
- lock: payload.lock ?? state?.lock ?? null,
1599
+ lock: resolveLockAfterValidate(state?.lock, payload.lock),
1573
1600
  conflict: payload.conflict ?? state?.conflict ?? null,
1574
1601
  pendingConflictId: payload.conflict?.id ?? conflictId ?? state?.pendingConflictId ?? null,
1575
1602
  pendingResolution: resolution === 'normal' ? 'normal' : resolution,