@open-mercato/search 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd

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.
@@ -323,7 +323,7 @@ function GlobalSearchDialog({
323
323
  loading && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })
324
324
  ] }),
325
325
  error ? /* @__PURE__ */ jsx("p", { className: "rounded bg-destructive/10 px-3 py-2 text-sm text-destructive", children: error }) : null,
326
- showVectorWarning ? /* @__PURE__ */ jsx("p", { className: "rounded bg-amber-100 dark:bg-amber-900/20 px-3 py-2 text-sm text-amber-800 dark:text-amber-200", children: missingConfigMessage }) : null,
326
+ showVectorWarning ? /* @__PURE__ */ jsx("p", { className: "rounded bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text", children: missingConfigMessage }) : null,
327
327
  showScopeHint ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("search.scopeHint.currentOrg", "Scoped to current organization") }) : null
328
328
  ] }),
329
329
  /* @__PURE__ */ jsxs("div", { ref: listRef, className: "max-h-96 overflow-y-auto px-2 pb-3", children: [
@@ -341,7 +341,7 @@ function GlobalSearchDialog({
341
341
  onMouseEnter: () => setSelectedIndex(index),
342
342
  className: cn(
343
343
  "w-full rounded-lg px-4 py-3 text-left transition border",
344
- isActive ? "border-primary bg-primary/10 text-foreground shadow-sm" : "border-transparent hover:border-muted-foreground/30 hover:bg-muted/60",
344
+ isActive ? "border-primary bg-primary/10 text-foreground shadow-sm" : "border-transparent hover:border-muted-foreground/30 hover:bg-muted/50",
345
345
  !hasLink && "opacity-60"
346
346
  ),
347
347
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
@@ -349,7 +349,7 @@ function GlobalSearchDialog({
349
349
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
350
350
  /* @__PURE__ */ jsx("span", { className: cn("font-medium text-base whitespace-normal break-all", !hasLink && "text-muted-foreground"), children: presenter?.title ?? result.recordId }),
351
351
  /* @__PURE__ */ jsx("span", { className: "rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground", children: formatEntityId(result.entityId) }),
352
- !hasLink && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-amber-500/50 bg-amber-50 dark:bg-amber-900/20 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-400", children: t("search.dialog.noLink") })
352
+ !hasLink && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text", children: t("search.dialog.noLink") })
353
353
  ] }),
354
354
  presenter?.subtitle ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground whitespace-normal break-words", children: presenter.subtitle }) : null,
355
355
  normalizeLinks(result.links).length ? /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: normalizeLinks(result.links).map((link) => /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/frontend/components/GlobalSearchDialog.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link,\n Folder,\n Database,\n Activity,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = 2\n\n/** Default strategies used when none are configured */\nconst DEFAULT_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: Link,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n return `${humanizeSegment(module)} \u00B7 ${humanizeSegment(entity)}`\n}\n\nexport type GlobalSearchDialogProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Message to show when embedding is not configured */\n missingConfigMessage: string\n /** Enabled strategies from tenant configuration (optional - uses defaults if not provided) */\n enabledStrategies?: SearchStrategyId[]\n}\n\nexport function GlobalSearchDialog({\n embeddingConfigured,\n missingConfigMessage,\n enabledStrategies: propStrategies,\n}: GlobalSearchDialogProps) {\n const router = useRouter()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Use configured strategies or fall back to defaults\n const enabledStrategies = React.useMemo(() => {\n if (propStrategies && propStrategies.length > 0) {\n return propStrategies\n }\n return DEFAULT_STRATEGIES\n }, [propStrategies])\n\n const resetState = React.useCallback(() => {\n setQuery('')\n setResults([])\n setError(null)\n setSelectedIndex(0)\n setLoading(false)\n }, [])\n\n React.useEffect(() => {\n if (!open) {\n resetState()\n return\n }\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, resetState])\n\n React.useEffect(() => {\n const shortcut = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n setOpen((prev) => !prev)\n }\n }\n window.addEventListener('keydown', shortcut)\n return () => window.removeEventListener('keydown', shortcut)\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const focusTimer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(focusTimer)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n\n abortRef.current?.abort()\n if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [open, query, enabledStrategies, t])\n\n const openResult = React.useCallback((result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n setOpen(false)\n }, [router])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n event.preventDefault()\n openResult(results[selectedIndex])\n return\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setSelectedIndex((prev) => (prev + 1) % Math.max(results.length || 1, 1))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setOpen(false)\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n }, [results, selectedIndex, openResult])\n\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n // Check if vector search is enabled but not configured\n const showVectorWarning = !embeddingConfigured && enabledStrategies.includes('vector') && !error\n\n // Check if selected result has a navigable link\n const selectedResult = results[selectedIndex]\n const selectedHasLink = selectedResult ? pickPrimaryLink(selectedResult) !== null : false\n\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(true)} className=\"hidden sm:inline-flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n <span>{t('search.dialog.actions.search')}</span>\n <span className=\"ml-2 rounded border px-1 text-xs text-muted-foreground\">\u2318K</span>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"sm:hidden\"\n onClick={() => setOpen(true)}\n aria-label={t('search.dialog.actions.openGlobalSearch')}\n >\n <Search className=\"h-4 w-4\" />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-xl p-0\" aria-describedby=\"global-search-description\">\n <DialogTitle className=\"sr-only\">\n {t('search.dialog.title', 'Global Search')}\n </DialogTitle>\n <span id=\"global-search-description\" className=\"sr-only\">\n {t('search.dialog.instructions')}\n </span>\n <div className=\"flex flex-col gap-3 border-b px-4 pb-3 pt-12\">\n <div className=\"flex items-center gap-2 rounded border border-border bg-background px-3 py-2 transition-colors focus-within:border-primary\">\n <Search className=\"h-4 w-4 text-muted-foreground\" />\n <TypedInput\n ref={inputRef}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.input.placeholder')}\n className=\"border-none px-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n autoFocus\n />\n {loading && <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />}\n </div>\n\n {error ? (\n <p className=\"rounded bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning ? (\n <p className=\"rounded bg-amber-100 dark:bg-amber-900/20 px-3 py-2 text-sm text-amber-800 dark:text-amber-200\">{missingConfigMessage}</p>\n ) : null}\n {showScopeHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n </div>\n <div ref={listRef} className=\"max-h-96 overflow-y-auto px-2 pb-3\">\n {results.length === 0 && !loading && !error ? (\n <div className=\"px-4 py-6 text-sm text-muted-foreground\">\n {query.trim().length < MIN_QUERY_LENGTH\n ? t('search.dialog.empty.hint')\n : t('search.dialog.empty.none')}\n </div>\n ) : null}\n <ul className=\"flex flex-col\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <li key={`${result.entityId}:${result.recordId}`} data-active={isActive}>\n <button\n type=\"button\"\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n className={cn(\n 'w-full rounded-lg px-4 py-3 text-left transition border',\n isActive\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-transparent hover:border-muted-foreground/30 hover:bg-muted/60',\n !hasLink && 'opacity-60'\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={cn('font-medium text-base whitespace-normal break-all', !hasLink && 'text-muted-foreground')}>{presenter?.title ?? result.recordId}</span>\n <span className=\"rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground\">\n {formatEntityId(result.entityId)}\n </span>\n {!hasLink && (\n <span className=\"rounded-full border border-amber-500/50 bg-amber-50 dark:bg-amber-900/20 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-400\">\n {t('search.dialog.noLink')}\n </span>\n )}\n </div>\n {presenter?.subtitle ? (\n <div className=\"text-sm text-muted-foreground whitespace-normal break-words\">{presenter.subtitle}</div>\n ) : null}\n {normalizeLinks(result.links).length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {normalizeLinks(result.links).map((link) => (\n <span\n key={`${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-primary text-primary'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n {Icon ? (\n <div className=\"flex flex-col items-end gap-2\">\n <Icon className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n ) : null}\n </div>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n <div className=\"flex items-center justify-between border-t px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n {selectedResult && !selectedHasLink\n ? t('search.dialog.noLinkHint')\n : t('search.dialog.shortcuts.hint')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)}>\n {t('search.dialog.actions.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openResult(results[selectedIndex])}\n disabled={!results.length || !selectedHasLink}\n >\n {t('search.dialog.actions.openSelected')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nexport default GlobalSearchDialog\nconst TypedInput = Input as React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport {\n Search,\n Loader2,\n Zap,\n User,\n Users,\n Building,\n StickyNote,\n Briefcase,\n CheckSquare,\n FileText,\n Mail,\n Phone,\n Calendar,\n Clock,\n Star,\n Tag,\n Flag,\n Heart,\n Bookmark,\n Package,\n Truck,\n ShoppingCart,\n CreditCard,\n DollarSign,\n Target,\n Award,\n Trophy,\n Rocket,\n Lightbulb,\n MessageSquare,\n Bell,\n Settings,\n Globe,\n MapPin,\n Link,\n Folder,\n Database,\n Activity,\n} from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport type { SearchResult, SearchResultLink, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { ForbiddenError } from '@open-mercato/ui/backend/utils/api'\nimport { fetchGlobalSearchResults } from '../utils'\n\nconst MIN_QUERY_LENGTH = 2\n\n/** Default strategies used when none are configured */\nconst DEFAULT_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\nfunction normalizeLinks(links?: SearchResultLink[] | null): SearchResultLink[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string')\n}\n\nfunction pickPrimaryLink(result: SearchResult): string | null {\n if (result.url) return result.url\n const links = normalizeLinks(result.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n bolt: Zap,\n zap: Zap,\n user: User,\n users: Users,\n building: Building,\n 'sticky-note': StickyNote,\n briefcase: Briefcase,\n 'check-square': CheckSquare,\n 'file-text': FileText,\n mail: Mail,\n phone: Phone,\n calendar: Calendar,\n clock: Clock,\n star: Star,\n tag: Tag,\n flag: Flag,\n heart: Heart,\n bookmark: Bookmark,\n package: Package,\n truck: Truck,\n 'shopping-cart': ShoppingCart,\n 'credit-card': CreditCard,\n 'dollar-sign': DollarSign,\n target: Target,\n award: Award,\n trophy: Trophy,\n rocket: Rocket,\n lightbulb: Lightbulb,\n 'message-square': MessageSquare,\n bell: Bell,\n settings: Settings,\n globe: Globe,\n 'map-pin': MapPin,\n link: Link,\n folder: Folder,\n database: Database,\n activity: Activity,\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n return ICON_MAP[name.toLowerCase()] ?? null\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n return `${humanizeSegment(module)} \u00B7 ${humanizeSegment(entity)}`\n}\n\nexport type GlobalSearchDialogProps = {\n /** Whether embedding provider is configured for vector search */\n embeddingConfigured: boolean\n /** Message to show when embedding is not configured */\n missingConfigMessage: string\n /** Enabled strategies from tenant configuration (optional - uses defaults if not provided) */\n enabledStrategies?: SearchStrategyId[]\n}\n\nexport function GlobalSearchDialog({\n embeddingConfigured,\n missingConfigMessage,\n enabledStrategies: propStrategies,\n}: GlobalSearchDialogProps) {\n const router = useRouter()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<SearchResult[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n // Use configured strategies or fall back to defaults\n const enabledStrategies = React.useMemo(() => {\n if (propStrategies && propStrategies.length > 0) {\n return propStrategies\n }\n return DEFAULT_STRATEGIES\n }, [propStrategies])\n\n const resetState = React.useCallback(() => {\n setQuery('')\n setResults([])\n setError(null)\n setSelectedIndex(0)\n setLoading(false)\n }, [])\n\n React.useEffect(() => {\n if (!open) {\n resetState()\n return\n }\n const handler = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [open, resetState])\n\n React.useEffect(() => {\n const shortcut = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n event.preventDefault()\n setOpen((prev) => !prev)\n }\n }\n window.addEventListener('keydown', shortcut)\n return () => window.removeEventListener('keydown', shortcut)\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const focusTimer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(focusTimer)\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n\n abortRef.current?.abort()\n if (query.trim().length < MIN_QUERY_LENGTH) {\n setResults([])\n setError(null)\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n const handle = setTimeout(async () => {\n try {\n const data = await fetchGlobalSearchResults(query, {\n limit: 10,\n signal: controller.signal,\n })\n setResults(data.results)\n setError(data.error ?? null)\n setSelectedIndex(0)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n const abortError = err as { name?: string }\n if (abortError?.name === 'AbortError') return\n if (err instanceof ForbiddenError) {\n setError(t('search.dialog.errors.noPermission'))\n } else {\n setError(err instanceof Error ? err.message : t('search.dialog.errors.searchFailed'))\n }\n setResults([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 220)\n\n return () => {\n clearTimeout(handle)\n controller.abort()\n }\n }, [open, query, enabledStrategies, t])\n\n const openResult = React.useCallback((result: SearchResult | undefined) => {\n if (!result) return\n const href = pickPrimaryLink(result)\n if (!href) return\n router.push(href)\n setOpen(false)\n }, [router])\n\n const handleKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n event.preventDefault()\n openResult(results[selectedIndex])\n return\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setSelectedIndex((prev) => (prev + 1) % Math.max(results.length || 1, 1))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setSelectedIndex((prev) => {\n if (!results.length) return 0\n return prev <= 0 ? results.length - 1 : prev - 1\n })\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setOpen(false)\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n const target = results[selectedIndex]\n openResult(target)\n return\n }\n }, [results, selectedIndex, openResult])\n\n React.useEffect(() => {\n const container = listRef.current\n const active = container?.querySelector<HTMLElement>('[data-active=\"true\"]')\n if (!container || !active) return\n const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect()\n const { top: activeTop, bottom: activeBottom } = active.getBoundingClientRect()\n if (activeTop < containerTop) {\n container.scrollTop -= containerTop - activeTop\n } else if (activeBottom > containerBottom) {\n container.scrollTop += activeBottom - containerBottom\n }\n }, [selectedIndex])\n\n // Check if vector search is enabled but not configured\n const showVectorWarning = !embeddingConfigured && enabledStrategies.includes('vector') && !error\n\n // Check if selected result has a navigable link\n const selectedResult = results[selectedIndex]\n const selectedHasLink = selectedResult ? pickPrimaryLink(selectedResult) !== null : false\n\n return (\n <>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(true)} className=\"hidden sm:inline-flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n <span>{t('search.dialog.actions.search')}</span>\n <span className=\"ml-2 rounded border px-1 text-xs text-muted-foreground\">\u2318K</span>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"sm:hidden\"\n onClick={() => setOpen(true)}\n aria-label={t('search.dialog.actions.openGlobalSearch')}\n >\n <Search className=\"h-4 w-4\" />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-xl p-0\" aria-describedby=\"global-search-description\">\n <DialogTitle className=\"sr-only\">\n {t('search.dialog.title', 'Global Search')}\n </DialogTitle>\n <span id=\"global-search-description\" className=\"sr-only\">\n {t('search.dialog.instructions')}\n </span>\n <div className=\"flex flex-col gap-3 border-b px-4 pb-3 pt-12\">\n <div className=\"flex items-center gap-2 rounded border border-border bg-background px-3 py-2 transition-colors focus-within:border-primary\">\n <Search className=\"h-4 w-4 text-muted-foreground\" />\n <TypedInput\n ref={inputRef}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={t('search.dialog.input.placeholder')}\n className=\"border-none px-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n autoFocus\n />\n {loading && <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />}\n </div>\n\n {error ? (\n <p className=\"rounded bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error}</p>\n ) : null}\n {showVectorWarning ? (\n <p className=\"rounded bg-status-warning-bg px-3 py-2 text-sm text-status-warning-text\">{missingConfigMessage}</p>\n ) : null}\n {showScopeHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </p>\n ) : null}\n </div>\n <div ref={listRef} className=\"max-h-96 overflow-y-auto px-2 pb-3\">\n {results.length === 0 && !loading && !error ? (\n <div className=\"px-4 py-6 text-sm text-muted-foreground\">\n {query.trim().length < MIN_QUERY_LENGTH\n ? t('search.dialog.empty.hint')\n : t('search.dialog.empty.none')}\n </div>\n ) : null}\n <ul className=\"flex flex-col\">\n {results.map((result, index) => {\n const presenter = result.presenter\n const isActive = index === selectedIndex\n const hasLink = pickPrimaryLink(result) !== null\n const Icon = presenter?.icon ? resolveIcon(presenter.icon) : null\n return (\n <li key={`${result.entityId}:${result.recordId}`} data-active={isActive}>\n <button\n type=\"button\"\n onClick={() => openResult(result)}\n onMouseEnter={() => setSelectedIndex(index)}\n className={cn(\n 'w-full rounded-lg px-4 py-3 text-left transition border',\n isActive\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-transparent hover:border-muted-foreground/30 hover:bg-muted/50',\n !hasLink && 'opacity-60'\n )}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={cn('font-medium text-base whitespace-normal break-all', !hasLink && 'text-muted-foreground')}>{presenter?.title ?? result.recordId}</span>\n <span className=\"rounded-full border border-muted-foreground/30 px-2 py-0.5 text-xs text-muted-foreground\">\n {formatEntityId(result.entityId)}\n </span>\n {!hasLink && (\n <span className=\"rounded-full border border-status-warning-border bg-status-warning-bg px-2 py-0.5 text-xs text-status-warning-text\">\n {t('search.dialog.noLink')}\n </span>\n )}\n </div>\n {presenter?.subtitle ? (\n <div className=\"text-sm text-muted-foreground whitespace-normal break-words\">{presenter.subtitle}</div>\n ) : null}\n {normalizeLinks(result.links).length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {normalizeLinks(result.links).map((link) => (\n <span\n key={`${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-primary text-primary'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n {Icon ? (\n <div className=\"flex flex-col items-end gap-2\">\n <Icon className=\"h-5 w-5 text-muted-foreground\" />\n </div>\n ) : null}\n </div>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n <div className=\"flex items-center justify-between border-t px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n {selectedResult && !selectedHasLink\n ? t('search.dialog.noLinkHint')\n : t('search.dialog.shortcuts.hint')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)}>\n {t('search.dialog.actions.cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => openResult(results[selectedIndex])}\n disabled={!results.length || !selectedHasLink}\n >\n {t('search.dialog.actions.openSelected')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nexport default GlobalSearchDialog\nconst TypedInput = Input as React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -83,13 +83,13 @@ function createColumns(t) {
83
83
  function getStrategyColorClass(strategy) {
84
84
  switch (strategy) {
85
85
  case "fulltext":
86
- return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";
86
+ return "bg-status-info-bg text-status-info-text";
87
87
  case "vector":
88
88
  return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200";
89
89
  case "tokens":
90
- return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200";
90
+ return "bg-status-success-bg text-status-success-text";
91
91
  default:
92
- return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";
92
+ return "bg-status-neutral-bg text-status-neutral-text";
93
93
  }
94
94
  }
95
95
  function normalizeLinks(links) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/frontend/components/HybridSearchTable.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport * as LucideIcons from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { fetchHybridSearchResults } from '../utils'\n\ntype Row = {\n entityId: string\n recordId: string\n source: string\n score: number | null\n url: string | null\n presenter: SearchResult['presenter'] | null\n links: SearchResult['links'] | null\n metadata: Record<string, unknown> | null\n}\n\nconst MIN_QUERY_LENGTH = 2\nconst ALL_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\ntype Translator = (\n key: string,\n fallbackOrParams?: string | Record<string, string | number>,\n params?: Record<string, string | number>\n) => string\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'title',\n header: () => t('search.table.columns.result', 'Result'),\n cell: ({ row }) => {\n const item = row.original\n const title = resolveRowTitle(item)\n const iconName = item.presenter?.icon\n const Icon = iconName ? resolveIcon(iconName) : null\n const typeLabel = formatEntityId(item.entityId)\n const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata)\n const links = normalizeLinks(item.links)\n return (\n <div className=\"flex flex-col\">\n <div className=\"flex items-start gap-3\">\n {Icon ? <Icon className=\"mt-0.5 h-5 w-5 text-muted-foreground\" /> : null}\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium whitespace-normal break-all\">{title}</span>\n <span className=\"rounded border border-muted-foreground/40 px-2 py-0.5 text-xs text-muted-foreground\">\n {typeLabel}\n </span>\n </div>\n {snapshot ? (\n <span className=\"text-sm text-muted-foreground whitespace-normal break-words\">{snapshot}</span>\n ) : null}\n {links.length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {links.map((link) => (\n <span\n key={`${item.entityId}:${item.recordId}:${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-primary text-primary'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </div>\n )\n },\n meta: { priority: 1 },\n },\n {\n id: 'source',\n header: () => t('search.table.columns.source', 'Source'),\n cell: ({ row }) => {\n const source = row.original.source\n const colorClass = getStrategyColorClass(source)\n return (\n <span className={cn('rounded px-2 py-0.5 text-xs font-medium', colorClass)}>\n {source}\n </span>\n )\n },\n meta: { priority: 2 },\n },\n {\n id: 'score',\n header: () => t('search.table.columns.score', 'Score'),\n cell: ({ row }) => <span>{row.original.score != null ? row.original.score.toFixed(2) : '\u2014'}</span>,\n meta: { priority: 2 },\n },\n ]\n}\n\nfunction getStrategyColorClass(strategy: string): string {\n switch (strategy) {\n case 'fulltext':\n return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'\n case 'vector':\n return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'\n case 'tokens':\n return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'\n default:\n return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'\n }\n}\n\nfunction normalizeLinks(links?: Row['links']): { href: string; label?: string; kind?: string }[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string') as Array<{ href: string; label?: string; kind?: string }>\n}\n\nfunction toPascalCase(input: string): string {\n return input\n .split(/[-_ ]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n const key = toPascalCase(name)\n const candidate = (LucideIcons as Record<string, unknown>)[key]\n if (typeof candidate === 'function') {\n return candidate as LucideIcon\n }\n return null\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n const moduleLabel = humanizeSegment(module)\n const entityLabel = humanizeSegment(entity)\n return `${moduleLabel} \u00B7 ${entityLabel}`\n}\n\nfunction resolveRowTitle(row: Row): string {\n const presenterTitle = row.presenter?.title\n if (typeof presenterTitle === 'string') {\n const trimmed = presenterTitle.trim()\n if (trimmed.length) return trimmed\n }\n return row.recordId\n}\n\nfunction extractSnapshot(metadata: Record<string, unknown> | null): string | null {\n if (!metadata) return null\n const candidateKeys = ['snapshot', 'summary', 'description', 'body', 'content', 'note']\n for (const key of candidateKeys) {\n const value = metadata[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n}\n\nfunction pickPrimaryLink(row: Row): string | null {\n if (row.url) return row.url\n const links = normalizeLinks(row.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction normalizeErrorMessage(input: unknown, fallback?: string): string | null {\n const fallbackMessage = typeof fallback === 'string' && fallback.trim().length ? fallback.trim() : null\n let message: string | null = null\n if (typeof input === 'string') {\n message = input\n } else if (input instanceof Error && typeof input.message === 'string') {\n message = input.message\n }\n if (message) {\n const trimmed = message.trim()\n if (trimmed.length) {\n const sanitized = trimmed.replace(/^\\[[^\\]]+\\]\\s*/, '').trim()\n if (sanitized.length) return sanitized\n }\n }\n return fallbackMessage\n}\n\ntype HybridSearchTableProps = {\n /** Show strategy selector checkboxes (default: false - hidden from regular users) */\n showStrategySelector?: boolean\n /** Show source column in results (default: false - hidden from regular users) */\n showSourceColumn?: boolean\n}\n\nexport function HybridSearchTable({\n showStrategySelector = false,\n showSourceColumn = false,\n}: HybridSearchTableProps = {}) {\n const router = useRouter()\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [searchValue, setSearchValue] = React.useState('')\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [timing, setTiming] = React.useState<number | null>(null)\n const [strategiesUsed, setStrategiesUsed] = React.useState<string[]>([])\n const [enabledStrategies, setEnabledStrategies] = React.useState<Set<SearchStrategyId>>(\n new Set(ALL_STRATEGIES)\n )\n const debounceRef = React.useRef<number | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const columns = React.useMemo(() => {\n const allColumns = createColumns(t)\n if (!showSourceColumn) {\n return allColumns.filter((col) => col.id !== 'source')\n }\n return allColumns\n }, [t, showSourceColumn])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n const toggleStrategy = React.useCallback((strategy: SearchStrategyId) => {\n setEnabledStrategies((prev) => {\n const next = new Set(prev)\n if (next.has(strategy)) {\n next.delete(strategy)\n } else {\n next.add(strategy)\n }\n return next\n })\n }, [])\n\n const openRow = React.useCallback(\n (row: Row) => {\n const href = pickPrimaryLink(row)\n if (!href) return\n router.push(href)\n },\n [router]\n )\n\n React.useEffect(() => {\n const trimmed = searchValue.trim()\n abortRef.current?.abort()\n if (debounceRef.current) {\n window.clearTimeout(debounceRef.current)\n debounceRef.current = null\n }\n\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(null)\n setLoading(false)\n return\n }\n\n if (enabledStrategies.size === 0) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(t('search.table.errors.noSources', 'Select at least one search source'))\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n debounceRef.current = window.setTimeout(async () => {\n try {\n const data = await fetchHybridSearchResults(trimmed, {\n limit: 50,\n strategies: Array.from(enabledStrategies),\n signal: controller.signal,\n })\n const mapped = data.results.map<Row>((item) => ({\n entityId: item.entityId,\n recordId: item.recordId,\n source: item.source,\n score: typeof item.score === 'number' ? item.score : null,\n url: item.url ?? null,\n presenter: item.presenter ?? null,\n links: item.links ?? null,\n metadata: (item.metadata as Record<string, unknown> | null) ?? null,\n }))\n setRows(mapped)\n setTiming(data.timing)\n setStrategiesUsed(data.strategiesUsed)\n const message = data.error ? normalizeErrorMessage(data.error, t('search.table.errors.searchFailed', 'Search failed')) : null\n setError(message ?? null)\n setPage(1)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n if ((err as { name?: string })?.name === 'AbortError') return\n setError(normalizeErrorMessage(err, t('search.table.errors.searchFailed', 'Search failed')))\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 250)\n\n return () => {\n controller.abort()\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n }\n }, [searchValue, enabledStrategies, t])\n\n React.useEffect(() => {\n if (!error) return\n flash(error, 'error')\n }, [error])\n\n return (\n <div className=\"flex w-full flex-col gap-4\">\n {/* Source Selector - only shown when showStrategySelector is true */}\n {showStrategySelector && (\n <div className=\"flex flex-wrap items-center gap-4 rounded-lg border bg-muted/50 p-3\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('search.table.sources', 'Sources:')}\n </span>\n {ALL_STRATEGIES.map((strategy) => (\n <label key={strategy} className=\"flex cursor-pointer items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"size-4 rounded border-gray-300\"\n checked={enabledStrategies.has(strategy)}\n onChange={() => toggleStrategy(strategy)}\n />\n <span\n className={cn(\n 'rounded px-2 py-0.5 text-xs font-medium',\n getStrategyColorClass(strategy)\n )}\n >\n {strategy}\n </span>\n </label>\n ))}\n </div>\n )}\n\n {/* Stats Bar */}\n {timing !== null && rows.length > 0 && (\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{rows.length} {t('search.table.stats.results', 'results')}</span>\n <span>{timing}ms</span>\n {/* Only show sources when strategy selector is visible */}\n {showStrategySelector && strategiesUsed.length > 0 && (\n <span>\n {t('search.table.stats.sources', 'Sources:')} {strategiesUsed.join(', ')}\n </span>\n )}\n </div>\n )}\n\n {/* Error Alert */}\n {error ? (\n <div\n role=\"alert\"\n className=\"w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\"\n >\n {error}\n </div>\n ) : null}\n\n {showScopeHint ? (\n <div className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </div>\n ) : null}\n\n {/* Data Table */}\n <DataTable<Row>\n title={t('search.table.title', 'Search')}\n columns={columns}\n data={rows}\n searchValue={searchValue}\n onSearchChange={(value) => {\n setSearchValue(value)\n setPage(1)\n }}\n searchPlaceholder={t('search.table.searchPlaceholder', 'Search across all strategies...')}\n isLoading={loading}\n pagination={{ page, pageSize: rows.length || 1, total: rows.length, totalPages: 1, onPageChange: setPage }}\n onRowClick={(row) => openRow(row)}\n rowActions={(row) => {\n const primaryHref = pickPrimaryLink(row)\n if (!primaryHref) return null\n return <RowActions items={[{ id: 'open', label: t('search.table.actions.open', 'Open'), href: primaryHref }]} />\n }}\n embedded\n />\n </div>\n )\n}\n\nexport default HybridSearchTable\n"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport * as LucideIcons from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { SearchResult, SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n getCurrentOrganizationScope,\n subscribeOrganizationScopeChanged,\n} from '@open-mercato/shared/lib/frontend/organizationEvents'\nimport { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'\nimport { parseSelectedOrganizationCookie } from '@open-mercato/core/modules/directory/utils/scopeCookies'\nimport { fetchHybridSearchResults } from '../utils'\n\ntype Row = {\n entityId: string\n recordId: string\n source: string\n score: number | null\n url: string | null\n presenter: SearchResult['presenter'] | null\n links: SearchResult['links'] | null\n metadata: Record<string, unknown> | null\n}\n\nconst MIN_QUERY_LENGTH = 2\nconst ALL_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\ntype Translator = (\n key: string,\n fallbackOrParams?: string | Record<string, string | number>,\n params?: Record<string, string | number>\n) => string\n\nfunction hasActiveOrganizationSelection(): boolean {\n const fromEvent = getCurrentOrganizationScope().organizationId\n if (typeof fromEvent === 'string' && fromEvent.trim().length > 0) return true\n\n const cookieHeader = typeof document === 'undefined' ? null : document.cookie\n const cookieValue = parseSelectedOrganizationCookie(cookieHeader)\n if (!cookieValue) return false\n return !isAllOrganizationsSelection(cookieValue);\n}\n\nfunction createColumns(t: Translator): ColumnDef<Row>[] {\n return [\n {\n id: 'title',\n header: () => t('search.table.columns.result', 'Result'),\n cell: ({ row }) => {\n const item = row.original\n const title = resolveRowTitle(item)\n const iconName = item.presenter?.icon\n const Icon = iconName ? resolveIcon(iconName) : null\n const typeLabel = formatEntityId(item.entityId)\n const snapshot = item.presenter?.subtitle ?? extractSnapshot(item.metadata)\n const links = normalizeLinks(item.links)\n return (\n <div className=\"flex flex-col\">\n <div className=\"flex items-start gap-3\">\n {Icon ? <Icon className=\"mt-0.5 h-5 w-5 text-muted-foreground\" /> : null}\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium whitespace-normal break-all\">{title}</span>\n <span className=\"rounded border border-muted-foreground/40 px-2 py-0.5 text-xs text-muted-foreground\">\n {typeLabel}\n </span>\n </div>\n {snapshot ? (\n <span className=\"text-sm text-muted-foreground whitespace-normal break-words\">{snapshot}</span>\n ) : null}\n {links.length ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n {links.map((link) => (\n <span\n key={`${item.entityId}:${item.recordId}:${link.href}`}\n className={cn(\n 'rounded-full border px-2 py-0.5 text-xs',\n link.kind === 'primary'\n ? 'border-primary text-primary'\n : 'border-muted-foreground/40 text-muted-foreground'\n )}\n >\n {link.label ?? link.href}\n </span>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n </div>\n )\n },\n meta: { priority: 1 },\n },\n {\n id: 'source',\n header: () => t('search.table.columns.source', 'Source'),\n cell: ({ row }) => {\n const source = row.original.source\n const colorClass = getStrategyColorClass(source)\n return (\n <span className={cn('rounded px-2 py-0.5 text-xs font-medium', colorClass)}>\n {source}\n </span>\n )\n },\n meta: { priority: 2 },\n },\n {\n id: 'score',\n header: () => t('search.table.columns.score', 'Score'),\n cell: ({ row }) => <span>{row.original.score != null ? row.original.score.toFixed(2) : '\u2014'}</span>,\n meta: { priority: 2 },\n },\n ]\n}\n\nfunction getStrategyColorClass(strategy: string): string {\n switch (strategy) {\n case 'fulltext':\n return 'bg-status-info-bg text-status-info-text'\n case 'vector':\n return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'\n case 'tokens':\n return 'bg-status-success-bg text-status-success-text'\n default:\n return 'bg-status-neutral-bg text-status-neutral-text'\n }\n}\n\nfunction normalizeLinks(links?: Row['links']): { href: string; label?: string; kind?: string }[] {\n if (!Array.isArray(links)) return []\n return links.filter((link) => typeof link?.href === 'string') as Array<{ href: string; label?: string; kind?: string }>\n}\n\nfunction toPascalCase(input: string): string {\n return input\n .split(/[-_ ]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\nfunction resolveIcon(name?: string): LucideIcon | null {\n if (!name) return null\n const key = toPascalCase(name)\n const candidate = (LucideIcons as Record<string, unknown>)[key]\n if (typeof candidate === 'function') {\n return candidate as LucideIcon\n }\n return null\n}\n\nfunction humanizeSegment(segment: string): string {\n return segment\n .split(/[_-]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(' ')\n}\n\nfunction formatEntityId(entityId: string): string {\n if (!entityId.includes(':')) return humanizeSegment(entityId)\n const [module, entity] = entityId.split(':')\n const moduleLabel = humanizeSegment(module)\n const entityLabel = humanizeSegment(entity)\n return `${moduleLabel} \u00B7 ${entityLabel}`\n}\n\nfunction resolveRowTitle(row: Row): string {\n const presenterTitle = row.presenter?.title\n if (typeof presenterTitle === 'string') {\n const trimmed = presenterTitle.trim()\n if (trimmed.length) return trimmed\n }\n return row.recordId\n}\n\nfunction extractSnapshot(metadata: Record<string, unknown> | null): string | null {\n if (!metadata) return null\n const candidateKeys = ['snapshot', 'summary', 'description', 'body', 'content', 'note']\n for (const key of candidateKeys) {\n const value = metadata[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n}\n\nfunction pickPrimaryLink(row: Row): string | null {\n if (row.url) return row.url\n const links = normalizeLinks(row.links)\n if (!links.length) return null\n const primary = links.find((link) => link.kind === 'primary')\n return (primary ?? links[0]).href\n}\n\nfunction normalizeErrorMessage(input: unknown, fallback?: string): string | null {\n const fallbackMessage = typeof fallback === 'string' && fallback.trim().length ? fallback.trim() : null\n let message: string | null = null\n if (typeof input === 'string') {\n message = input\n } else if (input instanceof Error && typeof input.message === 'string') {\n message = input.message\n }\n if (message) {\n const trimmed = message.trim()\n if (trimmed.length) {\n const sanitized = trimmed.replace(/^\\[[^\\]]+\\]\\s*/, '').trim()\n if (sanitized.length) return sanitized\n }\n }\n return fallbackMessage\n}\n\ntype HybridSearchTableProps = {\n /** Show strategy selector checkboxes (default: false - hidden from regular users) */\n showStrategySelector?: boolean\n /** Show source column in results (default: false - hidden from regular users) */\n showSourceColumn?: boolean\n}\n\nexport function HybridSearchTable({\n showStrategySelector = false,\n showSourceColumn = false,\n}: HybridSearchTableProps = {}) {\n const router = useRouter()\n const t = useT()\n const [showScopeHint, setShowScopeHint] = React.useState<boolean>(() => hasActiveOrganizationSelection())\n const [searchValue, setSearchValue] = React.useState('')\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [timing, setTiming] = React.useState<number | null>(null)\n const [strategiesUsed, setStrategiesUsed] = React.useState<string[]>([])\n const [enabledStrategies, setEnabledStrategies] = React.useState<Set<SearchStrategyId>>(\n new Set(ALL_STRATEGIES)\n )\n const debounceRef = React.useRef<number | null>(null)\n const abortRef = React.useRef<AbortController | null>(null)\n const columns = React.useMemo(() => {\n const allColumns = createColumns(t)\n if (!showSourceColumn) {\n return allColumns.filter((col) => col.id !== 'source')\n }\n return allColumns\n }, [t, showSourceColumn])\n\n React.useEffect(() => {\n setShowScopeHint(hasActiveOrganizationSelection())\n return subscribeOrganizationScopeChanged((detail) => {\n setShowScopeHint(Boolean(detail.organizationId && detail.organizationId.trim().length > 0))\n })\n }, [])\n\n const toggleStrategy = React.useCallback((strategy: SearchStrategyId) => {\n setEnabledStrategies((prev) => {\n const next = new Set(prev)\n if (next.has(strategy)) {\n next.delete(strategy)\n } else {\n next.add(strategy)\n }\n return next\n })\n }, [])\n\n const openRow = React.useCallback(\n (row: Row) => {\n const href = pickPrimaryLink(row)\n if (!href) return\n router.push(href)\n },\n [router]\n )\n\n React.useEffect(() => {\n const trimmed = searchValue.trim()\n abortRef.current?.abort()\n if (debounceRef.current) {\n window.clearTimeout(debounceRef.current)\n debounceRef.current = null\n }\n\n if (trimmed.length < MIN_QUERY_LENGTH) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(null)\n setLoading(false)\n return\n }\n\n if (enabledStrategies.size === 0) {\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n setError(t('search.table.errors.noSources', 'Select at least one search source'))\n setLoading(false)\n return\n }\n\n const controller = new AbortController()\n abortRef.current = controller\n setLoading(true)\n\n debounceRef.current = window.setTimeout(async () => {\n try {\n const data = await fetchHybridSearchResults(trimmed, {\n limit: 50,\n strategies: Array.from(enabledStrategies),\n signal: controller.signal,\n })\n const mapped = data.results.map<Row>((item) => ({\n entityId: item.entityId,\n recordId: item.recordId,\n source: item.source,\n score: typeof item.score === 'number' ? item.score : null,\n url: item.url ?? null,\n presenter: item.presenter ?? null,\n links: item.links ?? null,\n metadata: (item.metadata as Record<string, unknown> | null) ?? null,\n }))\n setRows(mapped)\n setTiming(data.timing)\n setStrategiesUsed(data.strategiesUsed)\n const message = data.error ? normalizeErrorMessage(data.error, t('search.table.errors.searchFailed', 'Search failed')) : null\n setError(message ?? null)\n setPage(1)\n } catch (err: unknown) {\n if (controller.signal.aborted) return\n if ((err as { name?: string })?.name === 'AbortError') return\n setError(normalizeErrorMessage(err, t('search.table.errors.searchFailed', 'Search failed')))\n setRows([])\n setTiming(null)\n setStrategiesUsed([])\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }, 250)\n\n return () => {\n controller.abort()\n if (debounceRef.current) window.clearTimeout(debounceRef.current)\n }\n }, [searchValue, enabledStrategies, t])\n\n React.useEffect(() => {\n if (!error) return\n flash(error, 'error')\n }, [error])\n\n return (\n <div className=\"flex w-full flex-col gap-4\">\n {/* Source Selector - only shown when showStrategySelector is true */}\n {showStrategySelector && (\n <div className=\"flex flex-wrap items-center gap-4 rounded-lg border bg-muted/50 p-3\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('search.table.sources', 'Sources:')}\n </span>\n {ALL_STRATEGIES.map((strategy) => (\n <label key={strategy} className=\"flex cursor-pointer items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"size-4 rounded border-gray-300\"\n checked={enabledStrategies.has(strategy)}\n onChange={() => toggleStrategy(strategy)}\n />\n <span\n className={cn(\n 'rounded px-2 py-0.5 text-xs font-medium',\n getStrategyColorClass(strategy)\n )}\n >\n {strategy}\n </span>\n </label>\n ))}\n </div>\n )}\n\n {/* Stats Bar */}\n {timing !== null && rows.length > 0 && (\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>{rows.length} {t('search.table.stats.results', 'results')}</span>\n <span>{timing}ms</span>\n {/* Only show sources when strategy selector is visible */}\n {showStrategySelector && strategiesUsed.length > 0 && (\n <span>\n {t('search.table.stats.sources', 'Sources:')} {strategiesUsed.join(', ')}\n </span>\n )}\n </div>\n )}\n\n {/* Error Alert */}\n {error ? (\n <div\n role=\"alert\"\n className=\"w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\"\n >\n {error}\n </div>\n ) : null}\n\n {showScopeHint ? (\n <div className=\"text-xs text-muted-foreground\">\n {t('search.scopeHint.currentOrg', 'Scoped to current organization')}\n </div>\n ) : null}\n\n {/* Data Table */}\n <DataTable<Row>\n title={t('search.table.title', 'Search')}\n columns={columns}\n data={rows}\n searchValue={searchValue}\n onSearchChange={(value) => {\n setSearchValue(value)\n setPage(1)\n }}\n searchPlaceholder={t('search.table.searchPlaceholder', 'Search across all strategies...')}\n isLoading={loading}\n pagination={{ page, pageSize: rows.length || 1, total: rows.length, totalPages: 1, onPageChange: setPage }}\n onRowClick={(row) => openRow(row)}\n rowActions={(row) => {\n const primaryHref = pickPrimaryLink(row)\n if (!primaryHref) return null\n return <RowActions items={[{ id: 'open', label: t('search.table.actions.open', 'Open'), href: primaryHref }]} />\n }}\n embedded\n />\n </div>\n )\n}\n\nexport default HybridSearchTable\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -142,24 +142,24 @@ function FulltextSearchSection({
142
142
  /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
143
143
  ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
144
144
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 rounded-md border border-border bg-muted/30", children: [
145
- /* @__PURE__ */ jsx("div", { className: `flex h-10 w-10 items-center justify-center rounded-full ${fulltextConfig?.configured ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-400"}`, children: getStrategyIcon() }),
145
+ /* @__PURE__ */ jsx("div", { className: `flex h-10 w-10 items-center justify-center rounded-full ${fulltextConfig?.configured ? "bg-status-success-bg text-status-success-icon" : "bg-status-warning-bg text-status-warning-icon"}`, children: getStrategyIcon() }),
146
146
  /* @__PURE__ */ jsxs("div", { children: [
147
147
  /* @__PURE__ */ jsxs("p", { className: "font-medium", children: [
148
148
  t("search.settings.fulltext.driver", "Current Driver"),
149
149
  ": ",
150
150
  fulltextConfig?.driver ? "Meilisearch" : t("search.settings.fulltext.noDriver", "None")
151
151
  ] }),
152
- /* @__PURE__ */ jsx("p", { className: `text-sm ${fulltextConfig?.configured ? "text-emerald-600 dark:text-emerald-400" : "text-amber-600 dark:text-amber-400"}`, children: fulltextConfig?.configured ? t("search.settings.fulltext.ready", "Ready to use") : t("search.settings.fulltext.notReady", "Not configured - set environment variables below") })
152
+ /* @__PURE__ */ jsx("p", { className: `text-sm ${fulltextConfig?.configured ? "text-status-success-text" : "text-status-warning-text"}`, children: fulltextConfig?.configured ? t("search.settings.fulltext.ready", "Ready to use") : t("search.settings.fulltext.notReady", "Not configured - set environment variables below") })
153
153
  ] })
154
154
  ] }),
155
155
  /* @__PURE__ */ jsxs("div", { children: [
156
156
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-2", children: t("search.settings.fulltext.envVars", "Required Environment Variables") }),
157
157
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: fulltextConfig?.envVars && Object.entries(fulltextConfig.envVars).map(([key, status]) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 rounded-md border border-border", children: [
158
- /* @__PURE__ */ jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 mt-0.5 ${status.set ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400"}`, children: status.set ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }),
158
+ /* @__PURE__ */ jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 mt-0.5 ${status.set ? "bg-status-success-bg text-status-success-icon" : "bg-status-error-bg text-status-error-icon"}`, children: status.set ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) }),
159
159
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
160
160
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
161
161
  /* @__PURE__ */ jsx("code", { className: "text-sm font-mono bg-muted px-1.5 py-0.5 rounded", children: key }),
162
- /* @__PURE__ */ jsx("span", { className: `text-xs ${status.set ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400"}`, children: status.set ? t("search.settings.fulltext.envSet", "Set") : t("search.settings.fulltext.envMissing", "Missing") })
162
+ /* @__PURE__ */ jsx("span", { className: `text-xs ${status.set ? "text-status-success-text" : "text-status-error-text"}`, children: status.set ? t("search.settings.fulltext.envSet", "Set") : t("search.settings.fulltext.envMissing", "Missing") })
163
163
  ] }),
164
164
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: status.hint })
165
165
  ] })
@@ -171,7 +171,7 @@ function FulltextSearchSection({
171
171
  /* @__PURE__ */ jsx("code", { className: "text-xs font-mono bg-muted px-1.5 py-0.5 rounded", children: key }),
172
172
  /* @__PURE__ */ jsxs("div", { className: "flex-1 text-xs text-muted-foreground", children: [
173
173
  /* @__PURE__ */ jsx("span", { children: status.hint }),
174
- status.set ? /* @__PURE__ */ jsxs("span", { className: "ml-2 text-emerald-600 dark:text-emerald-400", children: [
174
+ status.set ? /* @__PURE__ */ jsxs("span", { className: "ml-2 text-status-success-text", children: [
175
175
  "(",
176
176
  t("search.settings.fulltext.currentValue", "Current"),
177
177
  ": ",
@@ -187,9 +187,9 @@ function FulltextSearchSection({
187
187
  ] })
188
188
  ] }, key)) })
189
189
  ] }),
190
- /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
191
- /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
192
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-blue-800 dark:text-blue-200", children: [
190
+ /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-status-info-bg border border-status-info-border", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
191
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-status-info-icon flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
192
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-status-info-text", children: [
193
193
  /* @__PURE__ */ jsx("p", { className: "font-medium mb-1", children: t("search.settings.fulltext.howTo", "How to set up") }),
194
194
  /* @__PURE__ */ jsx("p", { className: "text-xs", children: t("search.settings.fulltext.howToDescription", "Add these variables to your .env file or deployment environment. You can use a hosted Meilisearch instance or run it locally with Docker.") }),
195
195
  /* @__PURE__ */ jsxs(
@@ -198,7 +198,7 @@ function FulltextSearchSection({
198
198
  href: "https://www.meilisearch.com/docs/learn/getting_started/quick_start",
199
199
  target: "_blank",
200
200
  rel: "noopener noreferrer",
201
- className: "text-xs text-blue-600 dark:text-blue-400 hover:underline mt-1 inline-block",
201
+ className: "text-xs text-status-info-text hover:underline mt-1 inline-block",
202
202
  children: [
203
203
  t("search.settings.fulltext.learnMore", "Learn more: Meilisearch Quick Start"),
204
204
  " \u2192"
@@ -211,22 +211,22 @@ function FulltextSearchSection({
211
211
  /* @__PURE__ */ jsx(TabsContent, { value: "index", children: loading || fulltextConfigLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
212
212
  /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
213
213
  /* @__PURE__ */ jsx("span", { children: t("search.settings.loadingLabel", "Loading settings...") })
214
- ] }) : !fulltextConfig?.configured ? /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
215
- /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
214
+ ] }) : !fulltextConfig?.configured ? /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-status-warning-bg border border-status-warning-border", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
215
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-status-warning-icon flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
216
216
  /* @__PURE__ */ jsxs("div", { children: [
217
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-amber-800 dark:text-amber-200", children: t("search.settings.fulltextNotConfigured", "Full-text search driver not configured") }),
218
- /* @__PURE__ */ jsx("p", { className: "text-xs text-amber-700 dark:text-amber-300 mt-1", children: t("search.settings.fulltextNotConfiguredHint", "Configure the required environment variables in the Configuration tab to enable indexing.") })
217
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-status-warning-text", children: t("search.settings.fulltextNotConfigured", "Full-text search driver not configured") }),
218
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-status-warning-text mt-1", children: t("search.settings.fulltextNotConfiguredHint", "Configure the required environment variables in the Configuration tab to enable indexing.") })
219
219
  ] })
220
220
  ] }) }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
221
221
  fulltextStats ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border p-4 max-w-xs", children: [
222
222
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.documentsLabel", "Documents") }),
223
223
  /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold", children: fulltextStats.numberOfDocuments.toLocaleString() })
224
224
  ] }) : /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-muted/50", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.noIndexMessage", "No index found for this tenant. Click 'Full Reindex' to create one.") }) }),
225
- fulltextReindexLock && /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
226
- /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400" }),
225
+ fulltextReindexLock && /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-status-info-bg border border-status-info-border", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
226
+ /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "flex-shrink-0 mt-0.5 text-status-info-icon" }),
227
227
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
228
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-blue-800 dark:text-blue-200", children: t("search.settings.reindexInProgress", "Reindex operation in progress") }),
229
- /* @__PURE__ */ jsx("p", { className: "text-xs text-blue-700 dark:text-blue-300 mt-1", children: t("search.settings.reindexInProgressDetails", "Action: {{action}} | Started {{minutes}} minutes ago", {
228
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-status-info-text", children: t("search.settings.reindexInProgress", "Reindex operation in progress") }),
229
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-status-info-text mt-1", children: t("search.settings.reindexInProgressDetails", "Action: {{action}} | Started {{minutes}} minutes ago", {
230
230
  action: fulltextReindexLock.action,
231
231
  minutes: fulltextReindexLock.elapsedMinutes
232
232
  }) })
@@ -295,11 +295,11 @@ function FulltextSearchSection({
295
295
  ] }) : activityLogs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-4 rounded-md bg-muted/50 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("search.settings.activity.noLogs", "No recent indexing activity") }) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-80 overflow-y-auto", children: activityLogs.map((log) => /* @__PURE__ */ jsx(
296
296
  "div",
297
297
  {
298
- className: `p-2 rounded-md text-sm ${log.level === "error" ? "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800" : "bg-muted/50"}`,
298
+ className: `p-2 rounded-md text-sm ${log.level === "error" ? "bg-status-error-bg border border-status-error-border" : "bg-muted/50"}`,
299
299
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
300
- log.level === "error" && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
300
+ log.level === "error" && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-status-error-icon flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
301
301
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
302
- /* @__PURE__ */ jsx("p", { className: `text-xs ${log.level === "error" ? "text-red-800 dark:text-red-200" : "text-foreground"}`, children: log.message }),
302
+ /* @__PURE__ */ jsx("p", { className: `text-xs ${log.level === "error" ? "text-status-error-text" : "text-foreground"}`, children: log.message }),
303
303
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
304
304
  (() => {
305
305
  const d = new Date(log.occurredAt);
@@ -329,17 +329,17 @@ function FulltextSearchSection({
329
329
  ) })
330
330
  ] })
331
331
  ] }),
332
- showReindexDialog && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg", children: [
332
+ showReindexDialog && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-modal flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg", children: [
333
333
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 mb-4", children: [
334
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/40", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }) }),
334
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-status-warning-bg", children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-status-warning-icon", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }) }),
335
335
  /* @__PURE__ */ jsxs("div", { children: [
336
336
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: getDialogContent(showReindexDialog).title }),
337
337
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: getDialogContent(showReindexDialog).description })
338
338
  ] })
339
339
  ] }),
340
- /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 rounded-md bg-amber-50 dark:bg-amber-900/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
341
- /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
342
- /* @__PURE__ */ jsx("p", { className: "text-sm text-amber-800 dark:text-amber-200", children: getDialogContent(showReindexDialog).warning })
340
+ /* @__PURE__ */ jsx("div", { className: "mb-4 p-3 rounded-md bg-status-warning-bg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
341
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-status-warning-icon flex-shrink-0 mt-0.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
342
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-status-warning-text", children: getDialogContent(showReindexDialog).warning })
343
343
  ] }) }),
344
344
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3", children: [
345
345
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleReindexCancel, children: t("search.settings.cancelLabel", "Cancel") }),