@open-mercato/ui 0.6.6-develop.6551.1.1511bfa890 → 0.6.6

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.
Files changed (29) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/ai/ChatPaneTabs.js +2 -2
  3. package/dist/ai/ChatPaneTabs.js.map +2 -2
  4. package/dist/backend/AppShell.js +2 -2
  5. package/dist/backend/AppShell.js.map +1 -1
  6. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +20 -37
  7. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
  8. package/dist/backend/detail/AttachmentMetadataDialog.js +17 -9
  9. package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
  10. package/dist/backend/filters/AdvancedFilterBuilder.js +1 -1
  11. package/dist/backend/filters/AdvancedFilterBuilder.js.map +1 -1
  12. package/dist/backend/messages/useMessageCompose.js +0 -1
  13. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  14. package/dist/primitives/drawer.js +3 -6
  15. package/dist/primitives/drawer.js.map +2 -2
  16. package/dist/primitives/tabs.js +1 -2
  17. package/dist/primitives/tabs.js.map +2 -2
  18. package/package.json +4 -5
  19. package/src/ai/ChatPaneTabs.tsx +2 -2
  20. package/src/backend/AppShell.tsx +2 -2
  21. package/src/backend/__tests__/DataTable.extensions.test.tsx +10 -26
  22. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +24 -52
  23. package/src/backend/detail/AttachmentMetadataDialog.tsx +23 -16
  24. package/src/backend/filters/AdvancedFilterBuilder.tsx +1 -1
  25. package/src/backend/messages/useMessageCompose.ts +0 -4
  26. package/src/primitives/__tests__/drawer.test.tsx +1 -2
  27. package/src/primitives/drawer.tsx +3 -4
  28. package/src/primitives/tabs.tsx +1 -6
  29. package/src/backend/messages/__tests__/useMessageCompose.test.tsx +0 -60
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/useMessageCompose.ts"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '../FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '../utils/apiCall'\nimport { buildOptimisticLockHeader } from '../utils/optimisticLock'\nimport { surfaceRecordConflict } from '../conflicts'\nimport type {\n AttachmentListResponse,\n MessageComposerProps,\n MessageTypeItem,\n UserListItem,\n} from './message-composer.types'\nimport type { MessagePriority } from './message-priority'\nimport type { TagsInputOption } from '../inputs/TagsInput'\nimport {\n useComposeDraftOperation,\n useComposeSendOperation,\n useForwardSubmitOperation,\n useReplySubmitOperation,\n useSendDraftOperation,\n useUpdateDraftOperation,\n} from './useMessageComposeOperations'\n\nfunction toErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const item of payload) {\n const nested = toErrorMessage(item)\n if (nested) return nested\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n return (\n toErrorMessage(record.error)\n ?? toErrorMessage(record.message)\n ?? toErrorMessage(record.detail)\n ?? toErrorMessage(record.details)\n ?? null\n )\n }\n return null\n}\n\nfunction createTemporaryAttachmentRecordId(): string {\n const randomPart =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n return `messages-composer:${randomPart}`\n}\n\nexport type UseMessageComposeParams = MessageComposerProps\n\nexport type UseMessageComposeResult = {\n t: ReturnType<typeof useT>\n variant: NonNullable<MessageComposerProps['variant']>\n messageId?: string\n open?: boolean\n inline: boolean\n contextPreview: React.ReactNode\n isOpen: boolean\n messageTypes: MessageTypeItem[]\n createableMessageTypes: MessageTypeItem[]\n normalizedRequiredActionMode: 'none' | 'optional' | 'required'\n contextActionOptions: Array<{ id: string; label: string }>\n shouldShowContextActions: boolean\n isComposePublicVisibility: boolean\n attachmentEntityId: string\n attachmentRecordId: string\n recipientIds: string[]\n setRecipientIds: React.Dispatch<React.SetStateAction<string[]>>\n messageType: string\n setMessageType: React.Dispatch<React.SetStateAction<string>>\n subject: string\n setSubject: React.Dispatch<React.SetStateAction<string>>\n body: string\n setBody: React.Dispatch<React.SetStateAction<string>>\n bodyFormat: 'text' | 'markdown'\n setBodyFormat: React.Dispatch<React.SetStateAction<'text' | 'markdown'>>\n priority: MessagePriority\n setPriority: React.Dispatch<React.SetStateAction<MessagePriority>>\n visibility: 'public' | 'internal'\n setVisibility: React.Dispatch<React.SetStateAction<'public' | 'internal'>>\n externalEmail: string\n setExternalEmail: React.Dispatch<React.SetStateAction<string>>\n sendViaEmail: boolean\n setSendViaEmail: React.Dispatch<React.SetStateAction<boolean>>\n contextActionRequired: boolean\n setContextActionRequired: React.Dispatch<React.SetStateAction<boolean>>\n contextActionType: string\n setContextActionType: React.Dispatch<React.SetStateAction<string>>\n replyAll: boolean\n setReplyAll: React.Dispatch<React.SetStateAction<boolean>>\n includeAttachments: boolean\n setIncludeAttachments: React.Dispatch<React.SetStateAction<boolean>>\n submitting: boolean\n submitMode: 'send' | 'draft'\n submitError: string | null\n composerTitle: string\n submitLabel: string\n selectedRecipientOptions: TagsInputOption[]\n resolveRecipientLabel: (id: string) => string\n loadRecipientSuggestions: (query?: string) => Promise<TagsInputOption[]>\n loadAttachmentIds: () => Promise<string[]>\n handleSaveDraft: () => void\n handleBack: () => void\n handleSubmit: ({ saveAsDraft }?: { saveAsDraft?: boolean }) => Promise<boolean>\n handleDialogOpenChange: (nextOpen: boolean) => void\n handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void\n}\n\ntype ForwardPreviewResponse = {\n subject?: string\n body?: string\n}\n\nexport function useMessageCompose({\n variant: variantProp = 'compose',\n messageId,\n open,\n onOpenChange,\n inline = false,\n lockedType = null,\n contextObject = null,\n requiredActionConfig = null,\n contextPreview = null,\n defaultValues,\n expectedUpdatedAt = null,\n onSuccess,\n onCancel,\n}: UseMessageComposeParams): UseMessageComposeResult {\n const t = useT()\n const variant = variantProp\n const isOpen = inline ? true : Boolean(open)\n const recipientSuggestionsCacheRef = React.useRef<TagsInputOption[] | null>(null)\n\n const [recipientIds, setRecipientIds] = React.useState<string[]>([])\n const [recipientMap, setRecipientMap] = React.useState<Record<string, TagsInputOption>>({})\n const [messageType, setMessageType] = React.useState(lockedType ?? 'default')\n const [subject, setSubject] = React.useState('')\n const [body, setBody] = React.useState('')\n const [bodyFormat, setBodyFormat] = React.useState<'text' | 'markdown'>('text')\n const [priority, setPriority] = React.useState<MessagePriority>('normal')\n const [visibility, setVisibility] = React.useState<'public' | 'internal'>('internal')\n const [externalEmail, setExternalEmail] = React.useState('')\n const [attachmentIds, setAttachmentIds] = React.useState<string[]>([])\n const [sendViaEmail, setSendViaEmail] = React.useState(false)\n const [contextActionRequired, setContextActionRequired] = React.useState(false)\n const [contextActionType, setContextActionType] = React.useState('')\n const [replyAll, setReplyAll] = React.useState(false)\n const [includeAttachments, setIncludeAttachments] = React.useState(true)\n const [temporaryAttachmentRecordId, setTemporaryAttachmentRecordId] = React.useState<string>(() =>\n createTemporaryAttachmentRecordId(),\n )\n const [submitting, setSubmitting] = React.useState(false)\n const [submitMode, setSubmitMode] = React.useState<'send' | 'draft'>('send')\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n // Tracks whether the composer is currently in the \"open\" lifecycle so the init\n // effect below only runs on the closed \u2192 open transition, not on every parent\n // re-render that produces a new `defaultValues` / `contextObject` reference\n // while the user is typing. Without this guard, an inline literal\n // `defaultValues={{...}}` in a re-rendering parent (e.g. message detail page\n // with live notification badges or queue progress) would clear the body /\n // subject mid-keystroke. CI shard 9 surfaced this as TC-MSG-009 timing out\n // because `keyboard.type` characters appeared to \"type nowhere\" \u2014 they were\n // typed correctly, then immediately wiped by the next effect run.\n const isOpenRef = React.useRef(false)\n const wasOpenRef = React.useRef(false)\n const submitLockReleaseRef = React.useRef<(() => void) | null>(null)\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types'],\n enabled: variant === 'compose' && isOpen,\n staleTime: 5 * 60 * 1000,\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n const messageTypes = React.useMemo(\n () => messageTypesQuery.data ?? [],\n [messageTypesQuery.data],\n )\n const createableMessageTypes = React.useMemo(\n () => messageTypes.filter((item) => item.isCreateableByUser !== false),\n [messageTypes],\n )\n const normalizedRequiredActionMode = requiredActionConfig?.mode ?? 'none'\n const contextActionOptions = React.useMemo(\n () => (requiredActionConfig?.options ?? []).filter((option) => option.id.trim().length > 0),\n [requiredActionConfig?.options],\n )\n const shouldShowContextActions = (\n variant === 'compose'\n && Boolean(contextObject)\n && normalizedRequiredActionMode !== 'none'\n && contextActionOptions.length > 0\n )\n\n const isComposePublicVisibility = variant === 'compose' && visibility === 'public'\n\n const attachmentEntityId = variant === 'compose' && messageId ? 'messages:message' : 'attachments:library'\n const attachmentRecordId = variant === 'compose' && messageId ? messageId : temporaryAttachmentRecordId\n\n const loadAttachmentIds = React.useCallback(async (): Promise<string[]> => {\n const params = new URLSearchParams()\n params.set('entityId', attachmentEntityId)\n params.set('recordId', attachmentRecordId)\n\n const call = await apiCall<AttachmentListResponse>(`/api/attachments?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadAttachmentOptionsFailed', 'Failed to load attachments.'),\n )\n }\n\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const nextIds = items\n .map((item) => (typeof item?.id === 'string' ? item.id : ''))\n .filter((id) => id.length > 0)\n\n setAttachmentIds(nextIds)\n return nextIds\n }, [attachmentEntityId, attachmentRecordId, t])\n\n React.useEffect(() => {\n if (!isOpen) {\n isOpenRef.current = false\n return\n }\n // Only initialize on the closed \u2192 open transition. Subsequent parent\n // re-renders that change `defaultValues` / `contextObject` references\n // (inline object literals are a new reference on every render) MUST NOT\n // overwrite state the user has typed in.\n if (isOpenRef.current) return\n isOpenRef.current = true\n\n const nextRecipients = defaultValues?.recipients?.filter((value) => typeof value === 'string' && value.trim().length > 0) ?? []\n const dedupedRecipients = Array.from(new Set(nextRecipients))\n\n setRecipientIds(dedupedRecipients)\n setMessageType(lockedType ?? defaultValues?.type ?? 'default')\n setSubject(defaultValues?.subject ?? '')\n setBody(defaultValues?.body ?? '')\n setBodyFormat(defaultValues?.bodyFormat ?? 'text')\n setPriority(defaultValues?.priority ?? 'normal')\n setVisibility(defaultValues?.visibility ?? 'internal')\n setExternalEmail(defaultValues?.externalEmail ?? '')\n setAttachmentIds(\n Array.isArray(defaultValues?.attachmentIds)\n ? defaultValues.attachmentIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : [],\n )\n setSendViaEmail(Boolean(defaultValues?.sendViaEmail))\n if (contextObject) {\n const defaultContextActionType = requiredActionConfig?.defaultActionType?.trim() ?? ''\n const fallbackContextActionType = contextObject.actionType?.trim() ?? ''\n const selectedActionType = defaultContextActionType || fallbackContextActionType\n const selectedActionAllowed = contextActionOptions.some((option) => option.id === selectedActionType)\n const nextActionType = selectedActionAllowed ? selectedActionType : ''\n setContextActionType(nextActionType)\n if (normalizedRequiredActionMode === 'required') {\n setContextActionRequired(true)\n } else if (normalizedRequiredActionMode === 'optional') {\n setContextActionRequired(Boolean(nextActionType) || Boolean(contextObject.actionRequired))\n } else {\n setContextActionRequired(Boolean(contextObject.actionRequired))\n }\n } else {\n setContextActionType('')\n setContextActionRequired(false)\n }\n setReplyAll(Boolean(defaultValues?.replyAll))\n setIncludeAttachments(defaultValues?.includeAttachments !== false)\n setTemporaryAttachmentRecordId(createTemporaryAttachmentRecordId())\n setSubmitError(null)\n }, [\n contextActionOptions,\n contextObject,\n defaultValues,\n isOpen,\n lockedType,\n normalizedRequiredActionMode,\n requiredActionConfig?.defaultActionType,\n ])\n\n React.useEffect(() => {\n if (!isOpen) {\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n wasOpenRef.current = false\n return\n }\n\n const justOpened = isOpen && !wasOpenRef.current\n wasOpenRef.current = isOpen\n if (!justOpened) return\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n setSubmitting(false)\n setSubmitMode('send')\n }, [isOpen])\n\n React.useEffect(() => () => {\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n }, [])\n\n React.useEffect(() => {\n if (!isOpen) return\n if (variant !== 'forward') return\n if (!messageId) return\n\n let isActive = true\n\n void (async () => {\n const call = await apiCall<ForwardPreviewResponse>(`/api/messages/${encodeURIComponent(messageId)}/forward-preview`)\n if (!isActive) return\n\n if (!call.ok) {\n const message = toErrorMessage(call.result)\n ?? t('messages.errors.forwardPreviewFailed', 'Failed to load forward preview.')\n setSubmitError(message)\n flash(message, 'error')\n return\n }\n\n if (typeof call.result?.subject === 'string') {\n setSubject((previousValue) => (previousValue.trim().length > 0 ? previousValue : call.result?.subject ?? ''))\n }\n if (typeof call.result?.body === 'string') {\n setBody((previousValue) => (previousValue.trim().length > 0 ? previousValue : call.result?.body ?? ''))\n }\n setBodyFormat('text')\n })().catch((error) => {\n if (!isActive) return\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.forwardPreviewFailed', 'Failed to load forward preview.')\n setSubmitError(message)\n flash(message, 'error')\n })\n\n return () => {\n isActive = false\n }\n }, [isOpen, messageId, t, variant])\n\n React.useEffect(() => {\n if (!isOpen) return\n if (variant !== 'compose' && variant !== 'reply') return\n void loadAttachmentIds().catch(() => null)\n }, [isOpen, loadAttachmentIds, variant])\n\n React.useEffect(() => {\n if (variant !== 'compose') return\n if (!createableMessageTypes.length) return\n\n if (lockedType) {\n if (createableMessageTypes.some((item) => item.type === lockedType)) {\n setMessageType(lockedType)\n return\n }\n const defaultType = createableMessageTypes.find((item) => item.type === 'default')\n setMessageType(defaultType?.type ?? createableMessageTypes[0]?.type ?? 'default')\n return\n }\n\n if (createableMessageTypes.some((item) => item.type === messageType)) return\n\n const defaultType = createableMessageTypes.find((item) => item.type === 'default')\n setMessageType(defaultType?.type ?? createableMessageTypes[0]?.type ?? 'default')\n }, [createableMessageTypes, lockedType, messageType, variant])\n\n React.useEffect(() => {\n if (variant !== 'compose') return\n if (visibility !== 'public') return\n setSendViaEmail(true)\n setRecipientIds([])\n }, [variant, visibility])\n\n React.useEffect(() => {\n if (isOpen) return\n recipientSuggestionsCacheRef.current = null\n }, [isOpen])\n\n const resolveRecipientLabel = React.useCallback((id: string) => {\n return recipientMap[id]?.label ?? id\n }, [recipientMap])\n\n const selectedRecipientOptions = React.useMemo(() => {\n return recipientIds.map((id) => recipientMap[id] ?? { value: id, label: id })\n }, [recipientIds, recipientMap])\n\n const loadRecipientSuggestions = React.useCallback(async (_query?: string) => {\n const cachedOptions = recipientSuggestionsCacheRef.current\n if (cachedOptions) {\n return cachedOptions\n }\n\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '100')\n // Scope suggestions to the composer's active organization: a message is stamped with\n // that org and its detail endpoint denies cross-org recipients, so a recipient from\n // another org could never open what they were sent.\n params.set('scopeToActiveOrganization', '1')\n // Recipient lookup is filtered in TagsInput because incremental auth user search is unreliable.\n\n const call = await apiCall<{ items?: UserListItem[] }>(\n `/api/auth/users?${params.toString()}`,\n {\n headers: {\n 'x-om-forbidden-redirect': '0',\n },\n },\n ).catch(() => null)\n if (!call) {\n return []\n }\n if (!call.ok) {\n return []\n }\n\n const rawItems = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n const options: TagsInputOption[] = []\n for (const item of rawItems) {\n if (!item || typeof item !== 'object') continue\n const id = typeof item.id === 'string' ? item.id : ''\n if (!id) continue\n\n const email = typeof item.email === 'string' && item.email.trim().length ? item.email.trim() : id\n const name = typeof item.name === 'string' && item.name.trim().length ? item.name.trim() : undefined\n\n options.push({\n value: id,\n label: email,\n description: name,\n })\n }\n\n if (options.length) {\n setRecipientMap((prev) => {\n const next = { ...prev }\n for (const option of options) {\n next[option.value] = option\n }\n return next\n })\n }\n\n recipientSuggestionsCacheRef.current = options\n return options\n }, [])\n\n const handleCancel = React.useCallback(() => {\n if (submitting) return\n if (!inline) {\n onOpenChange?.(false)\n }\n onCancel?.()\n }, [inline, onCancel, onOpenChange, submitting])\n\n const composeSendOperation = useComposeSendOperation({\n t,\n messageType,\n createableMessageTypes,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const composeDraftOperation = useComposeDraftOperation({\n t,\n messageId,\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const replyOperation = useReplySubmitOperation({\n t,\n messageId,\n body,\n bodyFormat,\n replyAll,\n recipientIds,\n sendViaEmail,\n })\n\n const forwardOperation = useForwardSubmitOperation({\n t,\n messageId,\n recipientIds,\n body,\n includeAttachments,\n sendViaEmail,\n })\n\n const sendDraftOperation = useSendDraftOperation({\n t,\n messageId: messageId ?? '',\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const updateDraftOperation = useUpdateDraftOperation({\n t,\n messageId: messageId ?? '',\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const handleSubmit = React.useCallback(async ({ saveAsDraft = false }: { saveAsDraft?: boolean } = {}) => {\n if (submitting) return false\n\n setSubmitError(null)\n\n const isEditingExistingDraft = variant === 'compose' && Boolean(messageId)\n const isComposeDraftSubmit = saveAsDraft && variant === 'compose'\n const operation = isComposeDraftSubmit\n ? (isEditingExistingDraft ? updateDraftOperation : composeDraftOperation)\n : variant === 'compose'\n ? (isEditingExistingDraft ? sendDraftOperation : composeSendOperation)\n : variant === 'reply'\n ? replyOperation\n : forwardOperation\n\n const validationMessage = operation.validate()\n if (validationMessage) {\n setSubmitError(validationMessage)\n flash(validationMessage, 'error')\n return false\n }\n\n setSubmitMode(isComposeDraftSubmit ? 'draft' : 'send')\n setSubmitting(true)\n let keepSubmitLock = false\n let shouldReturnFalse = false\n\n try {\n let nextAttachmentIds = attachmentIds\n if (operation.requiresAttachmentRefresh) {\n try {\n nextAttachmentIds = await loadAttachmentIds()\n } catch (error) {\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.loadAttachmentOptionsFailed', 'Failed to load attachments.')\n setSubmitError(message)\n flash(message, 'error')\n shouldReturnFalse = true\n }\n }\n\n if (!shouldReturnFalse) {\n const { endpoint, method, payload } = operation.buildRequest({ attachmentIds: nextAttachmentIds })\n\n // Editing an existing draft is a PATCH on the message aggregate, so carry\n // the OSS optimistic-lock header to reject a stale overwrite. New\n // compose/reply/forward writes create fresh records and never lock.\n const lockHeaders = isEditingExistingDraft\n ? buildOptimisticLockHeader(expectedUpdatedAt)\n : {}\n\n const call = await withScopedApiRequestHeaders(lockHeaders, () =>\n apiCall<{ id?: string }>(endpoint, {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n )\n\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) {\n setSubmitError(\n t('ui.forms.flash.recordModified', 'This record was modified by someone else. Refresh and try again.'),\n )\n // The shared conflict banner renders at page level, so it is hidden\n // behind the compose modal. Close the dialog to reveal it (with its\n // Refresh action); the stale draft must be reloaded anyway (#3260 QA).\n if (!inline) {\n onOpenChange?.(false)\n }\n } else {\n const message = toErrorMessage(call.result) ?? t('messages.errors.sendFailed', 'Failed to send message.')\n setSubmitError(message)\n flash(message, 'error')\n }\n shouldReturnFalse = true\n } else {\n flash(operation.successMessage, 'success')\n keepSubmitLock = true\n\n onSuccess?.({ id: call.result?.id })\n\n if (!inline) {\n onOpenChange?.(false)\n }\n }\n }\n } catch (error) {\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.sendFailed', 'Failed to send message.')\n setSubmitError(message)\n flash(message, 'error')\n shouldReturnFalse = true\n } finally {\n if (!keepSubmitLock) {\n setSubmitting(false)\n }\n setSubmitMode('send')\n }\n\n if (shouldReturnFalse) {\n return false\n }\n\n if (keepSubmitLock) {\n return await new Promise<boolean>((resolve) => {\n submitLockReleaseRef.current = () => resolve(true)\n })\n }\n\n return true\n }, [\n attachmentIds,\n composeDraftOperation,\n composeSendOperation,\n expectedUpdatedAt,\n forwardOperation,\n inline,\n loadAttachmentIds,\n messageId,\n onOpenChange,\n onSuccess,\n replyOperation,\n sendDraftOperation,\n submitting,\n t,\n updateDraftOperation,\n variant,\n ])\n\n const handleSaveDraft = React.useCallback(() => {\n if (variant !== 'compose') return\n void handleSubmit({ saveAsDraft: true })\n }, [handleSubmit, variant])\n\n const handleBack = React.useCallback(() => {\n if (submitting) return\n handleCancel()\n }, [handleCancel, submitting])\n\n const handleDialogOpenChange = React.useCallback((nextOpen: boolean) => {\n if (nextOpen) {\n onOpenChange?.(true)\n return\n }\n void handleBack()\n }, [handleBack, onOpenChange])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n handleCancel()\n }\n }, [handleCancel, handleSubmit])\n\n const composerTitle = variant === 'reply'\n ? t('messages.reply', 'Reply')\n : variant === 'forward'\n ? t('messages.forward', 'Forward')\n : t('messages.compose', 'Compose message')\n\n const submitLabel = submitting\n ? submitMode === 'draft'\n ? t('messages.savingDraft', 'Saving draft...')\n : t('messages.sending', 'Sending...')\n : variant === 'reply'\n ? t('messages.reply', 'Reply')\n : variant === 'forward'\n ? t('messages.forward', 'Forward')\n : t('messages.send', 'Send')\n\n return {\n t,\n variant,\n messageId,\n open,\n inline,\n contextPreview,\n isOpen,\n messageTypes,\n createableMessageTypes,\n normalizedRequiredActionMode,\n contextActionOptions,\n shouldShowContextActions,\n isComposePublicVisibility,\n attachmentEntityId,\n attachmentRecordId,\n recipientIds,\n setRecipientIds,\n messageType,\n setMessageType,\n subject,\n setSubject,\n body,\n setBody,\n bodyFormat,\n setBodyFormat,\n priority,\n setPriority,\n visibility,\n setVisibility,\n externalEmail,\n setExternalEmail,\n sendViaEmail,\n setSendViaEmail,\n contextActionRequired,\n setContextActionRequired,\n contextActionType,\n setContextActionType,\n replyAll,\n setReplyAll,\n includeAttachments,\n setIncludeAttachments,\n submitting,\n submitMode,\n submitError,\n composerTitle,\n submitLabel,\n selectedRecipientOptions,\n resolveRecipientLabel,\n loadRecipientSuggestions,\n loadAttachmentIds,\n handleSaveDraft,\n handleBack,\n handleSubmit,\n handleDialogOpenChange,\n handleKeyDown,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '../FlashMessages'\nimport { apiCall, withScopedApiRequestHeaders } from '../utils/apiCall'\nimport { buildOptimisticLockHeader } from '../utils/optimisticLock'\nimport { surfaceRecordConflict } from '../conflicts'\nimport type {\n AttachmentListResponse,\n MessageComposerProps,\n MessageTypeItem,\n UserListItem,\n} from './message-composer.types'\nimport type { MessagePriority } from './message-priority'\nimport type { TagsInputOption } from '../inputs/TagsInput'\nimport {\n useComposeDraftOperation,\n useComposeSendOperation,\n useForwardSubmitOperation,\n useReplySubmitOperation,\n useSendDraftOperation,\n useUpdateDraftOperation,\n} from './useMessageComposeOperations'\n\nfunction toErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const item of payload) {\n const nested = toErrorMessage(item)\n if (nested) return nested\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n return (\n toErrorMessage(record.error)\n ?? toErrorMessage(record.message)\n ?? toErrorMessage(record.detail)\n ?? toErrorMessage(record.details)\n ?? null\n )\n }\n return null\n}\n\nfunction createTemporaryAttachmentRecordId(): string {\n const randomPart =\n typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`\n return `messages-composer:${randomPart}`\n}\n\nexport type UseMessageComposeParams = MessageComposerProps\n\nexport type UseMessageComposeResult = {\n t: ReturnType<typeof useT>\n variant: NonNullable<MessageComposerProps['variant']>\n messageId?: string\n open?: boolean\n inline: boolean\n contextPreview: React.ReactNode\n isOpen: boolean\n messageTypes: MessageTypeItem[]\n createableMessageTypes: MessageTypeItem[]\n normalizedRequiredActionMode: 'none' | 'optional' | 'required'\n contextActionOptions: Array<{ id: string; label: string }>\n shouldShowContextActions: boolean\n isComposePublicVisibility: boolean\n attachmentEntityId: string\n attachmentRecordId: string\n recipientIds: string[]\n setRecipientIds: React.Dispatch<React.SetStateAction<string[]>>\n messageType: string\n setMessageType: React.Dispatch<React.SetStateAction<string>>\n subject: string\n setSubject: React.Dispatch<React.SetStateAction<string>>\n body: string\n setBody: React.Dispatch<React.SetStateAction<string>>\n bodyFormat: 'text' | 'markdown'\n setBodyFormat: React.Dispatch<React.SetStateAction<'text' | 'markdown'>>\n priority: MessagePriority\n setPriority: React.Dispatch<React.SetStateAction<MessagePriority>>\n visibility: 'public' | 'internal'\n setVisibility: React.Dispatch<React.SetStateAction<'public' | 'internal'>>\n externalEmail: string\n setExternalEmail: React.Dispatch<React.SetStateAction<string>>\n sendViaEmail: boolean\n setSendViaEmail: React.Dispatch<React.SetStateAction<boolean>>\n contextActionRequired: boolean\n setContextActionRequired: React.Dispatch<React.SetStateAction<boolean>>\n contextActionType: string\n setContextActionType: React.Dispatch<React.SetStateAction<string>>\n replyAll: boolean\n setReplyAll: React.Dispatch<React.SetStateAction<boolean>>\n includeAttachments: boolean\n setIncludeAttachments: React.Dispatch<React.SetStateAction<boolean>>\n submitting: boolean\n submitMode: 'send' | 'draft'\n submitError: string | null\n composerTitle: string\n submitLabel: string\n selectedRecipientOptions: TagsInputOption[]\n resolveRecipientLabel: (id: string) => string\n loadRecipientSuggestions: (query?: string) => Promise<TagsInputOption[]>\n loadAttachmentIds: () => Promise<string[]>\n handleSaveDraft: () => void\n handleBack: () => void\n handleSubmit: ({ saveAsDraft }?: { saveAsDraft?: boolean }) => Promise<boolean>\n handleDialogOpenChange: (nextOpen: boolean) => void\n handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void\n}\n\ntype ForwardPreviewResponse = {\n subject?: string\n body?: string\n}\n\nexport function useMessageCompose({\n variant: variantProp = 'compose',\n messageId,\n open,\n onOpenChange,\n inline = false,\n lockedType = null,\n contextObject = null,\n requiredActionConfig = null,\n contextPreview = null,\n defaultValues,\n expectedUpdatedAt = null,\n onSuccess,\n onCancel,\n}: UseMessageComposeParams): UseMessageComposeResult {\n const t = useT()\n const variant = variantProp\n const isOpen = inline ? true : Boolean(open)\n const recipientSuggestionsCacheRef = React.useRef<TagsInputOption[] | null>(null)\n\n const [recipientIds, setRecipientIds] = React.useState<string[]>([])\n const [recipientMap, setRecipientMap] = React.useState<Record<string, TagsInputOption>>({})\n const [messageType, setMessageType] = React.useState(lockedType ?? 'default')\n const [subject, setSubject] = React.useState('')\n const [body, setBody] = React.useState('')\n const [bodyFormat, setBodyFormat] = React.useState<'text' | 'markdown'>('text')\n const [priority, setPriority] = React.useState<MessagePriority>('normal')\n const [visibility, setVisibility] = React.useState<'public' | 'internal'>('internal')\n const [externalEmail, setExternalEmail] = React.useState('')\n const [attachmentIds, setAttachmentIds] = React.useState<string[]>([])\n const [sendViaEmail, setSendViaEmail] = React.useState(false)\n const [contextActionRequired, setContextActionRequired] = React.useState(false)\n const [contextActionType, setContextActionType] = React.useState('')\n const [replyAll, setReplyAll] = React.useState(false)\n const [includeAttachments, setIncludeAttachments] = React.useState(true)\n const [temporaryAttachmentRecordId, setTemporaryAttachmentRecordId] = React.useState<string>(() =>\n createTemporaryAttachmentRecordId(),\n )\n const [submitting, setSubmitting] = React.useState(false)\n const [submitMode, setSubmitMode] = React.useState<'send' | 'draft'>('send')\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n // Tracks whether the composer is currently in the \"open\" lifecycle so the init\n // effect below only runs on the closed \u2192 open transition, not on every parent\n // re-render that produces a new `defaultValues` / `contextObject` reference\n // while the user is typing. Without this guard, an inline literal\n // `defaultValues={{...}}` in a re-rendering parent (e.g. message detail page\n // with live notification badges or queue progress) would clear the body /\n // subject mid-keystroke. CI shard 9 surfaced this as TC-MSG-009 timing out\n // because `keyboard.type` characters appeared to \"type nowhere\" \u2014 they were\n // typed correctly, then immediately wiped by the next effect run.\n const isOpenRef = React.useRef(false)\n const wasOpenRef = React.useRef(false)\n const submitLockReleaseRef = React.useRef<(() => void) | null>(null)\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types'],\n enabled: variant === 'compose' && isOpen,\n staleTime: 5 * 60 * 1000,\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n const messageTypes = React.useMemo(\n () => messageTypesQuery.data ?? [],\n [messageTypesQuery.data],\n )\n const createableMessageTypes = React.useMemo(\n () => messageTypes.filter((item) => item.isCreateableByUser !== false),\n [messageTypes],\n )\n const normalizedRequiredActionMode = requiredActionConfig?.mode ?? 'none'\n const contextActionOptions = React.useMemo(\n () => (requiredActionConfig?.options ?? []).filter((option) => option.id.trim().length > 0),\n [requiredActionConfig?.options],\n )\n const shouldShowContextActions = (\n variant === 'compose'\n && Boolean(contextObject)\n && normalizedRequiredActionMode !== 'none'\n && contextActionOptions.length > 0\n )\n\n const isComposePublicVisibility = variant === 'compose' && visibility === 'public'\n\n const attachmentEntityId = variant === 'compose' && messageId ? 'messages:message' : 'attachments:library'\n const attachmentRecordId = variant === 'compose' && messageId ? messageId : temporaryAttachmentRecordId\n\n const loadAttachmentIds = React.useCallback(async (): Promise<string[]> => {\n const params = new URLSearchParams()\n params.set('entityId', attachmentEntityId)\n params.set('recordId', attachmentRecordId)\n\n const call = await apiCall<AttachmentListResponse>(`/api/attachments?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadAttachmentOptionsFailed', 'Failed to load attachments.'),\n )\n }\n\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n const nextIds = items\n .map((item) => (typeof item?.id === 'string' ? item.id : ''))\n .filter((id) => id.length > 0)\n\n setAttachmentIds(nextIds)\n return nextIds\n }, [attachmentEntityId, attachmentRecordId, t])\n\n React.useEffect(() => {\n if (!isOpen) {\n isOpenRef.current = false\n return\n }\n // Only initialize on the closed \u2192 open transition. Subsequent parent\n // re-renders that change `defaultValues` / `contextObject` references\n // (inline object literals are a new reference on every render) MUST NOT\n // overwrite state the user has typed in.\n if (isOpenRef.current) return\n isOpenRef.current = true\n\n const nextRecipients = defaultValues?.recipients?.filter((value) => typeof value === 'string' && value.trim().length > 0) ?? []\n const dedupedRecipients = Array.from(new Set(nextRecipients))\n\n setRecipientIds(dedupedRecipients)\n setMessageType(lockedType ?? defaultValues?.type ?? 'default')\n setSubject(defaultValues?.subject ?? '')\n setBody(defaultValues?.body ?? '')\n setBodyFormat(defaultValues?.bodyFormat ?? 'text')\n setPriority(defaultValues?.priority ?? 'normal')\n setVisibility(defaultValues?.visibility ?? 'internal')\n setExternalEmail(defaultValues?.externalEmail ?? '')\n setAttachmentIds(\n Array.isArray(defaultValues?.attachmentIds)\n ? defaultValues.attachmentIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : [],\n )\n setSendViaEmail(Boolean(defaultValues?.sendViaEmail))\n if (contextObject) {\n const defaultContextActionType = requiredActionConfig?.defaultActionType?.trim() ?? ''\n const fallbackContextActionType = contextObject.actionType?.trim() ?? ''\n const selectedActionType = defaultContextActionType || fallbackContextActionType\n const selectedActionAllowed = contextActionOptions.some((option) => option.id === selectedActionType)\n const nextActionType = selectedActionAllowed ? selectedActionType : ''\n setContextActionType(nextActionType)\n if (normalizedRequiredActionMode === 'required') {\n setContextActionRequired(true)\n } else if (normalizedRequiredActionMode === 'optional') {\n setContextActionRequired(Boolean(nextActionType) || Boolean(contextObject.actionRequired))\n } else {\n setContextActionRequired(Boolean(contextObject.actionRequired))\n }\n } else {\n setContextActionType('')\n setContextActionRequired(false)\n }\n setReplyAll(Boolean(defaultValues?.replyAll))\n setIncludeAttachments(defaultValues?.includeAttachments !== false)\n setTemporaryAttachmentRecordId(createTemporaryAttachmentRecordId())\n setSubmitError(null)\n }, [\n contextActionOptions,\n contextObject,\n defaultValues,\n isOpen,\n lockedType,\n normalizedRequiredActionMode,\n requiredActionConfig?.defaultActionType,\n ])\n\n React.useEffect(() => {\n if (!isOpen) {\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n wasOpenRef.current = false\n return\n }\n\n const justOpened = isOpen && !wasOpenRef.current\n wasOpenRef.current = isOpen\n if (!justOpened) return\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n setSubmitting(false)\n setSubmitMode('send')\n }, [isOpen])\n\n React.useEffect(() => () => {\n submitLockReleaseRef.current?.()\n submitLockReleaseRef.current = null\n }, [])\n\n React.useEffect(() => {\n if (!isOpen) return\n if (variant !== 'forward') return\n if (!messageId) return\n\n let isActive = true\n\n void (async () => {\n const call = await apiCall<ForwardPreviewResponse>(`/api/messages/${encodeURIComponent(messageId)}/forward-preview`)\n if (!isActive) return\n\n if (!call.ok) {\n const message = toErrorMessage(call.result)\n ?? t('messages.errors.forwardPreviewFailed', 'Failed to load forward preview.')\n setSubmitError(message)\n flash(message, 'error')\n return\n }\n\n if (typeof call.result?.subject === 'string') {\n setSubject((previousValue) => (previousValue.trim().length > 0 ? previousValue : call.result?.subject ?? ''))\n }\n if (typeof call.result?.body === 'string') {\n setBody((previousValue) => (previousValue.trim().length > 0 ? previousValue : call.result?.body ?? ''))\n }\n setBodyFormat('text')\n })().catch((error) => {\n if (!isActive) return\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.forwardPreviewFailed', 'Failed to load forward preview.')\n setSubmitError(message)\n flash(message, 'error')\n })\n\n return () => {\n isActive = false\n }\n }, [isOpen, messageId, t, variant])\n\n React.useEffect(() => {\n if (!isOpen) return\n if (variant !== 'compose' && variant !== 'reply') return\n void loadAttachmentIds().catch(() => null)\n }, [isOpen, loadAttachmentIds, variant])\n\n React.useEffect(() => {\n if (variant !== 'compose') return\n if (!createableMessageTypes.length) return\n\n if (lockedType) {\n if (createableMessageTypes.some((item) => item.type === lockedType)) {\n setMessageType(lockedType)\n return\n }\n const defaultType = createableMessageTypes.find((item) => item.type === 'default')\n setMessageType(defaultType?.type ?? createableMessageTypes[0]?.type ?? 'default')\n return\n }\n\n if (createableMessageTypes.some((item) => item.type === messageType)) return\n\n const defaultType = createableMessageTypes.find((item) => item.type === 'default')\n setMessageType(defaultType?.type ?? createableMessageTypes[0]?.type ?? 'default')\n }, [createableMessageTypes, lockedType, messageType, variant])\n\n React.useEffect(() => {\n if (variant !== 'compose') return\n if (visibility !== 'public') return\n setSendViaEmail(true)\n setRecipientIds([])\n }, [variant, visibility])\n\n React.useEffect(() => {\n if (isOpen) return\n recipientSuggestionsCacheRef.current = null\n }, [isOpen])\n\n const resolveRecipientLabel = React.useCallback((id: string) => {\n return recipientMap[id]?.label ?? id\n }, [recipientMap])\n\n const selectedRecipientOptions = React.useMemo(() => {\n return recipientIds.map((id) => recipientMap[id] ?? { value: id, label: id })\n }, [recipientIds, recipientMap])\n\n const loadRecipientSuggestions = React.useCallback(async (_query?: string) => {\n const cachedOptions = recipientSuggestionsCacheRef.current\n if (cachedOptions) {\n return cachedOptions\n }\n\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '100')\n // Recipient lookup is filtered in TagsInput because incremental auth user search is unreliable.\n\n const call = await apiCall<{ items?: UserListItem[] }>(\n `/api/auth/users?${params.toString()}`,\n {\n headers: {\n 'x-om-forbidden-redirect': '0',\n },\n },\n ).catch(() => null)\n if (!call) {\n return []\n }\n if (!call.ok) {\n return []\n }\n\n const rawItems = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n const options: TagsInputOption[] = []\n for (const item of rawItems) {\n if (!item || typeof item !== 'object') continue\n const id = typeof item.id === 'string' ? item.id : ''\n if (!id) continue\n\n const email = typeof item.email === 'string' && item.email.trim().length ? item.email.trim() : id\n const name = typeof item.name === 'string' && item.name.trim().length ? item.name.trim() : undefined\n\n options.push({\n value: id,\n label: email,\n description: name,\n })\n }\n\n if (options.length) {\n setRecipientMap((prev) => {\n const next = { ...prev }\n for (const option of options) {\n next[option.value] = option\n }\n return next\n })\n }\n\n recipientSuggestionsCacheRef.current = options\n return options\n }, [])\n\n const handleCancel = React.useCallback(() => {\n if (submitting) return\n if (!inline) {\n onOpenChange?.(false)\n }\n onCancel?.()\n }, [inline, onCancel, onOpenChange, submitting])\n\n const composeSendOperation = useComposeSendOperation({\n t,\n messageType,\n createableMessageTypes,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const composeDraftOperation = useComposeDraftOperation({\n t,\n messageId,\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const replyOperation = useReplySubmitOperation({\n t,\n messageId,\n body,\n bodyFormat,\n replyAll,\n recipientIds,\n sendViaEmail,\n })\n\n const forwardOperation = useForwardSubmitOperation({\n t,\n messageId,\n recipientIds,\n body,\n includeAttachments,\n sendViaEmail,\n })\n\n const sendDraftOperation = useSendDraftOperation({\n t,\n messageId: messageId ?? '',\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const updateDraftOperation = useUpdateDraftOperation({\n t,\n messageId: messageId ?? '',\n messageType,\n priority,\n visibility,\n externalEmail,\n recipientIds,\n subject,\n body,\n bodyFormat,\n sendViaEmail,\n contextObject,\n defaultValues,\n contextActionOptions,\n normalizedRequiredActionMode,\n shouldShowContextActions,\n contextActionRequired,\n contextActionType,\n })\n\n const handleSubmit = React.useCallback(async ({ saveAsDraft = false }: { saveAsDraft?: boolean } = {}) => {\n if (submitting) return false\n\n setSubmitError(null)\n\n const isEditingExistingDraft = variant === 'compose' && Boolean(messageId)\n const isComposeDraftSubmit = saveAsDraft && variant === 'compose'\n const operation = isComposeDraftSubmit\n ? (isEditingExistingDraft ? updateDraftOperation : composeDraftOperation)\n : variant === 'compose'\n ? (isEditingExistingDraft ? sendDraftOperation : composeSendOperation)\n : variant === 'reply'\n ? replyOperation\n : forwardOperation\n\n const validationMessage = operation.validate()\n if (validationMessage) {\n setSubmitError(validationMessage)\n flash(validationMessage, 'error')\n return false\n }\n\n setSubmitMode(isComposeDraftSubmit ? 'draft' : 'send')\n setSubmitting(true)\n let keepSubmitLock = false\n let shouldReturnFalse = false\n\n try {\n let nextAttachmentIds = attachmentIds\n if (operation.requiresAttachmentRefresh) {\n try {\n nextAttachmentIds = await loadAttachmentIds()\n } catch (error) {\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.loadAttachmentOptionsFailed', 'Failed to load attachments.')\n setSubmitError(message)\n flash(message, 'error')\n shouldReturnFalse = true\n }\n }\n\n if (!shouldReturnFalse) {\n const { endpoint, method, payload } = operation.buildRequest({ attachmentIds: nextAttachmentIds })\n\n // Editing an existing draft is a PATCH on the message aggregate, so carry\n // the OSS optimistic-lock header to reject a stale overwrite. New\n // compose/reply/forward writes create fresh records and never lock.\n const lockHeaders = isEditingExistingDraft\n ? buildOptimisticLockHeader(expectedUpdatedAt)\n : {}\n\n const call = await withScopedApiRequestHeaders(lockHeaders, () =>\n apiCall<{ id?: string }>(endpoint, {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n )\n\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) {\n setSubmitError(\n t('ui.forms.flash.recordModified', 'This record was modified by someone else. Refresh and try again.'),\n )\n // The shared conflict banner renders at page level, so it is hidden\n // behind the compose modal. Close the dialog to reveal it (with its\n // Refresh action); the stale draft must be reloaded anyway (#3260 QA).\n if (!inline) {\n onOpenChange?.(false)\n }\n } else {\n const message = toErrorMessage(call.result) ?? t('messages.errors.sendFailed', 'Failed to send message.')\n setSubmitError(message)\n flash(message, 'error')\n }\n shouldReturnFalse = true\n } else {\n flash(operation.successMessage, 'success')\n keepSubmitLock = true\n\n onSuccess?.({ id: call.result?.id })\n\n if (!inline) {\n onOpenChange?.(false)\n }\n }\n }\n } catch (error) {\n const message = error instanceof Error\n ? error.message\n : t('messages.errors.sendFailed', 'Failed to send message.')\n setSubmitError(message)\n flash(message, 'error')\n shouldReturnFalse = true\n } finally {\n if (!keepSubmitLock) {\n setSubmitting(false)\n }\n setSubmitMode('send')\n }\n\n if (shouldReturnFalse) {\n return false\n }\n\n if (keepSubmitLock) {\n return await new Promise<boolean>((resolve) => {\n submitLockReleaseRef.current = () => resolve(true)\n })\n }\n\n return true\n }, [\n attachmentIds,\n composeDraftOperation,\n composeSendOperation,\n expectedUpdatedAt,\n forwardOperation,\n inline,\n loadAttachmentIds,\n messageId,\n onOpenChange,\n onSuccess,\n replyOperation,\n sendDraftOperation,\n submitting,\n t,\n updateDraftOperation,\n variant,\n ])\n\n const handleSaveDraft = React.useCallback(() => {\n if (variant !== 'compose') return\n void handleSubmit({ saveAsDraft: true })\n }, [handleSubmit, variant])\n\n const handleBack = React.useCallback(() => {\n if (submitting) return\n handleCancel()\n }, [handleCancel, submitting])\n\n const handleDialogOpenChange = React.useCallback((nextOpen: boolean) => {\n if (nextOpen) {\n onOpenChange?.(true)\n return\n }\n void handleBack()\n }, [handleBack, onOpenChange])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n handleCancel()\n }\n }, [handleCancel, handleSubmit])\n\n const composerTitle = variant === 'reply'\n ? t('messages.reply', 'Reply')\n : variant === 'forward'\n ? t('messages.forward', 'Forward')\n : t('messages.compose', 'Compose message')\n\n const submitLabel = submitting\n ? submitMode === 'draft'\n ? t('messages.savingDraft', 'Saving draft...')\n : t('messages.sending', 'Sending...')\n : variant === 'reply'\n ? t('messages.reply', 'Reply')\n : variant === 'forward'\n ? t('messages.forward', 'Forward')\n : t('messages.send', 'Send')\n\n return {\n t,\n variant,\n messageId,\n open,\n inline,\n contextPreview,\n isOpen,\n messageTypes,\n createableMessageTypes,\n normalizedRequiredActionMode,\n contextActionOptions,\n shouldShowContextActions,\n isComposePublicVisibility,\n attachmentEntityId,\n attachmentRecordId,\n recipientIds,\n setRecipientIds,\n messageType,\n setMessageType,\n subject,\n setSubject,\n body,\n setBody,\n bodyFormat,\n setBodyFormat,\n priority,\n setPriority,\n visibility,\n setVisibility,\n externalEmail,\n setExternalEmail,\n sendViaEmail,\n setSendViaEmail,\n contextActionRequired,\n setContextActionRequired,\n contextActionType,\n setContextActionType,\n replyAll,\n setReplyAll,\n includeAttachments,\n setIncludeAttachments,\n submitting,\n submitMode,\n submitError,\n composerTitle,\n submitLabel,\n selectedRecipientOptions,\n resolveRecipientLabel,\n loadRecipientSuggestions,\n loadAttachmentIds,\n handleSaveDraft,\n handleBack,\n handleSubmit,\n handleDialogOpenChange,\n handleKeyDown,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["defaultType"]
7
7
  }
@@ -33,11 +33,8 @@ const drawerContentVariants = cva(
33
33
  {
34
34
  variants: {
35
35
  side: {
36
- right: (
37
- // Figma Drawer [1.1] width: 400px (not the Tailwind max-w-md 448px)
38
- "inset-y-0 right-0 h-full w-full max-w-[400px] rounded-l-xl data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right"
39
- ),
40
- left: "inset-y-0 left-0 h-full w-full max-w-[400px] rounded-r-xl data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
36
+ right: "inset-y-0 right-0 h-full w-full max-w-md rounded-l-xl data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right",
37
+ left: "inset-y-0 left-0 h-full w-full max-w-md rounded-r-xl data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
41
38
  top: "inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top",
42
39
  bottom: "inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom"
43
40
  }
@@ -190,7 +187,7 @@ const DrawerTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
190
187
  {
191
188
  ref,
192
189
  "data-slot": "drawer-title",
193
- className: cn("text-lg font-semibold leading-tight text-foreground", className),
190
+ className: cn("text-base font-semibold leading-tight text-foreground", className),
194
191
  ...props
195
192
  }
196
193
  ));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/drawer.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Side-sheet primitive \u2014 slides in from `right` (default), `left`,\n * `top`, or `bottom`. Distinct from `Dialog`:\n *\n * `Drawer` \u2014 contextual / non-blocking-feeling. Used for detail\n * panes, secondary forms, mobile menus. The user can\n * glance at it while the surrounding page stays visible\n * at a glance behind the overlay.\n * `Dialog` \u2014 modal / focused. Used for confirmations, critical\n * forms, and full-content workflows that should fully\n * replace the page underneath.\n *\n * Built on `@radix-ui/react-dialog` (Radix Dialog under the hood \u2014\n * inherits role=\"dialog\", aria-modal, focus trap, ESC to close,\n * outside-click to dismiss).\n *\n * Figma source: DS Open Mercato `Drawer` page (`486:7366`) \u2014\n * `Drawer Header [1.1]` (`3187:2897`) covers 4 header variants\n * (title only / title + leading icon / title + description /\n * title + description + leading icon badge); `Drawer Footer [1.1]`\n * (`4096:21416`) covers 6 footer variants (50/50 stretched, right-\n * aligned compact, left checkbox + right buttons, left switch +\n * right buttons, left step dots + right buttons, left link button +\n * right buttons). Per the Figma examples the panel has rounded\n * corners on the inner (viewport-facing) edges only, no border on\n * the seam, and no chrome-level dividers between header / body /\n * footer (separators inside the body come from content, not the\n * Drawer).\n *\n * ```tsx\n * const [open, setOpen] = React.useState(false)\n * <Drawer open={open} onOpenChange={setOpen} side=\"right\">\n * <DrawerTrigger asChild>\n * <Button>Open drawer</Button>\n * </DrawerTrigger>\n * <DrawerContent>\n * <DrawerHeader leading={<Clock />}>\n * <DrawerTitle>Edit person</DrawerTitle>\n * <DrawerDescription>Update the person's contact info.</DrawerDescription>\n * </DrawerHeader>\n * <DrawerBody>{children}</DrawerBody>\n * <DrawerFooter layout=\"equal\">\n * <DrawerClose asChild>\n * <Button variant=\"outline\">Cancel</Button>\n * </DrawerClose>\n * <Button>Continue</Button>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\n\nconst Drawer = DialogPrimitive.Root\nconst DrawerTrigger = DialogPrimitive.Trigger\nconst DrawerPortal = DialogPrimitive.Portal\nconst DrawerClose = DialogPrimitive.Close\n\nconst DrawerOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n data-slot=\"drawer-overlay\"\n className={cn(\n 'fixed inset-0 z-overlay bg-foreground/40 backdrop-blur-sm',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',\n className,\n )}\n {...props}\n />\n))\nDrawerOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst drawerContentVariants = cva(\n // Base layout \u2014 fixed position, flex column so DrawerHeader / Body\n // / Footer compose vertically with the body filling free space.\n // Per Figma the panel has rounded corners on the inner (viewport-\n // facing) edges only, no border on the seam, and a generous shadow.\n 'fixed z-popover flex flex-col gap-0 bg-background shadow-2xl outline-none ' +\n 'data-[state=open]:animate-in data-[state=closed]:animate-out ' +\n 'data-[state=open]:duration-200 data-[state=closed]:duration-150',\n {\n variants: {\n side: {\n right:\n // Figma Drawer [1.1] width: 400px (not the Tailwind max-w-md 448px)\n 'inset-y-0 right-0 h-full w-full max-w-[400px] rounded-l-xl ' +\n 'data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',\n left:\n 'inset-y-0 left-0 h-full w-full max-w-[400px] rounded-r-xl ' +\n 'data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',\n top:\n 'inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl ' +\n 'data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',\n bottom:\n 'inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl ' +\n 'data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',\n },\n },\n defaultVariants: { side: 'right' },\n },\n)\n\nexport type DrawerContentProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Content\n> &\n VariantProps<typeof drawerContentVariants> & {\n /** Hide the default top-right close button. */\n hideCloseButton?: boolean\n /** ARIA label for the auto-rendered close button. Default `\"Close\"`. */\n closeAriaLabel?: string\n }\n\nconst DrawerContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n side,\n children,\n hideCloseButton = false,\n closeAriaLabel = 'Close',\n ...props\n },\n ref,\n ) => (\n <DrawerPortal>\n <DrawerOverlay />\n <DialogPrimitive.Content\n ref={ref}\n data-slot=\"drawer-content\"\n data-side={side ?? 'right'}\n className={cn(drawerContentVariants({ side }), className)}\n {...props}\n >\n {children}\n {!hideCloseButton ? (\n <DrawerClose\n data-slot=\"drawer-close-button\"\n aria-label={closeAriaLabel}\n className={cn(\n 'absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors',\n 'hover:bg-muted/40 hover:text-foreground',\n 'focus-visible:shadow-focus',\n )}\n >\n <X aria-hidden=\"true\" className=\"size-4\" />\n </DrawerClose>\n ) : null}\n </DialogPrimitive.Content>\n </DrawerPortal>\n ),\n)\nDrawerContent.displayName = 'DrawerContent'\n\nexport type DrawerHeaderProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Optional leading icon \u2014 rendered inside a size-10 bordered circle\n * badge to the left of the title block. Matches Figma `Drawer Header\n * [1.1]` variants 2 + 4 (icon-prefixed title). Pass a `lucide-react`\n * icon element (size-4 recommended) or any inline node.\n */\n leading?: React.ReactNode\n}\n\nconst DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, leading, children, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-header\"\n className={cn(\n // Per Figma: no chrome border below the header. Padding-right\n // reserves room for the auto-close button at top-right.\n 'flex items-start gap-3 px-6 pt-5 pb-4 pr-14',\n className,\n )}\n {...props}\n >\n {leading ? (\n <span\n data-slot=\"drawer-header-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground\"\n >\n {leading}\n </span>\n ) : null}\n <div data-slot=\"drawer-header-text\" className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children}\n </div>\n </div>\n ),\n)\nDrawerHeader.displayName = 'DrawerHeader'\n\nconst DrawerBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-body\"\n className={cn('flex-1 overflow-y-auto px-6 py-2', className)}\n {...props}\n />\n ),\n)\nDrawerBody.displayName = 'DrawerBody'\n\nexport type DrawerFooterProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Footer button layout per Figma `Drawer Footer [1.1]`:\n * `default` \u2014 right-aligned compact buttons (Cancel + Continue).\n * `equal` \u2014 children stretched to share the row equally (50/50\n * for two children). Matches Figma variant 1.\n * @default 'default'\n */\n layout?: 'default' | 'equal'\n /**\n * Optional left-side slot \u2014 checkbox (\"Don't show again\"), switch\n * (\"Remember me\"), step-indicator dots, link button, or any leading\n * content per Figma footer variants 3\u20136. When provided, children\n * stay right-aligned and the leading slot anchors left.\n * Mutually exclusive with `layout=\"equal\"` (Figma never combines\n * the two \u2014 the 50/50 split is a confirmation-flow shape).\n */\n leading?: React.ReactNode\n}\n\nconst DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, layout = 'default', leading, children, ...props }, ref) => {\n const childArray = React.Children.toArray(children)\n if (layout === 'equal') {\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"equal\"\n className={cn(\n // Per Figma: no chrome top-border separator; equal-stretch\n // children share the row (50/50 for two buttons).\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5 [&>*]:flex-1',\n className,\n )}\n {...props}\n >\n {childArray}\n </div>\n )\n }\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"default\"\n className={cn(\n // Per Figma: no chrome top-border. Optional `leading` anchors\n // left (mr-auto sibling), buttons stay right-aligned.\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5',\n className,\n )}\n {...props}\n >\n {leading ? (\n <div\n data-slot=\"drawer-footer-leading\"\n className=\"mr-auto inline-flex items-center gap-2\"\n >\n {leading}\n </div>\n ) : null}\n {childArray.length > 0 ? (\n <div\n data-slot=\"drawer-footer-trailing\"\n className={cn(\n 'inline-flex items-center gap-3',\n leading ? '' : 'ml-auto',\n )}\n >\n {childArray}\n </div>\n ) : null}\n </div>\n )\n },\n)\nDrawerFooter.displayName = 'DrawerFooter'\n\nconst DrawerTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n data-slot=\"drawer-title\"\n className={cn('text-lg font-semibold leading-tight text-foreground', className)}\n {...props}\n />\n))\nDrawerTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DrawerDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n data-slot=\"drawer-description\"\n className={cn('text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nDrawerDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n Drawer,\n DrawerTrigger,\n DrawerPortal,\n DrawerClose,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerBody,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n drawerContentVariants,\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Side-sheet primitive \u2014 slides in from `right` (default), `left`,\n * `top`, or `bottom`. Distinct from `Dialog`:\n *\n * `Drawer` \u2014 contextual / non-blocking-feeling. Used for detail\n * panes, secondary forms, mobile menus. The user can\n * glance at it while the surrounding page stays visible\n * at a glance behind the overlay.\n * `Dialog` \u2014 modal / focused. Used for confirmations, critical\n * forms, and full-content workflows that should fully\n * replace the page underneath.\n *\n * Built on `@radix-ui/react-dialog` (Radix Dialog under the hood \u2014\n * inherits role=\"dialog\", aria-modal, focus trap, ESC to close,\n * outside-click to dismiss).\n *\n * Figma source: DS Open Mercato `Drawer` page (`486:7366`) \u2014\n * `Drawer Header [1.1]` (`3187:2897`) covers 4 header variants\n * (title only / title + leading icon / title + description /\n * title + description + leading icon badge); `Drawer Footer [1.1]`\n * (`4096:21416`) covers 6 footer variants (50/50 stretched, right-\n * aligned compact, left checkbox + right buttons, left switch +\n * right buttons, left step dots + right buttons, left link button +\n * right buttons). Per the Figma examples the panel has rounded\n * corners on the inner (viewport-facing) edges only, no border on\n * the seam, and no chrome-level dividers between header / body /\n * footer (separators inside the body come from content, not the\n * Drawer).\n *\n * ```tsx\n * const [open, setOpen] = React.useState(false)\n * <Drawer open={open} onOpenChange={setOpen} side=\"right\">\n * <DrawerTrigger asChild>\n * <Button>Open drawer</Button>\n * </DrawerTrigger>\n * <DrawerContent>\n * <DrawerHeader leading={<Clock />}>\n * <DrawerTitle>Edit person</DrawerTitle>\n * <DrawerDescription>Update the person's contact info.</DrawerDescription>\n * </DrawerHeader>\n * <DrawerBody>{children}</DrawerBody>\n * <DrawerFooter layout=\"equal\">\n * <DrawerClose asChild>\n * <Button variant=\"outline\">Cancel</Button>\n * </DrawerClose>\n * <Button>Continue</Button>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\n\nconst Drawer = DialogPrimitive.Root\nconst DrawerTrigger = DialogPrimitive.Trigger\nconst DrawerPortal = DialogPrimitive.Portal\nconst DrawerClose = DialogPrimitive.Close\n\nconst DrawerOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n data-slot=\"drawer-overlay\"\n className={cn(\n 'fixed inset-0 z-overlay bg-foreground/40 backdrop-blur-sm',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',\n className,\n )}\n {...props}\n />\n))\nDrawerOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst drawerContentVariants = cva(\n // Base layout \u2014 fixed position, flex column so DrawerHeader / Body\n // / Footer compose vertically with the body filling free space.\n // Per Figma the panel has rounded corners on the inner (viewport-\n // facing) edges only, no border on the seam, and a generous shadow.\n 'fixed z-popover flex flex-col gap-0 bg-background shadow-2xl outline-none ' +\n 'data-[state=open]:animate-in data-[state=closed]:animate-out ' +\n 'data-[state=open]:duration-200 data-[state=closed]:duration-150',\n {\n variants: {\n side: {\n right:\n 'inset-y-0 right-0 h-full w-full max-w-md rounded-l-xl ' +\n 'data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',\n left:\n 'inset-y-0 left-0 h-full w-full max-w-md rounded-r-xl ' +\n 'data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',\n top:\n 'inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl ' +\n 'data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',\n bottom:\n 'inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl ' +\n 'data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',\n },\n },\n defaultVariants: { side: 'right' },\n },\n)\n\nexport type DrawerContentProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Content\n> &\n VariantProps<typeof drawerContentVariants> & {\n /** Hide the default top-right close button. */\n hideCloseButton?: boolean\n /** ARIA label for the auto-rendered close button. Default `\"Close\"`. */\n closeAriaLabel?: string\n }\n\nconst DrawerContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n side,\n children,\n hideCloseButton = false,\n closeAriaLabel = 'Close',\n ...props\n },\n ref,\n ) => (\n <DrawerPortal>\n <DrawerOverlay />\n <DialogPrimitive.Content\n ref={ref}\n data-slot=\"drawer-content\"\n data-side={side ?? 'right'}\n className={cn(drawerContentVariants({ side }), className)}\n {...props}\n >\n {children}\n {!hideCloseButton ? (\n <DrawerClose\n data-slot=\"drawer-close-button\"\n aria-label={closeAriaLabel}\n className={cn(\n 'absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors',\n 'hover:bg-muted/40 hover:text-foreground',\n 'focus-visible:shadow-focus',\n )}\n >\n <X aria-hidden=\"true\" className=\"size-4\" />\n </DrawerClose>\n ) : null}\n </DialogPrimitive.Content>\n </DrawerPortal>\n ),\n)\nDrawerContent.displayName = 'DrawerContent'\n\nexport type DrawerHeaderProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Optional leading icon \u2014 rendered inside a size-10 bordered circle\n * badge to the left of the title block. Matches Figma `Drawer Header\n * [1.1]` variants 2 + 4 (icon-prefixed title). Pass a `lucide-react`\n * icon element (size-4 recommended) or any inline node.\n */\n leading?: React.ReactNode\n}\n\nconst DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, leading, children, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-header\"\n className={cn(\n // Per Figma: no chrome border below the header. Padding-right\n // reserves room for the auto-close button at top-right.\n 'flex items-start gap-3 px-6 pt-5 pb-4 pr-14',\n className,\n )}\n {...props}\n >\n {leading ? (\n <span\n data-slot=\"drawer-header-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground\"\n >\n {leading}\n </span>\n ) : null}\n <div data-slot=\"drawer-header-text\" className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children}\n </div>\n </div>\n ),\n)\nDrawerHeader.displayName = 'DrawerHeader'\n\nconst DrawerBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-body\"\n className={cn('flex-1 overflow-y-auto px-6 py-2', className)}\n {...props}\n />\n ),\n)\nDrawerBody.displayName = 'DrawerBody'\n\nexport type DrawerFooterProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Footer button layout per Figma `Drawer Footer [1.1]`:\n * `default` \u2014 right-aligned compact buttons (Cancel + Continue).\n * `equal` \u2014 children stretched to share the row equally (50/50\n * for two children). Matches Figma variant 1.\n * @default 'default'\n */\n layout?: 'default' | 'equal'\n /**\n * Optional left-side slot \u2014 checkbox (\"Don't show again\"), switch\n * (\"Remember me\"), step-indicator dots, link button, or any leading\n * content per Figma footer variants 3\u20136. When provided, children\n * stay right-aligned and the leading slot anchors left.\n * Mutually exclusive with `layout=\"equal\"` (Figma never combines\n * the two \u2014 the 50/50 split is a confirmation-flow shape).\n */\n leading?: React.ReactNode\n}\n\nconst DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, layout = 'default', leading, children, ...props }, ref) => {\n const childArray = React.Children.toArray(children)\n if (layout === 'equal') {\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"equal\"\n className={cn(\n // Per Figma: no chrome top-border separator; equal-stretch\n // children share the row (50/50 for two buttons).\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5 [&>*]:flex-1',\n className,\n )}\n {...props}\n >\n {childArray}\n </div>\n )\n }\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"default\"\n className={cn(\n // Per Figma: no chrome top-border. Optional `leading` anchors\n // left (mr-auto sibling), buttons stay right-aligned.\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5',\n className,\n )}\n {...props}\n >\n {leading ? (\n <div\n data-slot=\"drawer-footer-leading\"\n className=\"mr-auto inline-flex items-center gap-2\"\n >\n {leading}\n </div>\n ) : null}\n {childArray.length > 0 ? (\n <div\n data-slot=\"drawer-footer-trailing\"\n className={cn(\n 'inline-flex items-center gap-3',\n leading ? '' : 'ml-auto',\n )}\n >\n {childArray}\n </div>\n ) : null}\n </div>\n )\n },\n)\nDrawerFooter.displayName = 'DrawerFooter'\n\nconst DrawerTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n data-slot=\"drawer-title\"\n className={cn('text-base font-semibold leading-tight text-foreground', className)}\n {...props}\n />\n))\nDrawerTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DrawerDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n data-slot=\"drawer-description\"\n className={cn('text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nDrawerDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n Drawer,\n DrawerTrigger,\n DrawerPortal,\n DrawerClose,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerBody,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n drawerContentVariants,\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -48,7 +48,7 @@ function Tabs({
48
48
  }
49
49
  ) });
50
50
  }
51
- function TabsList({ children, className, "aria-label": ariaLabel }) {
51
+ function TabsList({ children, className }) {
52
52
  const { variant, orientation } = useTabsContext();
53
53
  const baseClasses = variant === "underline" ? (
54
54
  // Underline strip — flat, full-width border-bottom for the rail,
@@ -64,7 +64,6 @@ function TabsList({ children, className, "aria-label": ariaLabel }) {
64
64
  "data-slot": "tabs-list",
65
65
  className: cn(baseClasses, className),
66
66
  role: "tablist",
67
- "aria-label": ariaLabel,
68
67
  "aria-orientation": orientation,
69
68
  children
70
69
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/tabs.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { createContext, useContext } from 'react'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from './button'\n\n/**\n * Tab navigation primitive \u2014 backward compatible with the original\n * pill-style API. Phase B.5 rewrite adds the Figma underline variant\n * (`Tab Menu Horizontal [1.1]` in `553:734`), the vertical orientation\n * (`Tab Menu Vertical [1.1]`), and per-tab `leading` icon + `count`\n * badge slots.\n *\n * Backward compatibility (6 import sites):\n * integrations/[id]/page.tsx\n * scheduler JobLogsModal.tsx\n * checkout LinkTemplateForm.tsx\n * search VectorSearchSection.tsx + FulltextSearchSection.tsx\n * ai-assistant AiPlaygroundPageClient.tsx\n *\n * All six call `<Tabs>` + `<TabsList>` + `<TabsTrigger>` +\n * `<TabsContent>` with the default pill look. The default `variant`\n * stays `pill` so they keep rendering verbatim.\n */\n\ntype TabsContextValue = {\n value: string\n onValueChange: (value: string) => void\n variant: 'pill' | 'underline'\n orientation: 'horizontal' | 'vertical'\n}\n\nexport const TabsContext = createContext<TabsContextValue | undefined>(undefined)\n\nexport function useTabsContext() {\n const context = useContext(TabsContext)\n if (!context) {\n throw new Error('Tabs components must be used within a Tabs provider')\n }\n return context\n}\n\nexport type TabsVariant = 'pill' | 'underline'\nexport type TabsOrientation = 'horizontal' | 'vertical'\n\nexport type TabsProps = {\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Visual style. `pill` is the original look (bg-muted container,\n * bg-background selected pill). `underline` matches Figma `Tab Menu\n * Horizontal [1.1]` \u2014 flat strip with a thin border-bottom and an\n * accent-indigo underline on the active tab. */\n variant?: TabsVariant\n /** Tab strip orientation. `horizontal` lays the strip in a row;\n * `vertical` lays it in a column. The default `pill` variant\n * supports both; the underline variant supports only `horizontal`\n * (per Figma \u2014 vertical underline is not a documented pattern). */\n orientation?: TabsOrientation\n children: React.ReactNode\n className?: string\n}\n\nexport function Tabs({\n value: controlledValue,\n defaultValue,\n onValueChange,\n variant = 'pill',\n orientation = 'horizontal',\n children,\n className,\n}: TabsProps) {\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? '')\n const isControlled = controlledValue !== undefined\n const value = isControlled ? controlledValue : uncontrolledValue\n\n const handleValueChange = React.useCallback(\n (newValue: string) => {\n if (!isControlled) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n },\n [isControlled, onValueChange],\n )\n\n const contextValue = React.useMemo<TabsContextValue>(\n () => ({ value, onValueChange: handleValueChange, variant, orientation }),\n [value, handleValueChange, variant, orientation],\n )\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div\n data-slot=\"tabs\"\n data-variant={variant}\n data-orientation={orientation}\n className={cn(orientation === 'vertical' ? 'flex gap-4' : '', className)}\n >\n {children}\n </div>\n </TabsContext.Provider>\n )\n}\n\nexport type TabsListProps = {\n children: React.ReactNode\n className?: string\n /** Accessible name for the `role=\"tablist\"` element. Set it when the\n * surrounding context does not already label the strip (e.g. a bare\n * detail-page section switcher). */\n 'aria-label'?: string\n}\n\nexport function TabsList({ children, className, 'aria-label': ariaLabel }: TabsListProps) {\n const { variant, orientation } = useTabsContext()\n\n const baseClasses =\n variant === 'underline'\n ? // Underline strip \u2014 flat, full-width border-bottom for the rail,\n // selected trigger gets its own border-b-2 accent below.\n orientation === 'vertical'\n ? 'inline-flex flex-col items-stretch gap-1 border-r border-input pr-3'\n : 'inline-flex h-10 items-center justify-start gap-4 border-b border-input text-muted-foreground'\n : // Pill strip (original) \u2014 rounded container, bg-muted shell.\n orientation === 'vertical'\n ? 'inline-flex flex-col items-stretch gap-1 rounded-lg bg-muted p-1 text-muted-foreground'\n : 'inline-flex h-9 items-center justify-start gap-1 rounded-lg bg-muted p-1 text-muted-foreground'\n\n return (\n <div\n data-slot=\"tabs-list\"\n className={cn(baseClasses, className)}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n >\n {children}\n </div>\n )\n}\n\nexport type TabsTriggerProps = {\n value: string\n children: React.ReactNode\n className?: string\n disabled?: boolean\n /** Optional leading icon slot \u2014 typically a lucide-react icon at\n * `size-4`. Per Figma `Tab Menu Horizontal Items [1.1]`. */\n leading?: React.ReactNode\n /** Optional trailing count badge \u2014 typically a numeric count or\n * \"New\". Rendered as a muted pill that highlights to indigo when\n * the tab is selected. */\n count?: React.ReactNode\n}\n\nexport function TabsTrigger({\n value,\n children,\n className,\n disabled,\n leading,\n count,\n}: TabsTriggerProps) {\n const { value: selectedValue, onValueChange, variant, orientation } = useTabsContext()\n const isSelected = selectedValue === value\n\n if (variant === 'underline') {\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected}\n disabled={disabled}\n onClick={() => onValueChange(value)}\n data-slot=\"tabs-trigger\"\n data-state={isSelected ? 'active' : 'inactive'}\n data-variant=\"underline\"\n className={cn(\n // Underline trigger \u2014 flat, bottom-border accent when active.\n // Negative margin-bottom -1px so the active accent sits on\n // top of the rail's border-bottom (rather than below it).\n orientation === 'vertical'\n ? 'group relative inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors'\n : 'group relative -mb-px inline-flex items-center gap-2 border-b-2 border-transparent px-1 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors',\n 'hover:text-foreground focus-visible:shadow-focus',\n 'disabled:pointer-events-none disabled:opacity-50',\n isSelected\n ? orientation === 'vertical'\n ? 'bg-muted/40 text-foreground'\n : 'border-accent-indigo font-semibold text-foreground'\n : '',\n className,\n )}\n >\n {leading ? (\n <span\n data-slot=\"tabs-trigger-leading\"\n aria-hidden=\"true\"\n className={cn(\n 'inline-flex shrink-0 items-center justify-center',\n isSelected ? 'text-accent-indigo' : 'text-muted-foreground',\n )}\n >\n {leading}\n </span>\n ) : null}\n <span className=\"min-w-0 truncate\">{children}</span>\n {count !== undefined ? (\n <span\n data-slot=\"tabs-trigger-count\"\n className={cn(\n 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',\n isSelected\n ? 'bg-accent-indigo/10 text-accent-indigo'\n : 'bg-muted text-muted-foreground',\n )}\n >\n {count}\n </span>\n ) : null}\n </button>\n )\n }\n\n // Pill variant (original look) \u2014 `<Button variant=\"ghost\" size=\"sm\">`\n // with state classes. Backward compatible.\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={isSelected}\n disabled={disabled}\n onClick={() => onValueChange(value)}\n data-slot=\"tabs-trigger\"\n data-state={isSelected ? 'active' : 'inactive'}\n data-variant=\"pill\"\n className={cn(\n 'gap-2',\n isSelected\n ? 'bg-background text-foreground shadow'\n : 'hover:bg-background/80 hover:text-foreground',\n className,\n )}\n >\n {leading ? (\n <span\n data-slot=\"tabs-trigger-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center\"\n >\n {leading}\n </span>\n ) : null}\n <span>{children}</span>\n {count !== undefined ? (\n <span\n data-slot=\"tabs-trigger-count\"\n className={cn(\n 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',\n isSelected\n ? 'bg-accent-indigo/10 text-accent-indigo'\n : 'bg-background/60 text-muted-foreground',\n )}\n >\n {count}\n </span>\n ) : null}\n </Button>\n )\n}\n\nexport type TabsContentProps = {\n value: string\n children: React.ReactNode\n className?: string\n}\n\nexport function TabsContent({ value, children, className }: TabsContentProps) {\n const { value: selectedValue, orientation } = useTabsContext()\n\n if (selectedValue !== value) {\n return null\n }\n\n return (\n <div\n role=\"tabpanel\"\n data-slot=\"tabs-content\"\n className={cn(\n orientation === 'vertical' ? 'flex-1 min-w-0' : 'mt-2',\n 'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { createContext, useContext } from 'react'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from './button'\n\n/**\n * Tab navigation primitive \u2014 backward compatible with the original\n * pill-style API. Phase B.5 rewrite adds the Figma underline variant\n * (`Tab Menu Horizontal [1.1]` in `553:734`), the vertical orientation\n * (`Tab Menu Vertical [1.1]`), and per-tab `leading` icon + `count`\n * badge slots.\n *\n * Backward compatibility (6 import sites):\n * integrations/[id]/page.tsx\n * scheduler JobLogsModal.tsx\n * checkout LinkTemplateForm.tsx\n * search VectorSearchSection.tsx + FulltextSearchSection.tsx\n * ai-assistant AiPlaygroundPageClient.tsx\n *\n * All six call `<Tabs>` + `<TabsList>` + `<TabsTrigger>` +\n * `<TabsContent>` with the default pill look. The default `variant`\n * stays `pill` so they keep rendering verbatim.\n */\n\ntype TabsContextValue = {\n value: string\n onValueChange: (value: string) => void\n variant: 'pill' | 'underline'\n orientation: 'horizontal' | 'vertical'\n}\n\nexport const TabsContext = createContext<TabsContextValue | undefined>(undefined)\n\nexport function useTabsContext() {\n const context = useContext(TabsContext)\n if (!context) {\n throw new Error('Tabs components must be used within a Tabs provider')\n }\n return context\n}\n\nexport type TabsVariant = 'pill' | 'underline'\nexport type TabsOrientation = 'horizontal' | 'vertical'\n\nexport type TabsProps = {\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Visual style. `pill` is the original look (bg-muted container,\n * bg-background selected pill). `underline` matches Figma `Tab Menu\n * Horizontal [1.1]` \u2014 flat strip with a thin border-bottom and an\n * accent-indigo underline on the active tab. */\n variant?: TabsVariant\n /** Tab strip orientation. `horizontal` lays the strip in a row;\n * `vertical` lays it in a column. The default `pill` variant\n * supports both; the underline variant supports only `horizontal`\n * (per Figma \u2014 vertical underline is not a documented pattern). */\n orientation?: TabsOrientation\n children: React.ReactNode\n className?: string\n}\n\nexport function Tabs({\n value: controlledValue,\n defaultValue,\n onValueChange,\n variant = 'pill',\n orientation = 'horizontal',\n children,\n className,\n}: TabsProps) {\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? '')\n const isControlled = controlledValue !== undefined\n const value = isControlled ? controlledValue : uncontrolledValue\n\n const handleValueChange = React.useCallback(\n (newValue: string) => {\n if (!isControlled) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n },\n [isControlled, onValueChange],\n )\n\n const contextValue = React.useMemo<TabsContextValue>(\n () => ({ value, onValueChange: handleValueChange, variant, orientation }),\n [value, handleValueChange, variant, orientation],\n )\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div\n data-slot=\"tabs\"\n data-variant={variant}\n data-orientation={orientation}\n className={cn(orientation === 'vertical' ? 'flex gap-4' : '', className)}\n >\n {children}\n </div>\n </TabsContext.Provider>\n )\n}\n\nexport type TabsListProps = {\n children: React.ReactNode\n className?: string\n}\n\nexport function TabsList({ children, className }: TabsListProps) {\n const { variant, orientation } = useTabsContext()\n\n const baseClasses =\n variant === 'underline'\n ? // Underline strip \u2014 flat, full-width border-bottom for the rail,\n // selected trigger gets its own border-b-2 accent below.\n orientation === 'vertical'\n ? 'inline-flex flex-col items-stretch gap-1 border-r border-input pr-3'\n : 'inline-flex h-10 items-center justify-start gap-4 border-b border-input text-muted-foreground'\n : // Pill strip (original) \u2014 rounded container, bg-muted shell.\n orientation === 'vertical'\n ? 'inline-flex flex-col items-stretch gap-1 rounded-lg bg-muted p-1 text-muted-foreground'\n : 'inline-flex h-9 items-center justify-start gap-1 rounded-lg bg-muted p-1 text-muted-foreground'\n\n return (\n <div\n data-slot=\"tabs-list\"\n className={cn(baseClasses, className)}\n role=\"tablist\"\n aria-orientation={orientation}\n >\n {children}\n </div>\n )\n}\n\nexport type TabsTriggerProps = {\n value: string\n children: React.ReactNode\n className?: string\n disabled?: boolean\n /** Optional leading icon slot \u2014 typically a lucide-react icon at\n * `size-4`. Per Figma `Tab Menu Horizontal Items [1.1]`. */\n leading?: React.ReactNode\n /** Optional trailing count badge \u2014 typically a numeric count or\n * \"New\". Rendered as a muted pill that highlights to indigo when\n * the tab is selected. */\n count?: React.ReactNode\n}\n\nexport function TabsTrigger({\n value,\n children,\n className,\n disabled,\n leading,\n count,\n}: TabsTriggerProps) {\n const { value: selectedValue, onValueChange, variant, orientation } = useTabsContext()\n const isSelected = selectedValue === value\n\n if (variant === 'underline') {\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected}\n disabled={disabled}\n onClick={() => onValueChange(value)}\n data-slot=\"tabs-trigger\"\n data-state={isSelected ? 'active' : 'inactive'}\n data-variant=\"underline\"\n className={cn(\n // Underline trigger \u2014 flat, bottom-border accent when active.\n // Negative margin-bottom -1px so the active accent sits on\n // top of the rail's border-bottom (rather than below it).\n orientation === 'vertical'\n ? 'group relative inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors'\n : 'group relative -mb-px inline-flex items-center gap-2 border-b-2 border-transparent px-1 py-2 text-sm font-medium text-muted-foreground outline-none transition-colors',\n 'hover:text-foreground focus-visible:shadow-focus',\n 'disabled:pointer-events-none disabled:opacity-50',\n isSelected\n ? orientation === 'vertical'\n ? 'bg-muted/40 text-foreground'\n : 'border-accent-indigo font-semibold text-foreground'\n : '',\n className,\n )}\n >\n {leading ? (\n <span\n data-slot=\"tabs-trigger-leading\"\n aria-hidden=\"true\"\n className={cn(\n 'inline-flex shrink-0 items-center justify-center',\n isSelected ? 'text-accent-indigo' : 'text-muted-foreground',\n )}\n >\n {leading}\n </span>\n ) : null}\n <span className=\"min-w-0 truncate\">{children}</span>\n {count !== undefined ? (\n <span\n data-slot=\"tabs-trigger-count\"\n className={cn(\n 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',\n isSelected\n ? 'bg-accent-indigo/10 text-accent-indigo'\n : 'bg-muted text-muted-foreground',\n )}\n >\n {count}\n </span>\n ) : null}\n </button>\n )\n }\n\n // Pill variant (original look) \u2014 `<Button variant=\"ghost\" size=\"sm\">`\n // with state classes. Backward compatible.\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={isSelected}\n disabled={disabled}\n onClick={() => onValueChange(value)}\n data-slot=\"tabs-trigger\"\n data-state={isSelected ? 'active' : 'inactive'}\n data-variant=\"pill\"\n className={cn(\n 'gap-2',\n isSelected\n ? 'bg-background text-foreground shadow'\n : 'hover:bg-background/80 hover:text-foreground',\n className,\n )}\n >\n {leading ? (\n <span\n data-slot=\"tabs-trigger-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center\"\n >\n {leading}\n </span>\n ) : null}\n <span>{children}</span>\n {count !== undefined ? (\n <span\n data-slot=\"tabs-trigger-count\"\n className={cn(\n 'inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-xs font-medium',\n isSelected\n ? 'bg-accent-indigo/10 text-accent-indigo'\n : 'bg-background/60 text-muted-foreground',\n )}\n >\n {count}\n </span>\n ) : null}\n </Button>\n )\n}\n\nexport type TabsContentProps = {\n value: string\n children: React.ReactNode\n className?: string\n}\n\nexport function TabsContent({ value, children, className }: TabsContentProps) {\n const { value: selectedValue, orientation } = useTabsContext()\n\n if (selectedValue !== value) {\n return null\n }\n\n return (\n <div\n role=\"tabpanel\"\n data-slot=\"tabs-content\"\n className={cn(\n orientation === 'vertical' ? 'flex-1 min-w-0' : 'mt-2',\n 'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n className,\n )}\n >\n {children}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.6-develop.6551.1.1511bfa890",
3
+ "version": "0.6.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,13 +155,13 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.6-develop.6551.1.1511bfa890",
158
+ "@open-mercato/shared": "0.6.6",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
- "@open-mercato/shared": "0.6.6-develop.6551.1.1511bfa890",
164
+ "@open-mercato/shared": "0.6.6",
165
165
  "@testing-library/dom": "^10.4.1",
166
166
  "@testing-library/jest-dom": "^6.9.1",
167
167
  "@testing-library/react": "^16.3.1",
@@ -183,6 +183,5 @@
183
183
  "type": "git",
184
184
  "url": "https://github.com/open-mercato/open-mercato",
185
185
  "directory": "packages/ui"
186
- },
187
- "stableVersion": "0.6.5"
186
+ }
188
187
  }
@@ -97,7 +97,7 @@ export function ChatPaneTabs({ agentId, className }: ChatPaneTabsProps) {
97
97
  className={cn(
98
98
  'group flex max-w-[12rem] shrink-0 items-center gap-1 rounded-t-md border-b-2 px-2 py-1',
99
99
  isActive
100
- ? 'border-accent-indigo bg-background text-foreground'
100
+ ? 'border-primary bg-background text-foreground'
101
101
  : 'border-transparent text-muted-foreground hover:text-foreground',
102
102
  )}
103
103
  >
@@ -126,7 +126,7 @@ export function ChatPaneTabs({ agentId, className }: ChatPaneTabsProps) {
126
126
  onClick={() => sessions.setActiveSession(session.id)}
127
127
  onDoubleClick={() => startRename(session)}
128
128
  title={label}
129
- className="truncate rounded-sm text-xs font-medium outline-none focus-visible:shadow-focus"
129
+ className="truncate text-xs font-medium"
130
130
  >
131
131
  {label}
132
132
  </button>
@@ -1494,13 +1494,13 @@ function AppShellBody({ productName, logo, email, canManageUpgradeActions = fals
1494
1494
  aria-selected={isActive}
1495
1495
  aria-controls="mobile-drawer-tabpanel"
1496
1496
  onClick={() => setMobileDrawerView(tab.id === 'main' ? 'main' : 'auto')}
1497
- className="relative inline-flex items-center pb-2 text-sm font-medium leading-5 tracking-tight transition-colors outline-none focus-visible:shadow-focus data-[active=true]:text-foreground data-[active=false]:text-muted-foreground hover:text-foreground"
1497
+ className="relative inline-flex items-center pb-2 text-sm font-medium leading-5 tracking-tight transition-colors focus:outline-none data-[active=true]:text-foreground data-[active=false]:text-muted-foreground hover:text-foreground"
1498
1498
  data-active={isActive}
1499
1499
  >
1500
1500
  <span>{tab.label}</span>
1501
1501
  {isActive ? (
1502
1502
  <span
1503
- className="absolute -bottom-px left-0 right-0 h-0.5 bg-accent-indigo"
1503
+ className="absolute -bottom-px left-0 right-0 h-0.5 bg-foreground"
1504
1504
  aria-hidden="true"
1505
1505
  />
1506
1506
  ) : null}
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import * as React from 'react'
5
5
  import { renderToString } from 'react-dom/server'
6
- import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
6
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
7
7
  import type { ColumnDef } from '@tanstack/react-table'
8
8
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
9
9
  import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
@@ -21,16 +21,6 @@ const flashMock = jest.fn()
21
21
  jest.mock('../injection/useInjectionDataWidgets', () => ({
22
22
  useInjectionDataWidgets: (spotId: string) => useInjectionDataWidgetsMock(spotId),
23
23
  }))
24
- // InjectionSpot/useInjectionSpotEvents (rendered whenever injectionSpotId is
25
- // set) load spot widgets through the real async registry loader; unmocked it
26
- // resolves at an arbitrary point of the test run, racing waitFor() flushes
27
- // and firing setState outside act().
28
- jest.mock('@open-mercato/shared/modules/widgets/injection-loader', () => ({
29
- getInjectionRegistryVersion: () => 0,
30
- subscribeToInjectionRegistryChanges: () => () => {},
31
- loadInjectionWidgetsForSpot: jest.fn(async () => []),
32
- loadInjectionDataWidgetsForSpot: jest.fn(async () => []),
33
- }))
34
24
  jest.mock('../FlashMessages', () => ({
35
25
  flash: (...args: unknown[]) => flashMock(...args),
36
26
  }))
@@ -307,25 +297,19 @@ describe('DataTable extensions', () => {
307
297
  try {
308
298
  fireEvent.click(screen.getByRole('button', { name: 'Delete all filtered' }))
309
299
 
310
- // onExecute being CALLED is not enough — job-1 is registered as tracked
311
- // in the click handler's continuation after `await onExecute()`. The
312
- // "started" flash is the observable proof that continuation ran; without
313
- // it the completion event below can race the registration and be ignored.
314
300
  await waitFor(() => expect(onExecute).toHaveBeenCalledTimes(1))
315
- await waitFor(() => expect(flashMock).toHaveBeenCalledWith(expect.any(String), 'success'))
301
+ await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()))
316
302
  refreshButtonMock.mockClear()
317
303
  mockRouterRefresh.mockClear()
318
304
 
319
- act(() => {
320
- window.dispatchEvent(new CustomEvent(APP_EVENT_DOM_NAME, {
321
- detail: {
322
- id: 'progress.job.completed',
323
- payload: { jobId: 'job-1' },
324
- timestamp: Date.now(),
325
- organizationId: 'org-1',
326
- },
327
- }))
328
- })
305
+ window.dispatchEvent(new CustomEvent(APP_EVENT_DOM_NAME, {
306
+ detail: {
307
+ id: 'progress.job.completed',
308
+ payload: { jobId: 'job-1' },
309
+ timestamp: Date.now(),
310
+ organizationId: 'org-1',
311
+ },
312
+ }))
329
313
 
330
314
  await waitFor(() => expect(refreshButtonMock).toHaveBeenCalledTimes(1))
331
315
  expect(mockRouterRefresh).not.toHaveBeenCalled()