@fayz-ai/admin 0.11.0 → 0.14.0

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 (69) hide show
  1. package/dist/{CommandPalette-2AL327XQ.js → CommandPalette-U7MMA2WH.js} +27 -4
  2. package/dist/CommandPalette-U7MMA2WH.js.map +1 -0
  3. package/dist/SubscriptionPage-4KP7DKA2.js +7 -0
  4. package/dist/{SubscriptionPage-NLZAZO4P.js.map → SubscriptionPage-4KP7DKA2.js.map} +1 -1
  5. package/dist/app/AdminShell.d.ts.map +1 -1
  6. package/dist/app/admin-app.d.ts.map +1 -1
  7. package/dist/app/config.d.ts +7 -0
  8. package/dist/app/config.d.ts.map +1 -1
  9. package/dist/{chunk-5O2UJAQ7.js → chunk-B6NVEKAJ.js} +2 -6
  10. package/dist/chunk-B6NVEKAJ.js.map +1 -0
  11. package/dist/components/billing/SubscriptionPage.d.ts.map +1 -1
  12. package/dist/components/chat/AssistantInvite.d.ts +10 -0
  13. package/dist/components/chat/AssistantInvite.d.ts.map +1 -1
  14. package/dist/components/chat/ChatFab.d.ts.map +1 -1
  15. package/dist/components/chat/ConfirmActionCard.d.ts.map +1 -1
  16. package/dist/components/chat/fab-anchor.d.ts +15 -0
  17. package/dist/components/chat/fab-anchor.d.ts.map +1 -0
  18. package/dist/components/connect/ConnectorsTab.d.ts.map +1 -1
  19. package/dist/components/connect/connector-map.generated.d.ts.map +1 -1
  20. package/dist/components/layout/CommandPalette.d.ts +5 -2
  21. package/dist/components/layout/CommandPalette.d.ts.map +1 -1
  22. package/dist/components/organization/PermissionProfilesTab.d.ts.map +1 -1
  23. package/dist/components/organization/TeamTab.d.ts.map +1 -1
  24. package/dist/components/plugins/PluginSettingsPanel.d.ts +11 -1
  25. package/dist/components/plugins/PluginSettingsPanel.d.ts.map +1 -1
  26. package/dist/components/settings/FieldRulesSettings.d.ts.map +1 -1
  27. package/dist/components/settings/SettingsPage.d.ts.map +1 -1
  28. package/dist/components/shared/PersonLink.d.ts +8 -1
  29. package/dist/components/shared/PersonLink.d.ts.map +1 -1
  30. package/dist/components/shared/RecordPickerModal.d.ts +7 -1
  31. package/dist/components/shared/RecordPickerModal.d.ts.map +1 -1
  32. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  33. package/dist/crud/CrudListView.d.ts +17 -4
  34. package/dist/crud/CrudListView.d.ts.map +1 -1
  35. package/dist/crud/CrudPage.d.ts +4 -1
  36. package/dist/crud/CrudPage.d.ts.map +1 -1
  37. package/dist/crud/archetype-lookup.d.ts +13 -0
  38. package/dist/crud/archetype-lookup.d.ts.map +1 -1
  39. package/dist/crud/archetypes/OrderDetailView.d.ts.map +1 -1
  40. package/dist/crud/archetypes/OrderFormView.d.ts +4 -1
  41. package/dist/crud/archetypes/OrderFormView.d.ts.map +1 -1
  42. package/dist/crud/archetypes/OrderListView.d.ts +1 -1
  43. package/dist/crud/archetypes/OrderListView.d.ts.map +1 -1
  44. package/dist/hooks/useAuth.d.ts.map +1 -1
  45. package/dist/hooks/useGlobalSearch.d.ts +3 -1
  46. package/dist/hooks/useGlobalSearch.d.ts.map +1 -1
  47. package/dist/hooks/useModuleNavigation.d.ts.map +1 -1
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1542 -1128
  51. package/dist/index.js.map +1 -1
  52. package/dist/lib/assistant-invite.d.ts +18 -0
  53. package/dist/lib/assistant-invite.d.ts.map +1 -1
  54. package/dist/lib/cache.d.ts +2 -17
  55. package/dist/lib/cache.d.ts.map +1 -1
  56. package/dist/plugins/QuickActionsButton.d.ts +13 -4
  57. package/dist/plugins/QuickActionsButton.d.ts.map +1 -1
  58. package/dist/plugins/WidgetSlot.d.ts +12 -1
  59. package/dist/plugins/WidgetSlot.d.ts.map +1 -1
  60. package/dist/projects/ProjectsSettings.d.ts.map +1 -1
  61. package/dist/stores/createCrudStore.d.ts +8 -0
  62. package/dist/stores/createCrudStore.d.ts.map +1 -1
  63. package/dist/theme/utils.d.ts.map +1 -1
  64. package/dist/types/entity-lookup.d.ts +9 -0
  65. package/dist/types/entity-lookup.d.ts.map +1 -1
  66. package/package.json +9 -9
  67. package/dist/CommandPalette-2AL327XQ.js.map +0 -1
  68. package/dist/SubscriptionPage-NLZAZO4P.js +0 -7
  69. package/dist/chunk-5O2UJAQ7.js.map +0 -1
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ export { getScheduleBlockConfig, setScheduleBlockConfig, subscribeScheduleBlockC
2
2
  import { getFayzSupabaseClientOptional, createFayzSupabaseClient, createSupabaseOrgAdapter, createMockOrgAdapter, getFayzSupabaseClient } from './chunk-OI7RYSTM.js';
3
3
  export { createFayzSupabaseClient, createMockOrgAdapter, createSupabaseOrgAdapter, getFayzSupabaseClient, getFayzSupabaseClientOptional } from './chunk-OI7RYSTM.js';
4
4
  export { ImportWizard } from './chunk-UMRN6427.js';
5
- import { useLimitGuard, invalidateLimit, useLimit, useUpgradeModalStore, checkAccess, guardLimit, cn as cn$1, featureDisplayName, PlanFeatureItem, useAccessOptional, AccessProvider, CORE_LIMIT_DECLARATIONS, entityDerivedLimitDeclarations, getPlanEntitlements, resolvePlanCurrency } from './chunk-5O2UJAQ7.js';
6
- export { AccessProvider, UPGRADE_URL, agentDenial, checkAccess, getAccessSnapshot, guardLimit, invalidateLimit, isEntitledByPlan, resolveAccess, useAccess, useAccessOptional, useLimit, useLimitGuard, useModuleNavAccess, useUpgradeModalStore } from './chunk-5O2UJAQ7.js';
5
+ import { useLimitGuard, invalidateLimit, useLimit, useUpgradeModalStore, cn as cn$1, checkAccess, guardLimit, featureDisplayName, PlanFeatureItem, useAccessOptional, AccessProvider, CORE_LIMIT_DECLARATIONS, entityDerivedLimitDeclarations, getPlanEntitlements, resolvePlanCurrency } from './chunk-B6NVEKAJ.js';
6
+ export { AccessProvider, UPGRADE_URL, agentDenial, checkAccess, getAccessSnapshot, guardLimit, invalidateLimit, isEntitledByPlan, resolveAccess, useAccess, useAccessOptional, useLimit, useLimitGuard, useModuleNavAccess, useUpgradeModalStore } from './chunk-B6NVEKAJ.js';
7
7
  import { useTranslation as useTranslation$1 } from './chunk-YUHCC7JY.js';
8
8
  import { useBillingStore, resolvePlanBadge } from './chunk-VN5NTK26.js';
9
9
  export { useBillingStore } from './chunk-VN5NTK26.js';
@@ -11,23 +11,23 @@ import { useOrganizationStore, useTenantOptional, OrgProvider, useOrgAdapterOpti
11
11
  export { ORG_CACHE_TTL_MS, OrgProvider, clearOrgSnapshot, getPersistedOrgId, readOrgSnapshot, useOrgAdapter, useOrgAdapterOptional, useOrganizationStore, useTenant, useTenantOptional } from './chunk-PNAJODZM.js';
12
12
  import { usePermissionOptional, usePermissionsStore, PermissionsProvider } from './chunk-VTEP2AQX.js';
13
13
  export { PermissionsProvider, useHasPermission, usePermission, usePermissionOptional, usePermissions, usePermissionsStore } from './chunk-VTEP2AQX.js';
14
- import * as React63 from 'react';
15
- import React63__default, { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
16
- import { hashRouterAdapter, getSupabaseClientOptional, deriveEntityKey, registerEntity, getActiveTenantId, resolveDataProvider, bulkRemove, emitDomainEvent, entityEventKey, useTranslation, useDataChanged, exportCSV, formatDateTime as formatDateTime$1, formatDate as formatDate$1, formatCurrency as formatCurrency$1, emitDataChanged, bulkCreate, matchesRange, isConnectorCategory, reportAppError, usePluginRuntimeOptional, getWidgetsForZone, resolvePluginComponent, setCurrentLocale, resolvePluginRuntime, PluginRuntimeProvider, I18nProvider, getAllEntities, defineApp, registerScaffold, getCountry, DEFAULT_COUNTRY, useActiveTenantId, maskPhone, useConnectorsForPlugin, renderApp as renderApp$1, unmaskPhone, parseRangeFilter, isFilterObject, connectionKey, useManifest, usePluginRuntime, installGlobalErrorHandlers, mergeTranslations, coreTranslations, createMockProvider, listConnections, connectorPlanes, connectorPermissions, connectorEntities, preferredPlane, connectorDirection, searchEverything, registerEventSink, getComponent, getPluginFactory, connectorSettings, groupedPermissions, invocationPlane, missingRequiredSettings, isSettingSet, connectorSubscribesTo, eventKeyMatches } from '@fayz-ai/core';
14
+ import * as React65 from 'react';
15
+ import React65__default, { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
16
+ import { hashRouterAdapter, getSupabaseClientOptional, deriveEntityKey, registerEntity, getActiveTenantId, resolveDataProvider, bulkRemove, emitDomainEvent, entityEventKey, useTranslation, useDataChanged, emitDataChanged, exportCSV, formatDateTime as formatDateTime$1, formatDate as formatDate$1, formatCurrency as formatCurrency$1, bulkCreate, matchesRange, isConnectorCategory, reportAppError, usePluginRuntimeOptional, getWidgetsForZone, resolvePluginComponent, setCurrentLocale, resolvePluginRuntime, PluginRuntimeProvider, I18nProvider, getAllEntities, defineApp, registerScaffold, getCountry, DEFAULT_COUNTRY, useActiveTenantId, maskPhone, useConnectorsForPlugin, renderApp as renderApp$1, unmaskPhone, parseRangeFilter, isFilterObject, connectionKey, useManifest, usePluginRuntime, installGlobalErrorHandlers, mergeTranslations, coreTranslations, attachPersistentCache, getPersistentCache, createMockProvider, listConnections, connectorPlanes, connectorPermissions, connectorEntities, preferredPlane, connectorDirection, searchEverything, registerEventSink, getComponent, getPluginFactory, connectorSettings, groupedPermissions, invocationPlane, missingRequiredSettings, isSettingSet, connectorSubscribesTo, eventKeyMatches } from '@fayz-ai/core';
17
17
  export { ConnectorRuntimeTokenUnavailableError, createSafeDataProvider, formatDate, formatDateTime, getActiveLocale, getActiveTenantId, getConnectorRuntimeTokenSource, getCurrentLocale, getSupabaseClientOptional, preloadLocale, resolveBackendEnv, setConnectorRuntimeTokenSource, setCurrentLocale, tl, useActiveTenantId } from '@fayz-ai/core';
18
18
  import { usePasswordSetupGate, SetPasswordPage, AuthGate, AuthProvider, createAuthRuntime } from '@fayz-ai/plugin-auth';
19
19
  export { LoginPage } from '@fayz-ai/plugin-auth';
20
- import { toast, usePageTrailParent, navigateToCrumb, Button, PageHeader, ConfirmDialog, FilterChip, Dropdown, DropdownTrigger, Hint, cn, DropdownContent, DropdownLabel, DropdownItem, DropdownSeparator, HeaderTabs, StatTiles, BulkActionBar, Card, DataTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Badge, getLucideIcon, ICON_MAP, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, useLayoutStore, Skeleton, Input, useSaveBar, useBackHandler, SubpageHeader, Separator, Tabs, TabsList, TabsTrigger, TabsContent, CardContent, ToastProvider, NavTransitionProvider, ListView, Thumbnail, SearchSelect, CurrencyInput, SearchField, ChevronPager, useModuleLayout, Popover, PopoverTrigger, PopoverContent, PhoneLink, normalizePhone, ModalPortal, ModalOverlay, ModalBody, SearchCombobox, usePageTrail, AppShellSkeleton, ModuleLayoutProvider, AppShell, PageTransition, createFayzTheme, Checkbox, MarkdownEditor, Switch, Markdown, PageTrail, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, CardTitle, CardDescription, DropdownSub, DropdownSubTrigger, DropdownSubContent, DropdownRadioGroup, DropdownRadioItem, CardHeader, CardFooter } from '@fayz-ai/ui';
20
+ import { toast, usePageTrailParent, navigateToCrumb, Button, PageHeader, ConfirmDialog, FilterChip, Dropdown, DropdownTrigger, Hint, cn, DropdownContent, DropdownLabel, DropdownItem, DropdownSeparator, RefreshButton, PageHeaderActions, PageTabs, StatTiles, Card, BulkActionBar, DataTable, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Badge, useModuleLayout, ICON_MAP, getLucideIcon, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, useLayoutStore, Skeleton, Input, useSaveBar, useBackHandler, SubpageHeader, Separator, Tabs, TabsList, TabsTrigger, TabsContent, CardContent, ToastProvider, NavTransitionProvider, ListView, Thumbnail, SearchSelect, CurrencyInput, SearchField, ChevronPager, Popover, PopoverTrigger, PopoverContent, PhoneLink, normalizePhone, ModalPortal, ModalOverlay, ModalBody, SearchCombobox, usePageTrail, AppShellSkeleton, ModuleLayoutProvider, AppShell, PageTransition, PageRail, RailNavItem, createFayzTheme, Checkbox, MarkdownEditor, Switch, Markdown, PageTrail, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, CardTitle, CardDescription, DropdownSub, DropdownSubTrigger, DropdownSubContent, DropdownRadioGroup, DropdownRadioItem, CardHeader, CardFooter } from '@fayz-ai/ui';
21
21
  export { createFayzTheme, fayzThemePresets } from '@fayz-ai/ui';
22
22
  import * as LucideIcons3 from 'lucide-react';
23
- import { Globe, Store, LayoutTemplate, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, RefreshCw, Download, ChevronLeft, ChevronRight, AlertTriangle, Pencil, Inbox, Package, X, Sparkles, Plug, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, ArrowLeft, SquarePen, Settings2, MessageSquare, RotateCcw, Copy, ChevronsUpDown, ExternalLink, PenSquare, Plus, XCircle, Clock, Star, Edit, Eye, ShoppingBag, ChevronDown, Settings, Mail, MessageCircle, Cake, Calendar, TableProperties, Mic, BookOpen, Paperclip, ShieldCheck, Infinity, Play, Square, StickyNote, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, File, FileImage, FileSpreadsheet, AlertCircle, CreditCard, Users, MapPin, SlidersHorizontal, Puzzle, Building2, User, Shield, Palette, ShieldAlert, UserCog, LogOut, Bell, PinOff, Pin, ImagePlus, Database, Radio, Zap, Save, UserPlus, MoreHorizontal, Sun, Moon, Monitor, Languages, CheckCheck, Info, CheckSquare2, Camera, KeyRound, Bug, Wrench, Heart, Briefcase, BarChart3, Tag, Truck } from 'lucide-react';
23
+ import { Globe, Store, LayoutTemplate, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, Download, ChevronLeft, ChevronRight, AlertTriangle, Pencil, Plus, ChevronDown, Inbox, Package, X, Sparkles, Plug, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, ArrowLeft, SquarePen, Settings2, MessageSquare, RotateCcw, Copy, ChevronsUpDown, ExternalLink, PenSquare, RefreshCw, XCircle, Clock, Star, Edit, Eye, ShoppingBag, Settings, Mail, MessageCircle, Cake, Calendar, TableProperties, Mic, BookOpen, Paperclip, ShieldCheck, Infinity, Play, Square, StickyNote, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, File, FileImage, FileSpreadsheet, AlertCircle, CreditCard, Users, MapPin, SlidersHorizontal, Puzzle, Building2, User, Shield, Palette, ShieldAlert, UserCog, LogOut, Bell, PinOff, Pin, ImagePlus, Database, Radio, Zap, Save, UserPlus, MoreHorizontal, Sun, Moon, Monitor, Languages, CheckCheck, Info, CheckSquare2, Camera, KeyRound, Bug, Wrench, Heart, Briefcase, BarChart3, Tag, Truck } from 'lucide-react';
24
24
  import { create, useStore } from 'zustand';
25
25
  import { useAuthStore, useAuth } from '@fayz-ai/auth';
26
26
  export { useAuth } from '@fayz-ai/auth';
27
+ import { createPortal } from 'react-dom';
28
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
27
29
  import { profileHasSystemPermission, mergeLimitDeclarations } from '@fayz-ai/core/access';
28
30
  import { createFayzAgentClient } from '@fayz-ai/sdk/agent';
29
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
30
- import { createPortal } from 'react-dom';
31
31
  import { getConnectorCredentialSink, ConnectorOAuthStarterMissingError } from '@fayz-ai/sdk';
32
32
  export { ConnectorCredentialSinkError, ConnectorOAuthError, ConnectorOAuthStarterMissingError, createFayzConnectorCredentialSink, createFayzConnectorOAuthStarter, fayz, fayzConnectorOAuth, getConnectorCredentialSink, getConnectorOAuthStarter, setConnectorCredentialSink, setConnectorOAuthStarter, startConnectorOAuth } from '@fayz-ai/sdk';
33
33
  import { createFayzEventsClient } from '@fayz-ai/sdk/events';
@@ -843,7 +843,7 @@ function getServerVersion() {
843
843
  return 0;
844
844
  }
845
845
  function useAssistantActivity() {
846
- React63.useSyncExternalStore(subscribe, getVersion, getServerVersion);
846
+ React65.useSyncExternalStore(subscribe, getVersion, getServerVersion);
847
847
  return pickActivity(activities);
848
848
  }
849
849
  function pickActivity(list) {
@@ -853,18 +853,18 @@ function pickActivity(list) {
853
853
  return pool.find((a) => a.kind === "recording") ?? pool[0];
854
854
  }
855
855
  function useAssistantLaunchers() {
856
- React63.useSyncExternalStore(subscribe, getVersion, getServerVersion);
856
+ React65.useSyncExternalStore(subscribe, getVersion, getServerVersion);
857
857
  return launchers;
858
858
  }
859
859
  function usePublishAssistantActivity(activity) {
860
860
  const id = activity?.id;
861
861
  const key = activity ? `${activity.id}|${activity.kind}|${activity.label}|${activity.state}|${activity.elapsedMs ?? ""}|${(activity.actions ?? []).map((a) => a.intent).join(",")}` : null;
862
- const ref = React63.useRef(activity);
862
+ const ref = React65.useRef(activity);
863
863
  ref.current = activity;
864
- React63.useEffect(() => {
864
+ React65.useEffect(() => {
865
865
  if (ref.current) publishAssistantActivity(ref.current);
866
866
  }, [key]);
867
- React63.useEffect(() => {
867
+ React65.useEffect(() => {
868
868
  if (!id) return;
869
869
  return () => withdrawAssistantActivity(id);
870
870
  }, [id]);
@@ -872,12 +872,12 @@ function usePublishAssistantActivity(activity) {
872
872
  function useRegisterAssistantLauncher(launcher) {
873
873
  const id = launcher?.id;
874
874
  const key = launcher ? `${launcher.id}|${launcher.label}|${launcher.disabled ? "1" : "0"}` : null;
875
- const ref = React63.useRef(launcher);
875
+ const ref = React65.useRef(launcher);
876
876
  ref.current = launcher;
877
- React63.useEffect(() => {
877
+ React65.useEffect(() => {
878
878
  if (ref.current) registerAssistantLauncher(ref.current);
879
879
  }, [key]);
880
- React63.useEffect(() => {
880
+ React65.useEffect(() => {
881
881
  if (!id) return;
882
882
  return () => unregisterAssistantLauncher(id);
883
883
  }, [id]);
@@ -941,17 +941,17 @@ function useRightRailPanel(panel) {
941
941
  const icon = panel?.icon;
942
942
  const badgeCount = panel?.badge?.count;
943
943
  const badgeTone = panel?.badge?.tone;
944
- const initial = React63.useRef({ label, icon });
945
- React63.useEffect(() => {
944
+ const initial = React65.useRef({ label, icon });
945
+ React65.useEffect(() => {
946
946
  if (!id || !Component2) return;
947
947
  register({ id, label: initial.current.label ?? id, order, Component: Component2, icon: initial.current.icon });
948
948
  return () => unregister(id);
949
949
  }, [id, order, Component2, register, unregister]);
950
- React63.useEffect(() => {
950
+ React65.useEffect(() => {
951
951
  if (!id) return;
952
952
  updateMeta(id, { label, icon });
953
953
  }, [id, label, icon, updateMeta]);
954
- React63.useEffect(() => {
954
+ React65.useEffect(() => {
955
955
  if (!id) return;
956
956
  updateBadge(id, badgeCount !== void 0 ? { count: badgeCount, tone: badgeTone } : void 0);
957
957
  }, [id, badgeCount, badgeTone, updateBadge]);
@@ -997,15 +997,29 @@ function rememberDismissal(id) {
997
997
  var useAssistantInviteStore = create((set, get) => ({
998
998
  invite: null,
999
999
  stage: "dock",
1000
+ dockBox: null,
1001
+ leaving: null,
1000
1002
  setInvite: (invite, id) => {
1001
1003
  if (!invite) {
1002
1004
  if (id && get().invite?.id !== id) return;
1003
- set({ invite: null, stage: "dock" });
1005
+ const { invite: going, dockBox } = get();
1006
+ set({
1007
+ invite: null,
1008
+ stage: "dock",
1009
+ dockBox: null,
1010
+ leaving: going && dockBox ? { invite: going, box: dockBox } : null
1011
+ });
1004
1012
  return;
1005
1013
  }
1006
1014
  const hero = invite.auto !== false && !dismissedIds().includes(invite.id);
1007
- set({ invite, stage: hero ? "hero" : "dock" });
1015
+ set({ invite, stage: hero ? "hero" : "dock", leaving: null });
1016
+ },
1017
+ setDockBox: (box) => {
1018
+ const current = get().dockBox;
1019
+ if (current?.left === box?.left && current?.width === box?.width) return;
1020
+ set({ dockBox: box });
1008
1021
  },
1022
+ endLeave: () => set({ leaving: null }),
1009
1023
  dismiss: () => {
1010
1024
  const { invite } = get();
1011
1025
  if (invite) rememberDismissal(invite.id);
@@ -1013,85 +1027,480 @@ var useAssistantInviteStore = create((set, get) => ({
1013
1027
  }
1014
1028
  }));
1015
1029
 
1016
- // src/app/core-entities.ts
1017
- var addressEntity = {
1018
- name: "Address",
1019
- namePlural: "Addresses",
1020
- icon: "MapPin",
1021
- data: {
1022
- table: "addresses",
1023
- tenantScoped: true,
1024
- tenantIdColumn: "tenant_id",
1025
- searchColumns: ["label", "street", "district", "city", "postal_code", "recipient"]
1026
- },
1027
- fields: [
1028
- // owner_id/owner_type are how an address attaches to its person. They are
1029
- // required and described rather than hidden: the agent reaches this table
1030
- // from a record it already has in hand ("adiciona o endereço X pra ele"),
1031
- // so the id it must pass is the one it just searched for or created.
1032
- {
1033
- key: "owner_id",
1034
- label: "Owner record id",
1035
- type: "text",
1036
- required: true,
1037
- hint: "Id of the person (client, contact, supplier, staff\u2026) this address belongs to."
1030
+ // src/components/chat/fab-anchor.ts
1031
+ var FAB_MARGIN_RIGHT = 21;
1032
+ var FAB_SIZE = 48;
1033
+ var DOCK_MAX_WIDTH = 640;
1034
+ var MORPH_MS = 380;
1035
+ var MORPH_EASE = "cubic-bezier(0.22, 1, 0.36, 1)";
1036
+ function fabRightEdge(railOpen, railWidth) {
1037
+ if (typeof window === "undefined") return 0;
1038
+ return window.innerWidth - (railOpen ? railWidth + FAB_MARGIN_RIGHT : FAB_MARGIN_RIGHT);
1039
+ }
1040
+ function AssistantInvite({ className, ...def }) {
1041
+ const setInvite = useAssistantInviteStore((s) => s.setInvite);
1042
+ const stage = useAssistantInviteStore((s) => s.stage);
1043
+ const dismiss = useAssistantInviteStore((s) => s.dismiss);
1044
+ const railOpen = useRightRailStore((s) => s.open);
1045
+ const hasAssistant = useRightRailStore((s) => s.panels.some((p) => p.id === "chat"));
1046
+ const { id, persona, headline, pitch, description, placeholder, auto } = def;
1047
+ const suggestions = def.suggestions;
1048
+ const suggestionKey = JSON.stringify(suggestions ?? []);
1049
+ React65.useEffect(() => {
1050
+ if (!hasAssistant) return;
1051
+ setInvite({
1052
+ id,
1053
+ persona,
1054
+ headline,
1055
+ pitch,
1056
+ description,
1057
+ placeholder,
1058
+ auto,
1059
+ suggestions: JSON.parse(suggestionKey)
1060
+ });
1061
+ return () => setInvite(null, id);
1062
+ }, [hasAssistant, id, persona, headline, pitch, description, placeholder, auto, suggestionKey, setInvite]);
1063
+ const anchorRef = React65.useRef(null);
1064
+ const [box, setBox] = React65.useState(null);
1065
+ const docked = stage === "dock" && !railOpen && hasAssistant;
1066
+ const showing = hasAssistant && (stage === "hero" || docked);
1067
+ React65.useEffect(() => {
1068
+ if (!showing) return;
1069
+ const el = anchorRef.current;
1070
+ if (!el) return;
1071
+ const measure = () => {
1072
+ const rect = el.getBoundingClientRect();
1073
+ setBox({ left: rect.left, width: rect.width, top: rect.top });
1074
+ };
1075
+ measure();
1076
+ const observer = new ResizeObserver(measure);
1077
+ observer.observe(el);
1078
+ window.addEventListener("resize", measure);
1079
+ window.addEventListener("scroll", measure, true);
1080
+ return () => {
1081
+ observer.disconnect();
1082
+ window.removeEventListener("resize", measure);
1083
+ window.removeEventListener("scroll", measure, true);
1084
+ };
1085
+ }, [showing]);
1086
+ const setDockBox = useAssistantInviteStore((s) => s.setDockBox);
1087
+ const wide = useWideViewport();
1088
+ React65.useEffect(() => {
1089
+ if (!docked || !wide || !box) {
1090
+ setDockBox(null);
1091
+ return;
1092
+ }
1093
+ setDockBox({ left: box.left + box.width / 2, width: Math.min(box.width, DOCK_MAX_WIDTH) });
1094
+ }, [docked, wide, box?.left, box?.width, setDockBox]);
1095
+ if (!hasAssistant) return null;
1096
+ const floating = box && stage === "hero" ? /* @__PURE__ */ jsx(InviteHero, { def, box, onDismiss: dismiss }) : box && docked ? /* @__PURE__ */ jsx(InviteDock, { def, left: box.left + box.width / 2, maxWidth: box.width }) : null;
1097
+ return /* @__PURE__ */ jsx("div", { ref: anchorRef, className: cn$1("h-0 w-full", className), "aria-hidden": !showing, children: floating && typeof document !== "undefined" ? createPortal(floating, document.body) : null });
1098
+ }
1099
+ function useAsk() {
1100
+ const queuePrompt = useChatStore((s) => s.queuePrompt);
1101
+ const dismiss = useAssistantInviteStore((s) => s.dismiss);
1102
+ return React65.useCallback(
1103
+ (text) => {
1104
+ const trimmed = text.trim();
1105
+ if (!trimmed) return;
1106
+ dismiss();
1107
+ queuePrompt(trimmed);
1038
1108
  },
1109
+ [queuePrompt, dismiss]
1110
+ );
1111
+ }
1112
+ function InviteHero({
1113
+ def,
1114
+ box,
1115
+ onDismiss
1116
+ }) {
1117
+ const { t } = useTranslation$1();
1118
+ const user = useAuthStore((s) => s.user);
1119
+ const ask = useAsk();
1120
+ const [draft, setDraft] = React65.useState("");
1121
+ const name = (user?.fullName || user?.email || "").split(/[\s@]/)[0];
1122
+ React65.useEffect(() => {
1123
+ const onKey = (event) => {
1124
+ if (event.key === "Escape") onDismiss();
1125
+ };
1126
+ window.addEventListener("keydown", onKey);
1127
+ return () => window.removeEventListener("keydown", onKey);
1128
+ }, [onDismiss]);
1129
+ return /* @__PURE__ */ jsxs(
1130
+ "section",
1039
1131
  {
1040
- key: "owner_type",
1041
- label: "Owner kind",
1042
- type: "select",
1043
- options: ["person", "location", "tenant"],
1044
- hint: "Defaults to 'person'. Only set it for an address that belongs to a business location or to the tenant itself."
1132
+ className: cn$1(
1133
+ "fixed z-30 flex flex-col items-center justify-center overflow-y-auto px-4 pb-14 text-center",
1134
+ "bg-background/95 backdrop-blur-sm motion-safe:animate-in motion-safe:fade-in-0"
1135
+ ),
1136
+ style: { left: box.left, width: box.width, top: box.top, bottom: 0 },
1137
+ "aria-label": def.persona ?? t("chat.defaultTitle"),
1138
+ children: [
1139
+ /* @__PURE__ */ jsx(
1140
+ "button",
1141
+ {
1142
+ type: "button",
1143
+ onClick: onDismiss,
1144
+ "aria-label": t("assistant.invite.dismiss"),
1145
+ title: t("assistant.invite.dismiss"),
1146
+ className: "absolute right-1 top-2 inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground/60 transition-colors hover:bg-muted hover:text-foreground",
1147
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1148
+ }
1149
+ ),
1150
+ /* @__PURE__ */ jsx("h2", { className: "max-w-2xl text-balance text-[26px] font-semibold leading-tight tracking-tight text-foreground sm:text-[32px]", children: def.headline.replace("{{name}}", name) }),
1151
+ /* @__PURE__ */ jsx(
1152
+ InviteComposer,
1153
+ {
1154
+ className: "mt-7 w-full max-w-3xl",
1155
+ placeholder: def.placeholder ?? t("assistant.invite.placeholder"),
1156
+ value: draft,
1157
+ onChange: setDraft,
1158
+ onSubmit: () => {
1159
+ ask(draft);
1160
+ setDraft("");
1161
+ },
1162
+ size: "lg"
1163
+ }
1164
+ ),
1165
+ def.suggestions && def.suggestions.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center justify-center gap-2", children: def.suggestions.slice(0, 4).map((suggestion) => /* @__PURE__ */ jsx(
1166
+ "button",
1167
+ {
1168
+ type: "button",
1169
+ onClick: () => ask(suggestion.prompt ?? suggestion.label),
1170
+ className: cn$1(
1171
+ "rounded-full border border-border/70 bg-card px-4 py-2 text-[13px] text-foreground/80 transition-colors",
1172
+ "hover:border-border hover:bg-muted hover:text-foreground",
1173
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1174
+ ),
1175
+ children: suggestion.label
1176
+ },
1177
+ suggestion.label
1178
+ )) })
1179
+ ]
1180
+ }
1181
+ );
1182
+ }
1183
+ function useMorphIn(skip = false) {
1184
+ const [entered, setEntered] = React65.useState(() => skip || prefersReducedMotion());
1185
+ React65.useEffect(() => {
1186
+ if (entered) return;
1187
+ let second = 0;
1188
+ const first = requestAnimationFrame(() => {
1189
+ second = requestAnimationFrame(() => setEntered(true));
1190
+ });
1191
+ return () => {
1192
+ cancelAnimationFrame(first);
1193
+ cancelAnimationFrame(second);
1194
+ };
1195
+ }, [entered]);
1196
+ return entered;
1197
+ }
1198
+ function prefersReducedMotion() {
1199
+ return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
1200
+ }
1201
+ var lastDockExit = null;
1202
+ var RESUME_WINDOW_MS = 700;
1203
+ function useWideViewport() {
1204
+ const [wide, setWide] = React65.useState(
1205
+ () => typeof window !== "undefined" && !!window.matchMedia?.("(min-width: 768px)").matches
1206
+ );
1207
+ React65.useEffect(() => {
1208
+ const query = window.matchMedia?.("(min-width: 768px)");
1209
+ if (!query) return;
1210
+ const sync = () => setWide(query.matches);
1211
+ sync();
1212
+ query.addEventListener("change", sync);
1213
+ return () => query.removeEventListener("change", sync);
1214
+ }, []);
1215
+ return wide;
1216
+ }
1217
+ function morphStyles(box, railOpen, railWidth) {
1218
+ const rightEdge = box.left + box.width / 2;
1219
+ const dx = fabRightEdge(railOpen, railWidth) - rightEdge;
1220
+ return {
1221
+ open: {
1222
+ transform: "translateX(-50%)",
1223
+ clipPath: "inset(0 round 9999px)"
1045
1224
  },
1046
- { key: "label", label: "Label", type: "text", searchable: true, hint: "Casa, Trabalho, Dep\xF3sito\u2026" },
1047
- {
1048
- key: "kind",
1049
- label: "Use",
1050
- type: "select",
1051
- options: ["both", "shipping", "billing"],
1052
- hint: "Defaults to 'both'."
1225
+ collapsed: {
1226
+ transform: `translateX(calc(-50% + ${dx}px))`,
1227
+ clipPath: `inset(0 0 0 ${Math.max(0, box.width - FAB_SIZE)}px round 9999px)`
1053
1228
  },
1054
- { key: "postal_code", label: "Postal code", type: "text", required: true, searchable: true, hint: "CEP." },
1055
- { key: "street", label: "Street", type: "text", required: true, searchable: true },
1056
- { key: "number", label: "Number", type: "text" },
1057
- { key: "complement", label: "Complement", type: "text" },
1058
- { key: "district", label: "District", type: "text", searchable: true, hint: "Bairro." },
1059
- { key: "city", label: "City", type: "text", required: true, searchable: true },
1060
- { key: "state", label: "State", type: "text", required: true, hint: "Two-letter UF." },
1061
- { key: "country", label: "Country", type: "text", hint: "Defaults to 'BR'." },
1062
- { key: "recipient", label: "Recipient", type: "text", searchable: true, hint: "Who receives, if not the owner." },
1063
- { key: "phone", label: "Phone", type: "text" },
1229
+ transition: `transform ${MORPH_MS}ms ${MORPH_EASE}, clip-path ${MORPH_MS}ms ${MORPH_EASE}`
1230
+ };
1231
+ }
1232
+ function InviteDock({ def, left, maxWidth }) {
1233
+ const { t } = useTranslation$1();
1234
+ const ask = useAsk();
1235
+ const setOpen = useChatStore((s) => s.setOpen);
1236
+ const railOpen = useRightRailStore((s) => s.open);
1237
+ const railWidth = useRightRailStore((s) => s.width);
1238
+ const [draft, setDraft] = React65.useState("");
1239
+ const resumed = React65.useRef(
1240
+ !!lastDockExit && lastDockExit.id === def.id && Date.now() - lastDockExit.at < RESUME_WINDOW_MS
1241
+ ).current;
1242
+ React65.useEffect(() => () => {
1243
+ lastDockExit = { id: def.id, at: Date.now() };
1244
+ }, [def.id]);
1245
+ const entered = useMorphIn(resumed);
1246
+ const width = Math.min(maxWidth, DOCK_MAX_WIDTH);
1247
+ const morph = morphStyles({ left, width }, railOpen, railWidth);
1248
+ const [settled, setSettled] = React65.useState(resumed);
1249
+ React65.useEffect(() => {
1250
+ if (!entered || settled) return;
1251
+ const id = setTimeout(() => setSettled(true), MORPH_MS + 60);
1252
+ return () => clearTimeout(id);
1253
+ }, [entered, settled]);
1254
+ return /* @__PURE__ */ jsx(
1255
+ "div",
1064
1256
  {
1065
- key: "is_default",
1066
- label: "Default address",
1067
- type: "boolean",
1068
- hint: "Only one default per owner and kind \u2014 clear the previous one before setting this."
1257
+ className: "fixed bottom-4 z-40 hidden md:block",
1258
+ style: {
1259
+ left,
1260
+ width,
1261
+ // Arrives as the FAB and unrolls leftward into a composer. The circle it
1262
+ // grows out of is the one that just disappeared from that exact spot.
1263
+ ...entered ? morph.open : morph.collapsed,
1264
+ transition: prefersReducedMotion() ? void 0 : morph.transition,
1265
+ ...settled ? { transform: "none", clipPath: "none", marginLeft: -width / 2 } : null
1266
+ },
1267
+ children: /* @__PURE__ */ jsx(
1268
+ InviteComposer,
1269
+ {
1270
+ tone: "glass",
1271
+ className: "fayz-assistant-glow",
1272
+ placeholder: def.placeholder ?? t("assistant.invite.placeholder"),
1273
+ value: draft,
1274
+ onChange: setDraft,
1275
+ onSubmit: () => {
1276
+ if (draft.trim()) {
1277
+ ask(draft);
1278
+ setDraft("");
1279
+ } else {
1280
+ setOpen(true);
1281
+ }
1282
+ },
1283
+ onFocusAction: () => setOpen(true),
1284
+ label: def.persona
1285
+ }
1286
+ )
1069
1287
  }
1070
- ]
1071
- };
1072
- var CORE_QUERY_ENTITIES = [
1073
- {
1074
- key: "address",
1075
- entity: addressEntity,
1076
- writable: true,
1077
- agentHint: "`address` holds the street address of a PERSON (client, contact, supplier, staff). A person record has NO street/city/postal_code fields of its own. To read where someone lives, search `address` with filters {\"owner_id\": <the person's id>} \u2014 a person with no address field is not a person without an address. To record one, create an `address` with owner_id set to the person's id. Never write an address into a notes field."
1078
- }
1079
- ];
1080
-
1081
- // src/lib/core-ai-tools.ts
1082
- var coreAITools = [
1083
- {
1084
- id: "core.business-summary",
1085
- name: "getBusinessSummary",
1086
- description: "Returns a summary of the current business: name, plan, vertical, team size, and key metrics.",
1087
- icon: "Building2",
1088
- mode: "read",
1089
- parameters: { type: "object", properties: {}, required: [] },
1090
- category: "Core",
1091
- suggestions: [
1092
- { label: "How's my business doing today?" }
1093
- ]
1094
- },
1288
+ );
1289
+ }
1290
+ function InviteComposer({
1291
+ className,
1292
+ placeholder,
1293
+ value,
1294
+ onChange,
1295
+ onSubmit,
1296
+ onFocusAction,
1297
+ label,
1298
+ size = "md",
1299
+ tone = "card"
1300
+ }) {
1301
+ const { t } = useTranslation$1();
1302
+ const glass = tone === "glass";
1303
+ return /* @__PURE__ */ jsxs(
1304
+ "form",
1305
+ {
1306
+ onSubmit: (event) => {
1307
+ event.preventDefault();
1308
+ onSubmit();
1309
+ },
1310
+ className: cn$1(
1311
+ "relative flex items-center gap-2.5 pl-4 pr-2",
1312
+ glass ? "fayz-assistant-surface rounded-full bg-primary text-primary-foreground focus-within:-translate-y-0.5" : "rounded-2xl border border-border/70 bg-card transition-shadow focus-within:border-border focus-within:shadow-md",
1313
+ size === "lg" ? "py-2.5" : "py-2",
1314
+ className
1315
+ ),
1316
+ children: [
1317
+ /* @__PURE__ */ jsx(
1318
+ Sparkles,
1319
+ {
1320
+ className: cn$1(
1321
+ "relative shrink-0",
1322
+ glass ? "text-primary-foreground drop-shadow-sm" : "text-primary",
1323
+ size === "lg" ? "h-5 w-5" : "h-[18px] w-[18px]"
1324
+ )
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsx(
1328
+ "input",
1329
+ {
1330
+ value,
1331
+ onChange: (event) => onChange(event.target.value),
1332
+ placeholder,
1333
+ "aria-label": label ?? placeholder,
1334
+ className: cn$1(
1335
+ "relative min-w-0 flex-1 bg-transparent focus:outline-none",
1336
+ // Glass text and placeholder come from .fayz-assistant-surface: on a
1337
+ // tinted bar the label is not decoration, so it cannot depend on a
1338
+ // utility class the app's Tailwind build may never have generated.
1339
+ !glass && "text-foreground placeholder:text-muted-foreground/70",
1340
+ size === "lg" ? "py-1.5 text-[15px]" : "text-sm"
1341
+ )
1342
+ }
1343
+ ),
1344
+ /* @__PURE__ */ jsx(
1345
+ "button",
1346
+ {
1347
+ type: "submit",
1348
+ onClick: () => {
1349
+ if (!value.trim()) onFocusAction?.();
1350
+ },
1351
+ "aria-label": t("chat.send"),
1352
+ "data-ready": value.trim() ? "true" : "false",
1353
+ className: cn$1(
1354
+ "relative inline-flex shrink-0 items-center justify-center rounded-full",
1355
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1356
+ // Opaque in both states, and coloured from CSS: a translucent circle
1357
+ // let the page's own lines print through it, and a primary-on-primary
1358
+ // arrow — what an ungenerated utility leaves behind — is no button.
1359
+ glass ? "fayz-assistant-send" : cn$1(
1360
+ "transition-colors",
1361
+ value.trim() ? "bg-primary text-primary-foreground hover:opacity-90" : "bg-muted text-muted-foreground hover:bg-muted/80",
1362
+ size === "lg" ? "h-9 w-9" : "h-8 w-8"
1363
+ )
1364
+ ),
1365
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
1366
+ }
1367
+ )
1368
+ ]
1369
+ }
1370
+ );
1371
+ }
1372
+ function InviteMorphBack() {
1373
+ const leaving = useAssistantInviteStore((s) => s.leaving);
1374
+ return leaving ? /* @__PURE__ */ jsx(MorphBackPill, {}, leaving.invite.id) : null;
1375
+ }
1376
+ function MorphBackPill() {
1377
+ const leaving = useAssistantInviteStore((s) => s.leaving);
1378
+ const endLeave = useAssistantInviteStore((s) => s.endLeave);
1379
+ const railOpen = useRightRailStore((s) => s.open);
1380
+ const railWidth = useRightRailStore((s) => s.width);
1381
+ const { t } = useTranslation$1();
1382
+ const collapsed = useMorphIn();
1383
+ React65.useEffect(() => {
1384
+ if (prefersReducedMotion()) {
1385
+ endLeave();
1386
+ return;
1387
+ }
1388
+ const timer = setTimeout(endLeave, MORPH_MS + 40);
1389
+ return () => clearTimeout(timer);
1390
+ }, [endLeave]);
1391
+ if (!leaving || typeof document === "undefined") return null;
1392
+ const morph = morphStyles(leaving.box, railOpen, railWidth);
1393
+ return createPortal(
1394
+ /* @__PURE__ */ jsx(
1395
+ "div",
1396
+ {
1397
+ "aria-hidden": true,
1398
+ className: "pointer-events-none fixed bottom-4 z-40 hidden md:block",
1399
+ style: {
1400
+ left: leaving.box.left,
1401
+ width: leaving.box.width,
1402
+ ...collapsed ? morph.collapsed : morph.open,
1403
+ transition: morph.transition
1404
+ },
1405
+ children: /* @__PURE__ */ jsx(
1406
+ InviteComposer,
1407
+ {
1408
+ tone: "glass",
1409
+ className: collapsed ? "fayz-assistant-leaving" : void 0,
1410
+ placeholder: leaving.invite.placeholder ?? t("assistant.invite.placeholder"),
1411
+ value: "",
1412
+ onChange: () => {
1413
+ },
1414
+ onSubmit: () => {
1415
+ },
1416
+ label: leaving.invite.persona
1417
+ }
1418
+ )
1419
+ }
1420
+ ),
1421
+ document.body
1422
+ );
1423
+ }
1424
+
1425
+ // src/app/core-entities.ts
1426
+ var addressEntity = {
1427
+ name: "Address",
1428
+ namePlural: "Addresses",
1429
+ icon: "MapPin",
1430
+ data: {
1431
+ table: "addresses",
1432
+ tenantScoped: true,
1433
+ tenantIdColumn: "tenant_id",
1434
+ searchColumns: ["label", "street", "district", "city", "postal_code", "recipient"]
1435
+ },
1436
+ fields: [
1437
+ // owner_id/owner_type are how an address attaches to its person. They are
1438
+ // required and described rather than hidden: the agent reaches this table
1439
+ // from a record it already has in hand ("adiciona o endereço X pra ele"),
1440
+ // so the id it must pass is the one it just searched for or created.
1441
+ {
1442
+ key: "owner_id",
1443
+ label: "Owner record id",
1444
+ type: "text",
1445
+ required: true,
1446
+ hint: "Id of the person (client, contact, supplier, staff\u2026) this address belongs to."
1447
+ },
1448
+ {
1449
+ key: "owner_type",
1450
+ label: "Owner kind",
1451
+ type: "select",
1452
+ options: ["person", "location", "tenant"],
1453
+ hint: "Defaults to 'person'. Only set it for an address that belongs to a business location or to the tenant itself."
1454
+ },
1455
+ { key: "label", label: "Label", type: "text", searchable: true, hint: "Casa, Trabalho, Dep\xF3sito\u2026" },
1456
+ {
1457
+ key: "kind",
1458
+ label: "Use",
1459
+ type: "select",
1460
+ options: ["both", "shipping", "billing"],
1461
+ hint: "Defaults to 'both'."
1462
+ },
1463
+ { key: "postal_code", label: "Postal code", type: "text", required: true, searchable: true, hint: "CEP." },
1464
+ { key: "street", label: "Street", type: "text", required: true, searchable: true },
1465
+ { key: "number", label: "Number", type: "text" },
1466
+ { key: "complement", label: "Complement", type: "text" },
1467
+ { key: "district", label: "District", type: "text", searchable: true, hint: "Bairro." },
1468
+ { key: "city", label: "City", type: "text", required: true, searchable: true },
1469
+ { key: "state", label: "State", type: "text", required: true, hint: "Two-letter UF." },
1470
+ { key: "country", label: "Country", type: "text", hint: "Defaults to 'BR'." },
1471
+ { key: "recipient", label: "Recipient", type: "text", searchable: true, hint: "Who receives, if not the owner." },
1472
+ { key: "phone", label: "Phone", type: "text" },
1473
+ {
1474
+ key: "is_default",
1475
+ label: "Default address",
1476
+ type: "boolean",
1477
+ hint: "Only one default per owner and kind \u2014 clear the previous one before setting this."
1478
+ }
1479
+ ]
1480
+ };
1481
+ var CORE_QUERY_ENTITIES = [
1482
+ {
1483
+ key: "address",
1484
+ entity: addressEntity,
1485
+ writable: true,
1486
+ agentHint: "`address` holds the street address of a PERSON (client, contact, supplier, staff). A person record has NO street/city/postal_code fields of its own. To read where someone lives, search `address` with filters {\"owner_id\": <the person's id>} \u2014 a person with no address field is not a person without an address. To record one, create an `address` with owner_id set to the person's id. Never write an address into a notes field."
1487
+ }
1488
+ ];
1489
+
1490
+ // src/lib/core-ai-tools.ts
1491
+ var coreAITools = [
1492
+ {
1493
+ id: "core.business-summary",
1494
+ name: "getBusinessSummary",
1495
+ description: "Returns a summary of the current business: name, plan, vertical, team size, and key metrics.",
1496
+ icon: "Building2",
1497
+ mode: "read",
1498
+ parameters: { type: "object", properties: {}, required: [] },
1499
+ category: "Core",
1500
+ suggestions: [
1501
+ { label: "How's my business doing today?" }
1502
+ ]
1503
+ },
1095
1504
  {
1096
1505
  id: "core.team-members",
1097
1506
  name: "getTeamMembers",
@@ -1507,12 +1916,12 @@ function detectActivePluginId(runtime) {
1507
1916
  function useAITools() {
1508
1917
  const runtime = usePluginRuntimeOptional();
1509
1918
  const access = useAccessOptional();
1510
- const surfaceVersion = React63.useSyncExternalStore(subscribeSurfaces, getSurfaceVersion, getSurfaceVersion);
1919
+ const surfaceVersion = React65.useSyncExternalStore(subscribeSurfaces, getSurfaceVersion, getSurfaceVersion);
1511
1920
  const features = usePermissionsStore((s) => s.features);
1512
1921
  const profiles = usePermissionsStore((s) => s.profiles);
1513
1922
  const hasSystem = useSystemPermission();
1514
1923
  const canManagePermissions = hasSystem("manage_permissions");
1515
- return React63.useMemo(() => {
1924
+ return React65.useMemo(() => {
1516
1925
  const hasPermission = runtime?.context.hasPermission;
1517
1926
  const can = access.can;
1518
1927
  const verticalId = runtime?.context.tenant?.verticalId;
@@ -1595,8 +2004,8 @@ function navigateTo(to) {
1595
2004
  adapter.navigate(normalizeAdminPath(to));
1596
2005
  }
1597
2006
  function useAdminPath() {
1598
- const [path, setPath] = React63.useState(() => normalizeAdminPath(adapter.getCurrentPath() || "/"));
1599
- React63.useEffect(() => {
2007
+ const [path, setPath] = React65.useState(() => normalizeAdminPath(adapter.getCurrentPath() || "/"));
2008
+ React65.useEffect(() => {
1600
2009
  return adapter.onPathChange((p) => {
1601
2010
  setPath(normalizeAdminPath(p || "/"));
1602
2011
  if (typeof window !== "undefined") window.scrollTo(0, 0);
@@ -2843,6 +3252,20 @@ async function executeRpcTool(rpcName, args, userId) {
2843
3252
 
2844
3253
  // src/components/connect/connector-map.generated.ts
2845
3254
  var SDK_INTEGRATIONS = [
3255
+ {
3256
+ slug: "99food",
3257
+ name: "99Food",
3258
+ description: "Traga para a cozinha cada pedido feito no 99Food, e avise o 99Food quando ele for confirmado e sair.",
3259
+ icon: "Bike",
3260
+ logoUrl: "https://assets.fayz.ai/connectors/99food.jpeg",
3261
+ category: "marketplace",
3262
+ direction: "inbound",
3263
+ planes: ["sync"],
3264
+ moduleId: "orders",
3265
+ status: "available",
3266
+ connectorId: "99food",
3267
+ declaredBy: { pluginId: "orders", package: "@fayz-ai/plugin-orders" }
3268
+ },
2846
3269
  {
2847
3270
  slug: "abacatepay",
2848
3271
  name: "AbacatePay",
@@ -2912,6 +3335,20 @@ var SDK_INTEGRATIONS = [
2912
3335
  status: "soon",
2913
3336
  declaredBy: { pluginId: "agenda", package: "@fayz-ai/plugin-agenda" }
2914
3337
  },
3338
+ {
3339
+ slug: "cielo",
3340
+ name: "Cielo",
3341
+ description: "Traga para o Financeiro tudo que entrou na Cielo \u2014 cr\xE9dito, d\xE9bito e Pix \u2014 com a taxa e a data do repasse.",
3342
+ icon: "CreditCard",
3343
+ logoUrl: "https://assets.fayz.ai/connectors/cielo.jpg",
3344
+ category: "payments",
3345
+ direction: "inbound",
3346
+ planes: ["sync"],
3347
+ moduleId: "financial",
3348
+ status: "available",
3349
+ connectorId: "cielo",
3350
+ declaredBy: { pluginId: "financial", package: "@fayz-ai/plugin-financial" }
3351
+ },
2915
3352
  {
2916
3353
  slug: "google-analytics",
2917
3354
  name: "Google Analytics",
@@ -2980,6 +3417,34 @@ var SDK_INTEGRATIONS = [
2980
3417
  status: "soon",
2981
3418
  declaredBy: { pluginId: "crm", package: "@fayz-ai/plugin-crm" }
2982
3419
  },
3420
+ {
3421
+ slug: "ifood",
3422
+ name: "iFood",
3423
+ description: "Traga para a cozinha cada pedido feito no iFood, e avise o iFood quando ele for confirmado e sair.",
3424
+ icon: "Bike",
3425
+ logoUrl: "https://assets.fayz.ai/connectors/ifood.jpg",
3426
+ category: "marketplace",
3427
+ direction: "inbound",
3428
+ planes: ["sync"],
3429
+ moduleId: "orders",
3430
+ status: "available",
3431
+ connectorId: "ifood",
3432
+ declaredBy: { pluginId: "orders", package: "@fayz-ai/plugin-orders" }
3433
+ },
3434
+ {
3435
+ slug: "keeta",
3436
+ name: "Keeta",
3437
+ description: "Traga para a cozinha cada pedido feito no Keeta, e avise o Keeta quando ele for confirmado e sair.",
3438
+ icon: "Bike",
3439
+ logoUrl: "https://assets.fayz.ai/connectors/keeta.jpeg",
3440
+ category: "marketplace",
3441
+ direction: "inbound",
3442
+ planes: ["sync"],
3443
+ moduleId: "orders",
3444
+ status: "available",
3445
+ connectorId: "keeta",
3446
+ declaredBy: { pluginId: "orders", package: "@fayz-ai/plugin-orders" }
3447
+ },
2983
3448
  {
2984
3449
  slug: "kiwify",
2985
3450
  name: "Kiwify",
@@ -3037,11 +3502,16 @@ var SDK_INTEGRATIONS = [
3037
3502
  },
3038
3503
  {
3039
3504
  slug: "mercadopago",
3040
- name: "MercadoPago",
3505
+ name: "Mercado Pago",
3506
+ description: "Traga para o Financeiro tudo que entrou no Mercado Pago \u2014 Pix, cart\xE3o e maquininha \u2014 com a taxa e a data do repasse.",
3507
+ icon: "CreditCard",
3041
3508
  logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
3042
3509
  category: "payments",
3510
+ direction: "inbound",
3511
+ planes: ["sync"],
3043
3512
  moduleId: "financial",
3044
- status: "soon",
3513
+ status: "available",
3514
+ connectorId: "mercadopago",
3045
3515
  declaredBy: { pluginId: "financial", package: "@fayz-ai/plugin-financial" }
3046
3516
  },
3047
3517
  {
@@ -3062,6 +3532,20 @@ var SDK_INTEGRATIONS = [
3062
3532
  status: "soon",
3063
3533
  declaredBy: { pluginId: "financial", package: "@fayz-ai/plugin-financial" }
3064
3534
  },
3535
+ {
3536
+ slug: "pagbank",
3537
+ name: "PagBank",
3538
+ description: "Traga para o Financeiro tudo que entrou no PagBank \u2014 Pix, cart\xE3o e maquininha \u2014 com a taxa e a data do repasse.",
3539
+ icon: "CreditCard",
3540
+ logoUrl: "https://assets.fayz.ai/connectors/pagbank_pagseguro.png",
3541
+ category: "payments",
3542
+ direction: "inbound",
3543
+ planes: ["sync"],
3544
+ moduleId: "financial",
3545
+ status: "available",
3546
+ connectorId: "pagbank",
3547
+ declaredBy: { pluginId: "financial", package: "@fayz-ai/plugin-financial" }
3548
+ },
3065
3549
  {
3066
3550
  slug: "ranklayer",
3067
3551
  name: "RankLayer",
@@ -3106,10 +3590,15 @@ var SDK_INTEGRATIONS = [
3106
3590
  {
3107
3591
  slug: "stone",
3108
3592
  name: "Stone",
3593
+ description: "Traga para o Financeiro tudo que entrou na Stone \u2014 cr\xE9dito, d\xE9bito, Pix e vale \u2014 com a taxa e a data do repasse.",
3594
+ icon: "CreditCard",
3109
3595
  logoUrl: "https://assets.fayz.ai/connectors/stone.jpeg",
3110
3596
  category: "payments",
3597
+ direction: "inbound",
3598
+ planes: ["sync"],
3111
3599
  moduleId: "financial",
3112
- status: "soon",
3600
+ status: "available",
3601
+ connectorId: "stone",
3113
3602
  declaredBy: { pluginId: "financial", package: "@fayz-ai/plugin-financial" }
3114
3603
  },
3115
3604
  {
@@ -3163,6 +3652,20 @@ var SDK_INTEGRATIONS = [
3163
3652
  status: "soon",
3164
3653
  declaredBy: { pluginId: "reputation", package: "@fayz-ai/plugin-reputation" }
3165
3654
  },
3655
+ {
3656
+ slug: "vindi",
3657
+ name: "Vindi",
3658
+ description: "Pix e boleto pela Vindi. A loja recebe na conta dela, com o cliente pagando sem sair da p\xE1gina.",
3659
+ icon: "CreditCard",
3660
+ logoUrl: "https://assets.fayz.ai/connectors/vindi.png",
3661
+ category: "payments",
3662
+ direction: "outbound",
3663
+ planes: ["request", "sync"],
3664
+ moduleId: "shop",
3665
+ status: "available",
3666
+ connectorId: "vindi",
3667
+ declaredBy: { pluginId: "shop", package: "@fayz-ai/plugin-shop" }
3668
+ },
3166
3669
  {
3167
3670
  slug: "whatsapp",
3168
3671
  name: "WhatsApp",
@@ -3527,8 +4030,8 @@ function loadCatalog() {
3527
4030
  return catalogPromise;
3528
4031
  }
3529
4032
  function useConnectorCatalog() {
3530
- const [catalog, setCatalog] = React63__default.useState(null);
3531
- React63__default.useEffect(() => {
4033
+ const [catalog, setCatalog] = React65__default.useState(null);
4034
+ React65__default.useEffect(() => {
3532
4035
  let cancelled = false;
3533
4036
  void loadCatalog().then((list) => {
3534
4037
  if (!cancelled) setCatalog(list);
@@ -3552,7 +4055,7 @@ function refreshConnectors() {
3552
4055
  _revisionListeners.forEach((listener) => listener());
3553
4056
  }
3554
4057
  function useConnectorsRevision() {
3555
- return React63__default.useSyncExternalStore(
4058
+ return React65__default.useSyncExternalStore(
3556
4059
  (listener) => {
3557
4060
  _revisionListeners.add(listener);
3558
4061
  return () => _revisionListeners.delete(listener);
@@ -3571,11 +4074,11 @@ function loadGrants(key, fetcher) {
3571
4074
  }
3572
4075
  function useInstalledModuleIds() {
3573
4076
  const runtime = usePluginRuntimeOptional();
3574
- return React63__default.useMemo(() => (runtime?.plugins ?? []).map((p) => p.id), [runtime]);
4077
+ return React65__default.useMemo(() => (runtime?.plugins ?? []).map((p) => p.id), [runtime]);
3575
4078
  }
3576
4079
  function useInstalledDefinitions(hostPluginId) {
3577
4080
  const runtime = usePluginRuntimeOptional();
3578
- return React63__default.useMemo(() => {
4081
+ return React65__default.useMemo(() => {
3579
4082
  const byHost = runtime?.connectorsByHost;
3580
4083
  if (!byHost) return [];
3581
4084
  if (hostPluginId) return byHost.get(hostPluginId) ?? [];
@@ -3588,12 +4091,12 @@ function useConnectors(options = {}) {
3588
4091
  const catalog = useConnectorCatalog();
3589
4092
  const tenant = useTenantOptional();
3590
4093
  const tenantId2 = tenant?.org?.id;
3591
- const connection = React63__default.useMemo(() => resolveFayzAgentConnection(), []);
3592
- const [installed, setInstalled] = React63__default.useState(null);
3593
- const [grants, setGrants] = React63__default.useState(null);
3594
- const [error, setError] = React63__default.useState(null);
4094
+ const connection = React65__default.useMemo(() => resolveFayzAgentConnection(), []);
4095
+ const [installed, setInstalled] = React65__default.useState(null);
4096
+ const [grants, setGrants] = React65__default.useState(null);
4097
+ const [error, setError] = React65__default.useState(null);
3595
4098
  const nonce = useConnectorsRevision();
3596
- const call = React63__default.useCallback(
4099
+ const call = React65__default.useCallback(
3597
4100
  async (path, init) => {
3598
4101
  if (!connection) throw new Error("Este app n\xE3o est\xE1 ligado a um projeto Fayz.");
3599
4102
  const supabase = getFayzSupabaseClientOptional();
@@ -3613,7 +4116,7 @@ function useConnectors(options = {}) {
3613
4116
  },
3614
4117
  [connection]
3615
4118
  );
3616
- React63__default.useEffect(() => {
4119
+ React65__default.useEffect(() => {
3617
4120
  let cancelled = false;
3618
4121
  void loadConnectorStatuses(definitions, tenantId2).then((list) => {
3619
4122
  if (!cancelled) setInstalled(list);
@@ -3622,7 +4125,7 @@ function useConnectors(options = {}) {
3622
4125
  cancelled = true;
3623
4126
  };
3624
4127
  }, [definitions, tenantId2, nonce]);
3625
- React63__default.useEffect(() => {
4128
+ React65__default.useEffect(() => {
3626
4129
  if (!includeGrants || !connection || !tenantId2) {
3627
4130
  setGrants(includeGrants ? null : []);
3628
4131
  return;
@@ -3646,7 +4149,7 @@ function useConnectors(options = {}) {
3646
4149
  cancelled = true;
3647
4150
  };
3648
4151
  }, [call, tenantId2, includeGrants, connection, nonce]);
3649
- const revokeGrant = React63__default.useCallback(
4152
+ const revokeGrant = React65__default.useCallback(
3650
4153
  async (connector) => {
3651
4154
  const response = await call(`/${connector.id}`, { method: "DELETE" });
3652
4155
  if (!response.ok) {
@@ -3659,7 +4162,7 @@ function useConnectors(options = {}) {
3659
4162
  [call]
3660
4163
  );
3661
4164
  const waitingGrants = includeGrants && !!connection && !!tenantId2 && grants === null;
3662
- const connectors = React63__default.useMemo(() => {
4165
+ const connectors = React65__default.useMemo(() => {
3663
4166
  if (installed === null || waitingGrants) return null;
3664
4167
  return buildConnectorList({
3665
4168
  installed,
@@ -4294,7 +4797,6 @@ ${JSON.stringify(snapshot.length === 1 ? snapshot[0] : snapshot)}` : void 0
4294
4797
  startNewConversation
4295
4798
  };
4296
4799
  }
4297
- var FAB_MARGIN_RIGHT = 21;
4298
4800
  var TEASER_MAX_PER_SESSION = 2;
4299
4801
  var KEY_DISMISSED2 = "fayz.agent.teaser.dismissed";
4300
4802
  var KEY_SHOWN = "fayz.agent.teaser.shown";
@@ -4325,14 +4827,14 @@ function recordTeaser(label) {
4325
4827
  store.setItem(KEY_SHOWN, String(teaserShownCount() + 1));
4326
4828
  store.setItem(KEY_SEEN, JSON.stringify([...teaserSeen(), label].slice(-12)));
4327
4829
  }
4328
- function prefersReducedMotion() {
4830
+ function prefersReducedMotion2() {
4329
4831
  return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
4330
4832
  }
4331
4833
  function TypewriterText({ text, speed = 26, onDone }) {
4332
- const [displayed, setDisplayed] = React63.useState("");
4333
- const indexRef = React63.useRef(0);
4334
- React63.useEffect(() => {
4335
- if (prefersReducedMotion()) {
4834
+ const [displayed, setDisplayed] = React65.useState("");
4835
+ const indexRef = React65.useRef(0);
4836
+ React65.useEffect(() => {
4837
+ if (prefersReducedMotion2()) {
4336
4838
  setDisplayed(text);
4337
4839
  onDone?.();
4338
4840
  return;
@@ -4415,11 +4917,11 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4415
4917
  const { contextualSuggestions } = useAITools();
4416
4918
  const { sendMessage } = useChat({ apiEndpoint, systemPrompt, agent });
4417
4919
  const { t } = useTranslation$1();
4418
- const [phase, setPhase] = React63.useState("idle");
4419
- const [activeSuggestion, setActiveSuggestion] = React63.useState(null);
4420
- const timersRef = React63.useRef([]);
4421
- const prevSuggestionRef = React63.useRef(null);
4422
- React63.useEffect(ensureStyles, []);
4920
+ const [phase, setPhase] = React65.useState("idle");
4921
+ const [activeSuggestion, setActiveSuggestion] = React65.useState(null);
4922
+ const timersRef = React65.useRef([]);
4923
+ const prevSuggestionRef = React65.useRef(null);
4924
+ React65.useEffect(ensureStyles, []);
4423
4925
  const clearTimers = () => {
4424
4926
  timersRef.current.forEach(clearTimeout);
4425
4927
  timersRef.current = [];
@@ -4430,10 +4932,11 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4430
4932
  const topSuggestion = contextualSuggestions[0] ?? null;
4431
4933
  const topLabel = topSuggestion?.label ?? null;
4432
4934
  const invited = useAssistantInviteStore((s) => !!s.invite && s.stage === "dock");
4433
- const [wide, setWide] = React63.useState(
4935
+ const morphingBack = useAssistantInviteStore((s) => !!s.leaving);
4936
+ const [wide, setWide] = React65.useState(
4434
4937
  () => typeof window !== "undefined" && !!window.matchMedia?.("(min-width: 768px)").matches
4435
4938
  );
4436
- React63.useEffect(() => {
4939
+ React65.useEffect(() => {
4437
4940
  const query = window.matchMedia?.("(min-width: 768px)");
4438
4941
  if (!query) return;
4439
4942
  const sync = () => setWide(query.matches);
@@ -4442,7 +4945,7 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4442
4945
  return () => query.removeEventListener("change", sync);
4443
4946
  }, []);
4444
4947
  const teaserAllowed = !hasMessages && !activity && !invited && !teaserDismissed() && teaserShownCount() < TEASER_MAX_PER_SESSION;
4445
- React63.useEffect(() => {
4948
+ React65.useEffect(() => {
4446
4949
  if (expanded || !topSuggestion || !topLabel || !teaserAllowed || teaserSeen().includes(topLabel)) {
4447
4950
  if (phase !== "idle") {
4448
4951
  clearTimers();
@@ -4473,16 +4976,16 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4473
4976
  }, 9e3);
4474
4977
  return clearTimers;
4475
4978
  }, [topLabel, expanded, teaserAllowed]);
4476
- React63.useEffect(() => {
4979
+ React65.useEffect(() => {
4477
4980
  if (expanded && phase !== "idle") {
4478
4981
  clearTimers();
4479
4982
  setPhase("idle");
4480
4983
  }
4481
4984
  }, [expanded]);
4482
- const [entrance, setEntrance] = React63.useState("pending");
4483
- React63.useEffect(() => {
4985
+ const [entrance, setEntrance] = React65.useState("pending");
4986
+ React65.useEffect(() => {
4484
4987
  if (!currentOrg || entrance !== "pending") return;
4485
- if (prefersReducedMotion()) {
4988
+ if (prefersReducedMotion2()) {
4486
4989
  setEntrance("done");
4487
4990
  return;
4488
4991
  }
@@ -4490,9 +4993,19 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4490
4993
  const timer = setTimeout(() => setEntrance("done"), 1600);
4491
4994
  return () => clearTimeout(timer);
4492
4995
  }, [currentOrg, entrance]);
4493
- const entering = entrance === "playing" && !expanded;
4996
+ const [poppingBack, setPoppingBack] = React65.useState(false);
4997
+ const wasMorphing = React65.useRef(false);
4998
+ React65.useEffect(() => {
4999
+ const was = wasMorphing.current;
5000
+ wasMorphing.current = morphingBack;
5001
+ if (!was || morphingBack || prefersReducedMotion2()) return;
5002
+ setPoppingBack(true);
5003
+ const timer = setTimeout(() => setPoppingBack(false), 620);
5004
+ return () => clearTimeout(timer);
5005
+ }, [morphingBack]);
5006
+ const entering = (entrance === "playing" || poppingBack) && !expanded;
4494
5007
  if (!currentOrg) return null;
4495
- if (invited && wide && !expanded && !activity) return null;
5008
+ if ((invited || morphingBack) && wide && !expanded && !activity) return /* @__PURE__ */ jsx(InviteMorphBack, {});
4496
5009
  const resolveLabel = (s, index) => {
4497
5010
  const key = `chat.suggestion.${s.toolId}.${index}`;
4498
5011
  const translated = t(key);
@@ -4516,433 +5029,213 @@ function ChatFab({ className, apiEndpoint, systemPrompt, agent, voice, mobile })
4516
5029
  addTimer(() => setPhase("idle"), 400);
4517
5030
  };
4518
5031
  const showPill = phase === "expanding" || phase === "typing" || phase === "visible";
4519
- return /* @__PURE__ */ jsxs(
4520
- "div",
4521
- {
4522
- className: cn$1(
4523
- "fixed z-50 md:block md:bottom-4",
4524
- // Anchored to the CONTENT, not the screen: with the rail open the FAB
4525
- // slides left and keeps its bottom-right spot relative to the app —
4526
- // outside the column — gliding in step with the aside's width tween.
4527
- "motion-safe:transition-[right] motion-safe:duration-200 motion-safe:ease-out",
4528
- // On a phone the open panel is full-screen and carries its own close
4529
- // button — a floating FAB there only lands on top of the composer.
4530
- mobile && !expanded ? "block bottom-[calc(1rem+env(safe-area-inset-bottom))]" : "hidden bottom-4",
4531
- className
4532
- ),
4533
- style: { right: railOpen ? railWidth + FAB_MARGIN_RIGHT : FAB_MARGIN_RIGHT },
4534
- children: [
4535
- activity && !expanded && /* @__PURE__ */ jsxs(
4536
- "div",
4537
- {
4538
- className: cn$1(
4539
- "absolute bottom-0 right-0 flex items-center overflow-hidden rounded-full shadow-lg",
4540
- activity.state === "done" ? "bg-primary text-primary-foreground" : "bg-foreground text-background"
4541
- ),
4542
- style: {
4543
- paddingRight: "3rem",
4544
- height: "3rem",
4545
- animation: "fabPillGrow 500ms cubic-bezier(0.22, 1, 0.36, 1) forwards"
4546
- },
4547
- children: [
4548
- /* @__PURE__ */ jsxs(
4549
- "button",
4550
- {
4551
- type: "button",
4552
- onClick: () => setOpen(true),
4553
- title: activity.label,
4554
- className: "flex min-w-0 items-center gap-2 py-1 pl-4 pr-1 text-left transition-opacity hover:opacity-90",
4555
- children: [
4556
- /* @__PURE__ */ jsxs("span", { className: "relative inline-flex h-2 w-2 shrink-0", children: [
4557
- activity.state === "running" && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-70" }),
4558
- /* @__PURE__ */ jsx(
4559
- "span",
4560
- {
4561
- className: cn$1(
4562
- "relative inline-flex h-2 w-2 rounded-full",
4563
- activity.state === "done" ? "bg-background" : "bg-red-500"
4564
- )
4565
- }
4566
- )
4567
- ] }),
4568
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
4569
- /* @__PURE__ */ jsx("span", { className: "max-w-[11rem] truncate text-[10px] uppercase tracking-wide opacity-60", children: activity.label }),
4570
- activity.elapsedMs !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold tabular-nums leading-tight", children: formatActivityElapsed(activity.elapsedMs) })
4571
- ] })
4572
- ]
4573
- }
4574
- ),
4575
- (activity.actions ?? []).filter((action) => action.primary).map((action) => {
4576
- const Icon = FAB_INTENT_ICON[action.intent];
4577
- return /* @__PURE__ */ jsx(
4578
- "button",
4579
- {
4580
- type: "button",
4581
- onClick: (event) => {
4582
- event.stopPropagation();
4583
- void action.run();
4584
- },
4585
- "aria-label": action.label,
4586
- title: action.label,
4587
- className: "mr-1.5 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full opacity-70 transition-opacity hover:bg-white/15 hover:opacity-100",
4588
- children: /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" })
4589
- },
4590
- action.intent
4591
- );
4592
- })
4593
- ]
4594
- }
5032
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5033
+ /* @__PURE__ */ jsx(InviteMorphBack, {}),
5034
+ /* @__PURE__ */ jsxs(
5035
+ "div",
5036
+ {
5037
+ className: cn$1(
5038
+ "fixed z-50 md:block md:bottom-4",
5039
+ // Anchored to the CONTENT, not the screen: with the rail open the FAB
5040
+ // slides left and keeps its bottom-right spot relative to the app —
5041
+ // outside the column — gliding in step with the aside's width tween.
5042
+ "motion-safe:transition-[right] motion-safe:duration-200 motion-safe:ease-out",
5043
+ // On a phone the open panel is full-screen and carries its own close
5044
+ // button — a floating FAB there only lands on top of the composer.
5045
+ mobile && !expanded ? "block bottom-[calc(1rem+env(safe-area-inset-bottom))]" : "hidden bottom-4",
5046
+ className
4595
5047
  ),
4596
- (showPill || phase === "collapsing") && !expanded && /* @__PURE__ */ jsxs(
4597
- "div",
4598
- {
4599
- className: cn$1(
4600
- "absolute bottom-0 right-0 flex items-center overflow-hidden rounded-full shadow-lg",
4601
- // Same fill as the FAB: the pill is the FAB stretching out, not a
4602
- // second black object parked next to a purple one. Reserving
4603
- // exactly the circle's width also closes the dead gap the dismiss
4604
- // button used to float in.
4605
- "bg-primary text-primary-foreground"
4606
- ),
4607
- style: {
4608
- paddingRight: "3rem",
4609
- height: "3rem",
4610
- animation: showPill ? "fabPillGrow 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards" : "fabPillShrink 400ms cubic-bezier(0.55, 0, 1, 0.45) forwards"
4611
- },
4612
- children: [
4613
- /* @__PURE__ */ jsxs(
4614
- "button",
4615
- {
4616
- onClick: handleSuggestionClick,
4617
- className: "flex min-w-0 flex-1 flex-col items-start justify-center py-1 pl-5 pr-1 text-left transition-opacity hover:opacity-90",
4618
- children: [
4619
- /* @__PURE__ */ jsx("span", { className: "text-[7px] font-semibold uppercase tracking-widest opacity-50", children: t("chat.fab.tryIt") }),
4620
- /* @__PURE__ */ jsx(
4621
- "span",
4622
- {
4623
- className: cn$1(
4624
- "min-h-[1.15em] whitespace-nowrap text-[13px] font-medium transition-opacity",
4625
- phase === "expanding" ? "opacity-0 duration-200" : "opacity-100 duration-300"
4626
- ),
4627
- children: phase === "typing" && activeSuggestion ? /* @__PURE__ */ jsx(TypewriterText, { text: `"${activeLabel}"`, onDone: () => setPhase("visible") }) : phase === "visible" && activeSuggestion ? `"${activeLabel}"` : "\xA0"
4628
- }
4629
- )
4630
- ]
4631
- }
5048
+ style: { right: railOpen ? railWidth + FAB_MARGIN_RIGHT : FAB_MARGIN_RIGHT },
5049
+ children: [
5050
+ activity && !expanded && /* @__PURE__ */ jsxs(
5051
+ "div",
5052
+ {
5053
+ className: cn$1(
5054
+ "absolute bottom-0 right-0 flex items-center overflow-hidden rounded-full shadow-lg",
5055
+ activity.state === "done" ? "bg-primary text-primary-foreground" : "bg-foreground text-background"
4632
5056
  ),
4633
- /* @__PURE__ */ jsx(
4634
- "button",
4635
- {
4636
- onClick: dismissTeaser,
4637
- "aria-label": t("chat.fab.dismiss"),
4638
- title: t("chat.fab.dismiss"),
4639
- className: "mr-1.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-50 transition-opacity hover:bg-black/10 hover:opacity-100",
4640
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4641
- }
4642
- )
4643
- ]
4644
- }
4645
- ),
4646
- /* @__PURE__ */ jsxs(
4647
- "button",
4648
- {
4649
- onClick: () => {
4650
- if (expanded) {
4651
- closeRail();
4652
- setOpen(false);
4653
- } else {
4654
- setOpen(true);
4655
- }
4656
- },
4657
- className: cn$1(
4658
- "relative z-10 flex h-12 w-12 items-center justify-center transition-all duration-200",
4659
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
4660
- "rounded-full bg-primary text-primary-foreground shadow-lg hover:scale-105 hover:shadow-xl"
4661
- ),
4662
- "aria-label": expanded ? t("chat.fab.close") : t("chat.fab.open"),
4663
- "aria-expanded": expanded,
4664
- style: entering ? { animation: "fabEnter 600ms cubic-bezier(0.34, 1.56, 0.64, 1) both" } : void 0,
4665
- children: [
4666
- entering && /* @__PURE__ */ jsxs(Fragment, { children: [
4667
- /* @__PURE__ */ jsx(
4668
- "span",
5057
+ style: {
5058
+ paddingRight: "3rem",
5059
+ height: "3rem",
5060
+ animation: "fabPillGrow 500ms cubic-bezier(0.22, 1, 0.36, 1) forwards"
5061
+ },
5062
+ children: [
5063
+ /* @__PURE__ */ jsxs(
5064
+ "button",
4669
5065
  {
4670
- "aria-hidden": true,
4671
- className: "absolute inset-0 rounded-full bg-primary/40",
4672
- style: { animation: "fabEnterRing 900ms ease-out 250ms both" }
5066
+ type: "button",
5067
+ onClick: () => setOpen(true),
5068
+ title: activity.label,
5069
+ className: "flex min-w-0 items-center gap-2 py-1 pl-4 pr-1 text-left transition-opacity hover:opacity-90",
5070
+ children: [
5071
+ /* @__PURE__ */ jsxs("span", { className: "relative inline-flex h-2 w-2 shrink-0", children: [
5072
+ activity.state === "running" && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-70" }),
5073
+ /* @__PURE__ */ jsx(
5074
+ "span",
5075
+ {
5076
+ className: cn$1(
5077
+ "relative inline-flex h-2 w-2 rounded-full",
5078
+ activity.state === "done" ? "bg-background" : "bg-red-500"
5079
+ )
5080
+ }
5081
+ )
5082
+ ] }),
5083
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
5084
+ /* @__PURE__ */ jsx("span", { className: "max-w-[11rem] truncate text-[10px] uppercase tracking-wide opacity-60", children: activity.label }),
5085
+ activity.elapsedMs !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold tabular-nums leading-tight", children: formatActivityElapsed(activity.elapsedMs) })
5086
+ ] })
5087
+ ]
5088
+ }
5089
+ ),
5090
+ (activity.actions ?? []).filter((action) => action.primary).map((action) => {
5091
+ const Icon = FAB_INTENT_ICON[action.intent];
5092
+ return /* @__PURE__ */ jsx(
5093
+ "button",
5094
+ {
5095
+ type: "button",
5096
+ onClick: (event) => {
5097
+ event.stopPropagation();
5098
+ void action.run();
5099
+ },
5100
+ "aria-label": action.label,
5101
+ title: action.label,
5102
+ className: "mr-1.5 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full opacity-70 transition-opacity hover:bg-white/15 hover:opacity-100",
5103
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" })
5104
+ },
5105
+ action.intent
5106
+ );
5107
+ })
5108
+ ]
5109
+ }
5110
+ ),
5111
+ (showPill || phase === "collapsing") && !expanded && /* @__PURE__ */ jsxs(
5112
+ "div",
5113
+ {
5114
+ className: cn$1(
5115
+ "absolute bottom-0 right-0 flex items-center overflow-hidden rounded-full shadow-lg",
5116
+ // Same fill as the FAB: the pill is the FAB stretching out, not a
5117
+ // second black object parked next to a purple one. Reserving
5118
+ // exactly the circle's width also closes the dead gap the dismiss
5119
+ // button used to float in.
5120
+ "bg-primary text-primary-foreground"
5121
+ ),
5122
+ style: {
5123
+ paddingRight: "3rem",
5124
+ height: "3rem",
5125
+ animation: showPill ? "fabPillGrow 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards" : "fabPillShrink 400ms cubic-bezier(0.55, 0, 1, 0.45) forwards"
5126
+ },
5127
+ children: [
5128
+ /* @__PURE__ */ jsxs(
5129
+ "button",
5130
+ {
5131
+ onClick: handleSuggestionClick,
5132
+ className: "flex min-w-0 flex-1 flex-col items-start justify-center py-1 pl-5 pr-1 text-left transition-opacity hover:opacity-90",
5133
+ children: [
5134
+ /* @__PURE__ */ jsx("span", { className: "text-[7px] font-semibold uppercase tracking-widest opacity-50", children: t("chat.fab.tryIt") }),
5135
+ /* @__PURE__ */ jsx(
5136
+ "span",
5137
+ {
5138
+ className: cn$1(
5139
+ "min-h-[1.15em] whitespace-nowrap text-[13px] font-medium transition-opacity",
5140
+ phase === "expanding" ? "opacity-0 duration-200" : "opacity-100 duration-300"
5141
+ ),
5142
+ children: phase === "typing" && activeSuggestion ? /* @__PURE__ */ jsx(TypewriterText, { text: `"${activeLabel}"`, onDone: () => setPhase("visible") }) : phase === "visible" && activeSuggestion ? `"${activeLabel}"` : "\xA0"
5143
+ }
5144
+ )
5145
+ ]
4673
5146
  }
4674
5147
  ),
4675
5148
  /* @__PURE__ */ jsx(
4676
- "span",
5149
+ "button",
4677
5150
  {
4678
- "aria-hidden": true,
4679
- className: "absolute inset-0 rounded-full bg-primary/25",
4680
- style: { animation: "fabEnterRing 1100ms ease-out 450ms both" }
5151
+ onClick: dismissTeaser,
5152
+ "aria-label": t("chat.fab.dismiss"),
5153
+ title: t("chat.fab.dismiss"),
5154
+ className: "mr-1.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-50 transition-opacity hover:bg-black/10 hover:opacity-100",
5155
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4681
5156
  }
4682
5157
  )
4683
- ] }),
4684
- isStreaming && !expanded && /* @__PURE__ */ jsx(
4685
- "span",
4686
- {
4687
- "aria-hidden": true,
4688
- className: "absolute inset-0 rounded-full bg-primary motion-safe:[animation:fabHalo_1.8s_ease-out_infinite]"
4689
- }
4690
- ),
4691
- activity && activity.state !== "done" && expanded && /* @__PURE__ */ jsxs(
4692
- "span",
4693
- {
4694
- "aria-hidden": true,
4695
- className: "absolute -right-0.5 -top-0.5 flex h-3 w-3 items-center justify-center",
4696
- children: [
4697
- activity.state === "running" && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-70" }),
4698
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full border-2 border-primary bg-red-500" })
4699
- ]
5158
+ ]
5159
+ }
5160
+ ),
5161
+ /* @__PURE__ */ jsxs(
5162
+ "button",
5163
+ {
5164
+ onClick: () => {
5165
+ if (expanded) {
5166
+ closeRail();
5167
+ setOpen(false);
5168
+ } else {
5169
+ setOpen(true);
4700
5170
  }
5171
+ },
5172
+ className: cn$1(
5173
+ "relative z-10 flex h-12 w-12 items-center justify-center",
5174
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
5175
+ // Same material as the docked composer — the two are one object in
5176
+ // two shapes, and the morph between them only reads if the surface
5177
+ // does not change on the way.
5178
+ "fayz-assistant-surface rounded-full bg-primary text-primary-foreground hover:scale-105"
4701
5179
  ),
4702
- expanded ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(
4703
- Sparkles,
4704
- {
4705
- className: "relative h-5 w-5",
4706
- style: entering ? { animation: "fabEnterIcon 700ms cubic-bezier(0.34, 1.56, 0.64, 1) 120ms both" } : void 0
4707
- }
4708
- )
4709
- ]
4710
- }
4711
- )
4712
- ]
4713
- }
4714
- );
4715
- }
4716
- function AssistantInvite({ className, ...def }) {
4717
- const setInvite = useAssistantInviteStore((s) => s.setInvite);
4718
- const stage = useAssistantInviteStore((s) => s.stage);
4719
- const dismiss = useAssistantInviteStore((s) => s.dismiss);
4720
- const railOpen = useRightRailStore((s) => s.open);
4721
- const hasAssistant = useRightRailStore((s) => s.panels.some((p) => p.id === "chat"));
4722
- const { id, persona, headline, pitch, description, placeholder, auto } = def;
4723
- const suggestions = def.suggestions;
4724
- const suggestionKey = JSON.stringify(suggestions ?? []);
4725
- React63.useEffect(() => {
4726
- if (!hasAssistant) return;
4727
- setInvite({
4728
- id,
4729
- persona,
4730
- headline,
4731
- pitch,
4732
- description,
4733
- placeholder,
4734
- auto,
4735
- suggestions: JSON.parse(suggestionKey)
4736
- });
4737
- return () => setInvite(null, id);
4738
- }, [hasAssistant, id, persona, headline, pitch, description, placeholder, auto, suggestionKey, setInvite]);
4739
- const anchorRef = React63.useRef(null);
4740
- const [box, setBox] = React63.useState(null);
4741
- const docked = stage === "dock" && !railOpen && hasAssistant;
4742
- const showing = hasAssistant && (stage === "hero" || docked);
4743
- React63.useEffect(() => {
4744
- if (!showing) return;
4745
- const el = anchorRef.current;
4746
- if (!el) return;
4747
- const measure = () => {
4748
- const rect = el.getBoundingClientRect();
4749
- setBox({ left: rect.left, width: rect.width, top: rect.top });
4750
- };
4751
- measure();
4752
- const observer = new ResizeObserver(measure);
4753
- observer.observe(el);
4754
- window.addEventListener("resize", measure);
4755
- window.addEventListener("scroll", measure, true);
4756
- return () => {
4757
- observer.disconnect();
4758
- window.removeEventListener("resize", measure);
4759
- window.removeEventListener("scroll", measure, true);
4760
- };
4761
- }, [showing]);
4762
- if (!hasAssistant) return null;
4763
- const floating = box && stage === "hero" ? /* @__PURE__ */ jsx(InviteHero, { def, box, onDismiss: dismiss }) : box && docked ? /* @__PURE__ */ jsx(InviteDock, { def, left: box.left + box.width / 2, maxWidth: box.width }) : null;
4764
- return /* @__PURE__ */ jsx("div", { ref: anchorRef, className: cn$1("h-0 w-full", className), "aria-hidden": !showing, children: floating && typeof document !== "undefined" ? createPortal(floating, document.body) : null });
4765
- }
4766
- function useAsk() {
4767
- const queuePrompt = useChatStore((s) => s.queuePrompt);
4768
- const dismiss = useAssistantInviteStore((s) => s.dismiss);
4769
- return React63.useCallback(
4770
- (text) => {
4771
- const trimmed = text.trim();
4772
- if (!trimmed) return;
4773
- dismiss();
4774
- queuePrompt(trimmed);
4775
- },
4776
- [queuePrompt, dismiss]
4777
- );
4778
- }
4779
- function InviteHero({
4780
- def,
4781
- box,
4782
- onDismiss
4783
- }) {
4784
- const { t } = useTranslation$1();
4785
- const user = useAuthStore((s) => s.user);
4786
- const ask = useAsk();
4787
- const [draft, setDraft] = React63.useState("");
4788
- const name = (user?.fullName || user?.email || "").split(/[\s@]/)[0];
4789
- React63.useEffect(() => {
4790
- const onKey = (event) => {
4791
- if (event.key === "Escape") onDismiss();
4792
- };
4793
- window.addEventListener("keydown", onKey);
4794
- return () => window.removeEventListener("keydown", onKey);
4795
- }, [onDismiss]);
4796
- return /* @__PURE__ */ jsxs(
4797
- "section",
4798
- {
4799
- className: cn$1(
4800
- "fixed z-30 flex flex-col items-center justify-center overflow-y-auto px-4 pb-14 text-center",
4801
- "bg-background/95 backdrop-blur-sm motion-safe:animate-in motion-safe:fade-in-0"
4802
- ),
4803
- style: { left: box.left, width: box.width, top: box.top, bottom: 0 },
4804
- "aria-label": def.persona ?? t("chat.defaultTitle"),
4805
- children: [
4806
- /* @__PURE__ */ jsx(
4807
- "button",
4808
- {
4809
- type: "button",
4810
- onClick: onDismiss,
4811
- "aria-label": t("assistant.invite.dismiss"),
4812
- title: t("assistant.invite.dismiss"),
4813
- className: "absolute right-1 top-2 inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground/60 transition-colors hover:bg-muted hover:text-foreground",
4814
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
4815
- }
4816
- ),
4817
- /* @__PURE__ */ jsx("h2", { className: "max-w-2xl text-balance text-[26px] font-semibold leading-tight tracking-tight text-foreground sm:text-[32px]", children: def.headline.replace("{{name}}", name) }),
4818
- /* @__PURE__ */ jsx(
4819
- InviteComposer,
4820
- {
4821
- className: "mt-7 w-full max-w-3xl",
4822
- placeholder: def.placeholder ?? t("assistant.invite.placeholder"),
4823
- value: draft,
4824
- onChange: setDraft,
4825
- onSubmit: () => {
4826
- ask(draft);
4827
- setDraft("");
4828
- },
4829
- size: "lg"
4830
- }
4831
- ),
4832
- def.suggestions && def.suggestions.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center justify-center gap-2", children: def.suggestions.slice(0, 4).map((suggestion) => /* @__PURE__ */ jsx(
4833
- "button",
4834
- {
4835
- type: "button",
4836
- onClick: () => ask(suggestion.prompt ?? suggestion.label),
4837
- className: cn$1(
4838
- "rounded-full border border-border/70 bg-card px-4 py-2 text-[13px] text-foreground/80 transition-colors",
4839
- "hover:border-border hover:bg-muted hover:text-foreground",
4840
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
4841
- ),
4842
- children: suggestion.label
4843
- },
4844
- suggestion.label
4845
- )) })
4846
- ]
4847
- }
4848
- );
4849
- }
4850
- function InviteDock({ def, left, maxWidth }) {
4851
- const { t } = useTranslation$1();
4852
- const ask = useAsk();
4853
- const setOpen = useChatStore((s) => s.setOpen);
4854
- const [draft, setDraft] = React63.useState("");
4855
- return /* @__PURE__ */ jsx(
4856
- "div",
4857
- {
4858
- className: "fixed bottom-4 z-40 hidden md:block",
4859
- style: { left, width: Math.min(maxWidth, 640), transform: "translateX(-50%)" },
4860
- children: /* @__PURE__ */ jsx(
4861
- InviteComposer,
4862
- {
4863
- className: "shadow-lg motion-safe:animate-slide-in-from-bottom",
4864
- placeholder: def.placeholder ?? t("assistant.invite.placeholder"),
4865
- value: draft,
4866
- onChange: setDraft,
4867
- onSubmit: () => {
4868
- if (draft.trim()) {
4869
- ask(draft);
4870
- setDraft("");
4871
- } else {
4872
- setOpen(true);
4873
- }
4874
- },
4875
- onFocusAction: () => setOpen(true),
4876
- label: def.persona
4877
- }
4878
- )
4879
- }
4880
- );
4881
- }
4882
- function InviteComposer({
4883
- className,
4884
- placeholder,
4885
- value,
4886
- onChange,
4887
- onSubmit,
4888
- onFocusAction,
4889
- label,
4890
- size = "md"
4891
- }) {
4892
- const { t } = useTranslation$1();
4893
- return /* @__PURE__ */ jsxs(
4894
- "form",
4895
- {
4896
- onSubmit: (event) => {
4897
- event.preventDefault();
4898
- onSubmit();
4899
- },
4900
- className: cn$1(
4901
- "flex items-center gap-2 rounded-2xl border border-border/70 bg-card pl-4 pr-2 transition-shadow",
4902
- "focus-within:border-border focus-within:shadow-md",
4903
- size === "lg" ? "py-2.5" : "py-1.5",
4904
- className
4905
- ),
4906
- children: [
4907
- /* @__PURE__ */ jsx(Sparkles, { className: cn$1("shrink-0 text-primary", size === "lg" ? "h-5 w-5" : "h-4 w-4") }),
4908
- /* @__PURE__ */ jsx(
4909
- "input",
4910
- {
4911
- value,
4912
- onChange: (event) => onChange(event.target.value),
4913
- placeholder,
4914
- "aria-label": label ?? placeholder,
4915
- className: cn$1(
4916
- "min-w-0 flex-1 bg-transparent text-foreground placeholder:text-muted-foreground/70 focus:outline-none",
4917
- size === "lg" ? "py-1.5 text-[15px]" : "text-[13.5px]"
4918
- )
4919
- }
4920
- ),
4921
- /* @__PURE__ */ jsx(
4922
- "button",
4923
- {
4924
- type: "submit",
4925
- onClick: () => {
4926
- if (!value.trim()) onFocusAction?.();
4927
- },
4928
- "aria-label": t("chat.send"),
4929
- className: cn$1(
4930
- "inline-flex shrink-0 items-center justify-center rounded-full transition-colors",
4931
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
4932
- value.trim() ? "bg-primary text-primary-foreground hover:opacity-90" : "bg-muted text-muted-foreground hover:bg-muted/80",
4933
- size === "lg" ? "h-9 w-9" : "h-7 w-7"
4934
- ),
4935
- children: /* @__PURE__ */ jsx(ArrowUp, { className: size === "lg" ? "h-4 w-4" : "h-3.5 w-3.5" })
4936
- }
4937
- )
4938
- ]
4939
- }
4940
- );
5180
+ "aria-label": expanded ? t("chat.fab.close") : t("chat.fab.open"),
5181
+ "aria-expanded": expanded,
5182
+ style: entering ? { animation: "fabEnter 600ms cubic-bezier(0.34, 1.56, 0.64, 1) both" } : void 0,
5183
+ children: [
5184
+ entering && /* @__PURE__ */ jsxs(Fragment, { children: [
5185
+ /* @__PURE__ */ jsx(
5186
+ "span",
5187
+ {
5188
+ "aria-hidden": true,
5189
+ className: "absolute inset-0 rounded-full bg-primary/40",
5190
+ style: { animation: "fabEnterRing 900ms ease-out 250ms both" }
5191
+ }
5192
+ ),
5193
+ /* @__PURE__ */ jsx(
5194
+ "span",
5195
+ {
5196
+ "aria-hidden": true,
5197
+ className: "absolute inset-0 rounded-full bg-primary/25",
5198
+ style: { animation: "fabEnterRing 1100ms ease-out 450ms both" }
5199
+ }
5200
+ )
5201
+ ] }),
5202
+ isStreaming && !expanded && /* @__PURE__ */ jsx(
5203
+ "span",
5204
+ {
5205
+ "aria-hidden": true,
5206
+ className: "absolute inset-0 rounded-full bg-primary motion-safe:[animation:fabHalo_1.8s_ease-out_infinite]"
5207
+ }
5208
+ ),
5209
+ activity && activity.state !== "done" && expanded && /* @__PURE__ */ jsxs(
5210
+ "span",
5211
+ {
5212
+ "aria-hidden": true,
5213
+ className: "absolute -right-0.5 -top-0.5 flex h-3 w-3 items-center justify-center",
5214
+ children: [
5215
+ activity.state === "running" && /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-red-500 opacity-70" }),
5216
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full border-2 border-primary bg-red-500" })
5217
+ ]
5218
+ }
5219
+ ),
5220
+ expanded ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(
5221
+ Sparkles,
5222
+ {
5223
+ className: "relative h-5 w-5",
5224
+ style: entering ? { animation: "fabEnterIcon 700ms cubic-bezier(0.34, 1.56, 0.64, 1) 120ms both" } : void 0
5225
+ }
5226
+ )
5227
+ ]
5228
+ }
5229
+ )
5230
+ ]
5231
+ }
5232
+ )
5233
+ ] });
4941
5234
  }
4942
5235
  var MAX_SUGGESTIONS = 5;
4943
5236
  function ChatSuggestions({ suggestions, onSelect, onDismiss, compact }) {
4944
5237
  const { t } = useTranslation$1();
4945
- const [open, setOpen] = React63.useState(true);
5238
+ const [open, setOpen] = React65.useState(true);
4946
5239
  const visible = suggestions.slice(0, compact ? 3 : MAX_SUGGESTIONS);
4947
5240
  if (!visible.length) return null;
4948
5241
  return /* @__PURE__ */ jsxs(
@@ -5021,8 +5314,8 @@ function SuggestionRow({
5021
5314
  detail,
5022
5315
  onSelect
5023
5316
  }) {
5024
- const labelRef = React63.useRef(null);
5025
- const [showDetail, setShowDetail] = React63.useState(false);
5317
+ const labelRef = React65.useRef(null);
5318
+ const [showDetail, setShowDetail] = React65.useState(false);
5026
5319
  const evaluate = () => {
5027
5320
  const el = labelRef.current;
5028
5321
  const clipped = !!el && el.scrollWidth > el.clientWidth + 1;
@@ -5112,8 +5405,8 @@ function ChatToolsPanel({ toolGroups, onClose }) {
5112
5405
  }
5113
5406
  function AutoApprovalsSection() {
5114
5407
  const { t } = useTranslation$1();
5115
- const [rules, setRules] = React63.useState(() => listAutoApprovals());
5116
- React63.useEffect(() => subscribeAutoApprovals(() => setRules(listAutoApprovals())), []);
5408
+ const [rules, setRules] = React65.useState(() => listAutoApprovals());
5409
+ React65.useEffect(() => subscribeAutoApprovals(() => setRules(listAutoApprovals())), []);
5117
5410
  if (!rules.length) return null;
5118
5411
  return /* @__PURE__ */ jsxs("div", { className: "border-b border-border/40 px-3 py-2.5", children: [
5119
5412
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-1 pb-1.5", children: [
@@ -5180,9 +5473,9 @@ function ConnectorMark({
5180
5473
  connector,
5181
5474
  size = "md"
5182
5475
  }) {
5183
- const candidates = React63__default.useMemo(() => markCandidates(connector), [connector.logoUrl, connector.name]);
5184
- const [attempt, setAttempt] = React63__default.useState(0);
5185
- React63__default.useEffect(() => setAttempt(0), [connector.id]);
5476
+ const candidates = React65__default.useMemo(() => markCandidates(connector), [connector.logoUrl, connector.name]);
5477
+ const [attempt, setAttempt] = React65__default.useState(0);
5478
+ React65__default.useEffect(() => setAttempt(0), [connector.id]);
5186
5479
  const box = size === "sm" ? "h-9 w-9" : "h-10 w-10";
5187
5480
  const glyph = size === "sm" ? "h-4 w-4" : "h-5 w-5";
5188
5481
  if (connector.builtIn) {
@@ -5452,15 +5745,15 @@ function ConnectorSyncActions({
5452
5745
  actions,
5453
5746
  onRan
5454
5747
  }) {
5455
- const [running, setRunning] = React63__default.useState(null);
5456
- const [open, setOpen] = React63__default.useState(null);
5457
- const [pending, setPending] = React63__default.useState(null);
5458
- const [hidden, setHidden] = React63__default.useState(() => /* @__PURE__ */ new Set());
5459
- const [queued, setQueued] = React63__default.useState({});
5460
- const [nonce, setNonce] = React63__default.useState(0);
5461
- const [expanded, setExpanded] = React63__default.useState(false);
5462
- const [resolved, setResolved] = React63__default.useState(false);
5463
- React63__default.useEffect(() => {
5748
+ const [running, setRunning] = React65__default.useState(null);
5749
+ const [open, setOpen] = React65__default.useState(null);
5750
+ const [pending, setPending] = React65__default.useState(null);
5751
+ const [hidden, setHidden] = React65__default.useState(() => /* @__PURE__ */ new Set());
5752
+ const [queued, setQueued] = React65__default.useState({});
5753
+ const [nonce, setNonce] = React65__default.useState(0);
5754
+ const [expanded, setExpanded] = React65__default.useState(false);
5755
+ const [resolved, setResolved] = React65__default.useState(false);
5756
+ React65__default.useEffect(() => {
5464
5757
  let cancelled = false;
5465
5758
  setResolved(false);
5466
5759
  void Promise.all(
@@ -5645,10 +5938,10 @@ function formatRelative(iso, t) {
5645
5938
  }
5646
5939
  function useConnectorRuns(connector) {
5647
5940
  const listRuns = connector.definition?.listRuns;
5648
- const [runs, setRuns] = React63__default.useState([]);
5649
- const [loading, setLoading] = React63__default.useState(!!listRuns);
5650
- const [nonce, setNonce] = React63__default.useState(0);
5651
- React63__default.useEffect(() => {
5941
+ const [runs, setRuns] = React65__default.useState([]);
5942
+ const [loading, setLoading] = React65__default.useState(!!listRuns);
5943
+ const [nonce, setNonce] = React65__default.useState(0);
5944
+ React65__default.useEffect(() => {
5652
5945
  if (!listRuns) {
5653
5946
  setRuns([]);
5654
5947
  setLoading(false);
@@ -5670,7 +5963,7 @@ function useConnectorRuns(connector) {
5670
5963
  return {
5671
5964
  runs,
5672
5965
  loading,
5673
- reload: React63__default.useCallback(() => setNonce((n) => n + 1), []),
5966
+ reload: React65__default.useCallback(() => setNonce((n) => n + 1), []),
5674
5967
  failing: isFailing(runs)
5675
5968
  };
5676
5969
  }
@@ -5882,13 +6175,13 @@ function ConnectorSettingsPanel({
5882
6175
  const { t } = useTranslation$1();
5883
6176
  const def = connector.definition;
5884
6177
  const settings = def ? connectorSettings(def) : [];
5885
- const [values, setValues] = React63__default.useState(
6178
+ const [values, setValues] = React65__default.useState(
5886
6179
  () => initialValues(settings, {})
5887
6180
  );
5888
- const [loaded, setLoaded] = React63__default.useState(false);
5889
- const [saving, setSaving] = React63__default.useState(false);
5890
- const [dirty, setDirty] = React63__default.useState(false);
5891
- React63__default.useEffect(() => {
6181
+ const [loaded, setLoaded] = React65__default.useState(false);
6182
+ const [saving, setSaving] = React65__default.useState(false);
6183
+ const [dirty, setDirty] = React65__default.useState(false);
6184
+ React65__default.useEffect(() => {
5892
6185
  if (settings.length === 0 || !def?.getSettings) return;
5893
6186
  let cancelled = false;
5894
6187
  setLoaded(false);
@@ -6139,13 +6432,13 @@ function ConnectorInstall({
6139
6432
  }) {
6140
6433
  const { t } = useTranslation$1();
6141
6434
  const def = connector.definition;
6142
- const [plane, setPlane] = React63__default.useState(
6435
+ const [plane, setPlane] = React65__default.useState(
6143
6436
  connector.planes.includes("agentic") ? "agentic" : connector.planes[0]
6144
6437
  );
6145
- const [values, setValues] = React63__default.useState({});
6146
- const secrets = React63__default.useRef({});
6147
- const [busy, setBusy] = React63__default.useState(null);
6148
- React63__default.useEffect(() => {
6438
+ const [values, setValues] = React65__default.useState({});
6439
+ const secrets = React65__default.useRef({});
6440
+ const [busy, setBusy] = React65__default.useState(null);
6441
+ React65__default.useEffect(() => {
6149
6442
  setValues({});
6150
6443
  secrets.current = {};
6151
6444
  setBusy(null);
@@ -6186,13 +6479,13 @@ function ConnectorHome({
6186
6479
  const needsSetup = connector.state === "needs_setup";
6187
6480
  const settingsAvailable = hasOperatingSettings(connector);
6188
6481
  const runsAvailable = hasRunHistory(connector);
6189
- const [tab, setTab] = React63__default.useState(
6482
+ const [tab, setTab] = React65__default.useState(
6190
6483
  needsSetup && settingsAvailable ? "settings" : "overview"
6191
6484
  );
6192
- const [busy, setBusy] = React63__default.useState(null);
6485
+ const [busy, setBusy] = React65__default.useState(null);
6193
6486
  const run = useRunner(setBusy, onChanged);
6194
6487
  const runs = useConnectorRuns(connector);
6195
- React63__default.useEffect(() => {
6488
+ React65__default.useEffect(() => {
6196
6489
  setTab(needsSetup && settingsAvailable ? "settings" : "overview");
6197
6490
  setBusy(null);
6198
6491
  }, [connector.id]);
@@ -6269,7 +6562,7 @@ function ConnectorHome({
6269
6562
  }
6270
6563
  function useRunner(setBusy, onChanged) {
6271
6564
  const { t } = useTranslation$1();
6272
- return React63__default.useCallback(
6565
+ return React65__default.useCallback(
6273
6566
  async (kind, fn, okMessage) => {
6274
6567
  setBusy(kind);
6275
6568
  try {
@@ -6378,7 +6671,7 @@ function ConnectPanel({
6378
6671
  const tenantId2 = useTenantOptional()?.org?.id;
6379
6672
  const needsSink = needsCredentialSink(def);
6380
6673
  const sinkMissing = needsSink && !getConnectorCredentialSink();
6381
- const [formKey, setFormKey] = React63__default.useState(0);
6674
+ const [formKey, setFormKey] = React65__default.useState(0);
6382
6675
  if (def.authKind === "oauth") {
6383
6676
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
6384
6677
  /* @__PURE__ */ jsx(Note, { children: t("connectors.authorize.oauthOnly") }),
@@ -6510,7 +6803,7 @@ function GrantRevoke({
6510
6803
  onRevoke
6511
6804
  }) {
6512
6805
  const { t } = useTranslation$1();
6513
- const [revoking, setRevoking] = React63__default.useState(false);
6806
+ const [revoking, setRevoking] = React65__default.useState(false);
6514
6807
  if (connector.revocable === false) {
6515
6808
  return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("connectors.partOfApp") });
6516
6809
  }
@@ -6549,13 +6842,13 @@ function ConnectorsPanel({ onClose }) {
6549
6842
  const { connectors, reload, revokeGrant, agentAvailable, tenantReady } = useConnectors();
6550
6843
  const selectedId = useConnectorsSurface((s) => s.selected);
6551
6844
  const select = useConnectorsSurface((s) => s.select);
6552
- const [query, setQuery] = React63__default.useState("");
6553
- const [failure, setFailure] = React63__default.useState(null);
6554
- const selected = React63__default.useMemo(
6845
+ const [query, setQuery] = React65__default.useState("");
6846
+ const [failure, setFailure] = React65__default.useState(null);
6847
+ const selected = React65__default.useMemo(
6555
6848
  () => (connectors ?? []).find((c) => c.id === selectedId) ?? null,
6556
6849
  [connectors, selectedId]
6557
6850
  );
6558
- const filtered = React63__default.useMemo(
6851
+ const filtered = React65__default.useMemo(
6559
6852
  () => filterConnectors(connectors ?? [], "outbound", query),
6560
6853
  [connectors, query]
6561
6854
  );
@@ -6657,20 +6950,20 @@ function useDictation({
6657
6950
  onInterim
6658
6951
  }) {
6659
6952
  const resolvedLocale = locale ?? (typeof navigator !== "undefined" ? navigator.language || "pt-BR" : "pt-BR");
6660
- const [state, setState] = React63.useState("idle");
6661
- const [error, setError] = React63.useState(null);
6662
- const sessionRef = React63.useRef(null);
6663
- const callbacks = React63.useRef({ onTranscript, onInterim });
6953
+ const [state, setState] = React65.useState("idle");
6954
+ const [error, setError] = React65.useState(null);
6955
+ const sessionRef = React65.useRef(null);
6956
+ const callbacks = React65.useRef({ onTranscript, onInterim });
6664
6957
  callbacks.current = { onTranscript, onInterim };
6665
- const supported = React63.useMemo(
6958
+ const supported = React65.useMemo(
6666
6959
  () => isDictationSupported(!!transcribeEndpoint),
6667
6960
  [transcribeEndpoint]
6668
6961
  );
6669
- const finish = React63.useCallback(() => {
6962
+ const finish = React65.useCallback(() => {
6670
6963
  sessionRef.current = null;
6671
6964
  setState("idle");
6672
6965
  }, []);
6673
- const start = React63.useCallback(() => {
6966
+ const start = React65.useCallback(() => {
6674
6967
  if (sessionRef.current || !supported) return;
6675
6968
  stopSpeaking();
6676
6969
  setError(null);
@@ -6691,28 +6984,28 @@ function useDictation({
6691
6984
  }
6692
6985
  );
6693
6986
  }, [finish, resolvedLocale, supported, transcribeEndpoint]);
6694
- const stop = React63.useCallback(() => {
6987
+ const stop = React65.useCallback(() => {
6695
6988
  if (!sessionRef.current) return;
6696
6989
  if (transcribeEndpoint) setState("transcribing");
6697
6990
  sessionRef.current.stop();
6698
6991
  }, [transcribeEndpoint]);
6699
- const cancel = React63.useCallback(() => {
6992
+ const cancel = React65.useCallback(() => {
6700
6993
  sessionRef.current?.cancel();
6701
6994
  sessionRef.current = null;
6702
6995
  setState("idle");
6703
6996
  setError(null);
6704
6997
  }, []);
6705
- const toggle = React63.useCallback(() => {
6998
+ const toggle = React65.useCallback(() => {
6706
6999
  if (state === "listening" || state === "transcribing") stop();
6707
7000
  else start();
6708
7001
  }, [state, start, stop]);
6709
- React63.useEffect(() => () => sessionRef.current?.cancel(), []);
7002
+ React65.useEffect(() => () => sessionRef.current?.cancel(), []);
6710
7003
  return { supported, state, error, start, stop, cancel, toggle, clearError: () => setError(null) };
6711
7004
  }
6712
7005
  function useMicLevels(active2, bandCount = 28, externalStream) {
6713
- const levelsRef = React63.useRef(new Float32Array(bandCount));
6714
- const [available, setAvailable] = React63.useState(false);
6715
- React63.useEffect(() => {
7006
+ const levelsRef = React65.useRef(new Float32Array(bandCount));
7007
+ const [available, setAvailable] = React65.useState(false);
7008
+ React65.useEffect(() => {
6716
7009
  if (!active2) {
6717
7010
  levelsRef.current.fill(0);
6718
7011
  setAvailable(false);
@@ -6781,8 +7074,8 @@ var KIND_ICON = {
6781
7074
  binary: File
6782
7075
  };
6783
7076
  function useFileDrop(enabled, onFiles) {
6784
- const [dragging, setDragging] = React63.useState(false);
6785
- const depth = React63.useRef(0);
7077
+ const [dragging, setDragging] = React65.useState(false);
7078
+ const depth = React65.useRef(0);
6786
7079
  const carriesFiles = (event) => Array.from(event.dataTransfer?.types ?? []).includes("Files");
6787
7080
  const reset = () => {
6788
7081
  depth.current = 0;
@@ -6842,7 +7135,7 @@ function AttachmentChips({
6842
7135
  onRemove
6843
7136
  }) {
6844
7137
  const { t } = useTranslation$1();
6845
- const [openId, setOpenId] = React63.useState(null);
7138
+ const [openId, setOpenId] = React65.useState(null);
6846
7139
  if (!attachments.length) return null;
6847
7140
  const opened = attachments.find((a) => a.id === openId);
6848
7141
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -6953,8 +7246,8 @@ function ChatComposer({
6953
7246
  onToggleSuggestions
6954
7247
  }) {
6955
7248
  const { t } = useTranslation$1();
6956
- const [input, setInput] = React63.useState("");
6957
- const textareaRef = React63.useRef(null);
7249
+ const [input, setInput] = React65.useState("");
7250
+ const textareaRef = React65.useRef(null);
6958
7251
  const voiceStartRequested = useChatStore((s) => s.voiceStartRequested);
6959
7252
  const consumeVoiceStart = useChatStore((s) => s.consumeVoiceStart);
6960
7253
  const draft = useChatStore((s) => s.draft);
@@ -6962,13 +7255,13 @@ function ChatComposer({
6962
7255
  const attachments = useChatStore((s) => s.attachments);
6963
7256
  const addAttachments = useChatStore((s) => s.addAttachments);
6964
7257
  const removeAttachment = useChatStore((s) => s.removeAttachment);
6965
- const fileInputRef = React63.useRef(null);
7258
+ const fileInputRef = React65.useRef(null);
6966
7259
  const autoSend = voice?.autoSend === true;
6967
7260
  const micEnabled = voice?.input !== false;
6968
7261
  const canAttach = isConfigured && !blocked && !isStreaming;
6969
7262
  const parsing = attachments.some((a) => a.status === "parsing");
6970
7263
  const readyAttachments = attachments.filter((a) => a.status === "ready").length;
6971
- const submit = React63.useCallback(
7264
+ const submit = React65.useCallback(
6972
7265
  (text) => {
6973
7266
  const trimmed = text.trim();
6974
7267
  if (!trimmed && !readyAttachments || parsing || isStreaming || !isConfigured || blocked) return;
@@ -7001,7 +7294,7 @@ function ChatComposer({
7001
7294
  const WAVEFORM_BANDS = 56;
7002
7295
  const activity = useAssistantActivity();
7003
7296
  const sessionRecording = activity?.kind === "recording" && activity.state === "running";
7004
- const sessionStream = React63.useMemo(
7297
+ const sessionStream = React65.useMemo(
7005
7298
  () => sessionRecording ? activity?.stream?.() ?? null : null,
7006
7299
  [sessionRecording, activity]
7007
7300
  );
@@ -7013,12 +7306,12 @@ function ChatComposer({
7013
7306
  );
7014
7307
  const sessionPause = activity?.actions?.find((a) => a.intent === "pause" || a.intent === "resume");
7015
7308
  const sessionOwnsMic = activity?.kind === "recording" && (activity.state === "running" || activity.state === "paused");
7016
- React63.useEffect(() => {
7309
+ React65.useEffect(() => {
7017
7310
  if (!voiceStartRequested) return;
7018
7311
  consumeVoiceStart();
7019
7312
  if (showMic) dictation.start();
7020
7313
  }, [voiceStartRequested, showMic]);
7021
- React63.useEffect(() => {
7314
+ React65.useEffect(() => {
7022
7315
  if (!draft) return;
7023
7316
  setInput(draft);
7024
7317
  setDraft("");
@@ -7028,7 +7321,7 @@ function ChatComposer({
7028
7321
  el.setSelectionRange(draft.length, draft.length);
7029
7322
  }
7030
7323
  }, [draft, setDraft]);
7031
- React63.useLayoutEffect(() => {
7324
+ React65.useLayoutEffect(() => {
7032
7325
  const el = textareaRef.current;
7033
7326
  if (!el) return;
7034
7327
  const style = getComputedStyle(el);
@@ -7225,8 +7518,8 @@ function LiveWaveform({
7225
7518
  compact,
7226
7519
  tone = "primary"
7227
7520
  }) {
7228
- const barsRef = React63.useRef([]);
7229
- React63.useEffect(() => {
7521
+ const barsRef = React65.useRef([]);
7522
+ React65.useEffect(() => {
7230
7523
  if (!live) return;
7231
7524
  let frame = 0;
7232
7525
  const draw = () => {
@@ -7507,7 +7800,7 @@ function ConnectorSuggestionCard({ card }) {
7507
7800
  const setOpen = useChatStore((s) => s.setOpen);
7508
7801
  const category = categoryLabel(card.category);
7509
7802
  const live = isConnectorState(card.state) ? card.state : null;
7510
- const go = React63.useCallback(() => {
7803
+ const go = React65.useCallback(() => {
7511
7804
  if (panelAvailable && card.id) {
7512
7805
  openConnectorsPanel({ id: card.id });
7513
7806
  return;
@@ -7564,7 +7857,7 @@ function humanizeToolName(name) {
7564
7857
  }
7565
7858
  function ToolCallRow({ call }) {
7566
7859
  const { t } = useTranslation$1();
7567
- const [open, setOpen] = React63.useState(false);
7860
+ const [open, setOpen] = React65.useState(false);
7568
7861
  const failed = call.declined || /"error"\s*:/.test(call.result ?? "");
7569
7862
  return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-xl border border-border/50 bg-muted/30", children: [
7570
7863
  /* @__PURE__ */ jsxs(
@@ -7627,7 +7920,7 @@ function ToolCallRow({ call }) {
7627
7920
  }
7628
7921
  function MessageBubble({ message, isPending }) {
7629
7922
  const isUser = message.role === "user";
7630
- const routedLinks = React63.useMemo(
7923
+ const routedLinks = React65.useMemo(
7631
7924
  () => (message.links ?? []).filter(
7632
7925
  (l) => !!l.path || !!resolveEntityRoute(l.archetype, l.kind, l.entityKey)
7633
7926
  ),
@@ -7702,9 +7995,9 @@ function ConfirmActionCard({
7702
7995
  onResolve
7703
7996
  }) {
7704
7997
  const { t } = useTranslation$1();
7705
- const schema = action.fields ?? [];
7998
+ const schema = React65.useMemo(() => action.fields ?? [], [action.fields]);
7706
7999
  const partial = action.partial === true;
7707
- const seedValues = React63.useCallback(
8000
+ const seedValues = React65.useCallback(
7708
8001
  (params) => {
7709
8002
  if (partial) return { ...params };
7710
8003
  const seeded = {};
@@ -7713,17 +8006,17 @@ function ConfirmActionCard({
7713
8006
  },
7714
8007
  [schema, partial]
7715
8008
  );
7716
- const [values, setValues] = React63.useState(() => seedValues(action.params));
7717
- const [showAll, setShowAll] = React63.useState(false);
7718
- React63.useEffect(() => setValues(seedValues(action.params)), [action.params, seedValues]);
8009
+ const [values, setValues] = React65.useState(() => seedValues(action.params));
8010
+ const [showAll, setShowAll] = React65.useState(false);
8011
+ React65.useEffect(() => setValues(seedValues(action.params)), [action.params, seedValues]);
7719
8012
  const isSet = (v) => v !== void 0 && v !== null && v !== "";
7720
- const primaryKeys = React63.useMemo(() => {
8013
+ const primaryKeys = React65.useMemo(() => {
7721
8014
  const keys = Object.keys(action.params).filter((k) => isSet(action.params[k]));
7722
8015
  if (partial) return keys;
7723
8016
  for (const f of schema) if (f.required && !keys.includes(f.key)) keys.push(f.key);
7724
8017
  return keys;
7725
8018
  }, [action.params, schema, partial]);
7726
- const extraKeys = React63.useMemo(
8019
+ const extraKeys = React65.useMemo(
7727
8020
  () => schema.map((f) => f.key).filter((k) => !primaryKeys.includes(k) && k !== "id"),
7728
8021
  [schema, primaryKeys]
7729
8022
  );
@@ -7740,20 +8033,20 @@ function ConfirmActionCard({
7740
8033
  const translatedTool = t(toolKey);
7741
8034
  const heading = action.title ?? (translatedTool !== toolKey ? translatedTool : humanizeToolName(action.toolName));
7742
8035
  const ruleLabel = action.scopeLabel ?? heading;
7743
- const missingRequiredKeys = React63.useMemo(
8036
+ const missingRequiredKeys = React65.useMemo(
7744
8037
  () => partial ? [] : schema.filter((f) => f.required && !isSet(values[f.key])).map((f) => f.key),
7745
8038
  [schema, values, partial]
7746
8039
  );
7747
8040
  const canConfirm = missingRequiredKeys.length === 0;
7748
- const submitted = React63.useCallback(
8041
+ const submitted = React65.useCallback(
7749
8042
  () => partial ? Object.fromEntries(Object.entries(values).filter(([, v]) => isSet(v))) : values,
7750
8043
  [partial, values]
7751
8044
  );
7752
- const approve = React63.useCallback(() => {
8045
+ const approve = React65.useCallback(() => {
7753
8046
  if (canConfirm) onResolve(true, submitted());
7754
8047
  }, [canConfirm, onResolve, submitted]);
7755
- const decline = React63.useCallback(() => onResolve(false), [onResolve]);
7756
- const approveAlways = React63.useCallback(() => {
8048
+ const decline = React65.useCallback(() => onResolve(false), [onResolve]);
8049
+ const approveAlways = React65.useCallback(() => {
7757
8050
  if (!canConfirm) return;
7758
8051
  grantAutoApproval({
7759
8052
  toolName: action.toolName,
@@ -7763,7 +8056,7 @@ function ConfirmActionCard({
7763
8056
  });
7764
8057
  onResolve(true, submitted());
7765
8058
  }, [canConfirm, action.toolName, action.scope, ruleLabel, destructive, onResolve, submitted]);
7766
- React63.useEffect(() => {
8059
+ React65.useEffect(() => {
7767
8060
  const handler = (event) => {
7768
8061
  const target = event.target;
7769
8062
  const typing = target?.tagName === "INPUT" || target?.tagName === "TEXTAREA" || target?.tagName === "SELECT";
@@ -8097,7 +8390,7 @@ function UpgradePrompt({ feature, inline, className }) {
8097
8390
  const currentPlan = plans.find((p) => p.id === currentPlanId);
8098
8391
  const currentPrice = currentPlan?.price ?? 0;
8099
8392
  const currentPlanName = currentPlan?.name ?? tr("billing.free", "Free");
8100
- const featureName = React63.useMemo(() => {
8393
+ const featureName = React65.useMemo(() => {
8101
8394
  if (!feature) return null;
8102
8395
  const label = features.find((f) => f.id === feature)?.label;
8103
8396
  if (label) return featureDisplayName(label);
@@ -8105,7 +8398,7 @@ function UpgradePrompt({ feature, inline, className }) {
8105
8398
  const named = t(key);
8106
8399
  return named && named !== key ? named : featureDisplayName(feature);
8107
8400
  }, [features, feature, t]);
8108
- const unlockingPlans = React63.useMemo(
8401
+ const unlockingPlans = React65.useMemo(
8109
8402
  () => plans.filter((p) => p.id !== currentPlanId && p.price > currentPrice && planUnlocks(p, feature)).sort((a, b) => a.price - b.price),
8110
8403
  [plans, currentPlanId, currentPrice, feature]
8111
8404
  );
@@ -8283,30 +8576,30 @@ function ChatConversation({
8283
8576
  usable && !pendingAction && !isStreaming,
8284
8577
  addAttachments
8285
8578
  );
8286
- React63.useEffect(ensureIntroStyles, []);
8579
+ React65.useEffect(ensureIntroStyles, []);
8287
8580
  const conversationsLoaded = useChatStore((s) => s.conversationsLoaded);
8288
- const hasHistoryBackend = React63.useMemo(
8581
+ const hasHistoryBackend = React65.useMemo(
8289
8582
  () => !apiEndpoint && !!resolveFayzAgentConnection(agent),
8290
8583
  [apiEndpoint, agent]
8291
8584
  );
8292
8585
  const conversationsPending = hasHistoryBackend && !conversationsLoaded;
8293
- const [view, setView] = React63.useState(
8586
+ const [view, setView] = React65.useState(
8294
8587
  () => useChatStore.getState().messages.length > 0 ? "thread" : "list"
8295
8588
  );
8296
- const [drawer, setDrawer] = React63.useState("none");
8297
- const [atBottom, setAtBottom] = React63.useState(true);
8298
- const scrollRef = React63.useRef(null);
8589
+ const [drawer, setDrawer] = React65.useState("none");
8590
+ const [atBottom, setAtBottom] = React65.useState(true);
8591
+ const scrollRef = React65.useRef(null);
8299
8592
  const hasMessages = messages.length > 0;
8300
8593
  const totalTools = toolGroups.reduce((sum, g) => sum + g.tools.length, 0);
8301
- const scrollToBottom = React63.useCallback((behavior = "smooth") => {
8594
+ const scrollToBottom = React65.useCallback((behavior = "smooth") => {
8302
8595
  const el = scrollRef.current;
8303
8596
  if (!el) return;
8304
8597
  el.scrollTo({ top: el.scrollHeight, behavior });
8305
8598
  }, []);
8306
- React63.useEffect(() => {
8599
+ React65.useEffect(() => {
8307
8600
  if (atBottom) scrollToBottom();
8308
8601
  }, [messages, activeTools, pendingAction, atBottom, scrollToBottom]);
8309
- React63.useEffect(() => {
8602
+ React65.useEffect(() => {
8310
8603
  loadConversations();
8311
8604
  setSuggestionsOpen(useChatStore.getState().messages.length === 0);
8312
8605
  return () => stopSpeaking();
@@ -8323,7 +8616,7 @@ function ChatConversation({
8323
8616
  };
8324
8617
  const queuedPrompt = useChatStore((s) => s.queuedPrompt);
8325
8618
  const consumeQueuedPrompt = useChatStore((s) => s.consumeQueuedPrompt);
8326
- React63.useEffect(() => {
8619
+ React65.useEffect(() => {
8327
8620
  if (!queuedPrompt) return;
8328
8621
  const prompt = consumeQueuedPrompt();
8329
8622
  if (prompt && usable) send(prompt);
@@ -8331,7 +8624,7 @@ function ChatConversation({
8331
8624
  const toggleDrawer = (next) => setDrawer((current) => current === next ? "none" : next);
8332
8625
  const connectorsPending = useConnectorsSurface((s) => s.pending);
8333
8626
  const consumeConnectorsRequest = useConnectorsSurface((s) => s.consume);
8334
- React63.useEffect(() => {
8627
+ React65.useEffect(() => {
8335
8628
  if (!connectorsPending) return;
8336
8629
  setDrawer("connectors");
8337
8630
  consumeConnectorsRequest();
@@ -8618,7 +8911,7 @@ function HeaderButton({
8618
8911
  function useDelegateToAssistant() {
8619
8912
  const queuePrompt = useChatStore((s) => s.queuePrompt);
8620
8913
  const openPanel = useRightRailStore((s) => s.openPanel);
8621
- return React63.useCallback(
8914
+ return React65.useCallback(
8622
8915
  (prompt) => {
8623
8916
  queuePrompt(prompt);
8624
8917
  openPanel("chat");
@@ -8677,12 +8970,12 @@ function NotesPanel() {
8677
8970
  const user = useAuthStore((s) => s.user);
8678
8971
  const currentOrg = useOrganizationStore((s) => s.currentOrg);
8679
8972
  const key = storageKey2(currentOrg?.id ?? "local", user?.id ?? "anon");
8680
- const [notes, setNotes] = React63.useState(() => loadNotes(key));
8681
- const [openId, setOpenId] = React63.useState(null);
8682
- const [search, setSearch] = React63.useState("");
8683
- const [preview, setPreview] = React63.useState(false);
8684
- const [confirmingDelete, setConfirmingDelete] = React63.useState(null);
8685
- React63.useEffect(() => {
8973
+ const [notes, setNotes] = React65.useState(() => loadNotes(key));
8974
+ const [openId, setOpenId] = React65.useState(null);
8975
+ const [search, setSearch] = React65.useState("");
8976
+ const [preview, setPreview] = React65.useState(false);
8977
+ const [confirmingDelete, setConfirmingDelete] = React65.useState(null);
8978
+ React65.useEffect(() => {
8686
8979
  setNotes(loadNotes(key));
8687
8980
  setOpenId(null);
8688
8981
  }, [key]);
@@ -8915,10 +9208,10 @@ ${note.content}`
8915
9208
  function NoteTodosSection({ noteId, noteTitle, tr }) {
8916
9209
  const adapter2 = useNotesTasksBridge((s) => s.adapter);
8917
9210
  const delegate = useDelegateToAssistant();
8918
- const [todos, setTodos] = React63.useState([]);
8919
- const [draft, setDraft] = React63.useState("");
8920
- const [busy, setBusy] = React63.useState(false);
8921
- React63.useEffect(() => {
9211
+ const [todos, setTodos] = React65.useState([]);
9212
+ const [draft, setDraft] = React65.useState("");
9213
+ const [busy, setBusy] = React65.useState(false);
9214
+ React65.useEffect(() => {
8922
9215
  if (!adapter2) return;
8923
9216
  let alive = true;
8924
9217
  adapter2.listTodos(noteId).then((rows) => {
@@ -9069,11 +9362,11 @@ function NotesRailPanel() {
9069
9362
  const currentOrg = useOrganizationStore((s) => s.currentOrg);
9070
9363
  const key = storageKey2(currentOrg?.id ?? "local", user?.id ?? "anon");
9071
9364
  const count = useNotesCount((s) => s.count);
9072
- React63.useEffect(() => {
9365
+ React65.useEffect(() => {
9073
9366
  useNotesCount.getState().setCount(loadNotes(key).length);
9074
9367
  }, [key]);
9075
9368
  useRightRailPanel(
9076
- React63.useMemo(
9369
+ React65.useMemo(
9077
9370
  () => ({ id: "notes", label, icon: StickyNote, order: 30, badge: { count }, Component: NotesPanel }),
9078
9371
  [label, count]
9079
9372
  )
@@ -9487,7 +9780,13 @@ var BOOT_THEME_STORAGE_KEY = "fayz:boot-theme";
9487
9780
  function persistBootPalette(colors) {
9488
9781
  if (typeof localStorage === "undefined") return;
9489
9782
  try {
9490
- localStorage.setItem(BOOT_THEME_STORAGE_KEY, JSON.stringify({ sidebar: colors.sidebar }));
9783
+ localStorage.setItem(BOOT_THEME_STORAGE_KEY, JSON.stringify({
9784
+ sidebar: colors.sidebar,
9785
+ // The boot script seeds --sidebar-foreground too: the React skeleton's
9786
+ // spinner draws in it, and the default (dark-on-light) token is
9787
+ // invisible on a seeded dark rail.
9788
+ sidebarForeground: colors.sidebarForeground
9789
+ }));
9491
9790
  } catch {
9492
9791
  }
9493
9792
  }
@@ -9698,12 +9997,13 @@ function useGlobalSearch() {
9698
9997
  );
9699
9998
  const generation = useRef(0);
9700
9999
  const search = useCallback(
9701
- async (query, onPartial) => {
10000
+ async (query, onPartial, opts) => {
9702
10001
  const token = ++generation.current;
9703
10002
  return searchEverything(query, {
9704
10003
  targets,
9705
10004
  limit: 30,
9706
10005
  perTarget: 5,
10006
+ fresh: opts?.fresh,
9707
10007
  onPartial: onPartial ? (partial) => {
9708
10008
  if (token === generation.current) onPartial(partial);
9709
10009
  } : void 0
@@ -9720,7 +10020,7 @@ function useGlobalSearch() {
9720
10020
  );
9721
10021
  return { search, select, canOpen, targetCount: targets.length };
9722
10022
  }
9723
- var RouteErrorBoundary = class extends React63.Component {
10023
+ var RouteErrorBoundary = class extends React65.Component {
9724
10024
  constructor() {
9725
10025
  super(...arguments);
9726
10026
  this.state = { crashed: false, report: null };
@@ -9762,7 +10062,7 @@ var RouteErrorBoundary = class extends React63.Component {
9762
10062
  }
9763
10063
  };
9764
10064
  function RouteErrorScreen({ report, tr, onBack, onRetry, backLabel }) {
9765
- const [copied, setCopied] = React63.useState(false);
10065
+ const [copied, setCopied] = React65.useState(false);
9766
10066
  const copyDetails = () => {
9767
10067
  if (!report || typeof navigator === "undefined" || !navigator.clipboard) return;
9768
10068
  const details = [
@@ -9809,16 +10109,21 @@ function RouteErrorScreen({ report, tr, onBack, onRetry, backLabel }) {
9809
10109
  ] })
9810
10110
  ] });
9811
10111
  }
10112
+ var WidgetSurfaceContext = React65.createContext("rail");
10113
+ function useWidgetSurface() {
10114
+ return React65.useContext(WidgetSurfaceContext);
10115
+ }
9812
10116
  function WidgetSlot({
9813
10117
  zone,
9814
10118
  contextOverrides,
10119
+ surface,
9815
10120
  ...props
9816
10121
  }) {
9817
10122
  const runtime = usePluginRuntimeOptional();
9818
10123
  if (!runtime) return null;
9819
10124
  const widgets = getWidgetsForZone(runtime, zone, contextOverrides);
9820
10125
  if (!widgets.length) return null;
9821
- return /* @__PURE__ */ jsx(Fragment, { children: widgets.map((widget, i) => {
10126
+ return /* @__PURE__ */ jsx(WidgetSurfaceContext.Provider, { value: surface ?? "rail", children: widgets.map((widget, i) => {
9822
10127
  const Component2 = resolvePluginComponent(widget);
9823
10128
  if (!Component2) return null;
9824
10129
  const w = widget;
@@ -9850,7 +10155,7 @@ function useNavContribution(id, entries2) {
9850
10155
  const setContribution = useNavContributionsStore((s) => s.setContribution);
9851
10156
  const clearContribution = useNavContributionsStore((s) => s.clearContribution);
9852
10157
  const key = JSON.stringify(entries2);
9853
- React63.useEffect(() => {
10158
+ React65.useEffect(() => {
9854
10159
  setContribution(id, entries2);
9855
10160
  return () => clearContribution(id);
9856
10161
  }, [id, key]);
@@ -9880,7 +10185,7 @@ function useContextualNav(nav) {
9880
10185
  const setNav = useContextualNavStore((s) => s.setNav);
9881
10186
  const clearNav = useContextualNavStore((s) => s.clearNav);
9882
10187
  const key = nav ? JSON.stringify(nav) : null;
9883
- React63.useEffect(() => {
10188
+ React65.useEffect(() => {
9884
10189
  if (!nav) return;
9885
10190
  setNav(nav);
9886
10191
  return () => clearNav(nav.base);
@@ -10031,10 +10336,10 @@ function SeuSiteWidget({ fallback, onEdit }) {
10031
10336
  const load = useFayzProjectsStore((s) => s.load);
10032
10337
  const loadedFor = useFayzProjectsStore((s) => s.loadedFor);
10033
10338
  const collapsed = useLayoutStore((s) => s.sidebarCollapsed);
10034
- const [selectedId, setSelectedId] = React63.useState(null);
10035
- const [hovered, setHovered] = React63.useState(false);
10036
- const [primed, setPrimed] = React63.useState(false);
10037
- const closeTimer = React63.useRef(null);
10339
+ const [selectedId, setSelectedId] = React65.useState(null);
10340
+ const [hovered, setHovered] = React65.useState(false);
10341
+ const [primed, setPrimed] = React65.useState(false);
10342
+ const closeTimer = React65.useRef(null);
10038
10343
  const cancelClose = () => {
10039
10344
  if (closeTimer.current) {
10040
10345
  clearTimeout(closeTimer.current);
@@ -10050,8 +10355,8 @@ function SeuSiteWidget({ fallback, onEdit }) {
10050
10355
  cancelClose();
10051
10356
  closeTimer.current = setTimeout(() => setHovered(false), 140);
10052
10357
  };
10053
- React63.useEffect(() => () => cancelClose(), []);
10054
- React63.useEffect(() => {
10358
+ React65.useEffect(() => () => cancelClose(), []);
10359
+ React65.useEffect(() => {
10055
10360
  if (loadedFor !== (currentOrgId ?? null)) void load(currentOrgId);
10056
10361
  }, [currentOrgId, loadedFor, load]);
10057
10362
  const active2 = projects.find((p) => p.id === selectedId) ?? defaultProjectOf(projects) ?? synthFromFallback(fallback);
@@ -10140,10 +10445,10 @@ function SitePreview({
10140
10445
  onEdit,
10141
10446
  tr
10142
10447
  }) {
10143
- const [nonce, setNonce] = React63.useState(0);
10144
- const [loading, setLoading] = React63.useState(true);
10448
+ const [nonce, setNonce] = React65.useState(0);
10449
+ const [loading, setLoading] = React65.useState(true);
10145
10450
  const url = project.previewUrl;
10146
- React63.useEffect(() => {
10451
+ React65.useEffect(() => {
10147
10452
  setLoading(true);
10148
10453
  }, [nonce, url]);
10149
10454
  const action = "flex items-center gap-1.5 rounded-button px-2 py-1 text-[11.5px] font-medium text-sidebar-foreground/80 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground disabled:cursor-not-allowed disabled:opacity-40";
@@ -10240,8 +10545,8 @@ function RightRail({ frame = true }) {
10240
10545
  const openPanel = useRightRailStore((s) => s.openPanel);
10241
10546
  const close = useRightRailStore((s) => s.close);
10242
10547
  const setWidth = useRightRailStore((s) => s.setWidth);
10243
- const rootRef = React63.useRef(null);
10244
- React63.useEffect(() => {
10548
+ const rootRef = React65.useRef(null);
10549
+ React65.useEffect(() => {
10245
10550
  if (!open) return;
10246
10551
  const el = rootRef.current;
10247
10552
  if (!el) return;
@@ -10466,9 +10771,9 @@ var TYPE_COLORS = {
10466
10771
  fix: "text-success"
10467
10772
  };
10468
10773
  function ChangelogFeed({ changelogUrl, className }) {
10469
- const [entries2, setEntries] = React63.useState([]);
10470
- const [loading, setLoading] = React63.useState(false);
10471
- React63.useEffect(() => {
10774
+ const [entries2, setEntries] = React65.useState([]);
10775
+ const [loading, setLoading] = React65.useState(false);
10776
+ React65.useEffect(() => {
10472
10777
  if (!changelogUrl) return;
10473
10778
  setLoading(true);
10474
10779
  fetch(changelogUrl).then((r) => r.json()).then((data) => setEntries(data)).catch(() => setEntries([])).finally(() => setLoading(false));
@@ -10531,7 +10836,7 @@ function NotificationInbox({
10531
10836
  changelogUrl,
10532
10837
  className
10533
10838
  }) {
10534
- const [activeTab, setActiveTab] = React63.useState("inbox");
10839
+ const [activeTab, setActiveTab] = React65.useState("inbox");
10535
10840
  const unreadCount = notifications.filter((n) => !n.read).length;
10536
10841
  const inboxNotifications = notifications.filter((n) => n.category !== "changelog");
10537
10842
  return /* @__PURE__ */ jsxs("div", { className: cn$1("flex w-80 flex-col", className), children: [
@@ -10627,15 +10932,15 @@ function NotificationInbox({
10627
10932
  ] });
10628
10933
  }
10629
10934
  function ConnectConsentPage({ appName, logo }) {
10630
- const [state, setState] = React63__default.useState({ status: "loading" });
10631
- const [tenantId2, setTenantId] = React63__default.useState("");
10632
- const request = React63__default.useMemo(
10935
+ const [state, setState] = React65__default.useState({ status: "loading" });
10936
+ const [tenantId2, setTenantId] = React65__default.useState("");
10937
+ const request = React65__default.useMemo(
10633
10938
  () => new URLSearchParams(window.location.search).get("request") ?? "",
10634
10939
  []
10635
10940
  );
10636
- const connection = React63__default.useMemo(() => resolveFayzAgentConnection(), []);
10941
+ const connection = React65__default.useMemo(() => resolveFayzAgentConnection(), []);
10637
10942
  const product = appName ?? document.title ?? "este app";
10638
- const authorizedFetch = React63__default.useCallback(
10943
+ const authorizedFetch = React65__default.useCallback(
10639
10944
  async (path, init) => {
10640
10945
  if (!connection) throw new Error("Este app n\xE3o est\xE1 ligado a um projeto Fayz.");
10641
10946
  const supabase = getFayzSupabaseClientOptional();
@@ -10659,7 +10964,7 @@ function ConnectConsentPage({ appName, logo }) {
10659
10964
  },
10660
10965
  [connection]
10661
10966
  );
10662
- React63__default.useEffect(() => {
10967
+ React65__default.useEffect(() => {
10663
10968
  let cancelled = false;
10664
10969
  (async () => {
10665
10970
  if (!request) {
@@ -10900,25 +11205,25 @@ function AddConnectorPanel({
10900
11205
  onSelect
10901
11206
  }) {
10902
11207
  const { t } = useTranslation$1();
10903
- const [query, setQuery] = React63__default.useState("");
11208
+ const [query, setQuery] = React65__default.useState("");
10904
11209
  const modules = useInstalledModuleIds();
10905
11210
  const catalog = useConnectorCatalog();
10906
11211
  const scoped = !!categories?.length;
10907
- const addable = React63__default.useMemo(
11212
+ const addable = React65__default.useMemo(
10908
11213
  () => inCategories(addableConnectors(connectors ?? []), categories),
10909
11214
  [connectors, categories]
10910
11215
  );
10911
- const shelf = React63__default.useMemo(() => {
11216
+ const shelf = React65__default.useMemo(() => {
10912
11217
  if (connectors === null) return [];
10913
11218
  return inCategories(
10914
11219
  shelfIntegrations({ present: connectors, installedModules: modules, catalog: catalog ?? [] }),
10915
11220
  categories
10916
11221
  );
10917
11222
  }, [connectors, modules, catalog, categories]);
10918
- const filtered = React63__default.useMemo(() => filterConnectors(addable, "all", query), [addable, query]);
10919
- const filteredShelf = React63__default.useMemo(() => filterAvailable(shelf, query), [shelf, query]);
10920
- const soon = React63__default.useMemo(() => comingSoonHere(filteredShelf), [filteredShelf]);
10921
- const elsewhere = React63__default.useMemo(() => withAnotherModule(filteredShelf), [filteredShelf]);
11223
+ const filtered = React65__default.useMemo(() => filterConnectors(addable, "all", query), [addable, query]);
11224
+ const filteredShelf = React65__default.useMemo(() => filterAvailable(shelf, query), [shelf, query]);
11225
+ const soon = React65__default.useMemo(() => comingSoonHere(filteredShelf), [filteredShelf]);
11226
+ const elsewhere = React65__default.useMemo(() => withAnotherModule(filteredShelf), [filteredShelf]);
10922
11227
  const searchable = addable.length + shelf.length > 6;
10923
11228
  return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "2xl", children: [
10924
11229
  /* @__PURE__ */ jsxs(ModalHeader, { children: [
@@ -11077,20 +11382,20 @@ function AddConnectorButton({ onClick }) {
11077
11382
  function ConnectorsTab() {
11078
11383
  const { t } = useTranslation$1();
11079
11384
  const { connectors, error, reload, revokeGrant, agentAvailable, tenantReady } = useConnectors();
11080
- const [adding, setAdding] = React63__default.useState(false);
11081
- const [selected, setSelected] = React63__default.useState(null);
11082
- const [filter, setFilter] = React63__default.useState("all");
11083
- const [failure, setFailure] = React63__default.useState(null);
11385
+ const [adding, setAdding] = React65__default.useState(false);
11386
+ const [selected, setSelected] = React65__default.useState(null);
11387
+ const [filter, setFilter] = React65__default.useState("all");
11388
+ const [failure, setFailure] = React65__default.useState(null);
11084
11389
  const assistantAvailable = useConnectorsPanelAvailable();
11085
- const openJourney = React63__default.useCallback(
11390
+ const openJourney = React65__default.useCallback(
11086
11391
  (connector) => {
11087
11392
  if (assistantAvailable) openConnectorsPanel(connector);
11088
11393
  else setSelected(connector);
11089
11394
  },
11090
11395
  [assistantAvailable]
11091
11396
  );
11092
- const visible = React63__default.useMemo(() => filterConnectors(connectors ?? [], filter), [connectors, filter]);
11093
- React63__default.useEffect(() => {
11397
+ const visible = React65__default.useMemo(() => filterConnectors(connectors ?? [], filter), [connectors, filter]);
11398
+ React65__default.useEffect(() => {
11094
11399
  if (!selected || !connectors) return;
11095
11400
  const fresh = connectors.find((c) => c.id === selected.id);
11096
11401
  if (fresh && fresh !== selected) setSelected(fresh);
@@ -11103,23 +11408,17 @@ function ConnectorsTab() {
11103
11408
  return /* @__PURE__ */ jsx(Empty, { title: t("connectors.noOrg"), body: t("connectors.noOrgBody") });
11104
11409
  }
11105
11410
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
11106
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
11107
- /* @__PURE__ */ jsxs("div", { children: [
11108
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: t("settings.connectors") }),
11109
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: t("connectors.hubSubtitle") })
11110
- ] }),
11111
- /* @__PURE__ */ jsxs(
11112
- "button",
11113
- {
11114
- onClick: () => setAdding(true),
11115
- className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90",
11116
- children: [
11117
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
11118
- t("connectors.add")
11119
- ]
11120
- }
11121
- )
11122
- ] }),
11411
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(
11412
+ "button",
11413
+ {
11414
+ onClick: () => setAdding(true),
11415
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90",
11416
+ children: [
11417
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
11418
+ t("connectors.add")
11419
+ ]
11420
+ }
11421
+ ) }),
11123
11422
  /* @__PURE__ */ jsx(
11124
11423
  AddConnectorPanel,
11125
11424
  {
@@ -11136,7 +11435,7 @@ function ConnectorsTab() {
11136
11435
  (connectors?.length ?? 0) > 4 && /* @__PURE__ */ jsx(Filters, { value: filter, onChange: setFilter }),
11137
11436
  connectors === null && /* @__PURE__ */ jsx(ConnectionsSkeleton, {}),
11138
11437
  connectors !== null && visible.length === 0 && /* @__PURE__ */ jsx(Empty, { title: t("connectors.emptyTitle"), body: t("connectors.emptyBody") }),
11139
- visible.length > 0 && /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border", children: visible.map((c) => /* @__PURE__ */ jsx(ConnectorRow, { connector: c, onOpen: openJourney }, c.id)) }),
11438
+ visible.length > 0 && /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", children: visible.map((c) => /* @__PURE__ */ jsx(ConnectorRow, { connector: c, onOpen: openJourney }, c.id)) }),
11140
11439
  /* @__PURE__ */ jsx(
11141
11440
  ConnectorDetail,
11142
11441
  {
@@ -11171,7 +11470,7 @@ function Filters({ value, onChange }) {
11171
11470
  )) });
11172
11471
  }
11173
11472
  function ConnectionsSkeleton() {
11174
- return /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border", "aria-hidden": true, children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-4 p-4", children: [
11473
+ return /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", "aria-hidden": true, children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-4 p-4", children: [
11175
11474
  /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-10 shrink-0 rounded-lg" }),
11176
11475
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2 py-0.5", children: [
11177
11476
  /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" }),
@@ -11250,7 +11549,7 @@ function oauthFailureMessage(t, result) {
11250
11549
  function useConnectorOAuthReturn() {
11251
11550
  const { t } = useTranslation$1();
11252
11551
  const runtime = usePluginRuntimeOptional();
11253
- React63__default.useEffect(() => {
11552
+ React65__default.useEffect(() => {
11254
11553
  if (typeof window === "undefined") return;
11255
11554
  const result = readConnectorOAuthReturn(window.location.search);
11256
11555
  if (!result) return;
@@ -11285,8 +11584,8 @@ function ConnectorDeliveries({
11285
11584
  limit = 10
11286
11585
  }) {
11287
11586
  const { t } = useTranslation$1();
11288
- const [rows, setRows] = React63__default.useState(null);
11289
- React63__default.useEffect(() => {
11587
+ const [rows, setRows] = React65__default.useState(null);
11588
+ React65__default.useEffect(() => {
11290
11589
  let cancelled = false;
11291
11590
  void load({ limit }).then((r) => {
11292
11591
  if (!cancelled) setRows(r);
@@ -11360,11 +11659,11 @@ var CURRENCIES = [
11360
11659
  function CompanySettings({ tenant, onSave }) {
11361
11660
  const { t } = useTranslation$1();
11362
11661
  const { locale: uiLocale, setLocale: setUiLocale } = useLocaleStore();
11363
- const [name, setName] = React63.useState(tenant?.name ?? "");
11364
- const [timezone, setTimezone] = React63.useState(tenant?.settings?.timezone ?? "UTC");
11365
- const [currency, setCurrency] = React63.useState(tenant?.settings?.currency ?? "USD");
11366
- const [saving, setSaving] = React63.useState(false);
11367
- React63.useEffect(() => {
11662
+ const [name, setName] = React65.useState(tenant?.name ?? "");
11663
+ const [timezone, setTimezone] = React65.useState(tenant?.settings?.timezone ?? "UTC");
11664
+ const [currency, setCurrency] = React65.useState(tenant?.settings?.currency ?? "USD");
11665
+ const [saving, setSaving] = React65.useState(false);
11666
+ React65.useEffect(() => {
11368
11667
  if (tenant) {
11369
11668
  setName(tenant.name);
11370
11669
  setTimezone(tenant.settings?.timezone ?? "UTC");
@@ -11502,10 +11801,10 @@ function getInitials(name) {
11502
11801
  }
11503
11802
  function UserProfile({ user, onSave, onAvatarChange }) {
11504
11803
  const { t } = useTranslation$1();
11505
- const [fullName, setFullName] = React63.useState(user?.fullName ?? "");
11506
- const [saving, setSaving] = React63.useState(false);
11507
- const fileInputRef = React63.useRef(null);
11508
- React63.useEffect(() => {
11804
+ const [fullName, setFullName] = React65.useState(user?.fullName ?? "");
11805
+ const [saving, setSaving] = React65.useState(false);
11806
+ const fileInputRef = React65.useRef(null);
11807
+ React65.useEffect(() => {
11509
11808
  if (user) {
11510
11809
  setFullName(user.fullName);
11511
11810
  }
@@ -11610,8 +11909,8 @@ function UserProfile({ user, onSave, onAvatarChange }) {
11610
11909
  function ConnectedUserProfile() {
11611
11910
  const user = useAuthStore((s) => s.user);
11612
11911
  const setUser = useAuthStore((s) => s.setUser);
11613
- const [profile, setProfile] = React63.useState(null);
11614
- React63.useEffect(() => {
11912
+ const [profile, setProfile] = React65.useState(null);
11913
+ React65.useEffect(() => {
11615
11914
  const supabase = getFayzSupabaseClientOptional();
11616
11915
  if (!user || !supabase) return;
11617
11916
  let cancelled = false;
@@ -11711,9 +12010,9 @@ function SecuritySettings({
11711
12010
  onTenantSecurityChange
11712
12011
  }) {
11713
12012
  const { t } = useTranslation$1();
11714
- const [resetting, setResetting] = React63.useState(false);
11715
- const [resetSent, setResetSent] = React63.useState(false);
11716
- const [savingTenant, setSavingTenant] = React63.useState(false);
12013
+ const [resetting, setResetting] = React65.useState(false);
12014
+ const [resetSent, setResetSent] = React65.useState(false);
12015
+ const [savingTenant, setSavingTenant] = React65.useState(false);
11717
12016
  const handleResetPassword = async () => {
11718
12017
  if (!onResetPassword) return;
11719
12018
  setResetting(true);
@@ -11889,8 +12188,8 @@ function FileUploadField({
11889
12188
  hint
11890
12189
  }) {
11891
12190
  const { t } = useTranslation$1();
11892
- const inputRef = React63.useRef(null);
11893
- const [preview, setPreview] = React63.useState(null);
12191
+ const inputRef = React65.useRef(null);
12192
+ const [preview, setPreview] = React65.useState(null);
11894
12193
  const handleChange = (e) => {
11895
12194
  const file = e.target.files?.[0];
11896
12195
  if (file) {
@@ -11957,14 +12256,14 @@ function FileUploadField({
11957
12256
  }
11958
12257
  function BrandingSettings({ branding, onSave }) {
11959
12258
  const { t } = useTranslation$1();
11960
- const [primaryColor, setPrimaryColor] = React63.useState(branding?.primaryColor ?? "#000000");
11961
- const [accentColor, setAccentColor] = React63.useState(branding?.accentColor ?? "#6366f1");
11962
- const [logoFile, setLogoFile] = React63.useState(null);
11963
- const [faviconFile, setFaviconFile] = React63.useState(null);
11964
- const [logoUrl, setLogoUrl] = React63.useState(branding?.logoUrl ?? "");
11965
- const [faviconUrl, setFaviconUrl] = React63.useState(branding?.faviconUrl ?? "");
11966
- const [saving, setSaving] = React63.useState(false);
11967
- React63.useEffect(() => {
12259
+ const [primaryColor, setPrimaryColor] = React65.useState(branding?.primaryColor ?? "#000000");
12260
+ const [accentColor, setAccentColor] = React65.useState(branding?.accentColor ?? "#6366f1");
12261
+ const [logoFile, setLogoFile] = React65.useState(null);
12262
+ const [faviconFile, setFaviconFile] = React65.useState(null);
12263
+ const [logoUrl, setLogoUrl] = React65.useState(branding?.logoUrl ?? "");
12264
+ const [faviconUrl, setFaviconUrl] = React65.useState(branding?.faviconUrl ?? "");
12265
+ const [saving, setSaving] = React65.useState(false);
12266
+ React65.useEffect(() => {
11968
12267
  if (branding) {
11969
12268
  setPrimaryColor(branding.primaryColor ?? "#000000");
11970
12269
  setAccentColor(branding.accentColor ?? "#6366f1");
@@ -12170,9 +12469,9 @@ function ProjectsSettings() {
12170
12469
  const projects = useFayzProjectsStore((s) => s.projects);
12171
12470
  const load = useFayzProjectsStore((s) => s.load);
12172
12471
  const refresh = useFayzProjectsStore((s) => s.refresh);
12173
- const [adding, setAdding] = React63.useState(false);
12174
- const [editing, setEditing] = React63.useState(null);
12175
- React63.useEffect(() => {
12472
+ const [adding, setAdding] = React65.useState(false);
12473
+ const [editing, setEditing] = React65.useState(null);
12474
+ React65.useEffect(() => {
12176
12475
  void load(currentOrgId);
12177
12476
  }, [currentOrgId, load]);
12178
12477
  const onCreate = async (input) => {
@@ -12194,16 +12493,10 @@ function ProjectsSettings() {
12194
12493
  await refresh();
12195
12494
  };
12196
12495
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
12197
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
12198
- /* @__PURE__ */ jsxs("div", { children: [
12199
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: "Sites e projetos fayz" }),
12200
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "Os projetos fayz deste workspace \u2014 sites, lojas e landing pages. O projeto padr\xE3o aparece no menu como \u201CSeu site\u201D." })
12201
- ] }),
12202
- !adding && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => setAdding(true), children: [
12203
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
12204
- " Adicionar"
12205
- ] })
12206
- ] }),
12496
+ !adding && /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => setAdding(true), children: [
12497
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
12498
+ " Adicionar"
12499
+ ] }) }),
12207
12500
  adding && /* @__PURE__ */ jsx(ProjectForm, { onCancel: () => setAdding(false), onSubmit: onCreate }),
12208
12501
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
12209
12502
  projects.length === 0 && !adding && /* @__PURE__ */ jsx("p", { className: "rounded-card border border-dashed border-border p-6 text-center text-sm text-muted-foreground", children: "Nenhum projeto ainda. Adicione o site do cliente (id do projeto fayz + URLs de preview e editor)." }),
@@ -12244,12 +12537,12 @@ function ProjectRow({ project, onEdit, onDelete, onSetDefault }) {
12244
12537
  ] });
12245
12538
  }
12246
12539
  function ProjectForm({ initial, onCancel, onSubmit }) {
12247
- const [name, setName] = React63.useState(initial?.name ?? "");
12248
- const [kind, setKind] = React63.useState(initial?.kind ?? "site");
12249
- const [fayzProjectId, setFayzProjectId] = React63.useState(initial?.fayzProjectId ?? "");
12250
- const [previewUrl, setPreviewUrl] = React63.useState(initial?.previewUrl ?? "");
12251
- const [isDefault, setIsDefault] = React63.useState(initial?.isDefault ?? false);
12252
- const [storefrontEnabled, setStorefrontEnabled] = React63.useState(initial?.storefrontEnabled ?? false);
12540
+ const [name, setName] = React65.useState(initial?.name ?? "");
12541
+ const [kind, setKind] = React65.useState(initial?.kind ?? "site");
12542
+ const [fayzProjectId, setFayzProjectId] = React65.useState(initial?.fayzProjectId ?? "");
12543
+ const [previewUrl, setPreviewUrl] = React65.useState(initial?.previewUrl ?? "");
12544
+ const [isDefault, setIsDefault] = React65.useState(initial?.isDefault ?? false);
12545
+ const [storefrontEnabled, setStorefrontEnabled] = React65.useState(initial?.storefrontEnabled ?? false);
12253
12546
  const submit = () => onSubmit({
12254
12547
  name: name.trim() || "Meu site",
12255
12548
  kind,
@@ -12286,12 +12579,25 @@ function ProjectForm({ initial, onCancel, onSubmit }) {
12286
12579
  ] })
12287
12580
  ] });
12288
12581
  }
12582
+ function useIsWide() {
12583
+ const query = "(min-width: 768px)";
12584
+ const [wide, setWide] = React65.useState(
12585
+ () => typeof window === "undefined" ? true : window.matchMedia(query).matches
12586
+ );
12587
+ React65.useEffect(() => {
12588
+ const mq = window.matchMedia(query);
12589
+ const onChange = () => setWide(mq.matches);
12590
+ mq.addEventListener("change", onChange);
12591
+ return () => mq.removeEventListener("change", onChange);
12592
+ }, []);
12593
+ return wide;
12594
+ }
12289
12595
  function useCoreSettingsTabs({
12290
12596
  showCompany = true,
12291
12597
  showBranding = true
12292
12598
  } = {}) {
12293
12599
  const { t } = useTranslation$1();
12294
- return React63.useMemo(() => [
12600
+ return React65.useMemo(() => [
12295
12601
  ...showCompany ? [{ id: "general", label: t("settings.general"), icon: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4" }), iconName: "Building2", component: /* @__PURE__ */ jsx(ConnectedCompanySettings, {}) }] : [],
12296
12602
  { id: "profile", label: t("settings.profile"), icon: /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }), iconName: "User", component: /* @__PURE__ */ jsx(ConnectedUserProfile, {}) },
12297
12603
  { id: "security", label: t("settings.security"), icon: /* @__PURE__ */ jsx(Shield, { className: "h-4 w-4" }), iconName: "Shield", component: /* @__PURE__ */ jsx(ConnectedSecuritySettings, {}) },
@@ -12317,6 +12623,7 @@ function SettingsPage({
12317
12623
  activeTabId
12318
12624
  }) {
12319
12625
  const { t } = useTranslation$1();
12626
+ const wide = useIsWide();
12320
12627
  const defaultTabs = useCoreSettingsTabs({ showCompany, showBranding });
12321
12628
  const baseTabs = tabs ?? defaultTabs;
12322
12629
  const resolvedTabs = extraTabs ? [...baseTabs, ...extraTabs] : baseTabs;
@@ -12329,10 +12636,10 @@ function SettingsPage({
12329
12636
  }
12330
12637
  return null;
12331
12638
  }
12332
- const [activeTab, setActiveTab] = React63.useState(() => {
12639
+ const [activeTab, setActiveTab] = React65.useState(() => {
12333
12640
  return getTabFromHash() ?? defaultTab ?? null;
12334
12641
  });
12335
- React63.useEffect(() => {
12642
+ React65.useEffect(() => {
12336
12643
  const handler = () => {
12337
12644
  const tab = getTabFromHash();
12338
12645
  if (tab) setActiveTab(tab);
@@ -12355,7 +12662,7 @@ function SettingsPage({
12355
12662
  setActiveTab(null);
12356
12663
  window.location.hash = "/settings";
12357
12664
  };
12358
- const sections = React63.useMemo(() => {
12665
+ const sections = React65.useMemo(() => {
12359
12666
  const out = [];
12360
12667
  for (const tab of resolvedTabs) {
12361
12668
  const heading = tab.isPlugin ? tab.group ?? t("common.plugins") : null;
@@ -12382,40 +12689,39 @@ function SettingsPage({
12382
12689
  ) }, tab.id)) })
12383
12690
  ] }, section.key)) });
12384
12691
  return /* @__PURE__ */ jsxs("div", { className: cn$1("fz-container space-y-6", className), children: [
12385
- !controlled && showingContent && /* @__PURE__ */ jsxs(
12692
+ !controlled && showingContent && !wide && /* @__PURE__ */ jsxs(
12386
12693
  "button",
12387
12694
  {
12388
12695
  onClick: handleBack,
12389
- className: "flex items-center gap-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground md:hidden",
12696
+ className: "flex items-center gap-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground",
12390
12697
  children: [
12391
12698
  /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
12392
12699
  t("settings.title")
12393
12700
  ]
12394
12701
  }
12395
12702
  ),
12396
- !showingContent && /* @__PURE__ */ jsxs("div", { className: "md:hidden", children: [
12703
+ !showingContent && !wide && /* @__PURE__ */ jsxs("div", { children: [
12397
12704
  /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: t("settings.title") }),
12398
12705
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("settings.subtitle") })
12399
12706
  ] }),
12400
12707
  beforeContent,
12401
- /* @__PURE__ */ jsxs("div", { className: "hidden md:flex md:gap-6", children: [
12402
- !hideNav && /* @__PURE__ */ jsx("nav", { className: "w-52 shrink-0", children: /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
12403
- "button",
12708
+ !hideNav && /* @__PURE__ */ jsx(PageRail, { children: /* @__PURE__ */ jsx("nav", { className: "hidden w-52 shrink-0 overflow-y-auto border-r border-border p-3 md:block", children: sections.map((section, i) => /* @__PURE__ */ jsxs("div", { className: i > 0 ? "mt-3 border-t border-border pt-3" : void 0, children: [
12709
+ section.heading && /* @__PURE__ */ jsx("p", { className: "px-3 pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: section.heading }),
12710
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: section.items.map((tab) => /* @__PURE__ */ jsx(
12711
+ RailNavItem,
12404
12712
  {
12405
- onClick: () => handleSelectTab(tab.id),
12406
- className: cn$1(
12407
- "flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors",
12408
- desktopTab === tab.id ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
12409
- ),
12410
- children: [
12411
- tab.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: tab.icon }),
12412
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: tab.label })
12413
- ]
12414
- }
12415
- ) }, tab.id)) }) }),
12416
- /* @__PURE__ */ jsx("div", { className: "fz-container min-w-0 flex-1", children: withTrail(desktopTabDef?.component ?? null, desktopTabDef) })
12417
- ] }),
12418
- /* @__PURE__ */ jsx("div", { className: "md:hidden", children: showingContent ? /* @__PURE__ */ jsx("div", { className: "fz-container", children: activeContent }) : settingsIndex }),
12713
+ item: {
12714
+ id: tab.id,
12715
+ label: tab.label,
12716
+ icon: tab.iconName,
12717
+ active: desktopTab === tab.id,
12718
+ onClick: () => handleSelectTab(tab.id)
12719
+ }
12720
+ },
12721
+ tab.id
12722
+ )) })
12723
+ ] }, section.key)) }) }),
12724
+ wide ? /* @__PURE__ */ jsx("div", { className: "fz-container min-w-0", children: withTrail(desktopTabDef?.component ?? null, desktopTabDef) }) : showingContent ? /* @__PURE__ */ jsx("div", { className: "fz-container", children: activeContent }) : settingsIndex,
12419
12725
  afterContent
12420
12726
  ] });
12421
12727
  }
@@ -12434,10 +12740,10 @@ function InviteMemberDialog({ open, onOpenChange, defaultEmail }) {
12434
12740
  const profiles = usePermissionsStore((s) => s.profiles);
12435
12741
  const setInvites = useInviteStore((s) => s.setInvites);
12436
12742
  const { user } = useAuthStore();
12437
- const [emails, setEmails] = React63.useState([defaultEmail ?? ""]);
12438
- const [profileId, setProfileId] = React63.useState(profiles[1]?.id ?? profiles[0]?.id ?? "");
12439
- const [sending, setSending] = React63.useState(false);
12440
- const [sent, setSent] = React63.useState(false);
12743
+ const [emails, setEmails] = React65.useState([defaultEmail ?? ""]);
12744
+ const [profileId, setProfileId] = React65.useState(profiles[1]?.id ?? profiles[0]?.id ?? "");
12745
+ const [sending, setSending] = React65.useState(false);
12746
+ const [sent, setSent] = React65.useState(false);
12441
12747
  const addEmail = () => setEmails((prev) => [...prev, ""]);
12442
12748
  const updateEmail = (idx, value) => {
12443
12749
  setEmails((prev) => {
@@ -12534,14 +12840,14 @@ function TeamTab() {
12534
12840
  const { user } = useAuthStore();
12535
12841
  const hasSystemPermission = useSystemPermission();
12536
12842
  const personMode = teamPersonKinds.length > 0 && typeof adapter2?.listTeam === "function";
12537
- const [team, setTeam] = React63.useState(null);
12538
- const [inviteOpen, setInviteOpen] = React63.useState(false);
12843
+ const [team, setTeam] = React65.useState(null);
12844
+ const [inviteOpen, setInviteOpen] = React65.useState(false);
12539
12845
  const guardSeats = useLimitGuard("users");
12540
12846
  const handleOpenInvite = async () => {
12541
12847
  if (await guardSeats(1) === "blocked") return;
12542
12848
  setInviteOpen(true);
12543
12849
  };
12544
- const reloadTeam = React63.useCallback(async () => {
12850
+ const reloadTeam = React65.useCallback(async () => {
12545
12851
  if (!adapter2 || !currentOrg) return;
12546
12852
  if (personMode && adapter2.listTeam) {
12547
12853
  setTeam(await adapter2.listTeam(currentOrg.id, teamPersonKinds));
@@ -12549,12 +12855,12 @@ function TeamTab() {
12549
12855
  setMembers(await adapter2.listMembers(currentOrg.id));
12550
12856
  }
12551
12857
  }, [adapter2, currentOrg?.id, personMode, teamPersonKinds, setMembers]);
12552
- React63.useEffect(() => {
12858
+ React65.useEffect(() => {
12553
12859
  if (personMode && adapter2?.listTeam && currentOrg) {
12554
12860
  dedup("team:people:" + currentOrg.id, () => adapter2.listTeam(currentOrg.id, teamPersonKinds)).then(setTeam).catch(() => setTeam([]));
12555
12861
  }
12556
12862
  }, [personMode, adapter2, currentOrg?.id, teamPersonKinds.join(",")]);
12557
- React63.useEffect(() => {
12863
+ React65.useEffect(() => {
12558
12864
  if (adapter2 && currentOrg) {
12559
12865
  dedup("team:invites:" + currentOrg.id, () => adapter2.listInvites(currentOrg.id)).then(setInvites).catch(() => {
12560
12866
  });
@@ -12629,18 +12935,13 @@ function TeamTab() {
12629
12935
  const pendingInvites = invites.filter((i) => i.status === "pending");
12630
12936
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
12631
12937
  /* @__PURE__ */ jsxs("div", { children: [
12632
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
12633
- /* @__PURE__ */ jsxs("div", { children: [
12634
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: t("organization.team.title") }),
12635
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("organization.team.memberCount", { count: String(rows.length), plural: rows.length !== 1 ? "s" : "" }) })
12636
- ] }),
12637
- canManageTeam && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => {
12638
- void handleOpenInvite();
12639
- }, children: [
12640
- /* @__PURE__ */ jsx(UserPlus, { className: "h-4 w-4 mr-1" }),
12641
- t("organization.team.invite")
12642
- ] })
12643
- ] }),
12938
+ canManageTeam && /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => {
12939
+ void handleOpenInvite();
12940
+ }, children: [
12941
+ /* @__PURE__ */ jsx(UserPlus, { className: "h-4 w-4 mr-1" }),
12942
+ t("organization.team.invite")
12943
+ ] }) }),
12944
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: t("organization.team.memberCount", { count: String(rows.length), plural: rows.length !== 1 ? "s" : "" }) }),
12644
12945
  /* @__PURE__ */ jsx(Card, { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
12645
12946
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
12646
12947
  /* @__PURE__ */ jsx("th", { className: "text-left p-3 text-sm font-medium text-muted-foreground", children: t("organization.team.member") }),
@@ -12762,13 +13063,13 @@ var SYSTEM_PERMISSIONS = [
12762
13063
  ];
12763
13064
  function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving, isNew, parentLabel }) {
12764
13065
  const { t } = useTranslation$1();
12765
- const [name, setName] = React63.useState(profile?.name ?? "");
12766
- const [description, setDescription] = React63.useState(profile?.description ?? "");
12767
- const [systemPerms, setSystemPerms] = React63.useState(profile?.systemPermissions ?? []);
12768
- const [grants, setGrants] = React63.useState(profile?.grants ?? {});
12769
- const [collapsed, setCollapsed] = React63.useState(/* @__PURE__ */ new Set());
12770
- const [search, setSearch] = React63.useState("");
12771
- const initial = React63.useRef({
13066
+ const [name, setName] = React65.useState(profile?.name ?? "");
13067
+ const [description, setDescription] = React65.useState(profile?.description ?? "");
13068
+ const [systemPerms, setSystemPerms] = React65.useState(profile?.systemPermissions ?? []);
13069
+ const [grants, setGrants] = React65.useState(profile?.grants ?? {});
13070
+ const [collapsed, setCollapsed] = React65.useState(/* @__PURE__ */ new Set());
13071
+ const [search, setSearch] = React65.useState("");
13072
+ const initial = React65.useRef({
12772
13073
  name: profile?.name ?? "",
12773
13074
  description: profile?.description ?? "",
12774
13075
  systemPerms: JSON.stringify(profile?.systemPermissions ?? []),
@@ -12806,7 +13107,7 @@ function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving, i
12806
13107
  saveLabel: isNew ? t("organization.permissions.createProfile") : t("organization.permissions.updateProfile")
12807
13108
  });
12808
13109
  useBackHandler(onCancel);
12809
- const groups = React63.useMemo(() => {
13110
+ const groups = React65.useMemo(() => {
12810
13111
  const filtered = search ? features.filter((f) => f.label.toLowerCase().includes(search.toLowerCase())) : features;
12811
13112
  const map = /* @__PURE__ */ new Map();
12812
13113
  for (const f of filtered) {
@@ -12875,7 +13176,7 @@ function PermissionMatrixEditor({ features, profile, onSave, onCancel, saving, i
12875
13176
  }
12876
13177
  ) }) })
12877
13178
  ] }, sp.id)),
12878
- Array.from(groups.entries()).map(([groupName, groupFeatures]) => /* @__PURE__ */ jsxs(React63.Fragment, { children: [
13179
+ Array.from(groups.entries()).map(([groupName, groupFeatures]) => /* @__PURE__ */ jsxs(React65.Fragment, { children: [
12879
13180
  /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 5, className: "p-2 px-3", children: /* @__PURE__ */ jsxs(
12880
13181
  "button",
12881
13182
  {
@@ -12916,11 +13217,11 @@ function PermissionProfilesTab() {
12916
13217
  const startImpersonation = usePermissionsStore((s) => s.startImpersonation);
12917
13218
  const canPreview = (realProfile?.systemPermissions ?? []).includes("manage_permissions") ?? false;
12918
13219
  const { t } = useTranslation$1();
12919
- const [editingProfile, setEditingProfile] = React63.useState(null);
12920
- const [creating, setCreating] = React63.useState(false);
12921
- const [duplicateSource, setDuplicateSource] = React63.useState(null);
12922
- const [saving, setSaving] = React63.useState(false);
12923
- const [error, setError] = React63.useState(null);
13220
+ const [editingProfile, setEditingProfile] = React65.useState(null);
13221
+ const [creating, setCreating] = React65.useState(false);
13222
+ const [duplicateSource, setDuplicateSource] = React65.useState(null);
13223
+ const [saving, setSaving] = React65.useState(false);
13224
+ const [error, setError] = React65.useState(null);
12924
13225
  const handleDuplicate = (profile) => {
12925
13226
  setEditingProfile(null);
12926
13227
  setCreating(false);
@@ -12930,7 +13231,7 @@ function PermissionProfilesTab() {
12930
13231
  isSystem: false
12931
13232
  });
12932
13233
  };
12933
- const memberCountByProfile = React63.useMemo(() => {
13234
+ const memberCountByProfile = React65.useMemo(() => {
12934
13235
  const counts = {};
12935
13236
  for (const m of members) {
12936
13237
  counts[m.profileId] = (counts[m.profileId] ?? 0) + 1;
@@ -12987,16 +13288,10 @@ function PermissionProfilesTab() {
12987
13288
  );
12988
13289
  }
12989
13290
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
12990
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
12991
- /* @__PURE__ */ jsxs("div", { children: [
12992
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: t("organization.permissions.title") }),
12993
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("organization.permissions.subtitle") })
12994
- ] }),
12995
- /* @__PURE__ */ jsxs(Button, { onClick: () => setCreating(true), size: "sm", children: [
12996
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
12997
- t("organization.permissions.createProfile")
12998
- ] })
12999
- ] }),
13291
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { onClick: () => setCreating(true), size: "sm", children: [
13292
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
13293
+ t("organization.permissions.createProfile")
13294
+ ] }) }),
13000
13295
  error && /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error }),
13001
13296
  /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: profiles.length === 0 ? (
13002
13297
  // Roles load via central org hydration; show skeletons until they arrive.
@@ -13695,7 +13990,7 @@ function SubjectFormLayout({ fields, allFields, fieldGroups, values, onChange, r
13695
13990
  const IconComponent = getLucideIcon(entityIcon ?? "Heart") ?? Heart;
13696
13991
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
13697
13992
  /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "pt-5", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-5", children: [
13698
- /* @__PURE__ */ jsx("div", { className: "flex h-20 w-20 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: React63__default.createElement(IconComponent, { className: "h-8 w-8" }) }),
13993
+ /* @__PURE__ */ jsx("div", { className: "flex h-20 w-20 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: React65__default.createElement(IconComponent, { className: "h-8 w-8" }) }),
13699
13994
  /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4", children: [
13700
13995
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4", children: [
13701
13996
  nameField && /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
@@ -14043,11 +14338,11 @@ function ImageSlot({
14043
14338
  canEdit
14044
14339
  }) {
14045
14340
  const t = useTranslation();
14046
- const inputRef = React63__default.useRef(null);
14047
- const [url, setUrl] = React63__default.useState(() => image.get(row));
14048
- const [busy, setBusy] = React63__default.useState(false);
14049
- const [error, setError] = React63__default.useState(null);
14050
- React63__default.useEffect(() => {
14341
+ const inputRef = React65__default.useRef(null);
14342
+ const [url, setUrl] = React65__default.useState(() => image.get(row));
14343
+ const [busy, setBusy] = React65__default.useState(false);
14344
+ const [error, setError] = React65__default.useState(null);
14345
+ React65__default.useEffect(() => {
14051
14346
  setUrl(image.get(row));
14052
14347
  }, [image.get(row)]);
14053
14348
  const pick = () => {
@@ -14141,7 +14436,7 @@ function CrudFormPage({ entityDef, mode, initialData, onSubmit, onCancel, namePl
14141
14436
  const computeInit = () => mode === "edit" && initialData ? { ...getDefaultValues(formFields), ...initialData } : getDefaultValues(formFields);
14142
14437
  const [values, setValues] = useState(computeInit);
14143
14438
  const [saving, setSaving] = useState(false);
14144
- const initialRef = React63__default.useRef(JSON.stringify(values));
14439
+ const initialRef = React65__default.useRef(JSON.stringify(values));
14145
14440
  useEffect(() => {
14146
14441
  const init = computeInit();
14147
14442
  setValues(init);
@@ -14512,6 +14807,57 @@ function LimitGate({ limitKey, children, className }) {
14512
14807
  }
14513
14808
  );
14514
14809
  }
14810
+ function QuickActionsButton({ actions, label, variant: variantProp, className }) {
14811
+ const [open, setOpen] = useState(false);
14812
+ const ref = useRef(null);
14813
+ const moduleLayout = useModuleLayout();
14814
+ const variant = variantProp ?? (moduleLayout === "tabs" ? "default" : "outline");
14815
+ useEffect(() => {
14816
+ function handler(e) {
14817
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
14818
+ }
14819
+ document.addEventListener("mousedown", handler);
14820
+ return () => document.removeEventListener("mousedown", handler);
14821
+ }, []);
14822
+ if (actions.length === 0) return null;
14823
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
14824
+ /* @__PURE__ */ jsxs(
14825
+ Button,
14826
+ {
14827
+ variant,
14828
+ size: "sm",
14829
+ onClick: () => setOpen(!open),
14830
+ className: cn(open && variant === "outline" && "bg-muted"),
14831
+ children: [
14832
+ /* @__PURE__ */ jsx(Plus, { className: cn("h-3.5 w-3.5 transition-transform duration-200", open && "rotate-45") }),
14833
+ /* @__PURE__ */ jsx("span", { children: label ?? "New" }),
14834
+ /* @__PURE__ */ jsx(ChevronDown, { className: cn("-mr-0.5 h-3.5 w-3.5 opacity-70 transition-transform duration-200", open && "rotate-180") })
14835
+ ]
14836
+ }
14837
+ ),
14838
+ open && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute top-full right-0 z-50 mt-2 w-64 rounded-xl border bg-popover shadow-lg overflow-hidden animate-in fade-in-0 zoom-in-95 origin-top-right", children: /* @__PURE__ */ jsx("div", { className: "p-1.5", children: actions.map((action) => {
14839
+ const Icon = action.icon ? ICON_MAP[action.icon] ?? Plus : Plus;
14840
+ return /* @__PURE__ */ jsxs(
14841
+ "button",
14842
+ {
14843
+ onClick: () => {
14844
+ action.action();
14845
+ setOpen(false);
14846
+ },
14847
+ className: "w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-left hover:bg-muted/50 transition-colors group",
14848
+ children: [
14849
+ /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted group-hover:bg-primary/10 transition-colors shrink-0", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" }) }),
14850
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
14851
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: action.label }),
14852
+ action.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground leading-tight", children: action.description })
14853
+ ] })
14854
+ ]
14855
+ },
14856
+ action.id
14857
+ );
14858
+ }) }) })
14859
+ ] });
14860
+ }
14515
14861
  var ALL_TAB = "__all__";
14516
14862
  function getEntityIcon(name) {
14517
14863
  const icons = LucideIcons3;
@@ -14542,12 +14888,14 @@ function CrudListView({
14542
14888
  onExport,
14543
14889
  feature,
14544
14890
  readOnly,
14891
+ embedded,
14545
14892
  emptyMessage,
14546
14893
  navTabs,
14547
14894
  activeNavTab,
14548
14895
  headerActions,
14549
14896
  stats,
14550
14897
  onRefresh,
14898
+ refreshFromCache,
14551
14899
  onSort,
14552
14900
  onSortField,
14553
14901
  onSortDir,
@@ -14608,22 +14956,35 @@ function CrudListView({
14608
14956
  }] : []
14609
14957
  ];
14610
14958
  const selectable = display === "table" && barActions.length > 0;
14611
- const facetTab = facets?.find((f) => f.display === "tabs");
14612
- const headerTabs = navTabs?.length ? navTabs.map((t2) => ({ value: t2.id, label: t2.label, icon: t2.icon })) : facetTab ? [
14613
- { value: ALL_TAB, label: facetTab.allLabel ?? t("crud.list.allFacet") },
14614
- ...facetTab.options.map((o) => ({ value: o.value, label: o.label }))
14615
- ] : null;
14616
- const headerTabValue = navTabs?.length ? activeNavTab ?? navTabs[0].id : activeFilters?.[facetTab?.field ?? ""] ?? ALL_TAB;
14959
+ const hasSelection = selectable && selectedIds.length > 0;
14960
+ const headerTabs = navTabs?.length ? navTabs.map((t2) => ({ value: t2.id, label: t2.label, icon: t2.icon })) : null;
14961
+ const headerTabValue = activeNavTab ?? navTabs?.[0]?.id ?? ALL_TAB;
14617
14962
  const onHeaderTabChange = (next) => {
14618
- if (navTabs?.length) navTabs.find((t2) => t2.id === next)?.onSelect();
14619
- else if (facetTab) onFacetChange?.(facetTab.field, next === ALL_TAB ? void 0 : next);
14963
+ navTabs?.find((t2) => t2.id === next)?.onSelect();
14620
14964
  };
14621
- const pillFacets = facets?.filter((f) => f !== facetTab);
14622
- const newButton = onNew && !readOnly ? /* @__PURE__ */ jsx(Button, { onClick: onNew, children: addLabel ?? t("crud.list.addEntity", { entity: entityDef.name }) }) : null;
14623
- const gatedNewButton = newButton ? (() => {
14624
- const roleGated = feature ? /* @__PURE__ */ jsx(PermissionGate, { feature, action: "create", children: newButton }) : newButton;
14965
+ const pillFacets = facets;
14966
+ const newLabel = addLabel ?? t("crud.list.addEntity", { entity: entityDef.name });
14967
+ const plainNewLabel = newLabel.replace(/^\+\s*/, "");
14968
+ const newButton = onNew && !readOnly ? onImport ? /* @__PURE__ */ jsx(
14969
+ QuickActionsButton,
14970
+ {
14971
+ variant: "default",
14972
+ label: plainNewLabel,
14973
+ actions: [
14974
+ { id: "new", label: plainNewLabel, description: t("crud.list.newHint"), icon: "Plus", action: onNew },
14975
+ { id: "import", label: t("crud.import.action"), description: t("crud.import.hint"), icon: "Upload", action: onImport }
14976
+ ]
14977
+ }
14978
+ ) : /* @__PURE__ */ jsx(Button, { onClick: onNew, children: newLabel }) : onImport ? /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: onImport, children: [
14979
+ /* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
14980
+ t("crud.import.action")
14981
+ ] }) : null;
14982
+ const gate = (node) => {
14983
+ const roleGated = feature ? /* @__PURE__ */ jsx(PermissionGate, { feature, action: "create", children: node }) : node;
14625
14984
  return entityDef.limitKey ? /* @__PURE__ */ jsx(LimitGate, { limitKey: entityDef.limitKey, children: roleGated }) : roleGated;
14626
- })() : null;
14985
+ };
14986
+ const gatedNewButton = newButton ? gate(newButton) : null;
14987
+ const gatedCreateButton = onNew && !readOnly ? gate(/* @__PURE__ */ jsx(Button, { onClick: onNew, children: newLabel })) : null;
14627
14988
  const isFiltered = Boolean(search?.trim()) || Object.values(activeFilters ?? {}).some((v) => v != null);
14628
14989
  const EmptyIcon = getEntityIcon(entityDef.icon);
14629
14990
  const emptyState = /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 py-6", children: [
@@ -14631,18 +14992,13 @@ function CrudListView({
14631
14992
  /* @__PURE__ */ jsx("p", { className: "text-base font-semibold text-foreground", children: emptyMessage ?? (isFiltered ? t("crud.list.emptyFiltered", { entities: namePlural.toLowerCase() }) : t("crud.list.empty", { entities: namePlural.toLowerCase() })) }),
14632
14993
  !isFiltered && /* @__PURE__ */ jsxs(Fragment, { children: [
14633
14994
  /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: t("crud.list.emptyHint", { entities: namePlural.toLowerCase() }) }),
14634
- gatedNewButton && /* @__PURE__ */ jsx("div", { className: "mt-3", children: gatedNewButton })
14995
+ gatedCreateButton && /* @__PURE__ */ jsx("div", { className: "mt-3", children: gatedCreateButton })
14635
14996
  ] })
14636
14997
  ] });
14637
- const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
14998
+ const actions = headerActions || gatedNewButton ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
14638
14999
  headerActions,
14639
- onImport && /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: onImport, children: [
14640
- /* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
14641
- t("crud.import.action")
14642
- ] }),
14643
- onImport && gatedNewButton && /* @__PURE__ */ jsx("span", { className: "h-6 w-px bg-border", "aria-hidden": true }),
14644
15000
  gatedNewButton
14645
- ] });
15001
+ ] }) : null;
14646
15002
  const sortableColumns = useMemo(() => {
14647
15003
  const declared = entityDef.fields.filter((f) => f.sortable !== false).map((f) => ({ key: f.key, label: f.label }));
14648
15004
  const has = new Set(declared.map((f) => f.key));
@@ -14711,7 +15067,16 @@ function CrudListView({
14711
15067
  ] })
14712
15068
  ] })
14713
15069
  ] }),
14714
- onRefresh && /* @__PURE__ */ jsx(Hint, { label: t("crud.list.refresh"), children: /* @__PURE__ */ jsx("button", { type: "button", "aria-label": t("crud.list.refresh"), onClick: onRefresh, className: iconBtn, children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }) }) }),
15070
+ onRefresh && /* @__PURE__ */ jsx(
15071
+ RefreshButton,
15072
+ {
15073
+ onRefresh,
15074
+ fromCache: refreshFromCache,
15075
+ label: t("crud.list.refresh"),
15076
+ labelCached: t("crud.list.refreshCached"),
15077
+ className: iconBtn
15078
+ }
15079
+ ),
14715
15080
  onExport && /* @__PURE__ */ jsx(Hint, { label: t("crud.export.action"), children: /* @__PURE__ */ jsx("button", { type: "button", "aria-label": t("crud.export.action"), onClick: onExport, className: iconBtn, children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }) }) })
14716
15081
  ] }) : null;
14717
15082
  const rows = items?.length ?? 0;
@@ -14754,18 +15119,21 @@ function CrudListView({
14754
15119
  ] })
14755
15120
  ] }) : null;
14756
15121
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
14757
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
14758
- headerTabs ? /* @__PURE__ */ jsx(
14759
- HeaderTabs,
14760
- {
14761
- tabs: headerTabs,
14762
- value: headerTabValue,
14763
- onChange: onHeaderTabChange,
14764
- "aria-label": namePlural
14765
- }
14766
- ) : /* @__PURE__ */ jsx("span", {}),
14767
- actions
14768
- ] }),
15122
+ actions && (embedded ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-end gap-3", children: actions }) : /* @__PURE__ */ jsx(PageHeaderActions, { children: actions })),
15123
+ headerTabs && // The same band a module's sub-nav draws, closed by the hairline that
15124
+ // crosses the page. Alunos ▸ Equipe and Financeiro ▸ Mensalidades are
15125
+ // the same kind of move — switching what the page shows — so they are
15126
+ // the same object on screen.
15127
+ /* @__PURE__ */ jsx(
15128
+ PageTabs,
15129
+ {
15130
+ className: "mb-0",
15131
+ tabs: headerTabs,
15132
+ value: headerTabValue,
15133
+ onChange: onHeaderTabChange,
15134
+ "aria-label": namePlural
15135
+ }
15136
+ ),
14769
15137
  error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm", children: [
14770
15138
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-destructive" }),
14771
15139
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
@@ -14775,16 +15143,6 @@ function CrudListView({
14775
15143
  onRefresh && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "ml-auto shrink-0", onClick: onRefresh, children: t("crud.list.refresh") })
14776
15144
  ] }),
14777
15145
  stats && stats.length > 0 && /* @__PURE__ */ jsx(StatTiles, { tiles: stats, loading: isInitialLoad }),
14778
- selectable && /* @__PURE__ */ jsx(
14779
- BulkActionBar,
14780
- {
14781
- count: selectedIds.length,
14782
- total: items?.length ?? 0,
14783
- onSelectAll: () => setRawSelection((items ?? []).map((r) => r.id)),
14784
- onClear: () => setRawSelection([]),
14785
- actions: barActions
14786
- }
14787
- ),
14788
15146
  display === "cards" ? (
14789
15147
  // The card grid keeps the search box floating above it: there is no
14790
15148
  // table chrome for a toolbar to sit flush against.
@@ -14821,11 +15179,21 @@ function CrudListView({
14821
15179
  same rows, so a search box floating on the page background above a
14822
15180
  separate table card read as two unrelated controls. */
14823
15181
  /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
14824
- (searchBox || facetChips || tableTools) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
15182
+ (searchBox || facetChips || tableTools || hasSelection) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: hasSelection ? /* @__PURE__ */ jsx(
15183
+ BulkActionBar,
15184
+ {
15185
+ variant: "inline",
15186
+ count: selectedIds.length,
15187
+ total: items?.length ?? 0,
15188
+ onSelectAll: () => setRawSelection((items ?? []).map((r) => r.id)),
15189
+ onClear: () => setRawSelection([]),
15190
+ actions: barActions
15191
+ }
15192
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
14825
15193
  /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: searchBox }),
14826
15194
  facetChips,
14827
15195
  tableTools
14828
- ] }),
15196
+ ] }) }),
14829
15197
  /* @__PURE__ */ jsx(
14830
15198
  DataTable,
14831
15199
  {
@@ -15221,7 +15589,7 @@ function AddressesTab({ item }) {
15221
15589
  )
15222
15590
  ] });
15223
15591
  }
15224
- var ScheduleEditor = React63__default.lazy(
15592
+ var ScheduleEditor = React65__default.lazy(
15225
15593
  () => import('./ScheduleEditor-S3QI5MHO.js').then((m) => ({ default: m.ScheduleEditor }))
15226
15594
  );
15227
15595
  function ComingSoon({ icon: Icon, title, description }) {
@@ -15302,7 +15670,7 @@ function ScheduleTab({ item }) {
15302
15670
  }
15303
15671
  );
15304
15672
  }
15305
- return /* @__PURE__ */ jsx(React63__default.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(ScheduleEditor, { assigneeId: item.id }) });
15673
+ return /* @__PURE__ */ jsx(React65__default.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(ScheduleEditor, { assigneeId: item.id }) });
15306
15674
  }
15307
15675
  var PERSON_DETAIL_TABS = [
15308
15676
  { id: "schedule", label: "Schedule", icon: "CalendarDays", component: ScheduleTab, visibleFor: ["staff"] },
@@ -15608,6 +15976,10 @@ var SUBJECT_DETAIL_TABS = [
15608
15976
  { id: "history", label: "History", component: SubjectHistoryTab },
15609
15977
  { id: "documents", label: "Documents", component: SubjectDocumentsTab }
15610
15978
  ];
15979
+ function ActionSlot({ embedded, children }) {
15980
+ if (embedded) return /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1.5", children });
15981
+ return /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children }) });
15982
+ }
15611
15983
  function formatValue3(field, value, t) {
15612
15984
  if (value == null || value === "") return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50", children: "\u2014" });
15613
15985
  if (field.renderCell) return field.renderCell(value, {});
@@ -15679,7 +16051,7 @@ function InlineEditor({
15679
16051
  }) {
15680
16052
  const [draft, setDraft] = useState(value ?? "");
15681
16053
  const [saving, setSaving] = useState(false);
15682
- const inputRef = React63__default.useRef(null);
16054
+ const inputRef = React65__default.useRef(null);
15683
16055
  useEffect(() => {
15684
16056
  inputRef.current?.focus();
15685
16057
  if (inputRef.current instanceof HTMLInputElement) inputRef.current.select();
@@ -15955,7 +16327,7 @@ function CrudDetailPage({
15955
16327
  const newHash = value === "overview" ? detailPath : `${detailPath}/${value}`;
15956
16328
  window.history.replaceState(null, "", `#${newHash}`);
15957
16329
  }, [basePath, item.id, embedded]);
15958
- const headerNode = React63__default.useMemo(
16330
+ const headerNode = React65__default.useMemo(
15959
16331
  () => embedded ? null : /* @__PURE__ */ jsx(SubpageHeader, { title: displayValue, parentLabel: namePlural, onBack }),
15960
16332
  [embedded, displayValue, namePlural, onBack]
15961
16333
  );
@@ -15981,7 +16353,7 @@ function CrudDetailPage({
15981
16353
  ] }),
15982
16354
  subtitleValue && /* @__PURE__ */ jsx("p", { className: `text-muted-foreground mt-0.5 ${embedded ? "text-xs" : ""}`, children: subtitleValue })
15983
16355
  ] }),
15984
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
16356
+ /* @__PURE__ */ jsxs(ActionSlot, { embedded, children: [
15985
16357
  /* @__PURE__ */ jsx(WidgetSlot, { zone: headerActionsZone, item, entityId, embedded }),
15986
16358
  onEdit && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: embedded ? "xs" : "sm", onClick: onEdit, className: embedded ? "h-7 px-2 text-xs gap-1" : "", children: [
15987
16359
  /* @__PURE__ */ jsx(Edit, { className: embedded ? "h-3 w-3" : "h-4 w-4 mr-1.5" }),
@@ -16008,7 +16380,7 @@ function CrudDetailPage({
16008
16380
  })
16009
16381
  ] }),
16010
16382
  /* @__PURE__ */ jsx(TabsContent, { value: "overview", className: embedded ? "mt-2" : "mt-4", children: /* @__PURE__ */ jsx(OverviewTab, { entityDef, item, compact: embedded, onFieldSave }) }),
16011
- customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? React63__default.createElement(tab.component, { item, entityDef, ...tab.props ?? {} }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
16383
+ customTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-4", children: tab.component ? React65__default.createElement(tab.component, { item, entityDef, ...tab.props ?? {} }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t("common.noContent") }) }) }) }, tab.id))
16012
16384
  ] }),
16013
16385
  /* @__PURE__ */ jsx(WidgetSlot, { zone: widgetZone }),
16014
16386
  legacyWidgetZone !== widgetZone && /* @__PURE__ */ jsx(WidgetSlot, { zone: legacyWidgetZone }),
@@ -16060,7 +16432,7 @@ function useFieldRules(entityKey) {
16060
16432
  };
16061
16433
  }, [entityKey, fieldRules]);
16062
16434
  }
16063
- var ImportWizard2 = React63__default.lazy(
16435
+ var ImportWizard2 = React65__default.lazy(
16064
16436
  () => import('./ImportWizard-CISHGNQC.js').then((m) => ({ default: m.ImportWizard }))
16065
16437
  );
16066
16438
  var PAGE_SIZE = 50;
@@ -16172,7 +16544,7 @@ function useCrudColumns(entityDef, options) {
16172
16544
  return tanCols;
16173
16545
  }, [cols, displayField, options?.basePath, options?.onDelete, options?.feature, options?.readOnly]);
16174
16546
  }
16175
- function CrudPage({ entityDef: rawEntityDef, useStore, basePath, display, feature, readOnly, navTabs, activeNavTab, stats }) {
16547
+ function CrudPage({ entityDef: rawEntityDef, useStore, basePath, display, feature, readOnly, navTabs, activeNavTab, stats, embedded }) {
16176
16548
  const store = useStore();
16177
16549
  const t = useTranslation();
16178
16550
  const normalizedBasePath = normalizeCrudBasePath(basePath);
@@ -16449,13 +16821,16 @@ function CrudPage({ entityDef: rawEntityDef, useStore, basePath, display, featur
16449
16821
  onExport: handleExport,
16450
16822
  feature,
16451
16823
  readOnly,
16824
+ embedded,
16452
16825
  navTabs,
16453
16826
  activeNavTab,
16454
16827
  stats: stats?.(store.items, store.total),
16455
16828
  error: store.error,
16456
16829
  onRefresh: () => {
16457
- void store.fetch();
16830
+ emitDataChanged(entityDef.data?.table ? { table: entityDef.data.table, op: "unknown" } : { op: "unknown" });
16831
+ return store.refresh();
16458
16832
  },
16833
+ refreshFromCache: store.fromCache,
16459
16834
  onSort: handleSort,
16460
16835
  onSortField: handleSortField,
16461
16836
  onSortDir: handleSortDir,
@@ -16519,7 +16894,7 @@ function CrudPage({ entityDef: rawEntityDef, useStore, basePath, display, featur
16519
16894
  displayValue: deleteItem ? deleteItem[displayField] : void 0
16520
16895
  }
16521
16896
  ),
16522
- /* @__PURE__ */ jsx(React63__default.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
16897
+ /* @__PURE__ */ jsx(React65__default.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
16523
16898
  ImportWizard2,
16524
16899
  {
16525
16900
  open: importOpen,
@@ -16615,6 +16990,7 @@ function createCrudStore(dataProvider, entity) {
16615
16990
  query: {},
16616
16991
  error: null,
16617
16992
  errorAt: 0,
16993
+ fromCache: false,
16618
16994
  async fetch() {
16619
16995
  const currentKey = queryKey(get().query);
16620
16996
  if (inflightFetch && currentKey === lastQueryKey) {
@@ -16628,7 +17004,13 @@ function createCrudStore(dataProvider, entity) {
16628
17004
  const promise = (async () => {
16629
17005
  try {
16630
17006
  const result = await dataProvider.list(get().query);
16631
- set({ items: result.data, total: result.total, loading: false, error: null });
17007
+ set({
17008
+ items: result.data,
17009
+ total: result.total,
17010
+ loading: false,
17011
+ error: null,
17012
+ fromCache: Boolean(result.cached || result.stale)
17013
+ });
16632
17014
  } catch (err) {
16633
17015
  set({
16634
17016
  items: get().items ?? [],
@@ -16644,6 +17026,10 @@ function createCrudStore(dataProvider, entity) {
16644
17026
  inflightFetch = promise;
16645
17027
  return promise;
16646
17028
  },
17029
+ async refresh() {
17030
+ invalidate();
17031
+ return get().fetch();
17032
+ },
16647
17033
  async create(data) {
16648
17034
  const item = await dataProvider.create(data);
16649
17035
  emit3("created", { record_id: item?.id, record_name: displayName(item) });
@@ -17000,7 +17386,7 @@ function FieldRulesSettings({ entities, rules: initialRules, onSave }) {
17000
17386
  const [saving, setSaving] = useState(false);
17001
17387
  const [search, setSearch] = useState("");
17002
17388
  const [showMore, setShowMore] = useState(false);
17003
- React63.useEffect(() => {
17389
+ React65.useEffect(() => {
17004
17390
  setDraft(initialRules);
17005
17391
  }, [initialRules]);
17006
17392
  const isDirty = useMemo(
@@ -17121,16 +17507,10 @@ function FieldRulesSettings({ entities, rules: initialRules, onSave }) {
17121
17507
  entity.entityKey
17122
17508
  ));
17123
17509
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
17124
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
17125
- /* @__PURE__ */ jsxs("div", { children: [
17126
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("settings.fieldRules") }),
17127
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-0.5", children: t("settings.fieldRules.subtitle") })
17128
- ] }),
17129
- /* @__PURE__ */ jsxs(Button, { onClick: handleSave, disabled: !isDirty || saving, className: "shrink-0", children: [
17130
- saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
17131
- t("common.save")
17132
- ] })
17133
- ] }),
17510
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { onClick: handleSave, disabled: !isDirty || saving, className: "shrink-0", children: [
17511
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
17512
+ t("common.save")
17513
+ ] }) }),
17134
17514
  entities.length > 4 && /* @__PURE__ */ jsxs("div", { className: "relative max-w-sm", children: [
17135
17515
  /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }),
17136
17516
  /* @__PURE__ */ jsx(
@@ -17277,7 +17657,7 @@ function UpgradeModal() {
17277
17657
  }
17278
17658
  function LimitProbe({ limitKey, onReport }) {
17279
17659
  const info = useLimit(limitKey);
17280
- React63.useEffect(() => {
17660
+ React65.useEffect(() => {
17281
17661
  onReport(limitKey, info);
17282
17662
  }, [limitKey, info, onReport]);
17283
17663
  return null;
@@ -17290,16 +17670,16 @@ function SoftLimitBanner({ keys }) {
17290
17670
  if (params) for (const [k, val] of Object.entries(params)) v = v.replace(`{${k}}`, String(val)).replace(`{{${k}}}`, String(val));
17291
17671
  return v;
17292
17672
  };
17293
- const [over, setOver] = React63.useState({});
17294
- const [dismissed, setDismissed] = React63.useState({});
17295
- const report = React63.useCallback((key, info) => {
17673
+ const [over, setOver] = React65.useState({});
17674
+ const [dismissed, setDismissed] = React65.useState({});
17675
+ const report = React65.useCallback((key, info) => {
17296
17676
  const isOver = !info.unlimited && !info.loading && info.used > info.max;
17297
17677
  setOver((prev) => {
17298
17678
  if (isOver === Boolean(prev[key])) return prev;
17299
17679
  return { ...prev, [key]: isOver };
17300
17680
  });
17301
17681
  }, []);
17302
- const monitored = React63.useMemo(() => Array.from(new Set(keys)), [keys]);
17682
+ const monitored = React65.useMemo(() => Array.from(new Set(keys)), [keys]);
17303
17683
  const activeKey = monitored.find((k) => over[k] && !dismissed[k]);
17304
17684
  return /* @__PURE__ */ jsxs(Fragment, { children: [
17305
17685
  monitored.map((k) => /* @__PURE__ */ jsx(LimitProbe, { limitKey: k, onReport: report }, k)),
@@ -17322,11 +17702,11 @@ function SoftLimitBanner({ keys }) {
17322
17702
  ] })
17323
17703
  ] });
17324
17704
  }
17325
- var CommandPalette = React63.lazy(
17326
- () => import('./CommandPalette-2AL327XQ.js').then((m) => ({ default: m.CommandPalette }))
17705
+ var CommandPalette = React65.lazy(
17706
+ () => import('./CommandPalette-U7MMA2WH.js').then((m) => ({ default: m.CommandPalette }))
17327
17707
  );
17328
- var SubscriptionPage = React63.lazy(
17329
- () => import('./SubscriptionPage-NLZAZO4P.js').then((m) => ({ default: m.SubscriptionPage }))
17708
+ var SubscriptionPage = React65.lazy(
17709
+ () => import('./SubscriptionPage-4KP7DKA2.js').then((m) => ({ default: m.SubscriptionPage }))
17330
17710
  );
17331
17711
  function orgInitials(name) {
17332
17712
  const words = name.trim().split(/\s+/).filter(Boolean);
@@ -17555,9 +17935,9 @@ function AccessDenied({ tr }) {
17555
17935
  }
17556
17936
  function AdminNotifications() {
17557
17937
  const { notifications, unreadCount, fetchNotifications, markAsRead, markAllAsRead } = useNotifications();
17558
- const [open, setOpen] = React63.useState(false);
17559
- const fetchedRef = React63.useRef(false);
17560
- React63.useEffect(() => {
17938
+ const [open, setOpen] = React65.useState(false);
17939
+ const fetchedRef = React65.useRef(false);
17940
+ React65.useEffect(() => {
17561
17941
  if (fetchedRef.current) return;
17562
17942
  fetchedRef.current = true;
17563
17943
  void fetchNotifications();
@@ -17591,6 +17971,7 @@ function navEntryToItem(entry) {
17591
17971
  icon: entry.icon ?? "LayoutTemplate",
17592
17972
  route: entry.route,
17593
17973
  section: entry.section,
17974
+ group: entry.group,
17594
17975
  badge: entry.badge,
17595
17976
  href: entry.href,
17596
17977
  permission: entry.permission
@@ -17638,6 +18019,7 @@ function pageToNavigationItem(page, fallbackPosition) {
17638
18019
  icon: page.icon ?? "FileText",
17639
18020
  route: page.path,
17640
18021
  section: page.section ?? "main",
18022
+ group: page.group,
17641
18023
  badge: page.badge,
17642
18024
  href: page.href,
17643
18025
  permission: page.permission,
@@ -17672,7 +18054,7 @@ function buildSettingsTabs(pluginTabs, can, hasSystem, t, showOrgSettings, hasBi
17672
18054
  // sidebar ICON_MAP, which made the settings rail fall back to Home.
17673
18055
  icon: /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
17674
18056
  iconName: "CreditCard",
17675
- component: /* @__PURE__ */ jsx(React63.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SubscriptionPage, {}) })
18057
+ component: /* @__PURE__ */ jsx(React65.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SubscriptionPage, {}) })
17676
18058
  });
17677
18059
  }
17678
18060
  if (showOrgSettings) {
@@ -17696,9 +18078,9 @@ function buildSettingsTabs(pluginTabs, can, hasSystem, t, showOrgSettings, hasBi
17696
18078
  settingsTabs.push({
17697
18079
  id: tab.id,
17698
18080
  label: translated === labelKey ? tab.label : translated,
17699
- icon: React63.createElement(IconComp, { className: "h-4 w-4" }),
18081
+ icon: React65.createElement(IconComp, { className: "h-4 w-4" }),
17700
18082
  iconName: tab.icon ?? "Puzzle",
17701
- component: React63.createElement(tab.component),
18083
+ component: React65.createElement(tab.component),
17702
18084
  // Flag plugin-contributed tabs so SettingsPage draws the core/plugins divider.
17703
18085
  isPlugin: true,
17704
18086
  group: tab.group
@@ -17712,7 +18094,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17712
18094
  const can = usePermissionOptional();
17713
18095
  const access = useAccessOptional();
17714
18096
  const currentProfile = usePermissionsStore((s) => s.currentProfile);
17715
- const hasSystem = React63.useCallback(
18097
+ const hasSystem = React65.useCallback(
17716
18098
  (perm) => {
17717
18099
  if (!currentProfile) return true;
17718
18100
  if (currentProfile.id === "owner" || currentProfile.name?.toLowerCase() === "owner") return true;
@@ -17726,22 +18108,22 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17726
18108
  };
17727
18109
  const path = useAdminPath();
17728
18110
  const { user, signOut } = useAuth();
17729
- React63.useEffect(() => installGlobalErrorHandlers(), []);
18111
+ React65.useEffect(() => installGlobalErrorHandlers(), []);
17730
18112
  const railOpen = useRightRailStore((s) => s.open);
17731
18113
  const railHasPanels = useRightRailStore((s) => s.panels.length > 0);
17732
18114
  const rightRail = railHasPanels ? /* @__PURE__ */ jsx(RightRail, { frame: contentFrame }) : null;
17733
18115
  const setRailMounted = useRightRailStore((s) => s.setMounted);
17734
- React63.useEffect(() => {
18116
+ React65.useEffect(() => {
17735
18117
  setRailMounted(true);
17736
18118
  return () => setRailMounted(false);
17737
18119
  }, [setRailMounted]);
17738
18120
  useConnectorOAuthReturn();
17739
18121
  const railWidthLive = useRightRailStore((s) => s.width);
17740
18122
  const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
17741
- const railAutoCollapsedRef = React63.useRef(false);
17742
- React63.useEffect(() => {
18123
+ const railAutoCollapsedRef = React65.useRef(false);
18124
+ React65.useEffect(() => {
17743
18125
  if (layout !== "sidebar") return;
17744
- const SIDEBAR_EXPANDED = 256;
18126
+ const SIDEBAR_EXPANDED = 224;
17745
18127
  const MIN_CONTENT = 620;
17746
18128
  const apply = () => {
17747
18129
  if (typeof window === "undefined") return;
@@ -17761,20 +18143,20 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17761
18143
  window.addEventListener("resize", apply);
17762
18144
  return () => window.removeEventListener("resize", apply);
17763
18145
  }, [railOpen, railWidthLive, layout, setSidebarCollapsed]);
17764
- const [railWidth] = React63.useState(() => readRightRailWidth());
18146
+ const [railWidth] = React65.useState(() => readRightRailWidth());
17765
18147
  const billingPlans = useBillingStore((s) => s.plans);
17766
18148
  const hasBilling = billingPlans.length > 0;
17767
18149
  const orgPlan = useTenantOptional()?.org?.plan;
17768
- const planBadge = React63.useMemo(
18150
+ const planBadge = React65.useMemo(
17769
18151
  () => hasBilling ? resolvePlanBadge(orgPlan, billingPlans) : null,
17770
18152
  [hasBilling, orgPlan, billingPlans]
17771
18153
  );
17772
- const softLimitKeys = React63.useMemo(() => {
18154
+ const softLimitKeys = React65.useMemo(() => {
17773
18155
  const plan = billingPlans.find((p) => p.id === (orgPlan || "free"));
17774
18156
  return Object.keys(getPlanEntitlements(plan)?.limits ?? {});
17775
18157
  }, [billingPlans, orgPlan]);
17776
18158
  const navContributions = useNavContributionsStore((s) => s.contributions);
17777
- const navigation = React63.useMemo(() => {
18159
+ const navigation = React65.useMemo(() => {
17778
18160
  const contributed = Object.values(navContributions).flat();
17779
18161
  const flat = [...runtime.navigation, ...contributed].map((entry) => ({ ...navEntryToItem(entry), position: entry.position, indexChild: entry.indexChild })).sort(compareNavigation);
17780
18162
  const items = [];
@@ -17821,7 +18203,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17821
18203
  }, [runtime.navigation, navContributions, pages, access]);
17822
18204
  const commandPaletteOpen = useLayoutStore((s) => s.commandPaletteOpen);
17823
18205
  const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
17824
- const commandItems = React63.useMemo(() => {
18206
+ const commandItems = React65.useMemo(() => {
17825
18207
  const navGroup = tr("layout.commandPalette.navigation", "Navigation");
17826
18208
  const items = [];
17827
18209
  for (const nav of navigation) {
@@ -17833,7 +18215,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17833
18215
  return items;
17834
18216
  }, [navigation]);
17835
18217
  const globalSearch = useGlobalSearch();
17836
- const routes = React63.useMemo(() => {
18218
+ const routes = React65.useMemo(() => {
17837
18219
  const list = [];
17838
18220
  const entityRouteMap = /* @__PURE__ */ new Map();
17839
18221
  for (const r of runtime.routes) {
@@ -17855,25 +18237,25 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17855
18237
  setEntityRouteMap(entityRouteMap);
17856
18238
  return list.sort((a, b) => routeScore(b.path) - routeScore(a.path));
17857
18239
  }, [runtime.routes, pages, showSettings]);
17858
- const match = React63.useMemo(() => {
18240
+ const match = React65.useMemo(() => {
17859
18241
  for (const r of routes) {
17860
18242
  const params = matchRoute(r.path, path);
17861
18243
  if (params) return { route: r, params };
17862
18244
  }
17863
18245
  return null;
17864
18246
  }, [routes, path]);
17865
- React63.useEffect(() => {
18247
+ React65.useEffect(() => {
17866
18248
  if (!match && (path === "/" || path === "") && navigation.length > 0) {
17867
18249
  navigateTo(navigation[0].route);
17868
18250
  }
17869
18251
  }, [match, path, navigation]);
17870
18252
  const ActiveComponent = match?.route.Component;
17871
18253
  const activeParams = match?.params ?? {};
17872
- const settingsTabs = React63.useMemo(
18254
+ const settingsTabs = React65.useMemo(
17873
18255
  () => buildSettingsTabs(runtime.settingsTabs, can, hasSystem, t, showOrgSettings, hasBilling),
17874
18256
  [runtime.settingsTabs, can, hasSystem, t, showOrgSettings, hasBilling]
17875
18257
  );
17876
- const profileRoute = React63.useMemo(() => {
18258
+ const profileRoute = React65.useMemo(() => {
17877
18259
  const explicit = routes.find((r) => r.path === "/perfil" || r.path === "/profile");
17878
18260
  if (explicit) return explicit.path;
17879
18261
  return showSettings ? "/settings/profile" : null;
@@ -17881,7 +18263,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17881
18263
  const onProfile = profileRoute ? () => navigateTo(profileRoute) : void 0;
17882
18264
  const onBilling = hasBilling && showSettings ? () => navigateTo("/settings/subscription") : void 0;
17883
18265
  const coreSettingsTabs = useCoreSettingsTabs({ showCompany: showBranding, showBranding });
17884
- const allSettingsTabs = React63.useMemo(
18266
+ const allSettingsTabs = React65.useMemo(
17885
18267
  () => {
17886
18268
  const hidden = new Set(hiddenSettingsTabs ?? []);
17887
18269
  return [...coreSettingsTabs, ...settingsTabs].filter((tab) => !hidden.has(tab.id));
@@ -17893,12 +18275,12 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17893
18275
  const contextualNav = useContextualNavStore((s) => s.nav);
17894
18276
  const moduleRail = !inSettings && layout === "sidebar" && contextualNav && (path === contextualNav.base || path.startsWith(contextualNav.base + "/")) ? contextualNav : null;
17895
18277
  const activeSettingsTab = inSettings ? path === "/settings" ? null : path.slice("/settings/".length).split("/")[0] : void 0;
17896
- const settingsTabDef = React63.useMemo(() => {
18278
+ const settingsTabDef = React65.useMemo(() => {
17897
18279
  if (!showSettings || !path.startsWith("/settings/")) return void 0;
17898
18280
  const id = path.slice("/settings/".length).split("/")[0];
17899
18281
  return allSettingsTabs.find((tab) => tab.id === id);
17900
18282
  }, [showSettings, path, allSettingsTabs]);
17901
- const settingsNavigation = React63.useMemo(
18283
+ const settingsNavigation = React65.useMemo(
17902
18284
  () => allSettingsTabs.map((tab) => ({
17903
18285
  id: `settings:${tab.id}`,
17904
18286
  label: tab.label,
@@ -17916,20 +18298,20 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
17916
18298
  const shellUser = user ? { fullName: user.fullName ?? user.email, email: user.email, avatarUrl: user.avatarUrl } : void 0;
17917
18299
  const moduleVariant = moduleNav ?? (layout === "sidebar" ? "tabs" : "rail");
17918
18300
  const setRailAvailable = useContextualNavStore((s) => s.setRailAvailable);
17919
- React63.useEffect(() => {
18301
+ React65.useEffect(() => {
17920
18302
  setRailAvailable(layout === "sidebar" && moduleVariant !== "rail");
17921
18303
  }, [layout, moduleVariant, setRailAvailable]);
17922
- const activePageTitle = React63.useMemo(() => {
18304
+ const activePageTitle = React65.useMemo(() => {
17923
18305
  if (settingsTabDef) return settingsTabDef.label;
17924
18306
  if (path === "/settings" || path.startsWith("/settings/")) return tr("common.settings", "Settings");
17925
18307
  if (match?.route.title) return match.route.title;
17926
- const candidates = [...navigation, ...navigation.flatMap((n) => n.children ?? [])].filter((n) => n.route).sort((a, b) => b.route.length - a.route.length);
18308
+ const candidates = [...navigation, ...navigation.flatMap((n) => n.children ?? [])].filter((n) => n.route).sort((a, b) => b.route.length - a.route.length || (a.children?.length ? 1 : 0) - (b.children?.length ? 1 : 0));
17927
18309
  const hit = candidates.find(
17928
18310
  (n) => n.route === "/" ? path === "/" || path === "" : path === n.route || path.startsWith(n.route + "/") || path.startsWith(n.route)
17929
18311
  );
17930
18312
  return hit?.label ?? candidates.find((n) => n.route === "/")?.label ?? "";
17931
18313
  }, [navigation, path, tr, match, settingsTabDef]);
17932
- const parentBack = React63.useMemo(() => {
18314
+ const parentBack = React65.useMemo(() => {
17933
18315
  if (settingsTabDef) {
17934
18316
  return { onBack: () => navigateTo("/settings"), label: tr("common.settings", "Settings") };
17935
18317
  }
@@ -18008,11 +18390,15 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
18008
18390
  onEdit: site?.editRoute ? () => navigateTo(site.editRoute) : void 0
18009
18391
  }
18010
18392
  ),
18393
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.sidebar.footer" }),
18011
18394
  sidebarFooter
18012
18395
  ] }),
18013
18396
  orgSwitcher: layout === "topbar" && showOrgSwitcher ? /* @__PURE__ */ jsx(WorkspaceSwitcher, { variant: "topbar" }) : void 0,
18014
- topbarStart: /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.topbar.start" }),
18015
- topbarEnd: /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.topbar.end" }),
18397
+ topbarStart: /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.topbar.start", surface: "topbar" }),
18398
+ topbarEnd: /* @__PURE__ */ jsxs(Fragment, { children: [
18399
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.topbar.end", surface: "topbar" }),
18400
+ layout !== "sidebar" && /* @__PURE__ */ jsx(WidgetSlot, { zone: "shell.sidebar.footer", surface: "topbar" })
18401
+ ] }),
18016
18402
  rightRail,
18017
18403
  rightRailOpen: railOpen,
18018
18404
  defaultRightRailWidth: railWidth,
@@ -18059,10 +18445,15 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
18059
18445
  userId: user?.id,
18060
18446
  backLabel: parentBack?.label,
18061
18447
  onBack: parentBack ? parentBack.onBack : () => navigateTo(homeRoute),
18062
- children: match?.route.fullBleed ? /* @__PURE__ */ jsx("div", { className: "h-full min-h-0 overflow-hidden", children: activeContent }) : /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-3 md:space-y-6 md:p-6", children: [
18063
- softLimitKeys.length > 0 && /* @__PURE__ */ jsx(SoftLimitBanner, { keys: softLimitKeys }),
18064
- activeContent
18065
- ] })
18448
+ children: match?.route.fullBleed ? /* @__PURE__ */ jsx("div", { className: "h-full min-h-0 overflow-hidden", children: activeContent }) : (
18449
+ // Less air at the top than at the sides: the bar above already
18450
+ // ends in a hairline, so a full 24px under it read as a gap
18451
+ // before the page started rather than as the page's margin.
18452
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-3 md:space-y-6 md:px-6 md:pb-6 md:pt-4", children: [
18453
+ softLimitKeys.length > 0 && /* @__PURE__ */ jsx(SoftLimitBanner, { keys: softLimitKeys }),
18454
+ activeContent
18455
+ ] })
18456
+ )
18066
18457
  },
18067
18458
  match?.route.path ?? path
18068
18459
  )
@@ -18081,7 +18472,7 @@ function AdminShellInner({ appName, layout = "sidebar", logo, pages = [], showSe
18081
18472
  ]
18082
18473
  }
18083
18474
  ),
18084
- /* @__PURE__ */ jsx(React63.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
18475
+ /* @__PURE__ */ jsx(React65.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
18085
18476
  CommandPalette,
18086
18477
  {
18087
18478
  commands: commandItems,
@@ -18249,7 +18640,7 @@ function buildI18nConfig(config) {
18249
18640
  function BillingInitializer({ config }) {
18250
18641
  const setPlans = useBillingStore((s) => s.setPlans);
18251
18642
  const setCheckout = useBillingStore((s) => s.setCheckout);
18252
- React63.useEffect(() => {
18643
+ React65.useEffect(() => {
18253
18644
  if (config.billing?.plans) setPlans(config.billing.plans.map(normalizeBillingPlan));
18254
18645
  setCheckout(config.billing?.onCheckout ?? null);
18255
18646
  }, [config.billing, setPlans, setCheckout]);
@@ -18276,7 +18667,7 @@ function ThemeInitializer({ config }) {
18276
18667
  const initialize = useThemeStore((s) => s.initialize);
18277
18668
  const setOverrides = useThemeStore((s) => s.setOverrides);
18278
18669
  const setMode = useThemeStore((s) => s.setMode);
18279
- React63.useEffect(() => {
18670
+ React65.useEffect(() => {
18280
18671
  if (config.theme) {
18281
18672
  const resolved = isSaasTheme(config.theme) ? resolveTheme(config.theme) : config.theme;
18282
18673
  setOverrides(resolved);
@@ -18288,14 +18679,21 @@ function ThemeInitializer({ config }) {
18288
18679
  }, [config.defaultThemeMode, config.theme, initialize, setMode, setOverrides]);
18289
18680
  return null;
18290
18681
  }
18682
+ function PersistentCacheInitializer() {
18683
+ React65.useEffect(() => {
18684
+ attachPersistentCache();
18685
+ void getPersistentCache()?.hydrate();
18686
+ }, []);
18687
+ return null;
18688
+ }
18291
18689
  function FayzEventBridgeInitializer({ config }) {
18292
- React63.useEffect(() => startFayzEventBridge(config.chat?.agent), [config.chat?.agent]);
18690
+ React65.useEffect(() => startFayzEventBridge(config.chat?.agent), [config.chat?.agent]);
18293
18691
  return null;
18294
18692
  }
18295
18693
  function TeamInitializer({ config }) {
18296
18694
  const setTeamPersonKinds = useOrganizationStore((s) => s.setTeamPersonKinds);
18297
18695
  const kinds = config.team?.personKinds;
18298
- React63.useEffect(() => {
18696
+ React65.useEffect(() => {
18299
18697
  setTeamPersonKinds(kinds ?? []);
18300
18698
  }, [setTeamPersonKinds, kinds]);
18301
18699
  return null;
@@ -18311,7 +18709,7 @@ function ChatRailPanel({ config }) {
18311
18709
  const agent = config.chat?.agent;
18312
18710
  const voice = config.chat?.voice;
18313
18711
  const whatsapp = config.chat?.whatsapp;
18314
- const Component2 = React63.useMemo(() => {
18712
+ const Component2 = React65.useMemo(() => {
18315
18713
  const Panel = () => /* @__PURE__ */ jsx(
18316
18714
  ChatConversation,
18317
18715
  {
@@ -18328,15 +18726,15 @@ function ChatRailPanel({ config }) {
18328
18726
  const conversations = useChatStore((s) => s.conversations);
18329
18727
  const setConversations = useChatStore((s) => s.setConversations);
18330
18728
  const user = useAuthStore((s) => s.user);
18331
- const connection = React63.useMemo(() => resolveFayzAgentConnection(agent), [agent]);
18332
- React63.useEffect(() => {
18729
+ const connection = React65.useMemo(() => resolveFayzAgentConnection(agent), [agent]);
18730
+ React65.useEffect(() => {
18333
18731
  if (!connection) return;
18334
18732
  getFayzAgentClient(connection).listConversations(user?.id).then(setConversations).catch(() => {
18335
18733
  });
18336
18734
  }, [connection, user?.id, setConversations]);
18337
18735
  const persona = useAssistantInviteStore((s) => s.invite?.persona);
18338
18736
  useRightRailPanel(
18339
- React63.useMemo(
18737
+ React65.useMemo(
18340
18738
  () => ({
18341
18739
  id: "chat",
18342
18740
  label: persona ?? "Chat",
@@ -18348,17 +18746,17 @@ function ChatRailPanel({ config }) {
18348
18746
  [Component2, conversations.length, persona]
18349
18747
  )
18350
18748
  );
18351
- React63.useEffect(() => {
18749
+ React65.useEffect(() => {
18352
18750
  if (chatOpen) openPanel("chat");
18353
18751
  }, [chatOpen, openPanel]);
18354
- React63.useEffect(() => {
18752
+ React65.useEffect(() => {
18355
18753
  if (!(railOpen && railActive === "chat")) setChatOpen(false);
18356
18754
  }, [railOpen, railActive, setChatOpen]);
18357
18755
  return null;
18358
18756
  }
18359
18757
  function AdminProviders({ config, children }) {
18360
18758
  const currentOrg = useOrganizationStore((s) => s.currentOrg);
18361
- const hasFayzAgent = React63.useMemo(
18759
+ const hasFayzAgent = React65.useMemo(
18362
18760
  () => !!resolveFayzAgentConnection(config.chat?.agent),
18363
18761
  [config.chat?.agent]
18364
18762
  );
@@ -18366,7 +18764,7 @@ function AdminProviders({ config, children }) {
18366
18764
  const surface = config.chat?.surface ?? "dock";
18367
18765
  const onConsentScreen = typeof window !== "undefined" && window.location.pathname.replace(/\/+$/, "").endsWith("/connect");
18368
18766
  const showFab = assistantEnabled && surface === "dock" && !onConsentScreen;
18369
- const resolved = React63.useMemo(() => {
18767
+ const resolved = React65.useMemo(() => {
18370
18768
  if (config.supabaseUrl && config.supabaseAnonKey) {
18371
18769
  createFayzSupabaseClient(config.supabaseUrl, config.supabaseAnonKey);
18372
18770
  }
@@ -18382,7 +18780,7 @@ function AdminProviders({ config, children }) {
18382
18780
  };
18383
18781
  }, [config]);
18384
18782
  const path = useAdminPath();
18385
- const runtime = React63.useMemo(
18783
+ const runtime = React65.useMemo(
18386
18784
  () => resolvePluginRuntime({
18387
18785
  plugins: resolved.plugins,
18388
18786
  context: {
@@ -18408,6 +18806,7 @@ function AdminProviders({ config, children }) {
18408
18806
  cacheTtlMs: config.org?.cacheTtlMs,
18409
18807
  children: /* @__PURE__ */ jsx(PluginRuntimeProvider, { value: runtime, children: /* @__PURE__ */ jsx(PermissionsProvider, { config: config.permissions, children: /* @__PURE__ */ jsx(AccessProvider, { limitDeclarations: config.billing?.limitDeclarations, children: /* @__PURE__ */ jsxs(I18nProvider, { value: resolved.i18n, children: [
18410
18808
  /* @__PURE__ */ jsx(ThemeInitializer, { config }),
18809
+ /* @__PURE__ */ jsx(PersistentCacheInitializer, {}),
18411
18810
  /* @__PURE__ */ jsx(TeamInitializer, { config }),
18412
18811
  /* @__PURE__ */ jsx(BillingInitializer, { config }),
18413
18812
  /* @__PURE__ */ jsx(FayzEventBridgeInitializer, { config }),
@@ -18693,8 +19092,8 @@ function manifestToFayzConfig(manifest, surfaceName) {
18693
19092
  };
18694
19093
  }
18695
19094
  function AdminScaffold({ manifest, surface }) {
18696
- const config = React63.useMemo(() => manifestToFayzConfig(manifest, surface), [manifest, surface]);
18697
- const authShellProps = React63.useMemo(() => getAuthShellProps(config.auth), [config.auth]);
19095
+ const config = React65.useMemo(() => manifestToFayzConfig(manifest, surface), [manifest, surface]);
19096
+ const authShellProps = React65.useMemo(() => getAuthShellProps(config.auth), [config.auth]);
18698
19097
  return /* @__PURE__ */ jsx(AdminProviders, { config, children: /* @__PURE__ */ jsx(
18699
19098
  AdminShell,
18700
19099
  {
@@ -18973,10 +19372,10 @@ function OrderDetailView(props) {
18973
19372
  // três colunas viravam uma tira de 280px que quebrava todo rótulo.
18974
19373
  /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
18975
19374
  /* @__PURE__ */ jsx(PageHeader, { title, parentLabel: backLabel, onBack }),
18976
- (badges.length > 0 || props.badgesNode || subtitle || actions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
19375
+ actions && /* @__PURE__ */ jsx(PageHeaderActions, { children: actions }),
19376
+ (badges.length > 0 || props.badgesNode || subtitle) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
18977
19377
  props.badgesNode ?? badges.map((b, i) => /* @__PURE__ */ jsx(Badge15, { badge: b }, i)),
18978
- subtitle && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: subtitle }),
18979
- actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: actions })
19378
+ subtitle && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: subtitle })
18980
19379
  ] }),
18981
19380
  stats.length > 0 && /* @__PURE__ */ jsx("div", { className: "fz-fields fz-fields-4 gap-3", children: stats.map((tile, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-3", children: [
18982
19381
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
@@ -19064,7 +19463,9 @@ var ITEM_LABELS = {
19064
19463
  adjustments: "Desconto / acr\xE9scimo",
19065
19464
  removeAdjustments: "Remover ajustes",
19066
19465
  remove: "Remover item",
19067
- done: "Conclu\xEDdo"
19466
+ done: "Conclu\xEDdo",
19467
+ create: "Cadastrar",
19468
+ clear: "Limpar"
19068
19469
  };
19069
19470
  function OrderFormItemRow({
19070
19471
  index,
@@ -19091,7 +19492,7 @@ function OrderFormItemRow({
19091
19492
  const total = quantity * unitPrice - (discount ?? 0) + (surcharge ?? 0);
19092
19493
  const kindLabel = itemTypes.find((tp) => tp.value === itemKind)?.label ?? itemKind;
19093
19494
  const hasAdjustments = (discount ?? 0) > 0 || (surcharge ?? 0) > 0;
19094
- const [showAdjustments, setShowAdjustments] = React63.useState(hasAdjustments);
19495
+ const [showAdjustments, setShowAdjustments] = React65.useState(hasAdjustments);
19095
19496
  const lookup2 = entityLookups[itemKind];
19096
19497
  function handleFieldKeyDown(e) {
19097
19498
  if (e.key === "Enter" && !e.shiftKey) {
@@ -19166,6 +19567,12 @@ function OrderFormItemRow({
19166
19567
  const results = await lookup2.search(q);
19167
19568
  return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
19168
19569
  },
19570
+ createLabel: l.create,
19571
+ clearLabel: l.clear,
19572
+ onCreate: lookup2.create ? async (name) => {
19573
+ const created = await lookup2.create(name);
19574
+ onChange({ referenceId: created.id, description: created.label });
19575
+ } : void 0,
19169
19576
  placeholder: `${l.item}\u2026`,
19170
19577
  renderOption: (opt) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
19171
19578
  /* @__PURE__ */ jsx("p", { className: "truncate text-sm", children: opt.label }),
@@ -19361,7 +19768,7 @@ function OrderFormView({
19361
19768
  parentLabel,
19362
19769
  onBack,
19363
19770
  badges,
19364
- subtitle,
19771
+ subtitle: _subtitle,
19365
19772
  actions,
19366
19773
  banner,
19367
19774
  children,
@@ -19372,11 +19779,8 @@ function OrderFormView({
19372
19779
  // se cabem duas colunas é a LARGURA DESTA COLUNA, não a da janela.
19373
19780
  /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
19374
19781
  /* @__PURE__ */ jsx(PageHeader, { title, parentLabel, onBack }),
19375
- (badges || subtitle || actions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
19376
- badges,
19377
- subtitle && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: subtitle }),
19378
- actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: actions })
19379
- ] }),
19782
+ actions && /* @__PURE__ */ jsx(PageHeaderActions, { children: actions }),
19783
+ badges && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: badges }),
19380
19784
  banner,
19381
19785
  /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
19382
19786
  /* @__PURE__ */ jsx("div", { className: "fz-container space-y-4", children }),
@@ -19387,13 +19791,12 @@ function OrderFormView({
19387
19791
  }
19388
19792
  OrderFormView.displayName = "OrderFormView";
19389
19793
  function OrderFormSkeleton() {
19390
- return /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
19391
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" }),
19392
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
19393
- /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-20 rounded-full" }),
19394
- /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-20 rounded-full" })
19395
- ] }),
19396
- /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
19794
+ return (
19795
+ // Sem a linha da migalha nem a fileira de pastilhas: a migalha mora na barra
19796
+ // da shell (que já está desenhada quando isto aparece) e o formulário não
19797
+ // tem badges. Eram duas faixas cinzas prometendo conteúdo que nunca chega —
19798
+ // o "pulo" que um esqueleto existe para evitar, causado pelo esqueleto.
19799
+ /* @__PURE__ */ jsx("div", { className: "fz-container space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
19397
19800
  /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", children: [
19398
19801
  /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }) }),
19399
19802
  /* @__PURE__ */ jsx("div", { className: "space-y-3 p-4", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
@@ -19406,11 +19809,13 @@ function OrderFormSkeleton() {
19406
19809
  /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" }),
19407
19810
  /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-2", children: Array.from({ length: rows }).map((_, j) => /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full" }, j)) })
19408
19811
  ] }, i)) })
19409
- ] })
19410
- ] });
19812
+ ] }) })
19813
+ );
19411
19814
  }
19412
19815
  function OrderListView({
19413
- title,
19816
+ // `title` continua na API (é o que o chamador declara), mas quem escreve o
19817
+ // nome da tela é a barra da shell — ver o corpo.
19818
+ title: _title,
19414
19819
  subtitle,
19415
19820
  actions,
19416
19821
  stats,
@@ -19428,10 +19833,10 @@ function OrderListView({
19428
19833
  bulkActions,
19429
19834
  rowId
19430
19835
  }) {
19431
- const [search, setSearch] = React63.useState("");
19432
- const [page, setPage] = React63.useState(1);
19433
- const [rawSelection, setRawSelection] = React63.useState([]);
19434
- const cols = React63.useMemo(
19836
+ const [search, setSearch] = React65.useState("");
19837
+ const [page, setPage] = React65.useState(1);
19838
+ const [rawSelection, setRawSelection] = React65.useState([]);
19839
+ const cols = React65.useMemo(
19435
19840
  () => columns.map((c) => ({
19436
19841
  id: c.key,
19437
19842
  header: () => /* @__PURE__ */ jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.header }),
@@ -19439,29 +19844,30 @@ function OrderListView({
19439
19844
  })),
19440
19845
  [columns]
19441
19846
  );
19442
- const filtered = React63.useMemo(() => {
19847
+ const filtered = React65.useMemo(() => {
19443
19848
  const term = search.trim().toLowerCase();
19444
19849
  if (!term || !searchText) return rows;
19445
19850
  return rows.filter((row) => searchText(row).toLowerCase().includes(term));
19446
19851
  }, [rows, search, searchText]);
19447
- React63.useEffect(() => {
19852
+ React65.useEffect(() => {
19448
19853
  setPage(1);
19449
19854
  }, [search]);
19450
19855
  const paged = pageSize > 0 ? filtered.slice((page - 1) * pageSize, page * pageSize) : filtered;
19451
- const getId = React63.useCallback(
19856
+ const getId = React65.useCallback(
19452
19857
  (row) => rowId ? rowId(row) : String(row.id ?? ""),
19453
19858
  [rowId]
19454
19859
  );
19455
- const pageIds = React63.useMemo(() => paged.map(getId), [paged, getId]);
19456
- const selectedIds = React63.useMemo(
19860
+ const pageIds = React65.useMemo(() => paged.map(getId), [paged, getId]);
19861
+ const selectedIds = React65.useMemo(
19457
19862
  () => rawSelection.filter((id) => pageIds.includes(id)),
19458
19863
  [rawSelection, pageIds]
19459
19864
  );
19460
- const selectedRows = React63.useMemo(
19865
+ const selectedRows = React65.useMemo(
19461
19866
  () => paged.filter((row) => selectedIds.includes(getId(row))),
19462
19867
  [paged, selectedIds, getId]
19463
19868
  );
19464
19869
  const selectable = (bulkActions?.length ?? 0) > 0;
19870
+ const hasSelection = selectable && selectedIds.length > 0;
19465
19871
  const barActions = (bulkActions ?? []).map((action) => ({
19466
19872
  id: action.id,
19467
19873
  label: action.label,
@@ -19483,38 +19889,31 @@ function OrderListView({
19483
19889
  // `p-3 md:p-6` (AdminShell). Reaplicar `p-6 md:p-8` aqui empurrava a lista
19484
19890
  // 32px à direita do título na barra de topo e de todas as outras telas.
19485
19891
  /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
19486
- navTabs && navTabs.length > 0 && // Mesma linha de CrudListView: abas à esquerda, ações à direita.
19487
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
19488
- /* @__PURE__ */ jsx(
19489
- HeaderTabs,
19490
- {
19491
- tabs: navTabs,
19492
- value: activeNavTab ?? navTabs[0].value,
19493
- onChange: (v) => onNavTabChange?.(v)
19494
- }
19495
- ),
19496
- actions
19497
- ] }),
19498
- !navTabs?.length && (title || subtitle || actions) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
19499
- title || subtitle ? /* @__PURE__ */ jsxs("div", { children: [
19500
- title && /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }),
19501
- subtitle && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
19502
- ] }) : /* @__PURE__ */ jsx("span", {}),
19503
- actions
19504
- ] }),
19505
- tiles.length > 0 && /* @__PURE__ */ jsx(StatTiles, { tiles, loading }),
19506
- selectable && /* @__PURE__ */ jsx(
19507
- BulkActionBar,
19892
+ actions && /* @__PURE__ */ jsx(PageHeaderActions, { children: actions }),
19893
+ navTabs && navTabs.length > 0 && // Mesma faixa de CrudListView e da sub-nav de módulo: abas à esquerda,
19894
+ // fechada pelo filete que atravessa a página.
19895
+ /* @__PURE__ */ jsx(
19896
+ PageTabs,
19508
19897
  {
19509
- count: selectedIds.length,
19510
- total: paged.length,
19511
- onSelectAll: () => setRawSelection(pageIds),
19512
- onClear: () => setRawSelection([]),
19513
- actions: barActions
19898
+ className: "mb-0",
19899
+ tabs: navTabs,
19900
+ value: activeNavTab ?? navTabs[0].value,
19901
+ onChange: (v) => onNavTabChange?.(v)
19514
19902
  }
19515
19903
  ),
19904
+ tiles.length > 0 && /* @__PURE__ */ jsx(StatTiles, { tiles, loading }),
19516
19905
  /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
19517
- searchText && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
19906
+ (searchText || hasSelection) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: hasSelection ? /* @__PURE__ */ jsx(
19907
+ BulkActionBar,
19908
+ {
19909
+ variant: "inline",
19910
+ count: selectedIds.length,
19911
+ total: paged.length,
19912
+ onSelectAll: () => setRawSelection(pageIds),
19913
+ onClear: () => setRawSelection([]),
19914
+ actions: barActions
19915
+ }
19916
+ ) : /* @__PURE__ */ jsx("div", { className: "min-w-[10rem] flex-1", children: /* @__PURE__ */ jsx(
19518
19917
  SearchField,
19519
19918
  {
19520
19919
  value: search,
@@ -19645,6 +20044,13 @@ function createClientOrdersProvider() {
19645
20044
  }
19646
20045
  };
19647
20046
  }
20047
+ var PERSON_LOOKUPS = {};
20048
+ function registerPersonLookup(kind, lookup2) {
20049
+ PERSON_LOOKUPS[kind] = lookup2;
20050
+ }
20051
+ function getPersonLookup(kind) {
20052
+ return PERSON_LOOKUPS[kind] ?? null;
20053
+ }
19648
20054
  var ARCHETYPE_DISPLAY = {
19649
20055
  person: { table: "people", labelField: "name", subtitleFields: ["phone"], groupField: "kind" },
19650
20056
  product: { table: "products", labelField: "name", subtitleFields: ["sku", "description"], priceField: "price" },
@@ -19705,6 +20111,21 @@ function createArchetypeLookup(config) {
19705
20111
  (row) => toResult(row, display, config.kindLabels)
19706
20112
  );
19707
20113
  },
20114
+ // Cadastro na hora: só o nome (e o `kind`, quando a busca já está presa a
20115
+ // um). Um registro nascido aqui é um esboço de propósito — o campo que o
20116
+ // pediu precisa continuar, não abrir um formulário inteiro.
20117
+ async create(name) {
20118
+ const supabase = getSupabaseClientOptional();
20119
+ if (!supabase) throw new Error("No database connection");
20120
+ const row = {
20121
+ [display.labelField]: name,
20122
+ is_active: true
20123
+ };
20124
+ if (kinds && kinds.length === 1) row.kind = kinds[0];
20125
+ const { data, error } = await supabase.from(display.table).insert(row).select("*").single();
20126
+ if (error) throw new Error(error.message);
20127
+ return toResult(data, display, config.kindLabels);
20128
+ },
19708
20129
  async getById(id) {
19709
20130
  const supabase = getSupabaseClientOptional();
19710
20131
  if (!supabase) return null;
@@ -19888,11 +20309,11 @@ function PersonTimeline({
19888
20309
  pageSize,
19889
20310
  activeSourceIds
19890
20311
  });
19891
- const toneBySource = React63__default.useMemo(
20312
+ const toneBySource = React65__default.useMemo(
19892
20313
  () => new Map(sources2.map((s) => [s.id, s.tone ?? "neutral"])),
19893
20314
  [sources2]
19894
20315
  );
19895
- const hrefFor = React63__default.useCallback(
20316
+ const hrefFor = React65__default.useCallback(
19896
20317
  (item) => {
19897
20318
  if (item.href) return item.href;
19898
20319
  if (!recordPath || !item.targetTab) return void 0;
@@ -19935,19 +20356,19 @@ function PersonTimeline({
19935
20356
  }
19936
20357
  function createPlaceholder(title, description) {
19937
20358
  return function PlaceholderPage() {
19938
- return React63__default.createElement(
20359
+ return React65__default.createElement(
19939
20360
  "div",
19940
20361
  { className: "mx-auto max-w-4xl" },
19941
- React63__default.createElement("h1", { className: "text-3xl font-bold text-foreground" }, title),
19942
- description ? React63__default.createElement("p", { className: "mt-1 text-muted-foreground" }, description) : null,
19943
- React63__default.createElement(
20362
+ React65__default.createElement("h1", { className: "text-3xl font-bold text-foreground" }, title),
20363
+ description ? React65__default.createElement("p", { className: "mt-1 text-muted-foreground" }, description) : null,
20364
+ React65__default.createElement(
19944
20365
  "div",
19945
20366
  { className: "mt-8 flex items-center justify-center rounded-card border border-dashed border-border bg-card p-16" },
19946
- React63__default.createElement(
20367
+ React65__default.createElement(
19947
20368
  "div",
19948
20369
  { className: "text-center" },
19949
- React63__default.createElement("p", { className: "text-lg font-medium text-muted-foreground" }, "Coming Soon"),
19950
- React63__default.createElement(
20370
+ React65__default.createElement("p", { className: "text-lg font-medium text-muted-foreground" }, "Coming Soon"),
20371
+ React65__default.createElement(
19951
20372
  "p",
19952
20373
  { className: "mt-1 text-sm text-muted-foreground/70" },
19953
20374
  "This module is being built as a plugin."
@@ -19957,55 +20378,6 @@ function createPlaceholder(title, description) {
19957
20378
  );
19958
20379
  };
19959
20380
  }
19960
- function QuickActionsButton({ actions, className }) {
19961
- const [open, setOpen] = useState(false);
19962
- const ref = useRef(null);
19963
- const variant = useModuleLayout() === "tabs" ? "default" : "outline";
19964
- useEffect(() => {
19965
- function handler(e) {
19966
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
19967
- }
19968
- document.addEventListener("mousedown", handler);
19969
- return () => document.removeEventListener("mousedown", handler);
19970
- }, []);
19971
- if (actions.length === 0) return null;
19972
- return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
19973
- /* @__PURE__ */ jsxs(
19974
- Button,
19975
- {
19976
- variant,
19977
- size: "sm",
19978
- onClick: () => setOpen(!open),
19979
- className: cn(open && variant === "outline" && "bg-muted"),
19980
- children: [
19981
- /* @__PURE__ */ jsx(Plus, { className: cn("h-3.5 w-3.5 transition-transform duration-200", open && "rotate-45") }),
19982
- /* @__PURE__ */ jsx("span", { children: "New" })
19983
- ]
19984
- }
19985
- ),
19986
- open && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute top-full right-0 z-50 mt-2 w-64 rounded-xl border bg-popover shadow-lg overflow-hidden animate-in fade-in-0 zoom-in-95 origin-top-right", children: /* @__PURE__ */ jsx("div", { className: "p-1.5", children: actions.map((action) => {
19987
- const Icon = action.icon ? ICON_MAP[action.icon] ?? Plus : Plus;
19988
- return /* @__PURE__ */ jsxs(
19989
- "button",
19990
- {
19991
- onClick: () => {
19992
- action.action();
19993
- setOpen(false);
19994
- },
19995
- className: "w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-left hover:bg-muted/50 transition-colors group",
19996
- children: [
19997
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted group-hover:bg-primary/10 transition-colors shrink-0", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" }) }),
19998
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
19999
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: action.label }),
20000
- action.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground leading-tight", children: action.description })
20001
- ] })
20002
- ]
20003
- },
20004
- action.id
20005
- );
20006
- }) }) })
20007
- ] });
20008
- }
20009
20381
  function ModuleActionBar({
20010
20382
  quickActions,
20011
20383
  settingsPath,
@@ -20034,9 +20406,9 @@ function ModuleActionBar({
20034
20406
  ] });
20035
20407
  }
20036
20408
  function createPluginContext(displayName) {
20037
- const ConfigContext = React63__default.createContext(null);
20038
- const ProviderContext = React63__default.createContext(null);
20039
- const StoreContext = React63__default.createContext(null);
20409
+ const ConfigContext = React65__default.createContext(null);
20410
+ const ProviderContext = React65__default.createContext(null);
20411
+ const StoreContext = React65__default.createContext(null);
20040
20412
  const ContextProvider = ({
20041
20413
  config,
20042
20414
  provider,
@@ -20045,17 +20417,17 @@ function createPluginContext(displayName) {
20045
20417
  }) => /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx(ProviderContext.Provider, { value: provider, children: /* @__PURE__ */ jsx(StoreContext.Provider, { value: store, children }) }) });
20046
20418
  ContextProvider.displayName = `${displayName}ContextProvider`;
20047
20419
  function useConfig() {
20048
- const ctx = React63__default.useContext(ConfigContext);
20420
+ const ctx = React65__default.useContext(ConfigContext);
20049
20421
  if (!ctx) throw new Error(`useConfig must be used within ${displayName}`);
20050
20422
  return ctx;
20051
20423
  }
20052
20424
  function useProvider() {
20053
- const ctx = React63__default.useContext(ProviderContext);
20425
+ const ctx = React65__default.useContext(ProviderContext);
20054
20426
  if (!ctx) throw new Error(`useProvider must be used within ${displayName}`);
20055
20427
  return ctx;
20056
20428
  }
20057
20429
  function useStore$1(selector) {
20058
- const store = React63__default.useContext(StoreContext);
20430
+ const store = React65__default.useContext(StoreContext);
20059
20431
  if (!store) throw new Error(`useStore must be used within ${displayName}`);
20060
20432
  return useStore(store, selector);
20061
20433
  }
@@ -20112,7 +20484,7 @@ function PluginRegistryManager({ registries, routeBase, className }) {
20112
20484
  return registries[0]?.id ?? "";
20113
20485
  };
20114
20486
  const [activeTab, setActiveTab] = useState(getTabFromHash);
20115
- React63__default.useEffect(() => {
20487
+ React65__default.useEffect(() => {
20116
20488
  if (!routeBase) return;
20117
20489
  const handler = () => setActiveTab(getTabFromHash());
20118
20490
  window.addEventListener("hashchange", handler);
@@ -20170,6 +20542,14 @@ function useModuleNavigation(hashBase, depthMap, homeView = "summary") {
20170
20542
  const rest = hash.slice(hashBase.length + 1);
20171
20543
  const segments = rest.split("/");
20172
20544
  if (segments.length === 1 && segments[0] === homeView) return homeView;
20545
+ const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20546
+ const last = segments[segments.length - 1];
20547
+ if (segments.length >= 3 && !UUID_RE2.test(last) && UUID_RE2.test(segments[segments.length - 2])) {
20548
+ return segments.slice(0, -2).join("-") + "-" + last + ":" + segments[segments.length - 2];
20549
+ }
20550
+ if (segments.length === 2 && UUID_RE2.test(last)) {
20551
+ return segments[0] + "-detail:" + last;
20552
+ }
20173
20553
  const ACTION_KEYWORDS = ["edit", "detail", "view", "post"];
20174
20554
  if (segments.length >= 3) {
20175
20555
  const maybeAction = segments[segments.length - 2];
@@ -20177,9 +20557,8 @@ function useModuleNavigation(hashBase, depthMap, homeView = "summary") {
20177
20557
  return segments.slice(0, -2).join("-") + "-" + maybeAction + ":" + segments[segments.length - 1];
20178
20558
  }
20179
20559
  }
20180
- const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20181
- if (segments.length >= 2 && UUID_RE2.test(segments[segments.length - 1])) {
20182
- return segments.slice(0, -1).join("-") + ":" + segments[segments.length - 1];
20560
+ if (segments.length >= 2 && UUID_RE2.test(last)) {
20561
+ return segments.slice(0, -1).join("-") + ":" + last;
20183
20562
  }
20184
20563
  return segments.join("-");
20185
20564
  }
@@ -20234,9 +20613,17 @@ function useModuleNavigation(hashBase, depthMap, homeView = "summary") {
20234
20613
  } else {
20235
20614
  const colonIdx = nextView.indexOf(":");
20236
20615
  if (colonIdx !== -1) {
20237
- const prefix = nextView.slice(0, colonIdx).replace(/-/g, "/");
20616
+ const rawPrefix = nextView.slice(0, colonIdx);
20238
20617
  const id = nextView.slice(colonIdx + 1);
20239
- window.location.hash = `${hashBase}/${prefix}/${id}`;
20618
+ const parts = rawPrefix.split("-");
20619
+ const verb = parts.length > 1 ? parts[parts.length - 1] : void 0;
20620
+ if (verb === "detail") {
20621
+ window.location.hash = `${hashBase}/${parts.slice(0, -1).join("/")}/${id}`;
20622
+ } else if (verb === "edit") {
20623
+ window.location.hash = `${hashBase}/${parts.slice(0, -1).join("/")}/${id}/edit`;
20624
+ } else {
20625
+ window.location.hash = `${hashBase}/${rawPrefix.replace(/-/g, "/")}/${id}`;
20626
+ }
20240
20627
  } else {
20241
20628
  const path = nextView.replace(/-/g, "/");
20242
20629
  window.location.hash = `${hashBase}/${path}`;
@@ -20265,7 +20652,7 @@ function formatAge(dob) {
20265
20652
  return null;
20266
20653
  }
20267
20654
  }
20268
- function PersonLink2({ personId, name, lookup: lookup2, profileHref, tab, size = "default", className }) {
20655
+ function PersonLink2({ personId, name, lookup: lookup2, kind, profileHref, tab, size = "default", className }) {
20269
20656
  const [person, setPerson] = useState(null);
20270
20657
  const [loading, setLoading] = useState(false);
20271
20658
  const [fetched, setFetched] = useState(false);
@@ -20274,7 +20661,7 @@ function PersonLink2({ personId, name, lookup: lookup2, profileHref, tab, size =
20274
20661
  const fetchPerson = useCallback(() => {
20275
20662
  if (!fetched && personId) {
20276
20663
  setLoading(true);
20277
- (lookup2 ?? personLookup).getById(personId).then((result) => {
20664
+ (lookup2 ?? (kind ? getPersonLookup(kind) : null) ?? personLookup).getById(personId).then((result) => {
20278
20665
  setPerson(result);
20279
20666
  setFetched(true);
20280
20667
  setLoading(false);
@@ -20283,7 +20670,7 @@ function PersonLink2({ personId, name, lookup: lookup2, profileHref, tab, size =
20283
20670
  setLoading(false);
20284
20671
  });
20285
20672
  }
20286
- }, [personId, fetched, lookup2]);
20673
+ }, [personId, fetched, lookup2, kind]);
20287
20674
  const handleOpenChange = useCallback((v) => {
20288
20675
  setOpen(v);
20289
20676
  if (v) fetchPerson();
@@ -20306,13 +20693,14 @@ function PersonLink2({ personId, name, lookup: lookup2, profileHref, tab, size =
20306
20693
  const data = person?.data;
20307
20694
  const email = data?.email;
20308
20695
  const phone = data?.phone;
20309
- const kind = data?.kind ?? person?.group;
20696
+ const resolvedKind = kind ?? data?.kind ?? person?.group;
20310
20697
  const isActive = data?.is_active;
20311
20698
  const dob = data?.date_of_birth;
20312
20699
  const createdAt = data?.created_at;
20313
20700
  const age = dob ? formatAge(dob) : null;
20314
20701
  const initial = name.charAt(0).toUpperCase();
20315
- const resolvedHref = profileHref ?? (personId ? resolveEntityAnchor(personId, "person", kind ?? void 0, tab) : void 0);
20702
+ const registeredPath = kind ? getPersonPath(kind) : null;
20703
+ const resolvedHref = profileHref ?? (registeredPath ? `#${registeredPath}/${personId}${tab ? `/${tab}` : ""}` : void 0) ?? resolveEntityAnchor(personId, "person", resolvedKind ?? void 0, tab);
20316
20704
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
20317
20705
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
20318
20706
  "button",
@@ -20347,7 +20735,7 @@ function PersonLink2({ personId, name, lookup: lookup2, profileHref, tab, size =
20347
20735
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
20348
20736
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold truncate", children: name }),
20349
20737
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mt-0.5", children: [
20350
- kind && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground capitalize", children: kind }),
20738
+ resolvedKind && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground capitalize", children: resolvedKind }),
20351
20739
  isActive !== void 0 && /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 text-[10px] ${isActive ? "text-success" : "text-muted-foreground/50"}`, children: [
20352
20740
  /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${isActive ? "bg-success" : "bg-muted-foreground/30"}` }),
20353
20741
  isActive ? "Active" : "Inactive"
@@ -20414,12 +20802,12 @@ function CrudRecordModal({
20414
20802
  description,
20415
20803
  emptyLabel
20416
20804
  }) {
20417
- const [record, setRecord] = React63.useState(null);
20418
- const [loading, setLoading] = React63.useState(false);
20419
- const [saving, setSaving] = React63.useState(false);
20420
- const [error, setError] = React63.useState(null);
20421
- const formId = React63.useId();
20422
- React63.useEffect(() => {
20805
+ const [record, setRecord] = React65.useState(null);
20806
+ const [loading, setLoading] = React65.useState(false);
20807
+ const [saving, setSaving] = React65.useState(false);
20808
+ const [error, setError] = React65.useState(null);
20809
+ const formId = React65.useId();
20810
+ React65.useEffect(() => {
20423
20811
  if (!open) return;
20424
20812
  let cancelled = false;
20425
20813
  setLoading(true);
@@ -20491,13 +20879,27 @@ function RecordPickerModal({
20491
20879
  placeholder = "Buscar\u2026",
20492
20880
  confirmLabel = "Adicionar",
20493
20881
  emptyLabel = "Nada encontrado.",
20494
- columns
20882
+ columns,
20883
+ onRefresh,
20884
+ fromCache
20495
20885
  }) {
20886
+ const t = useTranslation();
20496
20887
  const [term, setTerm] = useState("");
20497
20888
  const [items, setItems] = useState([]);
20498
20889
  const [loading, setLoading] = useState(false);
20499
20890
  const [picked, setPicked] = useState([]);
20500
20891
  const input = useRef(null);
20892
+ async function refreshNow() {
20893
+ onRefresh?.();
20894
+ setLoading(true);
20895
+ try {
20896
+ setItems(await search(term.trim()));
20897
+ } catch {
20898
+ setItems([]);
20899
+ } finally {
20900
+ setLoading(false);
20901
+ }
20902
+ }
20501
20903
  useEffect(() => {
20502
20904
  if (!open) {
20503
20905
  setTerm("");
@@ -20558,6 +20960,17 @@ function RecordPickerModal({
20558
20960
  "data-testid": "record-picker-search",
20559
20961
  className: "w-full bg-transparent py-2 text-sm outline-none"
20560
20962
  }
20963
+ ),
20964
+ onRefresh && /* @__PURE__ */ jsx(
20965
+ RefreshButton,
20966
+ {
20967
+ onRefresh: refreshNow,
20968
+ fromCache,
20969
+ label: t("crud.list.refresh"),
20970
+ labelCached: t("crud.list.refreshCached"),
20971
+ "data-testid": "record-picker-refresh",
20972
+ className: "shrink-0 rounded p-1 text-muted-foreground hover:text-foreground"
20973
+ }
20561
20974
  )
20562
20975
  ] }) }),
20563
20976
  /* @__PURE__ */ jsx(ModalBody, { className: "px-5 py-3", children: /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border", children: [
@@ -21032,10 +21445,10 @@ function useTenantPluginSettings(pluginId, defaults) {
21032
21445
  );
21033
21446
  }
21034
21447
  function useAgentSurface(contribution) {
21035
- const ref = React63.useRef(contribution);
21448
+ const ref = React65.useRef(contribution);
21036
21449
  ref.current = contribution;
21037
21450
  const id = contribution.id;
21038
- React63.useEffect(() => {
21451
+ React65.useEffect(() => {
21039
21452
  registerSurface({ id, get: () => ref.current });
21040
21453
  const names = (ref.current.tools ?? []).map((t) => t.tool.name);
21041
21454
  for (const name of names) {
@@ -21055,21 +21468,21 @@ function ConnectorsHub({ hostPluginId }) {
21055
21468
  const { t } = useTranslation$1();
21056
21469
  const { connectors, reload } = useConnectors({ includeGrants: false });
21057
21470
  const assistantAvailable = useConnectorsPanelAvailable();
21058
- const [selected, setSelected] = React63__default.useState(null);
21059
- const [adding, setAdding] = React63__default.useState(false);
21060
- const hosted = React63__default.useMemo(
21471
+ const [selected, setSelected] = React65__default.useState(null);
21472
+ const [adding, setAdding] = React65__default.useState(false);
21473
+ const hosted = React65__default.useMemo(
21061
21474
  () => (connectors ?? []).filter((c) => c.hostPluginId === hostPluginId),
21062
21475
  [connectors, hostPluginId]
21063
21476
  );
21064
- const categories = React63__default.useMemo(
21477
+ const categories = React65__default.useMemo(
21065
21478
  () => [...new Set(hosted.map((c) => c.category).filter((c) => !!c))],
21066
21479
  [hosted]
21067
21480
  );
21068
- const scoped = React63__default.useMemo(() => {
21481
+ const scoped = React65__default.useMemo(() => {
21069
21482
  const siblings = inCategories(connectors ?? [], categories).filter((c) => c.hostPluginId !== hostPluginId);
21070
21483
  return [...hosted, ...siblings];
21071
21484
  }, [connectors, hosted, categories, hostPluginId]);
21072
- React63__default.useEffect(() => {
21485
+ React65__default.useEffect(() => {
21073
21486
  if (!selected || !connectors) return;
21074
21487
  const fresh = connectors.find((c) => c.id === selected.id);
21075
21488
  if (fresh && fresh !== selected) setSelected(fresh);
@@ -21081,7 +21494,7 @@ function ConnectorsHub({ hostPluginId }) {
21081
21494
  const only = categories.length === 1 ? categoryLabel(categories[0]) : void 0;
21082
21495
  const moreLabel = only ? t("connectors.viewCategory", { category: only }) : t("connectors.viewSimilar");
21083
21496
  return /* @__PURE__ */ jsxs("div", { className: "max-w-3xl space-y-3", children: [
21084
- /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border", children: scoped.map((c) => /* @__PURE__ */ jsx(
21497
+ /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", children: scoped.map((c) => /* @__PURE__ */ jsx(
21085
21498
  ConnectorRow,
21086
21499
  {
21087
21500
  connector: c,
@@ -21141,7 +21554,7 @@ function ConnectorsHub({ hostPluginId }) {
21141
21554
  ] });
21142
21555
  }
21143
21556
  function HubSkeleton() {
21144
- return /* @__PURE__ */ jsx("div", { className: "max-w-3xl", "aria-hidden": true, children: /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-4 p-4", children: [
21557
+ return /* @__PURE__ */ jsx("div", { className: "max-w-3xl", "aria-hidden": true, children: /* @__PURE__ */ jsx("ul", { className: "fayz-glass-surface divide-y divide-border overflow-hidden rounded-card border bg-card text-card-foreground shadow-sm", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-4 p-4", children: [
21145
21558
  /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-10 shrink-0 rounded-lg" }),
21146
21559
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2 py-0.5", children: [
21147
21560
  /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-44" }),
@@ -21150,7 +21563,7 @@ function HubSkeleton() {
21150
21563
  /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24 shrink-0" })
21151
21564
  ] }, i)) }) });
21152
21565
  }
21153
- function RegistryCrudView2({ registry: registry3, basePath }) {
21566
+ function RegistryCrudView2({ registry: registry3, basePath, embedded }) {
21154
21567
  const useStore = useMemo(() => {
21155
21568
  const mockData = registry3.mockData ?? registry3.seedData ?? [];
21156
21569
  const provider = registry3.dataProvider ?? resolveDataProvider(registry3.entity, mockData);
@@ -21163,7 +21576,8 @@ function RegistryCrudView2({ registry: registry3, basePath }) {
21163
21576
  useStore,
21164
21577
  basePath,
21165
21578
  display: registry3.display === "cards" ? "cards" : "table",
21166
- readOnly: registry3.readOnly
21579
+ readOnly: registry3.readOnly,
21580
+ embedded
21167
21581
  }
21168
21582
  );
21169
21583
  }
@@ -21183,7 +21597,7 @@ function PropertiesTab({ registries, routeBase, regLabel }) {
21183
21597
  return registries[0]?.id ?? "";
21184
21598
  }
21185
21599
  const [active2, setActive] = useState(getActive);
21186
- React63__default.useEffect(() => {
21600
+ React65__default.useEffect(() => {
21187
21601
  const handler = () => setActive(getActive());
21188
21602
  window.addEventListener("hashchange", handler);
21189
21603
  return () => window.removeEventListener("hashchange", handler);
@@ -21258,7 +21672,7 @@ function PluginSettingsPanel({ title, subtitle, generalSettings, registries, cus
21258
21672
  return tabs[0]?.id ?? "";
21259
21673
  }
21260
21674
  const [activeTab, setActiveTab] = useState(getActiveFromHash);
21261
- React63__default.useEffect(() => {
21675
+ React65__default.useEffect(() => {
21262
21676
  const handler = () => setActiveTab(getActiveFromHash());
21263
21677
  window.addEventListener("hashchange", handler);
21264
21678
  return () => window.removeEventListener("hashchange", handler);
@@ -21306,6 +21720,6 @@ var renderApp = (...args) => {
21306
21720
  return renderApp$1(...args);
21307
21721
  };
21308
21722
 
21309
- export { AddConnectorButton, AddConnectorPanel, AdminProviders, AdminScaffold, AdminShell, AssistantInvite, ChatConversation, ClientOrdersTab, ConnectConsentPage, ConnectorDeliveries, ConnectorDetail, ConnectorMark, ConnectorRow, ConnectorSyncActions, ConnectorsHub, ConnectorsTab, ContactPicker, CredentialSinkMissingError, CrudCardGrid, CrudDetailPage, CrudDetailSkeleton, CrudFormPage, CrudFormSkeleton, CrudListView, CrudPage, CrudRecordModal, DeleteConfirmDialog, EntitlementGate, EntityLink, LimitGate, ModuleActionBar, MultiSelectRow, NOTE_LABEL_PREFIX, OrderDetailBlock, OrderDetailRow, OrderDetailSkeleton, OrderDetailView, OrderFormAddItemStep, OrderDetailBlock as OrderFormBlock, OrderFormItemRow, OrderFormItemsCard, OrderFormSkeleton, OrderFormView, OrderListView, PermissionGate, PersonLink2 as PersonLink, PersonTimeline, PluginRegistryManager, PluginSettingsPanel, ProjectsSettings, QuickActionsButton, RecordPickerModal, RegistryCrudView2 as RegistryCrudView, RouteErrorBoundary, RouteErrorScreen, SelectRow, SettingsGroup, SeuSiteWidget, StateChip, TextRow, ToggleRow, UpgradePrompt, WidgetSlot, navigateTo as adminNavigateTo, categoryLabel, connectorRoute, createArchetypeLookup, createClientOrdersProvider, createCrudPage, createFayzApp, createFayzProject, createCrudPage as createNativeCrudPage, createPerson, createPlaceholder, createPluginContext, createViewRouter, dedup, defaultProjectOf, defineSaas, deleteFayzProject, deriveAgentContract, exportToCSV, filterConnectors, formatActivityElapsed, formatCurrency4 as formatCurrency, getDefaultFayzProject, getFayzAgentClient, getPersonPath, getTimelineSources, listFayzProjects, needsCredentialSink, openAssistant, openConnectorsPanel, openEntity, parseViewId, publishAssistantActivity, registerAdminScaffold, registerAssistantLauncher, registerEntityPath, registerNotesTasksAdapter, registerTimelineSource, renderApp, resolveEntityAnchor, resolveEntityHref, resolveFayzAgentConnection, splitConnectorValues, submitConnection, unregisterAssistantLauncher, unregisterTimelineSource, updateFayzProject, useAdminPath, useAgentSurface, useAssistantActivity, useAssistantInviteStore, useAssistantLaunchers, useChatStore, useConnectors, useConnectorsPanelAvailable, useContextualNav, useContextualNavStore, useDelegateToAssistant, useFayzProjectsStore, useFieldRules, useHasRightRail, useModuleNavigation, useNavContribution, useNavContributionsStore, useNotesTasksBridge, usePersonTimeline, usePluginPrefs, usePublishAssistantActivity, useRegisterAssistantLauncher, useRightRailPanel, useRightRailStore, useShellRailAvailable, useTenantPluginSettings, withdrawAssistantActivity };
21723
+ export { AddConnectorButton, AddConnectorPanel, AdminProviders, AdminScaffold, AdminShell, AssistantInvite, ChatConversation, ClientOrdersTab, ConnectConsentPage, ConnectorDeliveries, ConnectorDetail, ConnectorMark, ConnectorRow, ConnectorSyncActions, ConnectorsHub, ConnectorsTab, ContactPicker, CredentialSinkMissingError, CrudCardGrid, CrudDetailPage, CrudDetailSkeleton, CrudFormPage, CrudFormSkeleton, CrudListView, CrudPage, CrudRecordModal, DeleteConfirmDialog, EntitlementGate, EntityLink, LimitGate, ModuleActionBar, MultiSelectRow, NOTE_LABEL_PREFIX, OrderDetailBlock, OrderDetailRow, OrderDetailSkeleton, OrderDetailView, OrderFormAddItemStep, OrderDetailBlock as OrderFormBlock, OrderFormItemRow, OrderFormItemsCard, OrderFormSkeleton, OrderFormView, OrderListView, PermissionGate, PersonLink2 as PersonLink, PersonTimeline, PluginRegistryManager, PluginSettingsPanel, ProjectsSettings, QuickActionsButton, RecordPickerModal, RegistryCrudView2 as RegistryCrudView, RouteErrorBoundary, RouteErrorScreen, SelectRow, SettingsGroup, SeuSiteWidget, StateChip, TextRow, ToggleRow, UpgradePrompt, WidgetSlot, navigateTo as adminNavigateTo, categoryLabel, connectorRoute, createArchetypeLookup, createClientOrdersProvider, createCrudPage, createFayzApp, createFayzProject, createCrudPage as createNativeCrudPage, createPerson, createPlaceholder, createPluginContext, createViewRouter, dedup, defaultProjectOf, defineSaas, deleteFayzProject, deriveAgentContract, exportToCSV, filterConnectors, formatActivityElapsed, formatCurrency4 as formatCurrency, getDefaultFayzProject, getFayzAgentClient, getPersonLookup, getPersonPath, getTimelineSources, listFayzProjects, needsCredentialSink, openAssistant, openConnectorsPanel, openEntity, parseViewId, publishAssistantActivity, registerAdminScaffold, registerAssistantLauncher, registerEntityPath, registerNotesTasksAdapter, registerPersonLookup, registerTimelineSource, renderApp, resolveEntityAnchor, resolveEntityHref, resolveFayzAgentConnection, splitConnectorValues, submitConnection, unregisterAssistantLauncher, unregisterTimelineSource, updateFayzProject, useAdminPath, useAgentSurface, useAssistantActivity, useAssistantInviteStore, useAssistantLaunchers, useChatStore, useConnectors, useConnectorsPanelAvailable, useContextualNav, useContextualNavStore, useDelegateToAssistant, useFayzProjectsStore, useFieldRules, useHasRightRail, useModuleNavigation, useNavContribution, useNavContributionsStore, useNotesTasksBridge, usePersonTimeline, usePluginPrefs, usePublishAssistantActivity, useRegisterAssistantLauncher, useRightRailPanel, useRightRailStore, useShellRailAvailable, useTenantPluginSettings, useWidgetSurface, withdrawAssistantActivity };
21310
21724
  //# sourceMappingURL=index.js.map
21311
21725
  //# sourceMappingURL=index.js.map