@fayz-ai/saas 0.8.0 → 0.8.2

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 (236) hide show
  1. package/dist/access/context.d.ts +47 -0
  2. package/dist/access/context.d.ts.map +1 -0
  3. package/dist/access/headless.d.ts +21 -0
  4. package/dist/access/headless.d.ts.map +1 -0
  5. package/dist/access/index.d.ts +12 -0
  6. package/dist/access/index.d.ts.map +1 -0
  7. package/dist/access/limits-registry.d.ts +33 -0
  8. package/dist/access/limits-registry.d.ts.map +1 -0
  9. package/dist/access/module-nav.d.ts +15 -0
  10. package/dist/access/module-nav.d.ts.map +1 -0
  11. package/dist/access/resolver.d.ts +2 -0
  12. package/dist/access/resolver.d.ts.map +1 -0
  13. package/dist/access/types.d.ts +43 -0
  14. package/dist/access/types.d.ts.map +1 -0
  15. package/dist/access/upgrade-modal-store.d.ts +23 -0
  16. package/dist/access/upgrade-modal-store.d.ts.map +1 -0
  17. package/dist/app/AdminShell.d.ts +3 -0
  18. package/dist/app/AdminShell.d.ts.map +1 -1
  19. package/dist/app/admin-app.d.ts +3 -1
  20. package/dist/app/admin-app.d.ts.map +1 -1
  21. package/dist/app/config.d.ts +47 -2
  22. package/dist/app/config.d.ts.map +1 -1
  23. package/dist/app/core-entities.d.ts +25 -0
  24. package/dist/app/core-entities.d.ts.map +1 -0
  25. package/dist/app/derive-contract.d.ts +8 -0
  26. package/dist/app/derive-contract.d.ts.map +1 -0
  27. package/dist/app/scaffold.d.ts.map +1 -1
  28. package/dist/archetype-lookup.d.ts.map +1 -1
  29. package/dist/billing/index.cjs +6 -2
  30. package/dist/billing/index.d.ts +2 -0
  31. package/dist/billing/index.d.ts.map +1 -1
  32. package/dist/billing/index.js +1 -1
  33. package/dist/billing/plan-badge.d.ts +17 -0
  34. package/dist/billing/plan-badge.d.ts.map +1 -0
  35. package/dist/billing/store.d.ts +6 -1
  36. package/dist/billing/store.d.ts.map +1 -1
  37. package/dist/{chunk-3M25GRRU.js → chunk-I3DY47N5.js} +3 -4
  38. package/dist/chunk-I3DY47N5.js.map +1 -0
  39. package/dist/chunk-NNFGMUF2.cjs +39 -0
  40. package/dist/chunk-NNFGMUF2.cjs.map +1 -0
  41. package/dist/{chunk-4XTQ4V5L.cjs → chunk-SAZLW54Y.cjs} +16 -12
  42. package/dist/chunk-SAZLW54Y.cjs.map +1 -0
  43. package/dist/{chunk-IPWCHJPE.cjs → chunk-VKYZACG5.cjs} +5 -6
  44. package/dist/chunk-VKYZACG5.cjs.map +1 -0
  45. package/dist/chunk-VN5NTK26.js +36 -0
  46. package/dist/chunk-VN5NTK26.js.map +1 -0
  47. package/dist/{chunk-XNWCGJYH.js → chunk-VTEP2AQX.js} +15 -11
  48. package/dist/chunk-VTEP2AQX.js.map +1 -0
  49. package/dist/components/shared/ContactPicker.d.ts +92 -0
  50. package/dist/components/shared/ContactPicker.d.ts.map +1 -0
  51. package/dist/components/shared/PersonLink.d.ts +12 -1
  52. package/dist/components/shared/PersonLink.d.ts.map +1 -1
  53. package/dist/components/shared/create-person.d.ts +20 -0
  54. package/dist/components/shared/create-person.d.ts.map +1 -0
  55. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  56. package/dist/crud/CrudFormPage.d.ts.map +1 -1
  57. package/dist/crud/CrudListView.d.ts.map +1 -1
  58. package/dist/crud/CrudPage.d.ts.map +1 -1
  59. package/dist/crud/archetypes/AddressesTab.d.ts +21 -0
  60. package/dist/crud/archetypes/AddressesTab.d.ts.map +1 -0
  61. package/dist/crud/archetypes/PersonDetailTabs.d.ts +2 -1
  62. package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
  63. package/dist/crud/createCrudPage.d.ts.map +1 -1
  64. package/dist/crud/index.d.ts +1 -0
  65. package/dist/crud/index.d.ts.map +1 -1
  66. package/dist/index.cjs +4300 -722
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.ts +17 -2
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4080 -542
  71. package/dist/index.js.map +1 -1
  72. package/dist/org/index.cjs +11 -11
  73. package/dist/org/index.js +2 -2
  74. package/dist/org/store.d.ts.map +1 -1
  75. package/dist/permissions/context.d.ts +2 -0
  76. package/dist/permissions/context.d.ts.map +1 -1
  77. package/dist/permissions/index.cjs +7 -7
  78. package/dist/permissions/index.js +1 -1
  79. package/dist/shell/components/auth/ProtectedRoute.d.ts.map +1 -1
  80. package/dist/shell/components/billing/SoftLimitBanner.d.ts +5 -0
  81. package/dist/shell/components/billing/SoftLimitBanner.d.ts.map +1 -0
  82. package/dist/shell/components/billing/SubscriptionPage.d.ts +5 -0
  83. package/dist/shell/components/billing/SubscriptionPage.d.ts.map +1 -0
  84. package/dist/shell/components/billing/UpgradeModal.d.ts +3 -0
  85. package/dist/shell/components/billing/UpgradeModal.d.ts.map +1 -0
  86. package/dist/shell/components/billing/UpgradePrompt.d.ts +10 -0
  87. package/dist/shell/components/billing/UpgradePrompt.d.ts.map +1 -0
  88. package/dist/shell/components/billing/access-contract.d.ts +18 -0
  89. package/dist/shell/components/billing/access-contract.d.ts.map +1 -0
  90. package/dist/shell/components/billing/gates.d.ts +14 -0
  91. package/dist/shell/components/billing/gates.d.ts.map +1 -0
  92. package/dist/shell/components/billing/index.d.ts +9 -2
  93. package/dist/shell/components/billing/index.d.ts.map +1 -1
  94. package/dist/shell/components/chat/ChatFab.d.ts +3 -1
  95. package/dist/shell/components/chat/ChatFab.d.ts.map +1 -1
  96. package/dist/shell/components/chat/ChatPanel.d.ts +3 -1
  97. package/dist/shell/components/chat/ChatPanel.d.ts.map +1 -1
  98. package/dist/shell/components/chat/ConfirmActionCard.d.ts +13 -0
  99. package/dist/shell/components/chat/ConfirmActionCard.d.ts.map +1 -0
  100. package/dist/shell/components/chat/markdown.d.ts +5 -0
  101. package/dist/shell/components/chat/markdown.d.ts.map +1 -0
  102. package/dist/shell/components/organization/TeamTab.d.ts.map +1 -1
  103. package/dist/shell/components/organization/TenantOnboarding.d.ts.map +1 -1
  104. package/dist/shell/components/organization/index.d.ts +0 -1
  105. package/dist/shell/components/organization/index.d.ts.map +1 -1
  106. package/dist/shell/components/settings/ConnectedCompanySettings.d.ts.map +1 -1
  107. package/dist/shell/components/settings/ConnectedFieldRulesSettings.d.ts.map +1 -1
  108. package/dist/shell/components/settings/ConnectedSecuritySettings.d.ts.map +1 -1
  109. package/dist/shell/components/settings/LocationsCrudPage.d.ts +1 -1
  110. package/dist/shell/components/settings/LocationsCrudPage.d.ts.map +1 -1
  111. package/dist/shell/components/settings/SettingsPage.d.ts +23 -2
  112. package/dist/shell/components/settings/SettingsPage.d.ts.map +1 -1
  113. package/dist/shell/hooks/index.d.ts +3 -2
  114. package/dist/shell/hooks/index.d.ts.map +1 -1
  115. package/dist/shell/hooks/useAITools.d.ts +2 -0
  116. package/dist/shell/hooks/useAITools.d.ts.map +1 -1
  117. package/dist/shell/hooks/useChat.d.ts +22 -0
  118. package/dist/shell/hooks/useChat.d.ts.map +1 -1
  119. package/dist/shell/hooks/useSystemPermission.d.ts +3 -0
  120. package/dist/shell/hooks/useSystemPermission.d.ts.map +1 -0
  121. package/dist/shell/hooks/useTenantPluginSettings.d.ts +12 -0
  122. package/dist/shell/hooks/useTenantPluginSettings.d.ts.map +1 -0
  123. package/dist/shell/lib/ai-tool-handlers.d.ts +119 -0
  124. package/dist/shell/lib/ai-tool-handlers.d.ts.map +1 -0
  125. package/dist/shell/lib/core-ai-tools.d.ts +14 -0
  126. package/dist/shell/lib/core-ai-tools.d.ts.map +1 -1
  127. package/dist/shell/lib/create-client-orders-provider.d.ts.map +1 -1
  128. package/dist/shell/lib/fayz-agent.d.ts +23 -0
  129. package/dist/shell/lib/fayz-agent.d.ts.map +1 -0
  130. package/dist/shell/lib/i18n.d.ts.map +1 -1
  131. package/dist/shell/stores/chat.store.d.ts +69 -0
  132. package/dist/shell/stores/chat.store.d.ts.map +1 -1
  133. package/dist/shell/types/billing.d.ts +7 -0
  134. package/dist/shell/types/billing.d.ts.map +1 -1
  135. package/dist/shell/types/crud.d.ts +1 -1
  136. package/dist/shell/types/crud.d.ts.map +1 -1
  137. package/dist/shell/types/plugins.d.ts +2 -45
  138. package/dist/shell/types/plugins.d.ts.map +1 -1
  139. package/dist/stores/createCrudStore.d.ts.map +1 -1
  140. package/package.json +15 -12
  141. package/src/access/context.tsx +228 -0
  142. package/src/access/headless.ts +71 -0
  143. package/src/access/index.ts +27 -0
  144. package/src/access/limits-registry.ts +86 -0
  145. package/src/access/module-nav.test.ts +78 -0
  146. package/src/access/module-nav.ts +82 -0
  147. package/src/access/resolver.test.ts +143 -0
  148. package/src/access/resolver.ts +5 -0
  149. package/src/access/types.ts +47 -0
  150. package/src/access/upgrade-modal-store.ts +30 -0
  151. package/src/app/AdminShell.tsx +451 -68
  152. package/src/app/admin-app.tsx +46 -9
  153. package/src/app/config.ts +54 -1
  154. package/src/app/core-entities.ts +110 -0
  155. package/src/app/derive-contract.test.ts +155 -0
  156. package/src/app/derive-contract.ts +214 -0
  157. package/src/app/scaffold.tsx +4 -0
  158. package/src/archetype-lookup.ts +8 -2
  159. package/src/billing/index.ts +2 -0
  160. package/src/billing/plan-badge.ts +32 -0
  161. package/src/billing/store.ts +8 -1
  162. package/src/components/shared/ContactPicker.tsx +394 -0
  163. package/src/components/shared/PersonLink.tsx +11 -3
  164. package/src/components/shared/create-person.ts +74 -0
  165. package/src/crud/CrudDetailPage.tsx +8 -1
  166. package/src/crud/CrudFormPage.tsx +124 -6
  167. package/src/crud/CrudListView.tsx +16 -1
  168. package/src/crud/CrudPage.tsx +27 -5
  169. package/src/crud/DeleteConfirmDialog.tsx +1 -1
  170. package/src/crud/archetypes/AddressesTab.tsx +387 -0
  171. package/src/crud/archetypes/PersonDetailTabs.tsx +5 -0
  172. package/src/crud/createCrudPage.tsx +2 -0
  173. package/src/crud/index.ts +6 -0
  174. package/src/env.d.ts +7 -0
  175. package/src/index.ts +49 -1
  176. package/src/org/store.ts +9 -1
  177. package/src/permissions/context.tsx +29 -15
  178. package/src/shell/components/auth/ProtectedRoute.tsx +12 -2
  179. package/src/shell/components/billing/SoftLimitBanner.tsx +81 -0
  180. package/src/shell/components/billing/SubscriptionPage.tsx +372 -0
  181. package/src/shell/components/billing/UpgradeModal.tsx +102 -0
  182. package/src/shell/components/billing/UpgradePrompt.tsx +229 -0
  183. package/src/shell/components/billing/access-contract.ts +64 -0
  184. package/src/shell/components/billing/gates.tsx +62 -0
  185. package/src/shell/components/billing/index.ts +18 -2
  186. package/src/shell/components/chat/ChatFab.tsx +4 -2
  187. package/src/shell/components/chat/ChatPanel.tsx +174 -23
  188. package/src/shell/components/chat/ConfirmActionCard.tsx +57 -0
  189. package/src/shell/components/chat/markdown.tsx +102 -0
  190. package/src/shell/components/organization/TeamTab.tsx +21 -4
  191. package/src/shell/components/organization/TenantOnboarding.tsx +3 -1
  192. package/src/shell/components/organization/index.ts +0 -1
  193. package/src/shell/components/settings/ConnectedCompanySettings.tsx +4 -1
  194. package/src/shell/components/settings/ConnectedFieldRulesSettings.tsx +3 -1
  195. package/src/shell/components/settings/ConnectedSecuritySettings.tsx +3 -1
  196. package/src/shell/components/settings/LocationsCrudPage.ts +6 -1
  197. package/src/shell/components/settings/SettingsPage.tsx +63 -21
  198. package/src/shell/hooks/index.ts +3 -2
  199. package/src/shell/hooks/useAITools.ts +27 -23
  200. package/src/shell/hooks/useChat.ts +381 -16
  201. package/src/shell/hooks/useSystemPermission.ts +23 -0
  202. package/src/shell/hooks/useTenantPluginSettings.ts +127 -0
  203. package/src/shell/lib/ai-tool-handlers.ts +910 -0
  204. package/src/shell/lib/core-ai-tools.ts +234 -2
  205. package/src/shell/lib/create-client-orders-provider.ts +24 -2
  206. package/src/shell/lib/fayz-agent.ts +56 -0
  207. package/src/shell/lib/i18n.ts +71 -0
  208. package/src/shell/stores/chat.store.ts +118 -1
  209. package/src/shell/styles.css +30 -0
  210. package/src/shell/types/billing.ts +8 -0
  211. package/src/shell/types/crud.ts +1 -1
  212. package/src/shell/types/plugins.ts +13 -50
  213. package/src/stores/createCrudStore.ts +9 -0
  214. package/dist/chunk-3M25GRRU.js.map +0 -1
  215. package/dist/chunk-4XTQ4V5L.cjs.map +0 -1
  216. package/dist/chunk-5GYQK5IW.js +0 -21
  217. package/dist/chunk-5GYQK5IW.js.map +0 -1
  218. package/dist/chunk-IPWCHJPE.cjs.map +0 -1
  219. package/dist/chunk-KYID2MR6.cjs +0 -23
  220. package/dist/chunk-KYID2MR6.cjs.map +0 -1
  221. package/dist/chunk-XNWCGJYH.js.map +0 -1
  222. package/dist/shell/components/billing/BillingPage.d.ts +0 -7
  223. package/dist/shell/components/billing/BillingPage.d.ts.map +0 -1
  224. package/dist/shell/components/billing/PaywallGate.d.ts +0 -11
  225. package/dist/shell/components/billing/PaywallGate.d.ts.map +0 -1
  226. package/dist/shell/components/organization/PermissionGate.d.ts +0 -11
  227. package/dist/shell/components/organization/PermissionGate.d.ts.map +0 -1
  228. package/dist/shell/hooks/useBilling.d.ts +0 -14
  229. package/dist/shell/hooks/useBilling.d.ts.map +0 -1
  230. package/dist/shell/hooks/usePermission.d.ts +0 -16
  231. package/dist/shell/hooks/usePermission.d.ts.map +0 -1
  232. package/src/shell/components/billing/BillingPage.tsx +0 -56
  233. package/src/shell/components/billing/PaywallGate.tsx +0 -80
  234. package/src/shell/components/organization/PermissionGate.tsx +0 -16
  235. package/src/shell/hooks/useBilling.ts +0 -151
  236. package/src/shell/hooks/usePermission.ts +0 -101
@@ -7,11 +7,31 @@ import { ConnectedUserProfile } from './ConnectedUserProfile'
7
7
  import { ConnectedSecuritySettings } from './ConnectedSecuritySettings'
8
8
  import { ConnectedBrandingSettings } from './ConnectedBrandingSettings'
9
9
 
10
- interface SettingsTab {
10
+ export interface SettingsTab {
11
11
  id: string
12
12
  label: string
13
13
  icon?: React.ReactNode
14
+ /** Lucide icon name — used when these tabs are rendered in the app sidebar
15
+ * (which resolves icons by name) instead of in the in-page nav. */
16
+ iconName?: string
14
17
  component: React.ReactNode
18
+ /** Plugin-contributed tab — draws the core/plugins divider. */
19
+ isPlugin?: boolean
20
+ }
21
+
22
+ /** The core (non-plugin, non-org) settings tabs. Exported so the shell can
23
+ * build the same list for the contextual /settings sidebar. */
24
+ export function useCoreSettingsTabs({
25
+ showCompany = true,
26
+ showBranding = true,
27
+ }: { showCompany?: boolean; showBranding?: boolean } = {}): SettingsTab[] {
28
+ const { t } = useTranslation()
29
+ return React.useMemo(() => [
30
+ ...(showCompany ? [{ id: 'general', label: t('settings.general'), icon: <Building2 className="h-4 w-4" />, iconName: 'Building2', component: <ConnectedCompanySettings /> }] : []),
31
+ { id: 'profile', label: t('settings.profile'), icon: <User className="h-4 w-4" />, iconName: 'User', component: <ConnectedUserProfile /> },
32
+ { id: 'security', label: t('settings.security'), icon: <Shield className="h-4 w-4" />, iconName: 'Shield', component: <ConnectedSecuritySettings /> },
33
+ ...(showBranding ? [{ id: 'branding', label: t('settings.branding'), icon: <Palette className="h-4 w-4" />, iconName: 'Palette', component: <ConnectedBrandingSettings /> }] : []),
34
+ ], [t, showCompany, showBranding])
15
35
  }
16
36
 
17
37
  interface SettingsPageProps {
@@ -28,6 +48,16 @@ interface SettingsPageProps {
28
48
  /** Show the branding ("Identidade Visual") tab. Default: true. Ignored when
29
49
  * `tabs` is supplied explicitly. */
30
50
  showBranding?: boolean
51
+ /** The shell already renders this nav (contextual /settings sidebar), so the
52
+ * desktop in-page nav column is dropped and the content spans the page.
53
+ * Mobile keeps its master/detail list — the sidebar is a drawer there. */
54
+ hideNav?: boolean
55
+ /** Controlled active tab. When provided (the shell owns the settings nav and
56
+ * drives it from the URL), this — not the internal hashchange state — decides
57
+ * which tab's content renders, so navigating between tabs from the shell
58
+ * sidebar reliably swaps the content. Uncontrolled (undefined) keeps the
59
+ * self-managed behaviour used by standalone <SettingsPage> mounts. */
60
+ activeTabId?: string
31
61
  }
32
62
 
33
63
  export function SettingsPage({
@@ -39,19 +69,21 @@ export function SettingsPage({
39
69
  afterContent,
40
70
  showCompany = true,
41
71
  showBranding = true,
72
+ hideNav = false,
73
+ activeTabId,
42
74
  }: SettingsPageProps) {
43
75
  const { t } = useTranslation()
44
76
 
45
- const defaultTabs: SettingsTab[] = React.useMemo(() => [
46
- ...(showCompany ? [{ id: 'general', label: t('settings.general'), icon: <Building2 className="h-4 w-4" />, component: <ConnectedCompanySettings /> }] : []),
47
- { id: 'profile', label: t('settings.profile'), icon: <User className="h-4 w-4" />, component: <ConnectedUserProfile /> },
48
- { id: 'security', label: t('settings.security'), icon: <Shield className="h-4 w-4" />, component: <ConnectedSecuritySettings /> },
49
- ...(showBranding ? [{ id: 'branding', label: t('settings.branding'), icon: <Palette className="h-4 w-4" />, component: <ConnectedBrandingSettings /> }] : []),
50
- ], [t, showCompany, showBranding])
77
+ const defaultTabs = useCoreSettingsTabs({ showCompany, showBranding })
51
78
 
52
79
  const baseTabs = tabs ?? defaultTabs
53
80
  const resolvedTabs = extraTabs ? [...baseTabs, ...extraTabs] : baseTabs
54
81
 
82
+ // Controlled mode: the shell owns the settings nav (contextual sidebar) and
83
+ // passes the active tab parsed from the URL. In that mode the URL — not the
84
+ // internal hashchange state below — is the single source of truth.
85
+ const controlled = activeTabId !== undefined
86
+
55
87
  // Detect active tab from URL: /settings/financial/... → 'financial'
56
88
  function getTabFromHash(): string | null {
57
89
  const hash = typeof window !== 'undefined' ? window.location.hash.slice(1) || '/' : '/'
@@ -79,12 +111,15 @@ export function SettingsPage({
79
111
  // Desktop always shows a selected tab; when none is chosen yet (landing on
80
112
  // /settings), default to the first tab — 'general'. Mobile keeps its
81
113
  // list-first master/detail behaviour (driven by the raw activeTab below).
82
- const desktopTab = activeTab ?? resolvedTabs[0]?.id ?? null
114
+ // Controlled mode uses the shell-supplied tab instead of internal state.
115
+ const effectiveTab = controlled ? activeTabId ?? null : activeTab
116
+ const desktopTab = effectiveTab ?? resolvedTabs[0]?.id ?? null
83
117
  const activeContent = desktopTab ? resolvedTabs.find((t) => t.id === desktopTab)?.component : null
84
- const activeLabel = activeTab ? resolvedTabs.find((t) => t.id === activeTab)?.label : null
118
+ const desktopLabel = desktopTab ? resolvedTabs.find((t) => t.id === desktopTab)?.label : null
85
119
 
86
- // On mobile: show list OR content. On desktop: show both.
87
- const showingContent = activeTab !== null
120
+ // On mobile: show list OR content. On desktop: show both. In controlled mode
121
+ // the shell drawer holds the nav list, so SettingsPage always shows content.
122
+ const showingContent = controlled ? true : activeTab !== null
88
123
 
89
124
  const handleSelectTab = (tabId: string) => {
90
125
  setActiveTab(tabId)
@@ -100,7 +135,7 @@ export function SettingsPage({
100
135
  <ul className="space-y-0.5">
101
136
  {resolvedTabs.map((tab, i) => (
102
137
  <React.Fragment key={tab.id}>
103
- {(tab as any).isPlugin && (i === 0 || !(resolvedTabs[i - 1] as any)?.isPlugin) && (
138
+ {tab.isPlugin && (i === 0 || !resolvedTabs[i - 1]?.isPlugin) && (
104
139
  <li className="py-2">
105
140
  <div className="border-t" />
106
141
  <p className="px-3 pt-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">{t('common.plugins')}</p>
@@ -129,8 +164,9 @@ export function SettingsPage({
129
164
 
130
165
  return (
131
166
  <div className={cn('space-y-6', className)}>
132
- {/* Mobile: back button + tab title when viewing content */}
133
- {showingContent && (
167
+ {/* Mobile: back button + tab title when viewing content. Skipped in
168
+ controlled mode — the shell drawer owns the settings list + back. */}
169
+ {!controlled && showingContent && (
134
170
  <button
135
171
  onClick={handleBack}
136
172
  className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors md:hidden"
@@ -140,19 +176,25 @@ export function SettingsPage({
140
176
  </button>
141
177
  )}
142
178
 
143
- {/* Header — hidden on mobile when viewing content */}
179
+ {/* Header — hidden on mobile when viewing content. With the nav in the
180
+ shell sidebar the page title is the selected section, not "Settings"
181
+ (the shell top bar already says that). */}
144
182
  <div className={cn(showingContent && 'hidden md:block')}>
145
- <h1 className="text-2xl font-bold tracking-tight">{t('settings.title')}</h1>
146
- <p className="text-sm text-muted-foreground">
147
- {t('settings.subtitle')}
148
- </p>
183
+ <h1 className="text-2xl font-bold tracking-tight">
184
+ {hideNav ? desktopLabel ?? t('settings.title') : t('settings.title')}
185
+ </h1>
186
+ {!hideNav && (
187
+ <p className="text-sm text-muted-foreground">
188
+ {t('settings.subtitle')}
189
+ </p>
190
+ )}
149
191
  </div>
150
192
 
151
193
  {beforeContent}
152
194
 
153
- {/* Desktop: side-by-side layout */}
195
+ {/* Desktop: side-by-side layout, or content-only when the shell owns the nav */}
154
196
  <div className="hidden md:flex md:gap-6">
155
- <nav className="w-52 shrink-0">{renderNav(desktopTab)}</nav>
197
+ {!hideNav && <nav className="w-52 shrink-0">{renderNav(desktopTab)}</nav>}
156
198
  <div className="min-w-0 flex-1">{activeContent ?? resolvedTabs[0]?.component}</div>
157
199
  </div>
158
200
 
@@ -1,8 +1,7 @@
1
1
  export { useAuth } from './useAuth'
2
2
  export { useTenant } from './useTenant'
3
- export { usePermission } from './usePermission'
3
+ export { useSystemPermission } from './useSystemPermission'
4
4
  export { useLayout } from './useLayout'
5
- export { useBilling } from './useBilling'
6
5
  export { useNotifications } from './useNotifications'
7
6
  export { useTranslation } from './useTranslation'
8
7
  export { useFormatters } from './useFormatters'
@@ -15,4 +14,6 @@ export { useModuleNavigation } from './useModuleNavigation'
15
14
  export type { NavDirection, DepthMap } from './useModuleNavigation'
16
15
  export { usePluginPrefs, getPluginPref, setPluginPref } from './usePluginPrefs'
17
16
  export type { PluginPrefs } from './usePluginPrefs'
17
+ export { useTenantPluginSettings } from './useTenantPluginSettings'
18
+ export type { TenantPluginSettings } from './useTenantPluginSettings'
18
19
  export { useFieldRules } from './useFieldRules'
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react'
2
2
  import { usePluginRuntimeOptional } from '../lib/plugins'
3
- import { coreAITools, generateRegistryTools, formatToolSignature } from '../lib/core-ai-tools'
3
+ import { useAccessOptional } from '../../access/context'
4
+ import { getAllEntities } from '@fayz-ai/core'
5
+ import { coreAITools, buildDataPrimitiveTools, formatToolSignature } from '../lib/core-ai-tools'
4
6
  import type { PluginAITool, AIToolSuggestion } from '../types/plugins'
5
7
 
6
8
  export interface ResolvedSuggestion extends AIToolSuggestion {
@@ -49,40 +51,42 @@ function detectActivePluginId(runtime: ReturnType<typeof usePluginRuntimeOptiona
49
51
 
50
52
  export function useAITools(): {
51
53
  tools: PluginAITool[]
54
+ /** Plugin owning the current route, or null on non-plugin pages. */
55
+ activePluginId: string | null
52
56
  suggestions: ResolvedSuggestion[]
53
57
  /** Suggestions from the currently active plugin only (empty on non-plugin pages) */
54
58
  contextualSuggestions: ResolvedSuggestion[]
55
59
  toolGroups: ResolvedToolGroup[]
56
60
  } {
57
61
  const runtime = usePluginRuntimeOptional()
62
+ const access = useAccessOptional()
58
63
 
59
64
  return React.useMemo(() => {
60
65
  const hasPermission = runtime?.context.hasPermission
66
+ const can = access.can
61
67
  const verticalId = runtime?.context.tenant?.verticalId
62
68
  const activePluginId = detectActivePluginId(runtime)
63
69
 
64
- // Collect all tools: core + plugin-declared + auto-generated from registries
65
- const allTools: PluginAITool[] = [...coreAITools]
66
-
67
- if (runtime) {
68
- allTools.push(...runtime.aiTools)
69
-
70
- // Auto-generate registry tools for active plugins
71
- for (const [pluginId, registries] of runtime.registries) {
72
- const autoTools = generateRegistryTools(pluginId, registries)
73
- // Only add auto-tools that weren't explicitly declared
74
- const declaredIds = new Set(runtime.aiTools.map((t) => t.id))
75
- for (const tool of autoTools) {
76
- if (!declaredIds.has(tool.id)) {
77
- allTools.push(tool)
78
- }
79
- }
80
- }
81
- }
82
-
83
- // Filter by permission
70
+ // Collect all tools: core + the TWO data primitives (searchRecords/
71
+ // queryData over every CRUD entity and plugin registry — the target is a
72
+ // parameter, its read permission checked per call) + plugin-declared.
73
+ const allTools: PluginAITool[] = [
74
+ ...coreAITools,
75
+ ...buildDataPrimitiveTools({
76
+ entities: getAllEntities(),
77
+ registries: runtime?.registries ?? new Map(),
78
+ queryEntities: runtime?.activePlugins.flatMap((p) => p.queryEntities ?? []) ?? [],
79
+ }),
80
+ ]
81
+ if (runtime) allTools.push(...runtime.aiTools)
82
+
83
+ // Filter by role × plan. UX only — the REAL authorization runs server-side
84
+ // (broker catalog filter) and in the guarded executor (checkAccess/
85
+ // guardLimit); hiding here just keeps the visible catalog honest. Falls
86
+ // back to the runtime's role-only check when no AccessProvider is mounted.
84
87
  const tools = allTools.filter((tool) => {
85
88
  if (!tool.permission) return true
89
+ if (can) return can(tool.permission.feature, tool.permission.action).allowed
86
90
  if (!hasPermission) return true
87
91
  return hasPermission(tool.permission)
88
92
  })
@@ -134,6 +138,6 @@ export function useAITools(): {
134
138
  toolGroups.push({ category, tools: groupTools })
135
139
  }
136
140
 
137
- return { tools, suggestions, contextualSuggestions, toolGroups }
138
- }, [runtime])
141
+ return { tools, activePluginId, suggestions, contextualSuggestions, toolGroups }
142
+ }, [runtime, access.can])
139
143
  }