@open-mercato/ui 0.5.1-develop.2975.ccbadc8198 → 0.5.1-develop.2996.ce62fd491c

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 (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/AppShell.js +274 -697
  3. package/dist/backend/AppShell.js.map +3 -3
  4. package/dist/backend/CrudForm.js +1 -1
  5. package/dist/backend/CrudForm.js.map +2 -2
  6. package/dist/backend/crud/CollapsibleZoneLayout.js +23 -3
  7. package/dist/backend/crud/CollapsibleZoneLayout.js.map +2 -2
  8. package/dist/backend/section-page/SectionNav.js +10 -8
  9. package/dist/backend/section-page/SectionNav.js.map +2 -2
  10. package/dist/backend/section-page/SectionPage.js +2 -2
  11. package/dist/backend/section-page/SectionPage.js.map +2 -2
  12. package/dist/backend/sidebar/SidebarCustomizationEditor.js +1303 -0
  13. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +7 -0
  14. package/dist/backend/sidebar/customization-helpers.js +150 -0
  15. package/dist/backend/sidebar/customization-helpers.js.map +7 -0
  16. package/dist/primitives/switch.js +1 -2
  17. package/dist/primitives/switch.js.map +2 -2
  18. package/jest.setup.ts +13 -0
  19. package/package.json +3 -3
  20. package/src/backend/AppShell.tsx +245 -732
  21. package/src/backend/CrudForm.tsx +1 -1
  22. package/src/backend/__tests__/AppShell.test.tsx +1 -1
  23. package/src/backend/__tests__/CollapsibleZoneLayout.test.tsx +101 -0
  24. package/src/backend/__tests__/CrudForm.navigation.test.tsx +42 -0
  25. package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +200 -0
  26. package/src/backend/crud/CollapsibleZoneLayout.tsx +28 -3
  27. package/src/backend/section-page/SectionNav.tsx +14 -10
  28. package/src/backend/section-page/SectionPage.tsx +15 -10
  29. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +1562 -0
  30. package/src/backend/sidebar/customization-helpers.ts +203 -0
  31. package/src/primitives/switch.tsx +1 -2
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/sidebar/SidebarCustomizationEditor.tsx"],
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { ChevronUp, ChevronDown, GripVertical, RotateCcw, Trash2, Plus, Search, AlertTriangle } from 'lucide-react'\nimport { DndContext, closestCenter, PointerSensor, KeyboardSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'\nimport { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport Image from 'next/image'\nimport { resolveInjectedIcon } from '../injection/resolveInjectedIcon'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { Switch } from '../../primitives/switch'\nimport { Card, CardContent, CardHeader, CardTitle } from '../../primitives/card'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../primitives/dialog'\nimport { Tag } from '../../primitives/tag'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { apiCall } from '../utils/apiCall'\nimport { flash } from '../FlashMessages'\nimport { Page, PageBody } from '../Page'\nimport { useBackendChrome } from '../BackendChromeProvider'\nimport { useConfirmDialog } from '../confirm-dialog'\nimport { useGuardedMutation } from '../injection/useGuardedMutation'\nimport {\n applyCustomizationDraft,\n applyItemOrder,\n cloneSidebarGroups,\n collectSidebarDefaults,\n filterMainSidebarGroups,\n mergeGroupOrder,\n resolveGroupKey,\n resolveItemKey,\n type SidebarCustomizationDraft,\n type SidebarGroup,\n type SidebarItem,\n} from './customization-helpers'\n\nexport type SidebarCustomizationEditorProps = {\n onSaved?: () => void\n onCanceled?: () => void\n variantsApiPath?: string\n preferencesApiPath?: string\n groups?: SidebarGroup[]\n}\n\nconst VARIANTS_API_DEFAULT = '/api/auth/sidebar/variants'\nconst PREFERENCES_API_DEFAULT = '/api/auth/sidebar/preferences'\nconst REFRESH_SIDEBAR_EVENT = 'om:refresh-sidebar'\nconst NEW_VARIANT_KEY = '__new__'\n\n// Surface server-provided error messages directly when present (4xx with `error` field\n// like 409 duplicate-name); fall back to the generic copy + status code for opaque 5xx.\nfunction formatVariantApiError(\n call: { ok: boolean; status: number; result: unknown },\n t: (key: string, fallback?: string) => string,\n): string {\n const detail = (call.result as { error?: unknown } | null)?.error\n if (typeof detail === 'string' && detail.length > 0 && call.status >= 400 && call.status < 500) {\n return detail\n }\n if (typeof detail === 'string' && detail.length > 0) {\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status}: ${detail})`\n }\n return `${t('appShell.sidebarCustomizationSaveError')} (${call.status})`\n}\n\ntype RoleTarget = {\n id: string\n name: string\n hasPreference: boolean\n}\n\ntype VariantSettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder?: Record<string, string[]>\n}\n\ntype Variant = {\n id: string\n name: string\n isActive: boolean\n settings: VariantSettings\n createdAt: string\n updatedAt: string | null\n}\n\ntype VariantListResponse = { locale: string; variants: Variant[] }\ntype VariantSingleResponse = { locale: string; variant: Variant }\n\nfunction findItemByKey(items: SidebarItem[], targetKey: string): SidebarItem | null {\n for (const item of items) {\n if (resolveItemKey(item) === targetKey) return item\n if (item.children && item.children.length > 0) {\n const found = findItemByKey(item.children, targetKey)\n if (found) return found\n }\n }\n return null\n}\n\nfunction collectDescendantKeys(item: SidebarItem): string[] {\n const out: string[] = []\n const walk = (node: SidebarItem) => {\n if (!node.children) return\n for (const child of node.children) {\n out.push(resolveItemKey(child))\n walk(child)\n }\n }\n walk(item)\n return out\n}\n\nfunction parseDraftFromSettings(\n rawSettings: VariantSettings | null | undefined,\n baseSnapshot: SidebarGroup[],\n): SidebarCustomizationDraft {\n const responseOrder = Array.isArray(rawSettings?.groupOrder)\n ? rawSettings.groupOrder\n .map((id) => (typeof id === 'string' ? id.trim() : ''))\n .filter((id) => id.length > 0)\n : []\n const responseGroupLabels: Record<string, string> = {}\n if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.groupLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseGroupLabels[trimmedKey] = value\n }\n }\n const responseItemLabels: Record<string, string> = {}\n if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.itemLabels)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseItemLabels[trimmedKey] = value\n }\n }\n const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems)\n ? rawSettings.hiddenItems\n .map((itemId) => (typeof itemId === 'string' ? itemId.trim() : ''))\n .filter((itemId) => itemId.length > 0)\n : []\n const responseItemOrder: Record<string, string[]> = {}\n if (rawSettings?.itemOrder && typeof rawSettings.itemOrder === 'object') {\n for (const [groupKey, list] of Object.entries(rawSettings.itemOrder)) {\n if (!Array.isArray(list)) continue\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (typeof itemKey !== 'string') continue\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seen.has(trimmedItem)) continue\n seen.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) responseItemOrder[trimmedGroup] = values\n }\n }\n const currentIds = baseSnapshot.map((group) => resolveGroupKey(group))\n const order = mergeGroupOrder(responseOrder, currentIds)\n const { itemDefaults } = collectSidebarDefaults(baseSnapshot)\n const hiddenItemIds: Record<string, boolean> = {}\n for (const itemId of responseHiddenItems) {\n if (!itemDefaults.has(itemId)) continue\n hiddenItemIds[itemId] = true\n }\n return {\n order,\n groupLabels: responseGroupLabels,\n itemLabels: responseItemLabels,\n hiddenItemIds,\n itemOrder: responseItemOrder,\n }\n}\n\nfunction emptyDraftFor(baseSnapshot: SidebarGroup[]): SidebarCustomizationDraft {\n return {\n order: baseSnapshot.map((group) => resolveGroupKey(group)),\n groupLabels: {},\n itemLabels: {},\n hiddenItemIds: {},\n itemOrder: {},\n }\n}\n\nexport function SidebarCustomizationEditor({\n onSaved,\n onCanceled,\n variantsApiPath = VARIANTS_API_DEFAULT,\n preferencesApiPath = PREFERENCES_API_DEFAULT,\n groups: groupsProp,\n}: SidebarCustomizationEditorProps) {\n const t = useT()\n const locale = useLocale()\n const localeLabel = (locale || '').toUpperCase()\n const { payload: chromePayload, isLoading: chromeIsLoading } = useBackendChrome()\n const groupsFromChrome = chromePayload?.groups as SidebarGroup[] | undefined\n const sourceGroups = groupsProp ?? groupsFromChrome ?? []\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [deleting, setDeleting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [variants, setVariants] = React.useState<Variant[]>([])\n const [selectedVariantId, setSelectedVariantId] = React.useState<string | null>(null)\n const [variantName, setVariantName] = React.useState('')\n const [draft, setDraft] = React.useState<SidebarCustomizationDraft | null>(null)\n const [previewGroups, setPreviewGroups] = React.useState<SidebarGroup[]>([])\n const [dirty, setDirty] = React.useState(false)\n const [availableRoleTargets, setAvailableRoleTargets] = React.useState<RoleTarget[]>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)\n const [addDialogOpen, setAddDialogOpen] = React.useState(false)\n const [addDialogName, setAddDialogName] = React.useState('')\n const baseSnapshotRef = React.useRef<SidebarGroup[] | null>(null)\n const hasInitializedRef = React.useRef(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n variantId?: string | null\n operation: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'sidebar-customization',\n blockedMessage: t('appShell.sidebarCustomizationSaveError'),\n })\n\n const buildMutationContext = React.useCallback(\n (operation: string, variantId?: string | null) => ({\n formId: 'sidebar-customization',\n variantId: variantId ?? null,\n operation,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const isNewVariant = selectedVariantId === null\n const selectedVariant = React.useMemo(\n () => (selectedVariantId ? variants.find((v) => v.id === selectedVariantId) ?? null : null),\n [selectedVariantId, variants],\n )\n\n const updateDraft = React.useCallback((updater: (draft: SidebarCustomizationDraft) => SidebarCustomizationDraft) => {\n setDraft((prev) => {\n if (!prev) return prev\n const next = updater(prev)\n if (baseSnapshotRef.current) {\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, next))\n }\n return next\n })\n setDirty(true)\n }, [])\n\n const buildBaseSnapshot = React.useCallback((): SidebarGroup[] => {\n return filterMainSidebarGroups(cloneSidebarGroups(sourceGroups))\n }, [sourceGroups])\n\n const loadVariantsList = React.useCallback(async (): Promise<Variant[]> => {\n // Cache-bust to prevent stale browser/Next caches from masking just-created variants.\n const url = `${variantsApiPath}?_=${Date.now()}`\n const call = await apiCall<VariantListResponse>(url, { cache: 'no-store' })\n if (!call.ok) {\n throw new Error('list-failed')\n }\n return call.result?.variants ?? []\n }, [variantsApiPath])\n\n const loadRolesPayload = React.useCallback(async (): Promise<{ canApplyToRoles: boolean; roles: RoleTarget[] }> => {\n const call = await apiCall<{ canApplyToRoles?: boolean; roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }> }>(preferencesApiPath)\n if (!call.ok) {\n return { canApplyToRoles: false, roles: [] }\n }\n const data = call.result ?? null\n const can = data?.canApplyToRoles === true\n const roles = Array.isArray(data?.roles)\n ? (data!.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>)\n .filter((r) => typeof r?.id === 'string' && typeof r?.name === 'string')\n .map((r) => ({ id: r.id as string, name: r.name as string, hasPreference: r.hasPreference === true }))\n : []\n return { canApplyToRoles: can, roles }\n }, [preferencesApiPath])\n\n const selectVariantInternal = React.useCallback((variant: Variant | null, list: Variant[]) => {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n if (variant) {\n const initialDraft = parseDraftFromSettings(variant.settings, baseSnapshot)\n setSelectedVariantId(variant.id)\n setVariantName(variant.name)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshot)\n setSelectedVariantId(null)\n // Suggest a default name based on the existing variants count.\n const usedNumbers = new Set<number>()\n for (const v of list) {\n if (v.name === 'My preferences') usedNumbers.add(1)\n const match = v.name.match(/^My preferences\\s+(\\d+)$/)\n if (match) usedNumbers.add(Number.parseInt(match[1], 10))\n }\n let next = 1\n while (usedNumbers.has(next)) next += 1\n const suggestion = next === 1 ? 'My preferences' : `My preferences ${next}`\n setVariantName(suggestion)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshot, empty))\n }\n setDirty(false)\n }, [buildBaseSnapshot])\n\n // Initial load. No cancelled flag because React Strict Mode in dev runs effects twice\n // and the cleanup-driven cancellation made the only init pass abort silently \u2014 leaving\n // `loading` true forever and the editor stuck on the \"Loading\u2026\" placeholder. The init\n // gate (`hasInitializedRef`) prevents the second Strict-Mode run from doubling work.\n React.useEffect(() => {\n if (hasInitializedRef.current) return\n if (sourceGroups.length === 0) return\n hasInitializedRef.current = true\n async function init() {\n setLoading(true)\n setError(null)\n try {\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n setVariants(list)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n const active = list.find((v) => v.isActive)\n const initial = active ?? list[0] ?? null\n selectVariantInternal(initial, list)\n setSelectedRoleIds([])\n } catch (err) {\n console.error('Failed to load sidebar variants', err)\n setError(t('appShell.sidebarCustomizationLoadError'))\n } finally {\n setLoading(false)\n }\n }\n void init()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceGroups.length])\n\n const toggleRoleSelection = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => (prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]))\n setDirty(true)\n }, [])\n\n const createNewVariant = React.useCallback(async (proposedName?: string): Promise<boolean> => {\n if (saving || deleting) return false\n if (dirty && selectedVariantId !== null) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return false\n }\n setSaving(true)\n setError(null)\n try {\n const baseSnapshot = baseSnapshotRef.current ?? buildBaseSnapshot()\n baseSnapshotRef.current = baseSnapshot\n const groupOrder = baseSnapshot.map((g) => resolveGroupKey(g))\n const trimmed = (proposedName ?? '').trim()\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n // If name is omitted, server auto-names (\"My preferences\", \"My preferences 2\", \u2026).\n name: trimmed.length > 0 ? trimmed : undefined,\n settings: { groupOrder, groupLabels: {}, itemLabels: {}, hiddenItems: [], itemOrder: {} },\n isActive: true,\n }),\n }),\n context: buildMutationContext('createVariant'),\n mutationPayload: { name: trimmed.length > 0 ? trimmed : null },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return false\n }\n const created = call.result?.variant ?? null\n // Trust POST response as authoritative; refetch in background for any side-effects\n // (e.g. server-side deactivation of previous active variant).\n let nextList: Variant[]\n try {\n nextList = await loadVariantsList()\n } catch {\n nextList = variants\n }\n // Defensive merge: ensure the just-created variant is in the list even if the\n // refetch happened to be served from a stale cache.\n if (created && !nextList.some((v) => v.id === created.id)) {\n nextList = [...nextList, created]\n }\n setVariants(nextList)\n if (created) {\n const fresh = nextList.find((v) => v.id === created.id) ?? created\n selectVariantInternal(fresh, nextList)\n }\n flash(t('appShell.sidebarCustomizationVariantCreated', 'Variant created.'), 'success')\n return true\n } catch (err) {\n console.error('Failed to create sidebar variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n return false\n } finally {\n setSaving(false)\n }\n }, [saving, deleting, dirty, selectedVariantId, confirmDialog, t, buildBaseSnapshot, variantsApiPath, loadVariantsList, selectVariantInternal, variants, runMutation, buildMutationContext])\n\n const handleVariantSwitch = React.useCallback(async (key: string) => {\n if (saving || deleting) return\n if (key === selectedVariantId) return\n if (key === NEW_VARIANT_KEY && isNewVariant) return\n if (dirty) {\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationSwitchConfirmTitle', 'Discard unsaved changes?'),\n text: t('appShell.sidebarCustomizationSwitchConfirmText', 'You have unsaved changes for the current variant. Switching will discard them.'),\n confirmText: t('appShell.sidebarCustomizationSwitchConfirmYes', 'Discard and switch'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n }\n if (key === NEW_VARIANT_KEY) {\n selectVariantInternal(null, variants)\n return\n }\n const next = variants.find((v) => v.id === key) ?? null\n selectVariantInternal(next, variants)\n }, [saving, deleting, selectedVariantId, isNewVariant, dirty, confirmDialog, t, variants, selectVariantInternal])\n\n const moveGroup = React.useCallback((groupId: string, offset: number) => {\n updateDraft((draft) => {\n const order = [...draft.order]\n const index = order.indexOf(groupId)\n if (index === -1) return draft\n const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset))\n if (nextIndex === index) return draft\n order.splice(index, 1)\n order.splice(nextIndex, 0, groupId)\n return { ...draft, order }\n })\n }, [updateDraft])\n\n const dndSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n )\n\n const handleItemDragEnd = React.useCallback((groupKey: string, currentItemKeys: string[]) => (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n const fromId = String(active.id)\n const toId = String(over.id)\n updateDraft((draft) => {\n const baseOrder = draft.itemOrder?.[groupKey]?.length\n ? [...draft.itemOrder[groupKey]]\n : [...currentItemKeys]\n const fromIndex = baseOrder.indexOf(fromId)\n const toIndex = baseOrder.indexOf(toId)\n if (fromIndex === -1 || toIndex === -1) return draft\n const nextOrder = arrayMove(baseOrder, fromIndex, toIndex)\n return {\n ...draft,\n itemOrder: { ...(draft.itemOrder ?? {}), [groupKey]: nextOrder },\n }\n })\n }, [updateDraft])\n\n const setGroupLabel = React.useCallback((groupId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.groupLabels }\n if (value.trim().length === 0) delete next[groupId]\n else next[groupId] = value\n return { ...draft, groupLabels: next }\n })\n }, [updateDraft])\n\n const setItemLabel = React.useCallback((itemId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.itemLabels }\n if (value.trim().length === 0) delete next[itemId]\n else next[itemId] = value\n return { ...draft, itemLabels: next }\n })\n }, [updateDraft])\n\n const setItemHidden = React.useCallback((itemId: string, hidden: boolean) => {\n updateDraft((draft) => {\n const next = { ...draft.hiddenItemIds }\n const apply = (id: string) => {\n if (hidden) next[id] = true\n else delete next[id]\n }\n apply(itemId)\n // Cascade: hiding a parent hides every descendant; showing it reveals them too.\n if (baseSnapshotRef.current) {\n for (const group of baseSnapshotRef.current) {\n const target = findItemByKey(group.items, itemId)\n if (!target) continue\n for (const descendantKey of collectDescendantKeys(target)) apply(descendantKey)\n break\n }\n }\n return { ...draft, hiddenItemIds: next }\n })\n }, [updateDraft])\n\n const reset = React.useCallback(() => {\n if (!baseSnapshotRef.current) return\n if (selectedVariant) {\n const initialDraft = parseDraftFromSettings(selectedVariant.settings, baseSnapshotRef.current)\n setDraft(initialDraft)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, initialDraft))\n } else {\n const empty = emptyDraftFor(baseSnapshotRef.current)\n setDraft(empty)\n setPreviewGroups(applyCustomizationDraft(baseSnapshotRef.current, empty))\n }\n setDirty(false)\n }, [selectedVariant])\n\n const cancel = React.useCallback(() => {\n onCanceled?.()\n }, [onCanceled])\n\n const submitAddDialog = React.useCallback(async () => {\n const ok = await createNewVariant(addDialogName)\n if (ok) {\n setAddDialogOpen(false)\n setAddDialogName('')\n }\n }, [createNewVariant, addDialogName])\n\n const sanitizeSettingsPayload = React.useCallback(() => {\n if (!draft || !baseSnapshotRef.current) return null\n const baseGroups = baseSnapshotRef.current\n const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups)\n const sanitizedGroupLabels: Record<string, string> = {}\n for (const [key, value] of Object.entries(draft.groupLabels)) {\n const trimmed = value.trim()\n const base = groupDefaults.get(key)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedGroupLabels[key] = trimmed\n }\n const sanitizedItemLabels: Record<string, string> = {}\n for (const [itemId, value] of Object.entries(draft.itemLabels)) {\n const trimmed = value.trim()\n const base = itemDefaults.get(itemId)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedItemLabels[itemId] = trimmed\n }\n const sanitizedHiddenItems: string[] = []\n for (const [itemId, hidden] of Object.entries(draft.hiddenItemIds)) {\n if (!hidden) continue\n if (!itemDefaults.has(itemId)) continue\n sanitizedHiddenItems.push(itemId)\n }\n // Build a Set of valid group keys to drop stale itemOrder entries.\n const groupKeys = new Set<string>()\n for (const group of baseGroups) groupKeys.add(resolveGroupKey(group))\n const sanitizedItemOrder: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(draft.itemOrder ?? {})) {\n if (!groupKeys.has(groupKey)) continue\n const seen = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n if (seen.has(itemKey)) continue\n if (!itemDefaults.has(itemKey)) continue\n seen.add(itemKey)\n values.push(itemKey)\n }\n if (values.length > 0) sanitizedItemOrder[groupKey] = values\n }\n return {\n groupOrder: draft.order,\n groupLabels: sanitizedGroupLabels,\n itemLabels: sanitizedItemLabels,\n hiddenItems: sanitizedHiddenItems,\n itemOrder: sanitizedItemOrder,\n }\n }, [draft])\n\n const save = React.useCallback(async () => {\n const settings = sanitizeSettingsPayload()\n if (!settings) return\n setSaving(true)\n setError(null)\n try {\n const trimmedName = variantName.trim()\n const isCurrentlyActive = selectedVariant?.isActive ?? false\n let savedVariant: Variant | null = null\n if (isNewVariant) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(variantsApiPath, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n // New variants are activated by default \u2014 there's only one active per scope,\n // others get auto-deactivated server-side.\n isActive: true,\n }),\n }),\n context: buildMutationContext('saveVariant'),\n mutationPayload: { name: trimmedName.length > 0 ? trimmedName : null, isActive: true },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n } else if (selectedVariantId) {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariantId)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: trimmedName.length > 0 ? trimmedName : undefined,\n settings,\n isActive: isCurrentlyActive,\n }),\n }),\n context: buildMutationContext('saveVariant', selectedVariantId),\n mutationPayload: {\n id: selectedVariantId,\n name: trimmedName.length > 0 ? trimmedName : null,\n isActive: isCurrentlyActive,\n },\n })\n if (!call.ok) {\n setError(formatVariantApiError(call, t))\n return\n }\n savedVariant = call.result?.variant ?? null\n }\n // Sync user prefs and (optionally) push to roles via the legacy preferences endpoint.\n // The variant entity is the canonical \"saved layout\"; the preferences endpoint is what\n // the AppShell sidebar actually reads. Without this sync, the saved variant wouldn't\n // become the user's live sidebar.\n const preferencesPayload: Record<string, unknown> = {\n groupOrder: settings.groupOrder,\n groupLabels: settings.groupLabels,\n itemLabels: settings.itemLabels,\n hiddenItems: settings.hiddenItems,\n itemOrder: settings.itemOrder,\n }\n if (canApplyToRoles) {\n const applyToRolesPayload = [...selectedRoleIds]\n const clearRoleIdsPayload = availableRoleTargets\n .filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id))\n .map((role) => role.id)\n preferencesPayload.applyToRoles = applyToRolesPayload\n preferencesPayload.clearRoleIds = clearRoleIdsPayload\n }\n const preferencesCall = await runMutation({\n operation: () =>\n apiCall(preferencesApiPath, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(preferencesPayload),\n }),\n context: buildMutationContext('savePreferences', selectedVariantId),\n mutationPayload: preferencesPayload,\n })\n if (!preferencesCall.ok) {\n // The variant entity is the canonical layout; the preferences sync is what the\n // AppShell sidebar actually reads. A failed sync would leave the saved variant\n // not reflected live, so surface it as a save error rather than flashing success.\n setError(formatVariantApiError(preferencesCall, t))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n // Refresh the list so isActive flags are accurate, plus refresh roles so hasPreference flags update.\n const [list, rolesPayload] = await Promise.all([\n loadVariantsList(),\n loadRolesPayload(),\n ])\n // Defensive: ensure the just-saved variant lands in the list even if the refetch\n // was served stale (browser HTTP cache, etc.).\n const mergedList = savedVariant && !list.some((v) => v.id === savedVariant!.id)\n ? [...list, savedVariant]\n : list\n setVariants(mergedList)\n setCanApplyToRoles(rolesPayload.canApplyToRoles)\n setAvailableRoleTargets(rolesPayload.roles)\n if (savedVariant) {\n const fresh = mergedList.find((v) => v.id === savedVariant!.id) ?? savedVariant\n selectVariantInternal(fresh, mergedList)\n } else {\n const active = mergedList.find((v) => v.isActive) ?? mergedList[0] ?? null\n selectVariantInternal(active, mergedList)\n }\n flash(\n isNewVariant\n ? t('appShell.sidebarCustomizationVariantCreated', 'Variant created.')\n : t('appShell.sidebarCustomizationVariantSaved', 'Variant saved.'),\n 'success',\n )\n onSaved?.()\n } catch (err) {\n console.error('Failed to save sidebar variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setSaving(false)\n }\n }, [draft, variantName, isNewVariant, selectedVariant, selectedVariantId, variantsApiPath, preferencesApiPath, canApplyToRoles, selectedRoleIds, availableRoleTargets, t, sanitizeSettingsPayload, loadVariantsList, loadRolesPayload, selectVariantInternal, onSaved, runMutation, buildMutationContext])\n\n const toggleActive = React.useCallback(async (next: boolean) => {\n if (!selectedVariant || saving || deleting) return\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall<VariantSingleResponse>(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ isActive: next }),\n }),\n context: buildMutationContext('toggleVariantActive', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id, isActive: next },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fresh = list.find((v) => v.id === selectedVariant.id) ?? selectedVariant\n selectVariantInternal(fresh, list)\n } catch (err) {\n console.error('Failed to toggle variant active state', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n }\n }, [selectedVariant, saving, deleting, variantsApiPath, t, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const deleteVariant = React.useCallback(async () => {\n if (!selectedVariant) return\n const proceed = await confirmDialog({\n title: t('appShell.sidebarCustomizationDeleteVariantTitle', 'Delete variant?'),\n text: t(\n 'appShell.sidebarCustomizationDeleteVariantText',\n 'This variant will be removed from your library.',\n ),\n confirmText: t('appShell.sidebarCustomizationDeleteVariantConfirm', 'Delete variant'),\n cancelText: t('common.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!proceed) return\n setDeleting(true)\n setError(null)\n try {\n const call = await runMutation({\n operation: () =>\n apiCall(`${variantsApiPath}/${encodeURIComponent(selectedVariant.id)}`, { method: 'DELETE' }),\n context: buildMutationContext('deleteVariant', selectedVariant.id),\n mutationPayload: { id: selectedVariant.id },\n })\n if (!call.ok) {\n setError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n try { window.dispatchEvent(new Event(REFRESH_SIDEBAR_EVENT)) } catch { /* no listener attached \u2014 fine, AppShell will refresh on next navigation */ }\n const list = await loadVariantsList()\n setVariants(list)\n const fallback = list[0] ?? null\n selectVariantInternal(fallback, list)\n } catch (err) {\n console.error('Failed to delete variant', err)\n setError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setDeleting(false)\n }\n }, [selectedVariant, confirmDialog, t, variantsApiPath, loadVariantsList, selectVariantInternal, runMutation, buildMutationContext])\n\n const isBusy = saving || deleting\n\n if (loading && !draft) {\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <div className=\"h-7 w-64 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-96 animate-pulse rounded bg-muted/60\" />\n </div>\n <div className=\"h-64 animate-pulse rounded-lg border bg-muted/30\" />\n </div>\n </>\n )\n }\n\n if (!draft || !baseSnapshotRef.current) {\n // While chrome payload streams in or the initial fetch runs, show a neutral loading\n // state instead of the error fallback (otherwise the first visit looks like a crash).\n const stillLoading = loading || chromeIsLoading || sourceGroups.length === 0\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"rounded-lg border border-dashed bg-muted/30 p-6 text-sm text-muted-foreground\">\n {stillLoading\n ? t('appShell.sidebarCustomizationLoading', 'Loading\u2026')\n : (error ?? t('appShell.sidebarCustomizationLoadError'))}\n </div>\n </>\n )\n }\n\n const baseGroupsForDefaults = baseSnapshotRef.current\n const baseGroupMap = new Map<string, SidebarGroup>()\n for (const group of baseGroupsForDefaults) {\n baseGroupMap.set(resolveGroupKey(group), group)\n }\n const orderedGroupIds = mergeGroupOrder(draft.order, Array.from(baseGroupMap.keys()))\n const totalGroups = orderedGroupIds.length\n\n const selectValue = isNewVariant ? NEW_VARIANT_KEY : selectedVariantId ?? NEW_VARIANT_KEY\n const showVariantPicker = variants.length > 0 || isNewVariant\n\n return (\n <>\n {ConfirmDialogElement}\n <Dialog\n open={addDialogOpen}\n onOpenChange={(next) => {\n if (!next) {\n setAddDialogOpen(false)\n setAddDialogName('')\n }\n }}\n >\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {t('appShell.sidebarCustomizationAddDialogTitle', 'Add new variant')}\n </DialogTitle>\n <DialogDescription>\n {t('appShell.sidebarCustomizationAddDialogDescription', 'Choose a name for the new sidebar variant. Leave blank to auto-name it.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <Input\n autoFocus\n value={addDialogName}\n onChange={(event) => setAddDialogName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n void submitAddDialog()\n }\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={saving}\n />\n </div>\n <DialogFooter className=\"mt-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setAddDialogOpen(false)\n setAddDialogName('')\n }}\n disabled={saving}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => { void submitAddDialog() }}\n disabled={saving}\n >\n {saving\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationCreateVariant', 'Create variant')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <Page>\n <header className=\"space-y-1\">\n <h1 className=\"text-xl sm:text-2xl font-semibold leading-tight\">\n {t('appShell.sidebarCustomizationHeading')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationHint', { locale: localeLabel })}\n </p>\n </header>\n\n {error ? (\n <div className=\"rounded-lg border border-destructive/40 bg-destructive/5 px-4 py-3 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n\n {/* Two-column: editor (variant + roles + order) + preview */}\n <PageBody className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,360px)]\">\n <div className=\"space-y-6\">\n {(() => {\n const showRolesCard = canApplyToRoles && availableRoleTargets.length > 0\n if (!showVariantPicker && !showRolesCard) return null\n return (\n <Card>\n <CardContent className=\"flex flex-col gap-6\">\n {showVariantPicker ? (\n <div className=\"flex flex-col gap-4\">\n {/* Row: combobox-style name input with chevron picker + DS-compliant add button */}\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantNameLabel', 'Variant name')}\n </label>\n <div className=\"flex items-stretch gap-2\">\n {/* Combobox group: input + chevron picker share a single visual border. */}\n <div className=\"relative flex flex-1 items-stretch\">\n <Input\n value={variantName}\n onChange={(event) => {\n setVariantName(event.target.value)\n setDirty(true)\n }}\n placeholder={t('appShell.sidebarCustomizationVariantNamePlaceholder', 'My preferences')}\n disabled={isBusy}\n className=\"w-full pr-10\"\n />\n <Select\n value={selectValue}\n onValueChange={(value) => { void handleVariantSwitch(value) }}\n disabled={isBusy || loading}\n >\n <SelectTrigger\n className=\"pointer-events-none absolute inset-0 h-full w-full justify-end border-0 bg-transparent px-3 shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 [&>span]:hidden [&>svg]:pointer-events-auto\"\n aria-label={t('appShell.sidebarCustomizationVariantPickerLabel', 'Pick variant')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {variants.length > 0 ? (\n variants.map((variant) => (\n <SelectItem key={variant.id} value={variant.id}>\n {variant.name}\n </SelectItem>\n ))\n ) : (\n <SelectItem value={NEW_VARIANT_KEY} disabled>\n {t('appShell.sidebarCustomizationVariantsEmpty', 'No saved variants yet')}\n </SelectItem>\n )}\n </SelectContent>\n </Select>\n </div>\n <Button\n type=\"button\"\n onClick={() => {\n setAddDialogName('')\n setAddDialogOpen(true)\n }}\n disabled={isBusy}\n title={t('appShell.sidebarCustomizationVariantNew', 'Add new variant')}\n >\n <Plus className=\"size-4\" />\n {t('appShell.sidebarCustomizationCreateNew', 'Create new')}\n </Button>\n </div>\n </div>\n\n {isNewVariant ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationVariantNewHint', 'Saving will create a new variant. If you leave the name blank, it will be auto-named.')}\n </p>\n ) : null}\n\n {/* Row: active switch */}\n <div className=\"flex items-center gap-2\">\n <Switch\n checked={selectedVariant?.isActive ?? isNewVariant}\n onCheckedChange={(next) => {\n if (isNewVariant) return\n void toggleActive(next === true)\n }}\n disabled={isBusy || isNewVariant}\n aria-label={t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n />\n <span className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationVariantActiveLabel', 'Active')}\n </span>\n </div>\n </div>\n ) : null}\n\n {showVariantPicker && showRolesCard ? (\n <div className=\"-mx-6 border-t\" aria-hidden />\n ) : null}\n\n {showRolesCard ? (\n <div className=\"flex flex-col gap-3\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold leading-none text-foreground\">\n {t('appShell.sidebarApplyToRolesTitle')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">{t('appShell.sidebarApplyToRolesDescription')}</p>\n </div>\n <div className=\"flex flex-col gap-1.5 max-w-sm\">\n {availableRoleTargets.map((role) => {\n const checked = selectedRoleIds.includes(role.id)\n const willClear = role.hasPreference && !checked\n return (\n <label\n key={role.id}\n className=\"flex cursor-pointer items-center gap-3 rounded-lg border bg-background px-3 py-2 text-sm transition-colors hover:bg-muted\"\n >\n <Switch\n checked={checked}\n onCheckedChange={() => toggleRoleSelection(role.id)}\n disabled={isBusy}\n />\n <span className=\"flex-1 truncate font-medium text-foreground\">{role.name}</span>\n {role.hasPreference ? (\n <Tag variant={willClear ? 'error' : 'info'} dot={!willClear}>\n {willClear ? <AlertTriangle className=\"size-3\" aria-hidden /> : null}\n {willClear ? t('appShell.sidebarRoleWillClear') : t('appShell.sidebarRoleHasPreset')}\n </Tag>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n\n {/* Footer: Reset / Cancel / Save (right) + Delete (left). All gated on dirty\n except Delete which acts on the persisted variant regardless of edits. */}\n <div className=\"-mx-6 border-t\" aria-hidden />\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n {selectedVariant ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => { void deleteVariant() }}\n disabled={isBusy}\n className=\"text-destructive hover:text-destructive\"\n >\n <Trash2 className=\"size-4\" />\n {deleting\n ? t('appShell.sidebarCustomizationDeleteVariantInProgress', 'Deleting\u2026')\n : t('appShell.sidebarCustomizationDeleteVariant', 'Delete variant')}\n </Button>\n ) : <span />}\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={reset}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationReset')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={cancel}\n disabled={isBusy || !dirty}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={save}\n disabled={isBusy || (!isNewVariant && !dirty)}\n >\n {saving\n ? (isNewVariant\n ? t('appShell.sidebarCustomizationCreating', 'Creating\u2026')\n : t('appShell.sidebarCustomizationSaving'))\n : (isNewVariant\n ? t('appShell.sidebarCustomizationCreateVariant', 'Create variant')\n : t('appShell.sidebarCustomizationSave'))}\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n })()}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">\n {t('appShell.sidebarCustomizationOrderHeading', 'Order & visibility')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationOrderDescription', 'Reorder groups, rename them, and toggle individual items on or off.')}\n </p>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {orderedGroupIds.map((groupId, index) => {\n const baseGroup = baseGroupMap.get(groupId)\n if (!baseGroup) return null\n const placeholder = baseGroup.defaultName ?? baseGroup.name\n const value = draft.groupLabels[groupId] ?? ''\n const trimmedValue = value.trim()\n const isGroupModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n return (\n <div key={groupId} className=\"rounded-lg border bg-background\">\n <div className=\"flex items-start gap-3 border-b px-4 py-3\">\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-xs font-medium uppercase tracking-wider text-muted-foreground/70\">\n {t('appShell.sidebarCustomizationGroupLabel')}\n </label>\n <div className=\"flex items-center gap-2\">\n <Input\n value={value}\n onChange={(event) => setGroupLabel(groupId, event.target.value)}\n placeholder={placeholder}\n disabled={isBusy}\n className=\"flex-1\"\n />\n {isGroupModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setGroupLabel(groupId, '')}\n disabled={isBusy}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isGroupModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-1 mt-[26px]\">\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, -1)}\n disabled={index === 0 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveUp')}\n title={t('appShell.sidebarCustomizationMoveUp')}\n >\n <ChevronUp className=\"size-4\" />\n </IconButton>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, 1)}\n disabled={index === totalGroups - 1 || isBusy}\n aria-label={t('appShell.sidebarCustomizationMoveDown')}\n title={t('appShell.sidebarCustomizationMoveDown')}\n >\n <ChevronDown className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n <div className=\"flex flex-col divide-y\">\n <ItemRows\n items={baseGroup.items}\n draft={draft}\n saving={isBusy}\n onLabelChange={setItemLabel}\n onHiddenChange={setItemHidden}\n t={t}\n groupKey={groupId}\n sensors={dndSensors}\n onDragEnd={handleItemDragEnd(groupId, baseGroup.items.map((item) => resolveItemKey(item)))}\n />\n </div>\n </div>\n )\n })}\n </CardContent>\n </Card>\n </div>\n\n <aside className=\"hidden lg:block\">\n <div className=\"sticky top-6\">\n <div className=\"relative\">\n <span className=\"absolute left-1/2 top-0 z-10 -translate-x-1/2 -translate-y-1/2 rounded-md bg-accent-indigo px-3 py-1 text-xs font-semibold uppercase tracking-wider text-accent-indigo-foreground shadow-sm\">\n {t('appShell.sidebarCustomizationPreview', 'Preview')}\n </span>\n <SidebarPreview\n groups={previewGroups}\n productName={t('appShell.productName', 'Open Mercato')}\n pickFirstActive\n />\n </div>\n </div>\n </aside>\n </PageBody>\n </Page>\n </>\n )\n}\n\ntype ItemRowProps = {\n item: SidebarItem\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth: number\n dragHandle?: React.ReactNode\n /** True when an ancestor in the tree is hidden \u2014 child controls become read-only. */\n ancestorHidden?: boolean\n}\n\nfunction ItemRow({ item, draft, saving, onLabelChange, onHiddenChange, t, depth, dragHandle, ancestorHidden = false }: ItemRowProps) {\n const itemKey = resolveItemKey(item)\n const placeholder = item.defaultTitle ?? item.title\n const value = draft.itemLabels[itemKey] ?? ''\n const trimmedValue = value.trim()\n const isModified = trimmedValue.length > 0 && trimmedValue !== placeholder\n const hidden = draft.hiddenItemIds[itemKey] === true\n const effectivelyDimmed = hidden || ancestorHidden\n return (\n <div\n className=\"flex items-start gap-3 px-4 py-3 transition-colors hover:bg-muted/40\"\n style={depth ? { paddingLeft: 16 + depth * 24 } : undefined}\n >\n {dragHandle ?? (depth > 0 ? <span className=\"w-4 shrink-0\" aria-hidden /> : null)}\n <div className={`min-w-0 flex-1 flex flex-col gap-1.5 ${effectivelyDimmed ? 'opacity-60' : ''}`}>\n <div className=\"flex items-center gap-2\">\n <div className=\"min-w-0 flex-1\">\n <Input\n value={value}\n onChange={(event) => onLabelChange(itemKey, event.target.value)}\n placeholder={placeholder}\n disabled={saving}\n />\n </div>\n {isModified ? (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onLabelChange(itemKey, '')}\n disabled={saving}\n aria-label={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n title={t('appShell.sidebarCustomizationResetField', 'Reset to default')}\n >\n <RotateCcw className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {isModified ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('appShell.sidebarCustomizationDefault', 'Default:')}{' '}\n <span className=\"font-medium text-foreground/80\">{placeholder}</span>\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2 pt-1.5\">\n {hidden ? (\n <span className=\"rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('appShell.sidebarCustomizationHiddenBadge', 'Hidden')}\n </span>\n ) : null}\n <Switch\n checked={!hidden}\n onCheckedChange={(next) => onHiddenChange(itemKey, next !== true)}\n disabled={saving || ancestorHidden}\n aria-label={t('appShell.sidebarCustomizationShowItem')}\n title={ancestorHidden ? t('appShell.sidebarCustomizationParentHiddenHint', 'Parent is hidden \u2014 show parent first.') : undefined}\n />\n </div>\n </div>\n )\n}\n\ntype SortableItemRowProps = ItemRowProps & { id: string }\n\nfunction SortableItemRow({ id, ...rowProps }: SortableItemRowProps) {\n const t = useT()\n const { attributes, listeners, setNodeRef, transform, transition, isDragging, setActivatorNodeRef } = useSortable({ id })\n const style: React.CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n }\n const dragHandle = (\n <IconButton\n ref={setActivatorNodeRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"shrink-0 mt-1.5 cursor-grab touch-none active:cursor-grabbing\"\n aria-label={t('appShell.sidebarCustomizationDragToReorder', 'Drag to reorder')}\n disabled={rowProps.saving}\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"size-4\" />\n </IconButton>\n )\n return (\n <div ref={setNodeRef} style={style}>\n <ItemRow {...rowProps} dragHandle={dragHandle} />\n </div>\n )\n}\n\ntype ItemRowsProps = {\n items: SidebarItem[]\n draft: SidebarCustomizationDraft\n saving: boolean\n onLabelChange: (itemId: string, value: string) => void\n onHiddenChange: (itemId: string, hidden: boolean) => void\n t: ReturnType<typeof useT>\n depth?: number\n groupKey?: string\n sensors?: ReturnType<typeof useSensors>\n onDragEnd?: (event: DragEndEvent) => void\n ancestorHidden?: boolean\n}\n\nfunction ItemRows({\n items,\n draft,\n saving,\n onLabelChange,\n onHiddenChange,\n t,\n depth = 0,\n groupKey,\n sensors,\n onDragEnd,\n ancestorHidden = false,\n}: ItemRowsProps) {\n if (items.length === 0) return null\n\n const renderRecursiveChildren = (item: SidebarItem, parentHidden: boolean) =>\n item.children && item.children.length > 0 ? (\n <ItemRows\n items={item.children}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth + 1}\n ancestorHidden={parentHidden}\n />\n ) : null\n\n // Top-level rows in a group \u2192 enable DnD reordering.\n if (depth === 0 && groupKey && sensors && onDragEnd) {\n const ordered = applyItemOrder(items, resolveItemKey, draft.itemOrder?.[groupKey])\n const ids = ordered.map((item) => resolveItemKey(item))\n return (\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>\n <SortableContext items={ids} strategy={verticalListSortingStrategy}>\n {ordered.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <SortableItemRow\n id={itemKey}\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </SortableContext>\n </DndContext>\n )\n }\n\n // Nested children \u2192 static rendering, no drag handle.\n return (\n <>\n {items.map((item) => {\n const itemKey = resolveItemKey(item)\n const ownHidden = draft.hiddenItemIds[itemKey] === true\n return (\n <React.Fragment key={itemKey}>\n <ItemRow\n item={item}\n draft={draft}\n saving={saving}\n onLabelChange={onLabelChange}\n onHiddenChange={onHiddenChange}\n t={t}\n depth={depth}\n ancestorHidden={ancestorHidden}\n />\n {renderRecursiveChildren(item, ancestorHidden || ownHidden)}\n </React.Fragment>\n )\n })}\n </>\n )\n}\n\nfunction SidebarPreviewIcon({ item }: { item: SidebarItem }) {\n if (item.icon) return <>{item.icon}</>\n if (item.iconName) {\n const resolved = resolveInjectedIcon(item.iconName)\n if (resolved) return <>{resolved}</>\n }\n if (item.iconMarkup) {\n return <span aria-hidden=\"true\" dangerouslySetInnerHTML={{ __html: item.iconMarkup }} />\n }\n // Fallback default icon \u2014 same shape as AppShell's DefaultIcon\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n )\n}\n\nfunction SidebarPreview({\n groups,\n productName,\n pickFirstActive,\n}: {\n groups: SidebarGroup[]\n productName: string\n pickFirstActive: boolean\n}) {\n const t = useT()\n // Pre-compute the first visible item so we can render it as the \"active\" preview state.\n // This shows the user what the active state of their sidebar will look like.\n const activeKey = React.useMemo<string | null>(() => {\n if (!pickFirstActive) return null\n for (const group of groups) {\n for (const item of group.items) {\n if (item.hidden === true) continue\n return resolveItemKey(item)\n }\n }\n return null\n }, [groups, pickFirstActive])\n\n return (\n <div className=\"relative w-[240px] overflow-hidden rounded-xl border bg-background shadow-sm\">\n {/* Match AppShell's outer aside: border-r, py-4, px-3 \u2014 minus border-r since the\n card border already serves that purpose, plus rounded so it reads as a preview tile. */}\n <div className=\"flex flex-col gap-3 px-3 py-4\">\n {/* Brand block \u2014 same classes as AppShell brand tile */}\n <div className=\"mb-2\">\n <div className=\"flex items-center gap-3 rounded-xl p-3\">\n <Image\n src=\"/open-mercato.svg\"\n alt={productName}\n width={40}\n height={40}\n className=\"rounded-full shrink-0\"\n />\n <span className=\"text-sm font-medium text-foreground truncate\">{productName}</span>\n </div>\n </div>\n {/* Search input mock \u2014 same container styling as the real sidebar */}\n <div className=\"mb-2 flex items-center gap-2 rounded-lg border border-border bg-background pl-2.5 pr-2 py-2 shadow-sm\">\n <Search className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 text-sm text-muted-foreground/70 truncate\">\n {t('appShell.sidebarCustomizationPreviewSearchPlaceholder', 'Search...')}\n </span>\n </div>\n {groups.length === 0 ? (\n <p className=\"px-2 text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationPreviewEmpty', 'No groups to preview.')}\n </p>\n ) : (\n <nav className=\"flex flex-col gap-2\">\n {groups.map((group, gi) => {\n const visibleItems = group.items.filter((item) => item.hidden !== true)\n if (visibleItems.length === 0) return null\n return (\n <div key={resolveGroupKey(group)}>\n <div className=\"w-full px-1 justify-between flex text-xs font-medium uppercase tracking-wider text-muted-foreground/70 py-1\">\n <span>{group.name}</span>\n </div>\n <div className=\"flex flex-col gap-1\">\n {visibleItems.map((item) => {\n const itemKey = resolveItemKey(item)\n const isActive = activeKey === itemKey\n return (\n <div\n key={itemKey}\n className={`relative text-sm font-medium rounded-lg inline-flex items-center w-full px-3 py-2 gap-2 ${\n isActive ? 'bg-muted text-foreground' : 'text-muted-foreground'\n }`}\n >\n {isActive ? (\n <span\n aria-hidden\n className=\"absolute left-[-12px] top-2 w-1 h-5 rounded-r bg-foreground\"\n />\n ) : null}\n <span className=\"flex items-center justify-center shrink-0\">\n <SidebarPreviewIcon item={item} />\n </span>\n <span className=\"truncate\">{item.title}</span>\n </div>\n )\n })}\n </div>\n {gi < groups.length - 1 ? <div className=\"my-2 border-t -ml-3 -mr-4\" /> : null}\n </div>\n )\n })}\n </nav>\n )}\n </div>\n </div>\n )\n}\n\nexport default SidebarCustomizationEditor\n"],
5
+ "mappings": "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",
6
+ "names": ["draft"]
7
+ }
@@ -0,0 +1,150 @@
1
+ import { slugifySidebarId } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
2
+ function resolveGroupKey(group) {
3
+ if (group.id && group.id.length) return group.id;
4
+ if (group.defaultName && group.defaultName.length) return slugifySidebarId(group.defaultName);
5
+ return slugifySidebarId(group.name);
6
+ }
7
+ function resolveItemKey(item) {
8
+ const candidate = item.id?.trim();
9
+ if (candidate && candidate.length > 0) return candidate;
10
+ return item.href;
11
+ }
12
+ function cloneSidebarGroups(groups) {
13
+ const cloneItem = (item) => ({
14
+ id: item.id,
15
+ href: item.href,
16
+ title: item.title,
17
+ defaultTitle: item.defaultTitle,
18
+ icon: item.icon,
19
+ iconName: item.iconName,
20
+ iconMarkup: item.iconMarkup,
21
+ enabled: item.enabled,
22
+ hidden: item.hidden,
23
+ pageContext: item.pageContext,
24
+ children: item.children ? item.children.map((child) => cloneItem(child)) : void 0
25
+ });
26
+ return groups.map((group) => ({
27
+ id: group.id,
28
+ name: group.name,
29
+ defaultName: group.defaultName,
30
+ items: group.items.map((item) => cloneItem(item))
31
+ }));
32
+ }
33
+ function mergeGroupOrder(preferred, current) {
34
+ const seen = /* @__PURE__ */ new Set();
35
+ const merged = [];
36
+ for (const id of preferred) {
37
+ const trimmed = id.trim();
38
+ if (!trimmed || seen.has(trimmed) || !current.includes(trimmed)) continue;
39
+ seen.add(trimmed);
40
+ merged.push(trimmed);
41
+ }
42
+ for (const id of current) {
43
+ if (seen.has(id)) continue;
44
+ seen.add(id);
45
+ merged.push(id);
46
+ }
47
+ return merged;
48
+ }
49
+ function applyItemOrder(items, keyOf, preferred) {
50
+ if (!preferred || preferred.length === 0) return items;
51
+ const byKey = /* @__PURE__ */ new Map();
52
+ for (const item of items) byKey.set(keyOf(item), item);
53
+ const seen = /* @__PURE__ */ new Set();
54
+ const ordered = [];
55
+ for (const key of preferred) {
56
+ if (seen.has(key)) continue;
57
+ const match = byKey.get(key);
58
+ if (!match) continue;
59
+ ordered.push(match);
60
+ seen.add(key);
61
+ }
62
+ for (const item of items) {
63
+ const key = keyOf(item);
64
+ if (seen.has(key)) continue;
65
+ ordered.push(item);
66
+ seen.add(key);
67
+ }
68
+ return ordered;
69
+ }
70
+ function applyItemDraft(item, draft) {
71
+ const itemKey = resolveItemKey(item);
72
+ const baseTitle = item.defaultTitle ?? item.title;
73
+ const override = draft.itemLabels[itemKey]?.trim();
74
+ const children = item.children ? item.children.map((child) => applyItemDraft(child, draft)) : void 0;
75
+ const hidden = draft.hiddenItemIds[itemKey] === true;
76
+ return {
77
+ ...item,
78
+ title: override && override.length > 0 ? override : baseTitle,
79
+ hidden,
80
+ children
81
+ };
82
+ }
83
+ function applyCustomizationDraft(baseGroups, draft) {
84
+ const clones = cloneSidebarGroups(baseGroups);
85
+ const byId = /* @__PURE__ */ new Map();
86
+ for (const group of clones) {
87
+ byId.set(resolveGroupKey(group), group);
88
+ }
89
+ const orderedIds = mergeGroupOrder(draft.order, Array.from(byId.keys()));
90
+ const seen = /* @__PURE__ */ new Set();
91
+ const result = [];
92
+ for (const id of orderedIds) {
93
+ if (seen.has(id)) continue;
94
+ const group = byId.get(id);
95
+ if (!group) continue;
96
+ seen.add(id);
97
+ const baseName = group.defaultName ?? group.name;
98
+ const override = draft.groupLabels[id]?.trim();
99
+ const orderedItems = applyItemOrder(group.items, resolveItemKey, draft.itemOrder?.[id]);
100
+ result.push({
101
+ ...group,
102
+ name: override && override.length > 0 ? override : baseName,
103
+ items: orderedItems.map((item) => applyItemDraft(item, draft))
104
+ });
105
+ }
106
+ return result;
107
+ }
108
+ function filterMainSidebarGroups(groups) {
109
+ const isMainItem = (item) => {
110
+ if (item.pageContext && item.pageContext !== "main") return false;
111
+ return true;
112
+ };
113
+ return groups.map((group) => ({
114
+ ...group,
115
+ items: group.items.filter(isMainItem).map((item) => ({
116
+ ...item,
117
+ children: item.children?.filter(isMainItem)
118
+ }))
119
+ })).filter((group) => group.items.length > 0);
120
+ }
121
+ function collectSidebarDefaults(groups) {
122
+ const groupDefaults = /* @__PURE__ */ new Map();
123
+ const itemDefaults = /* @__PURE__ */ new Map();
124
+ const visitItems = (items) => {
125
+ for (const item of items) {
126
+ const key = resolveItemKey(item);
127
+ const baseTitle = item.defaultTitle ?? item.title;
128
+ itemDefaults.set(key, baseTitle);
129
+ itemDefaults.set(item.href, baseTitle);
130
+ if (item.children && item.children.length > 0) visitItems(item.children);
131
+ }
132
+ };
133
+ for (const group of groups) {
134
+ const key = resolveGroupKey(group);
135
+ groupDefaults.set(key, group.defaultName ?? group.name);
136
+ visitItems(group.items);
137
+ }
138
+ return { groupDefaults, itemDefaults };
139
+ }
140
+ export {
141
+ applyCustomizationDraft,
142
+ applyItemOrder,
143
+ cloneSidebarGroups,
144
+ collectSidebarDefaults,
145
+ filterMainSidebarGroups,
146
+ mergeGroupOrder,
147
+ resolveGroupKey,
148
+ resolveItemKey
149
+ };
150
+ //# sourceMappingURL=customization-helpers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/sidebar/customization-helpers.ts"],
4
+ "sourcesContent": ["import { slugifySidebarId } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\n\nexport type SidebarItem = {\n id?: string\n href: string\n title: string\n defaultTitle?: string\n icon?: React.ReactNode\n iconName?: string\n iconMarkup?: string\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n children?: SidebarItem[]\n}\n\nexport type SidebarGroup = {\n id?: string\n name: string\n defaultName?: string\n items: SidebarItem[]\n}\n\nexport type SidebarCustomizationDraft = {\n order: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItemIds: Record<string, boolean>\n /** Per-group ordered item keys. Missing items keep their natural position at the tail. */\n itemOrder: Record<string, string[]>\n}\n\nexport type SidebarRoleTarget = {\n id: string\n name: string\n hasPreference: boolean\n}\n\nexport function resolveGroupKey(group: SidebarGroup): string {\n if (group.id && group.id.length) return group.id\n if (group.defaultName && group.defaultName.length) return slugifySidebarId(group.defaultName)\n return slugifySidebarId(group.name)\n}\n\nexport function resolveItemKey(item: { id?: string; href: string }): string {\n const candidate = item.id?.trim()\n if (candidate && candidate.length > 0) return candidate\n return item.href\n}\n\nexport function cloneSidebarGroups(groups: SidebarGroup[]): SidebarGroup[] {\n const cloneItem = (item: SidebarItem): SidebarItem => ({\n id: item.id,\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n icon: item.icon,\n iconName: item.iconName,\n iconMarkup: item.iconMarkup,\n enabled: item.enabled,\n hidden: item.hidden,\n pageContext: item.pageContext,\n children: item.children ? item.children.map((child) => cloneItem(child)) : undefined,\n })\n return groups.map((group) => ({\n id: group.id,\n name: group.name,\n defaultName: group.defaultName,\n items: group.items.map((item) => cloneItem(item)),\n }))\n}\n\nexport function mergeGroupOrder(preferred: string[], current: string[]): string[] {\n const seen = new Set<string>()\n const merged: string[] = []\n for (const id of preferred) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed) || !current.includes(trimmed)) continue\n seen.add(trimmed)\n merged.push(trimmed)\n }\n for (const id of current) {\n if (seen.has(id)) continue\n seen.add(id)\n merged.push(id)\n }\n return merged\n}\n\n/** Reorders items by preferred keys; items missing from `preferred` keep their original\n * relative order at the tail. Drops keys that no longer exist. */\nexport function applyItemOrder<T>(items: T[], keyOf: (item: T) => string, preferred: string[] | undefined): T[] {\n if (!preferred || preferred.length === 0) return items\n const byKey = new Map<string, T>()\n for (const item of items) byKey.set(keyOf(item), item)\n const seen = new Set<string>()\n const ordered: T[] = []\n for (const key of preferred) {\n if (seen.has(key)) continue\n const match = byKey.get(key)\n if (!match) continue\n ordered.push(match)\n seen.add(key)\n }\n for (const item of items) {\n const key = keyOf(item)\n if (seen.has(key)) continue\n ordered.push(item)\n seen.add(key)\n }\n return ordered\n}\n\nfunction applyItemDraft(item: SidebarItem, draft: SidebarCustomizationDraft): SidebarItem {\n const itemKey = resolveItemKey(item)\n const baseTitle = item.defaultTitle ?? item.title\n const override = draft.itemLabels[itemKey]?.trim()\n const children = item.children\n ? item.children.map((child) => applyItemDraft(child, draft))\n : undefined\n const hidden = draft.hiddenItemIds[itemKey] === true\n return {\n ...item,\n title: override && override.length > 0 ? override : baseTitle,\n hidden,\n children,\n }\n}\n\nexport function applyCustomizationDraft(\n baseGroups: SidebarGroup[],\n draft: SidebarCustomizationDraft,\n): SidebarGroup[] {\n const clones = cloneSidebarGroups(baseGroups)\n const byId = new Map<string, SidebarGroup>()\n for (const group of clones) {\n byId.set(resolveGroupKey(group), group)\n }\n const orderedIds = mergeGroupOrder(draft.order, Array.from(byId.keys()))\n const seen = new Set<string>()\n const result: SidebarGroup[] = []\n for (const id of orderedIds) {\n if (seen.has(id)) continue\n const group = byId.get(id)\n if (!group) continue\n seen.add(id)\n const baseName = group.defaultName ?? group.name\n const override = draft.groupLabels[id]?.trim()\n const orderedItems = applyItemOrder(group.items, resolveItemKey, draft.itemOrder?.[id])\n result.push({\n ...group,\n name: override && override.length > 0 ? override : baseName,\n items: orderedItems.map((item) => applyItemDraft(item, draft)),\n })\n }\n return result\n}\n\n/**\n * Filters groups to include only main sidebar items.\n * Excludes items with pageContext 'settings' or 'profile' from customization.\n * Per SPEC-007: Sidebar customization applies only to the main sidebar.\n */\nexport function filterMainSidebarGroups(groups: SidebarGroup[]): SidebarGroup[] {\n const isMainItem = (item: SidebarItem): boolean => {\n if (item.pageContext && item.pageContext !== 'main') return false\n return true\n }\n\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(isMainItem).map((item) => ({\n ...item,\n children: item.children?.filter(isMainItem),\n })),\n }))\n .filter((group) => group.items.length > 0)\n}\n\nexport function collectSidebarDefaults(groups: SidebarGroup[]) {\n const groupDefaults = new Map<string, string>()\n const itemDefaults = new Map<string, string>()\n\n const visitItems = (items: SidebarItem[]) => {\n for (const item of items) {\n const key = resolveItemKey(item)\n const baseTitle = item.defaultTitle ?? item.title\n itemDefaults.set(key, baseTitle)\n // Backward-compatible alias for legacy stored href-based preferences.\n itemDefaults.set(item.href, baseTitle)\n if (item.children && item.children.length > 0) visitItems(item.children)\n }\n }\n\n for (const group of groups) {\n const key = resolveGroupKey(group)\n groupDefaults.set(key, group.defaultName ?? group.name)\n visitItems(group.items)\n }\n\n return { groupDefaults, itemDefaults }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -75,8 +75,7 @@ const Switch = React.forwardRef(
75
75
  "span",
76
76
  {
77
77
  className: cn(
78
- "block size-3 rounded-full bg-white transition-transform duration-200",
79
- "shadow-[0_1px_2px_rgba(10,13,20,0.10),0_0_0_0.5px_rgba(10,13,20,0.04)]",
78
+ "block size-3 rounded-full bg-white transition-transform duration-200 shadow-switch-thumb",
80
79
  currentChecked ? "translate-x-3" : "translate-x-0"
81
80
  )
82
81
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/switch.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype SwitchProps = {\n checked?: boolean\n defaultChecked?: boolean\n onCheckedChange?: (checked: boolean) => void\n} & Omit<React.ComponentProps<'button'>, 'onChange'>\n\nexport const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(\n ({ checked, defaultChecked, onCheckedChange, disabled, className, onClick, onKeyDown, ...props }, ref) => {\n const isControlled = typeof checked === 'boolean'\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultChecked ?? false)\n\n const currentChecked = isControlled ? checked : uncontrolledValue\n\n const toggle = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>) => {\n event.preventDefault()\n if (disabled) {\n return\n }\n const next = !currentChecked\n if (!isControlled) {\n setUncontrolledValue(next)\n }\n onCheckedChange?.(next)\n },\n [currentChecked, disabled, isControlled, onCheckedChange]\n )\n\n const handleClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event)\n if (!event.defaultPrevented) {\n toggle(event)\n }\n },\n [onClick, toggle]\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLButtonElement>) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) {\n return\n }\n if (event.key === ' ' || event.key === 'Enter') {\n toggle(event)\n }\n },\n [onKeyDown, toggle]\n )\n\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={currentChecked}\n aria-disabled={disabled}\n data-state={currentChecked ? 'checked' : 'unchecked'}\n data-disabled={disabled ? '' : undefined}\n ref={ref}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n className={cn(\n 'group relative inline-flex h-5 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full bg-transparent p-0',\n 'focus-visible:outline-none focus-visible:shadow-focus',\n 'disabled:cursor-not-allowed disabled:opacity-60',\n className\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n 'pointer-events-none flex h-4 w-7 items-center rounded-full px-0.5 transition-colors duration-150',\n 'bg-border group-hover:bg-muted-foreground/30',\n 'group-data-[state=checked]:bg-accent-indigo group-data-[state=checked]:group-hover:bg-accent-indigo/85'\n )}\n >\n <span\n className={cn(\n 'block size-3 rounded-full bg-white transition-transform duration-200',\n 'shadow-[0_1px_2px_rgba(10,13,20,0.10),0_0_0_0.5px_rgba(10,13,20,0.04)]',\n currentChecked ? 'translate-x-3' : 'translate-x-0'\n )}\n />\n </span>\n </button>\n )\n }\n)\n\nSwitch.displayName = 'Switch'\n"],
5
- "mappings": ";AAqFU;AAnFV,YAAY,WAAW;AAEvB,SAAS,UAAU;AAQZ,MAAM,SAAS,MAAM;AAAA,EAC1B,CAAC,EAAE,SAAS,gBAAgB,iBAAiB,UAAU,WAAW,SAAS,WAAW,GAAG,MAAM,GAAG,QAAQ;AACxG,UAAM,eAAe,OAAO,YAAY;AACxC,UAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,kBAAkB,KAAK;AAExF,UAAM,iBAAiB,eAAe,UAAU;AAEhD,UAAM,SAAS,MAAM;AAAA,MACnB,CAAC,UAAwF;AACvF,cAAM,eAAe;AACrB,YAAI,UAAU;AACZ;AAAA,QACF;AACA,cAAM,OAAO,CAAC;AACd,YAAI,CAAC,cAAc;AACjB,+BAAqB,IAAI;AAAA,QAC3B;AACA,0BAAkB,IAAI;AAAA,MACxB;AAAA,MACA,CAAC,gBAAgB,UAAU,cAAc,eAAe;AAAA,IAC1D;AAEA,UAAM,cAAc,MAAM;AAAA,MACxB,CAAC,UAA+C;AAC9C,kBAAU,KAAK;AACf,YAAI,CAAC,MAAM,kBAAkB;AAC3B,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,MACA,CAAC,SAAS,MAAM;AAAA,IAClB;AAEA,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,UAAkD;AACjD,oBAAY,KAAK;AACjB,YAAI,MAAM,kBAAkB;AAC1B;AAAA,QACF;AACA,YAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,SAAS;AAC9C,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,MACA,CAAC,WAAW,MAAM;AAAA,IACpB;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,gBAAc;AAAA,QACd,iBAAe;AAAA,QACf,cAAY,iBAAiB,YAAY;AAAA,QACzC,iBAAe,WAAW,KAAK;AAAA,QAC/B;AAAA,QACA,SAAS;AAAA,QACT,WAAW;AAAA,QACX;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,YAEA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA,iBAAiB,kBAAkB;AAAA,gBACrC;AAAA;AAAA,YACF;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,OAAO,cAAc;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\ntype SwitchProps = {\n checked?: boolean\n defaultChecked?: boolean\n onCheckedChange?: (checked: boolean) => void\n} & Omit<React.ComponentProps<'button'>, 'onChange'>\n\nexport const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(\n ({ checked, defaultChecked, onCheckedChange, disabled, className, onClick, onKeyDown, ...props }, ref) => {\n const isControlled = typeof checked === 'boolean'\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultChecked ?? false)\n\n const currentChecked = isControlled ? checked : uncontrolledValue\n\n const toggle = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>) => {\n event.preventDefault()\n if (disabled) {\n return\n }\n const next = !currentChecked\n if (!isControlled) {\n setUncontrolledValue(next)\n }\n onCheckedChange?.(next)\n },\n [currentChecked, disabled, isControlled, onCheckedChange]\n )\n\n const handleClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(event)\n if (!event.defaultPrevented) {\n toggle(event)\n }\n },\n [onClick, toggle]\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLButtonElement>) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) {\n return\n }\n if (event.key === ' ' || event.key === 'Enter') {\n toggle(event)\n }\n },\n [onKeyDown, toggle]\n )\n\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={currentChecked}\n aria-disabled={disabled}\n data-state={currentChecked ? 'checked' : 'unchecked'}\n data-disabled={disabled ? '' : undefined}\n ref={ref}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n className={cn(\n 'group relative inline-flex h-5 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full bg-transparent p-0',\n 'focus-visible:outline-none focus-visible:shadow-focus',\n 'disabled:cursor-not-allowed disabled:opacity-60',\n className\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n 'pointer-events-none flex h-4 w-7 items-center rounded-full px-0.5 transition-colors duration-150',\n 'bg-border group-hover:bg-muted-foreground/30',\n 'group-data-[state=checked]:bg-accent-indigo group-data-[state=checked]:group-hover:bg-accent-indigo/85'\n )}\n >\n <span\n className={cn(\n 'block size-3 rounded-full bg-white transition-transform duration-200 shadow-switch-thumb',\n currentChecked ? 'translate-x-3' : 'translate-x-0'\n )}\n />\n </span>\n </button>\n )\n }\n)\n\nSwitch.displayName = 'Switch'\n"],
5
+ "mappings": ";AAqFU;AAnFV,YAAY,WAAW;AAEvB,SAAS,UAAU;AAQZ,MAAM,SAAS,MAAM;AAAA,EAC1B,CAAC,EAAE,SAAS,gBAAgB,iBAAiB,UAAU,WAAW,SAAS,WAAW,GAAG,MAAM,GAAG,QAAQ;AACxG,UAAM,eAAe,OAAO,YAAY;AACxC,UAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,kBAAkB,KAAK;AAExF,UAAM,iBAAiB,eAAe,UAAU;AAEhD,UAAM,SAAS,MAAM;AAAA,MACnB,CAAC,UAAwF;AACvF,cAAM,eAAe;AACrB,YAAI,UAAU;AACZ;AAAA,QACF;AACA,cAAM,OAAO,CAAC;AACd,YAAI,CAAC,cAAc;AACjB,+BAAqB,IAAI;AAAA,QAC3B;AACA,0BAAkB,IAAI;AAAA,MACxB;AAAA,MACA,CAAC,gBAAgB,UAAU,cAAc,eAAe;AAAA,IAC1D;AAEA,UAAM,cAAc,MAAM;AAAA,MACxB,CAAC,UAA+C;AAC9C,kBAAU,KAAK;AACf,YAAI,CAAC,MAAM,kBAAkB;AAC3B,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,MACA,CAAC,SAAS,MAAM;AAAA,IAClB;AAEA,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,UAAkD;AACjD,oBAAY,KAAK;AACjB,YAAI,MAAM,kBAAkB;AAC1B;AAAA,QACF;AACA,YAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,SAAS;AAC9C,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,MACA,CAAC,WAAW,MAAM;AAAA,IACpB;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,gBAAc;AAAA,QACd,iBAAe;AAAA,QACf,cAAY,iBAAiB,YAAY;AAAA,QACzC,iBAAe,WAAW,KAAK;AAAA,QAC/B;AAAA,QACA,SAAS;AAAA,QACT,WAAW;AAAA,QACX;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,YAEA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA,iBAAiB,kBAAkB;AAAA,gBACrC;AAAA;AAAA,YACF;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,OAAO,cAAc;",
6
6
  "names": []
7
7
  }
package/jest.setup.ts CHANGED
@@ -34,6 +34,19 @@ if (typeof globalThis.Response === 'undefined') {
34
34
  (globalThis as any).Response = MockResponse
35
35
  }
36
36
 
37
+ // jsdom does not implement ResizeObserver. Components like AppShell (sticky sidebar
38
+ // scroll affordance) and TruncatedCell instantiate one in effects, so any test that
39
+ // renders them needs a stub. Provide a no-op global mock instead of forcing every
40
+ // test file to ship its own.
41
+ if (typeof globalThis.ResizeObserver === 'undefined') {
42
+ class ResizeObserverStub {
43
+ observe(): void {}
44
+ unobserve(): void {}
45
+ disconnect(): void {}
46
+ }
47
+ (globalThis as any).ResizeObserver = ResizeObserverStub
48
+ }
49
+
37
50
  // Mock window.location.reload globally for all tests
38
51
  if (typeof window !== 'undefined' && window.location) {
39
52
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.5.1-develop.2975.ccbadc8198",
3
+ "version": "0.5.1-develop.2996.ce62fd491c",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -134,12 +134,12 @@
134
134
  "recharts": "^3.8.1"
135
135
  },
136
136
  "peerDependencies": {
137
- "@open-mercato/shared": "0.5.1-develop.2975.ccbadc8198",
137
+ "@open-mercato/shared": "0.5.1-develop.2996.ce62fd491c",
138
138
  "react": ">=18.0.0",
139
139
  "react-dom": ">=18.0.0"
140
140
  },
141
141
  "devDependencies": {
142
- "@open-mercato/shared": "0.5.1-develop.2975.ccbadc8198",
142
+ "@open-mercato/shared": "0.5.1-develop.2996.ce62fd491c",
143
143
  "@testing-library/dom": "^10.4.1",
144
144
  "@testing-library/jest-dom": "^6.9.1",
145
145
  "@testing-library/react": "^16.3.1",