@carlonicora/nextjs-jsonapi 3.24.2 → 3.26.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 (220) hide show
  1. package/dist/{AssistantMessageInterface-1Kbz8RVs.d.ts → AssistantMessageInterface-CubxuBIJ.d.ts} +1 -1
  2. package/dist/{AssistantMessageInterface-CoxJyuFM.d.mts → AssistantMessageInterface-sHjI2zPN.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-35D4R7HJ.js → BlockNoteEditor-4EIKMNY3.js} +31 -21
  4. package/dist/BlockNoteEditor-4EIKMNY3.js.map +1 -0
  5. package/dist/{BlockNoteEditor-65HR6ULV.mjs → BlockNoteEditor-AXYVDDXM.mjs} +16 -6
  6. package/dist/{BlockNoteEditor-65HR6ULV.mjs.map → BlockNoteEditor-AXYVDDXM.mjs.map} +1 -1
  7. package/dist/{BlockNoteViewer-QDXDVYEM.js → BlockNoteViewer-7BFNTN4E.js} +4 -4
  8. package/dist/{BlockNoteViewer-QDXDVYEM.js.map → BlockNoteViewer-7BFNTN4E.js.map} +1 -1
  9. package/dist/{BlockNoteViewer-KDKGUIG5.mjs → BlockNoteViewer-KRTAFTUX.mjs} +3 -3
  10. package/dist/{HandbookSectionInterface-OePKPbqN.d.ts → HandbookSectionInterface-Ck1wZJ81.d.ts} +1 -1
  11. package/dist/{HandbookSectionInterface-BYsmnPEz.d.mts → HandbookSectionInterface-DgYH--Ik.d.mts} +1 -1
  12. package/dist/JsonApiRequest-TE5WD3WS.js +26 -0
  13. package/dist/{JsonApiRequest-G625QAH5.js.map → JsonApiRequest-TE5WD3WS.js.map} +1 -1
  14. package/dist/{JsonApiRequest-RRMNV7YE.mjs → JsonApiRequest-UHOWNI3O.mjs} +2 -2
  15. package/dist/analytics.modules-CfczU12P.d.ts +25 -0
  16. package/dist/analytics.modules-pzGwMASI.d.mts +25 -0
  17. package/dist/{auth.interface-BC1jp_ni.d.ts → auth.interface-BzI4onI_.d.ts} +1 -1
  18. package/dist/{auth.interface-dR2-6gPM.d.mts → auth.interface-Df4vXbNz.d.mts} +1 -1
  19. package/dist/billing/index.js +402 -402
  20. package/dist/billing/index.mjs +4 -4
  21. package/dist/{chunk-YXRVPEEJ.js → chunk-26MQIJNB.js} +1 -1
  22. package/dist/{chunk-YXRVPEEJ.js.map → chunk-26MQIJNB.js.map} +1 -1
  23. package/dist/{chunk-T76WLDE5.mjs → chunk-37DMFVLN.mjs} +1 -1
  24. package/dist/{chunk-3AR4KA5D.js → chunk-4Z45NYPR.js} +13 -13
  25. package/dist/{chunk-3AR4KA5D.js.map → chunk-4Z45NYPR.js.map} +1 -1
  26. package/dist/chunk-7B4E42R5.mjs +280 -0
  27. package/dist/chunk-7B4E42R5.mjs.map +1 -0
  28. package/dist/{chunk-2XCUAHQT.js → chunk-ABVTYQIJ.js} +1563 -1336
  29. package/dist/chunk-ABVTYQIJ.js.map +1 -0
  30. package/dist/{chunk-U7QVISQS.js → chunk-CAKOFJZD.js} +450 -40
  31. package/dist/chunk-CAKOFJZD.js.map +1 -0
  32. package/dist/{chunk-IBVKPLHT.mjs → chunk-E3I4MT53.mjs} +422 -12
  33. package/dist/chunk-E3I4MT53.mjs.map +1 -0
  34. package/dist/chunk-GHFMKH3M.js +280 -0
  35. package/dist/chunk-GHFMKH3M.js.map +1 -0
  36. package/dist/chunk-GKDHYB2R.js +110 -0
  37. package/dist/chunk-GKDHYB2R.js.map +1 -0
  38. package/dist/{chunk-ZJI5ZZW5.mjs → chunk-Q6J2ICKO.mjs} +3 -3
  39. package/dist/{chunk-4O7R2Z2O.mjs → chunk-QWZAGQWJ.mjs} +2175 -1948
  40. package/dist/chunk-QWZAGQWJ.mjs.map +1 -0
  41. package/dist/chunk-U65JPKO3.mjs +110 -0
  42. package/dist/chunk-U65JPKO3.mjs.map +1 -0
  43. package/dist/client/index.d.mts +6 -5
  44. package/dist/client/index.d.ts +6 -5
  45. package/dist/client/index.js +5 -5
  46. package/dist/client/index.js.map +1 -1
  47. package/dist/client/index.mjs +4 -4
  48. package/dist/components/index.d.mts +33 -10
  49. package/dist/components/index.d.ts +33 -10
  50. package/dist/components/index.js +7 -5
  51. package/dist/components/index.js.map +1 -1
  52. package/dist/components/index.mjs +6 -4
  53. package/dist/{config-B6UB7j69.d.ts → config-3N1LSrcC.d.ts} +1 -1
  54. package/dist/{config-Cd5xTEfZ.d.ts → config-AVtuvfM1.d.ts} +1 -1
  55. package/dist/{config-n-ZpPmOL.d.mts → config-BSKTYDoh.d.mts} +1 -1
  56. package/dist/{config-YyhFHxkU.d.mts → config-BmufyEj6.d.mts} +1 -1
  57. package/dist/contexts/index.d.mts +5 -4
  58. package/dist/contexts/index.d.ts +5 -4
  59. package/dist/contexts/index.js +5 -5
  60. package/dist/contexts/index.mjs +4 -4
  61. package/dist/core/index.d.mts +31 -14
  62. package/dist/core/index.d.ts +31 -14
  63. package/dist/core/index.js +9 -5
  64. package/dist/core/index.js.map +1 -1
  65. package/dist/core/index.mjs +8 -4
  66. package/dist/features/analytics/index.css +30 -0
  67. package/dist/features/analytics/index.css.map +1 -0
  68. package/dist/features/analytics/index.d.mts +508 -0
  69. package/dist/features/analytics/index.d.ts +508 -0
  70. package/dist/features/analytics/index.js +1166 -0
  71. package/dist/features/analytics/index.js.map +1 -0
  72. package/dist/features/analytics/index.mjs +1166 -0
  73. package/dist/features/analytics/index.mjs.map +1 -0
  74. package/dist/features/analytics/server.d.mts +28 -0
  75. package/dist/features/analytics/server.d.ts +28 -0
  76. package/dist/features/analytics/server.js +41 -0
  77. package/dist/features/analytics/server.js.map +1 -0
  78. package/dist/features/analytics/server.mjs +41 -0
  79. package/dist/features/analytics/server.mjs.map +1 -0
  80. package/dist/features/help/index.d.mts +2 -2
  81. package/dist/features/help/index.d.ts +2 -2
  82. package/dist/features/help/index.js +43 -43
  83. package/dist/features/help/index.js.map +1 -1
  84. package/dist/features/help/index.mjs +9 -9
  85. package/dist/features/help/index.mjs.map +1 -1
  86. package/dist/features/tokenusage/index.d.mts +2 -2
  87. package/dist/features/tokenusage/index.d.ts +2 -2
  88. package/dist/features/tokenusage/index.js +141 -223
  89. package/dist/features/tokenusage/index.js.map +1 -1
  90. package/dist/features/tokenusage/index.mjs +28 -110
  91. package/dist/features/tokenusage/index.mjs.map +1 -1
  92. package/dist/index.d.mts +11 -9
  93. package/dist/index.d.ts +11 -9
  94. package/dist/index.js +10 -6
  95. package/dist/index.js.map +1 -1
  96. package/dist/index.mjs +9 -5
  97. package/dist/notification.interface-BNCvnVCW.d.ts +34 -0
  98. package/dist/notification.interface-P_pNfzZQ.d.mts +34 -0
  99. package/dist/{s3.service-Bqr-fQmi.d.ts → s3.service-CbUa6a-8.d.ts} +3 -2
  100. package/dist/{s3.service-Buiy1ncj.d.mts → s3.service-DAp8FbuG.d.mts} +3 -2
  101. package/dist/server/index.d.mts +5 -4
  102. package/dist/server/index.d.ts +5 -4
  103. package/dist/server/index.js +39 -279
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/index.mjs +20 -260
  106. package/dist/server/index.mjs.map +1 -1
  107. package/dist/{types-bsm8Fp55.d.ts → types-Cn153-mH.d.ts} +1 -1
  108. package/dist/{types-Bq_UA8Lg.d.mts → types-D0uLYV2P.d.mts} +1 -1
  109. package/dist/{usePageUrlGenerator-J_ECv-oQ.d.ts → usePageUrlGenerator-B-3FRKBD.d.ts} +1 -1
  110. package/dist/{usePageUrlGenerator-uKBjAKqC.d.mts → usePageUrlGenerator-CCJ_cdQy.d.mts} +1 -1
  111. package/dist/{useSocket-BcH7l7B8.d.mts → useSocket-B9gTbuCj.d.mts} +1 -1
  112. package/dist/{useSocket-DCZd_eJk.d.ts → useSocket-BHVIyigI.d.ts} +1 -1
  113. package/dist/{notification.interface-MnRt1Tjm.d.mts → user.interface-CSaGWFbg.d.mts} +1 -31
  114. package/dist/{notification.interface-CjzV_KII.d.ts → user.interface-CrmwJLYW.d.ts} +1 -31
  115. package/package.json +11 -1
  116. package/src/client/index.ts +2 -0
  117. package/src/components/containers/PageContainer.tsx +1 -1
  118. package/src/components/containers/RoundPageContainer.tsx +60 -16
  119. package/src/components/containers/RoundPageContainerTitle.tsx +75 -35
  120. package/src/components/editors/BlockNoteEditor.tsx +13 -1
  121. package/src/components/forms/EntityMultiSelector.tsx +4 -4
  122. package/src/components/forms/EntitySelector.tsx +4 -4
  123. package/src/components/forms/FormBlockNote.tsx +21 -1
  124. package/src/components/forms/FormCheckbox.tsx +6 -2
  125. package/src/components/forms/__tests__/useEditorDialog.test.ts +47 -0
  126. package/src/components/forms/useEditorDialog.ts +22 -0
  127. package/src/components/navigations/Breadcrumb.tsx +45 -16
  128. package/src/components/navigations/Header.tsx +16 -3
  129. package/src/components/navigations/MobileNavigationBar.tsx +12 -0
  130. package/src/components/navigations/__tests__/MobileNavigationBar.spec.tsx +17 -0
  131. package/src/components/tables/ContentListTable.tsx +52 -32
  132. package/src/core/index.ts +4 -0
  133. package/src/core/registry/ModuleRegistry.ts +7 -0
  134. package/src/features/__tests__/i18n-keys.contract.spec.ts +1 -0
  135. package/src/features/analytics/__tests__/server-entry.spec.tsx +64 -0
  136. package/src/features/analytics/analytics.modules.ts +30 -0
  137. package/src/features/analytics/components/AnalyticsAdminContainer.tsx +97 -0
  138. package/src/features/analytics/components/AnalyticsAdminFilterBar.tsx +120 -0
  139. package/src/features/analytics/components/AnalyticsAdminPageContainer.tsx +25 -0
  140. package/src/features/analytics/components/AnalyticsAdminTiles.tsx +191 -0
  141. package/src/features/analytics/components/AnalyticsBreakdownTable.tsx +105 -0
  142. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +246 -0
  143. package/src/features/analytics/components/AnalyticsJourneyPageContainer.tsx +14 -0
  144. package/src/features/analytics/components/AnalyticsSessionsTable.tsx +107 -0
  145. package/src/features/analytics/components/AnalyticsTimelineChart.tsx +175 -0
  146. package/src/features/analytics/components/AnalyticsTracker.tsx +112 -0
  147. package/src/features/analytics/components/__tests__/AnalyticsAdminTiles.spec.tsx +39 -0
  148. package/src/features/analytics/components/__tests__/AnalyticsBreakdownTable.spec.tsx +39 -0
  149. package/src/features/analytics/components/__tests__/AnalyticsJourneyContainer.spec.tsx +124 -0
  150. package/src/features/analytics/components/__tests__/AnalyticsPageContainers.spec.tsx +117 -0
  151. package/src/features/analytics/components/__tests__/AnalyticsSessionsTable.spec.tsx +121 -0
  152. package/src/features/analytics/components/__tests__/AnalyticsTimelineChart.spec.tsx +35 -0
  153. package/src/features/analytics/components/__tests__/AnalyticsTracker.spec.tsx +248 -0
  154. package/src/features/analytics/components/server/AnalyticsAdminPage.tsx +28 -0
  155. package/src/features/analytics/components/server/AnalyticsJourneyPage.tsx +25 -0
  156. package/src/features/analytics/contexts/AnalyticsAdminContext.tsx +273 -0
  157. package/src/features/analytics/contexts/__tests__/AnalyticsAdminContext.spec.tsx +140 -0
  158. package/src/features/analytics/data/AnalyticsAdminService.ts +91 -0
  159. package/src/features/analytics/data/AnalyticsService.ts +23 -0
  160. package/src/features/analytics/data/__tests__/AnalyticsAdminService.spec.ts +117 -0
  161. package/src/features/analytics/data/__tests__/AnalyticsService.spec.ts +56 -0
  162. package/src/features/analytics/data/__tests__/models.spec.ts +300 -0
  163. package/src/features/analytics/data/analytics-breakdown.interface.ts +9 -0
  164. package/src/features/analytics/data/analytics-breakdown.ts +65 -0
  165. package/src/features/analytics/data/analytics-event.interface.ts +7 -0
  166. package/src/features/analytics/data/analytics-event.ts +63 -0
  167. package/src/features/analytics/data/analytics-page-view.interface.ts +9 -0
  168. package/src/features/analytics/data/analytics-page-view.ts +57 -0
  169. package/src/features/analytics/data/analytics-session.interface.ts +23 -0
  170. package/src/features/analytics/data/analytics-session.ts +144 -0
  171. package/src/features/analytics/data/analytics-summary.interface.ts +11 -0
  172. package/src/features/analytics/data/analytics-summary.ts +79 -0
  173. package/src/features/analytics/data/analytics-timeline.interface.ts +10 -0
  174. package/src/features/analytics/data/analytics-timeline.ts +67 -0
  175. package/src/features/analytics/data/analytics.types.ts +97 -0
  176. package/src/features/analytics/data/index.ts +15 -0
  177. package/src/features/analytics/i18n-keys.ts +76 -0
  178. package/src/features/analytics/index.ts +31 -0
  179. package/src/features/analytics/lib/__tests__/utm.spec.ts +20 -0
  180. package/src/features/analytics/lib/__tests__/visitorCookie.spec.ts +63 -0
  181. package/src/features/analytics/lib/utm.ts +29 -0
  182. package/src/features/analytics/lib/visitorCookie.ts +32 -0
  183. package/src/features/analytics/server-entry.ts +8 -0
  184. package/src/features/assistant/components/parts/AssistantBlockNoteComposer.tsx +1 -1
  185. package/src/features/assistant/components/parts/AssistantComposer.tsx +1 -1
  186. package/src/features/assistant-message/components/MessageItem.tsx +2 -2
  187. package/src/features/assistant-message/components/parts/tabs/CitationsTab.tsx +23 -5
  188. package/src/features/auth/components/containers/AuthContainer.tsx +8 -1
  189. package/src/features/company/components/details/CompanyContent.tsx +3 -1
  190. package/src/features/company/components/details/CompanyDetails.tsx +9 -1
  191. package/src/features/company/components/details/TokenStatusIndicator.tsx +42 -13
  192. package/src/features/help/components/HelpHeader.tsx +7 -5
  193. package/src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx +9 -6
  194. package/src/features/tokenusage/lib/__tests__/formatters.spec.ts +21 -0
  195. package/src/features/tokenusage/lib/formatters.ts +5 -1
  196. package/src/features/waitlist/components/lists/WaitlistList.tsx +68 -121
  197. package/src/features/waitlist/data/WaitlistService.ts +2 -2
  198. package/src/features/waitlist/data/index.ts +1 -0
  199. package/src/features/waitlist/data/waitlist.fields.ts +8 -0
  200. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +69 -30
  201. package/src/shadcnui/ui/button.tsx +1 -1
  202. package/src/shadcnui/ui/checkbox.tsx +1 -1
  203. package/src/shadcnui/ui/command.tsx +4 -4
  204. package/src/shadcnui/ui/dialog.tsx +2 -0
  205. package/src/shadcnui/ui/input.tsx +1 -1
  206. package/src/shadcnui/ui/popover.tsx +9 -0
  207. package/src/shadcnui/ui/select.tsx +1 -1
  208. package/src/shadcnui/ui/sheet.tsx +8 -1
  209. package/src/shadcnui/ui/table.tsx +2 -2
  210. package/src/shadcnui/ui/textarea.tsx +1 -1
  211. package/dist/BlockNoteEditor-35D4R7HJ.js.map +0 -1
  212. package/dist/JsonApiRequest-G625QAH5.js +0 -26
  213. package/dist/chunk-2XCUAHQT.js.map +0 -1
  214. package/dist/chunk-4O7R2Z2O.mjs.map +0 -1
  215. package/dist/chunk-IBVKPLHT.mjs.map +0 -1
  216. package/dist/chunk-U7QVISQS.js.map +0 -1
  217. /package/dist/{BlockNoteViewer-KDKGUIG5.mjs.map → BlockNoteViewer-KRTAFTUX.mjs.map} +0 -0
  218. /package/dist/{JsonApiRequest-RRMNV7YE.mjs.map → JsonApiRequest-UHOWNI3O.mjs.map} +0 -0
  219. /package/dist/{chunk-T76WLDE5.mjs.map → chunk-37DMFVLN.mjs.map} +0 -0
  220. /package/dist/{chunk-ZJI5ZZW5.mjs.map → chunk-Q6J2ICKO.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/cache.ts","../../src/server/ServerSession.ts","../../src/server/JsonApiServer.ts"],"sourcesContent":["// Server-only utilities (NOT server actions - these cannot be called from client components)\n\nimport { revalidateTag } from \"next/cache\";\n\nexport type CacheProfile = \"seconds\" | \"minutes\" | \"hours\" | \"days\" | \"weeks\" | \"max\" | \"default\";\n\n/**\n * Revalidate a cache tag to invalidate cached data.\n * Next.js 16+ requires a profile parameter.\n *\n * @param tag - The cache tag to invalidate\n * @param profile - The cache profile (defaults to \"max\" for immediate invalidation)\n */\nexport async function invalidateCacheTag(tag: string, profile: CacheProfile = \"max\"): Promise<void> {\n revalidateTag(tag, profile);\n}\n\n/**\n * Revalidate multiple cache tags.\n *\n * @param tags - Array of cache tags to invalidate\n * @param profile - The cache profile (defaults to \"max\" for immediate invalidation)\n */\nexport async function invalidateCacheTags(tags: string[], profile: CacheProfile = \"max\"): Promise<void> {\n for (const tag of tags) {\n revalidateTag(tag, profile);\n }\n}\n","import { cookies } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\nimport { ungzip } from \"pako\";\nimport { Action, checkPermissionsFromServer, ModuleWithPermissions } from \"../permissions\";\n\nexport class ServerSession {\n static async isLogged() {\n const cookieStore = await cookies();\n const token = cookieStore.get(\"token\");\n\n if (!token || !token.value) return false;\n return true;\n }\n\n static async companyId() {\n const cookieStore = await cookies();\n return cookieStore.get(\"companyId\")?.value ?? \"\";\n }\n\n static async userId() {\n const cookieStore = await cookies();\n return cookieStore.get(\"userId\")?.value ?? \"\";\n }\n\n static async checkPermission<M extends ModuleWithPermissions>(params: { module: M; action: Action; data?: any }) {\n if (!(await this.hasPermissionToModule(params))) redirect(`/401`);\n }\n\n static async hasRole(roleId: string): Promise<boolean> {\n const cookieStore = await cookies();\n const roles = cookieStore.get(\"roles\")?.value;\n\n if (!roles || !roles.includes(roleId)) return false;\n\n return true;\n }\n\n static async hasFeature(featureId: string): Promise<boolean> {\n const cookieStore = await cookies();\n const features = cookieStore.get(\"features\")?.value;\n\n if (!features || !features.includes(featureId)) return false;\n\n return true;\n }\n\n static async hasPermissionToModule<M extends ModuleWithPermissions>(params: {\n module: M;\n action: Action;\n data?: any;\n }): Promise<boolean> {\n const cookieStore = await cookies();\n\n if (params.module.feature) {\n const features = cookieStore.get(\"features\")?.value;\n\n if (features && !features.includes(params.module.feature)) return false;\n }\n\n const rawModules = cookieStore.get(\"modules\")?.value;\n if (!rawModules) return false;\n\n const modules: {\n id: string;\n permissions: {\n create: boolean | string;\n read: boolean | string;\n update: boolean | string;\n delete: boolean | string;\n };\n }[] = JSON.parse(ungzip(Buffer.from(rawModules, \"base64\"), { toText: true }));\n\n const selectedModule = modules.find((module) => module.id === params.module.moduleId);\n\n return checkPermissionsFromServer({\n module: params.module,\n action: params.action,\n data: params.data,\n userId: await this.userId(),\n selectedModule: selectedModule,\n });\n\n // if (!selectedModule) return false;\n\n // if (!selectedModule.permissions[params.action]) return false;\n // if (typeof selectedModule.permissions[params.action] === \"boolean\")\n // return selectedModule.permissions[params.action] as boolean;\n\n // if (!params.data) return false;\n\n // return params.data[selectedModule.permissions[params.action] as string] === this.userId;\n }\n}\n","// Server-only utilities (NOT server actions - these cannot be called from client components)\n\nimport { ENV } from \"../core/env\";\nimport { JsonApiDataFactory } from \"../core/factories/JsonApiDataFactory\";\nimport { ApiRequestDataTypeInterface } from \"../core/interfaces/ApiRequestDataTypeInterface\";\nimport { ApiResponseInterface } from \"../core/interfaces/ApiResponseInterface\";\nimport { translateResponse } from \"../core/utils/translateResponse\";\nimport { ModuleWithPermissions } from \"../permissions/types\";\nimport { serverRequest } from \"./request\";\nimport { getServerToken } from \"./token\";\n\n// Config storage for server contexts\nlet _serverConfig: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n} | null = null;\n\n/**\n * Configure the JSON:API server client.\n * Call this in your Bootstrapper or server initialization.\n */\nexport function configureServerJsonApi(config: {\n apiUrl: string;\n appUrl?: string;\n trackablePages?: ModuleWithPermissions[];\n bootstrapper?: () => void;\n additionalHeaders?: Record<string, string>;\n}): void {\n _serverConfig = config;\n if (config.bootstrapper) {\n config.bootstrapper();\n }\n}\n\nexport function getServerApiUrl(): string {\n if (_serverConfig?.apiUrl) {\n return _serverConfig.apiUrl;\n }\n const envUrl = ENV.API_URL;\n if (!envUrl) {\n throw new Error(\n \"API URL not configured. Use configureServerJsonApi() or set NEXT_PUBLIC_API_URL environment variable.\",\n );\n }\n return envUrl;\n}\n\nexport function getServerAppUrl(): string {\n if (_serverConfig?.appUrl) {\n return _serverConfig.appUrl;\n }\n const envUrl = ENV.APP_URL;\n if (!envUrl) {\n throw new Error(\n \"App URL not configured. Use configureServerJsonApi({ appUrl }) or set NEXT_PUBLIC_ADDRESS environment variable.\",\n );\n }\n return envUrl.trim().replace(/\\/+$/, \"\");\n}\n\nexport function getServerTrackablePages(): ModuleWithPermissions[] {\n return _serverConfig?.trackablePages ?? [];\n}\n\nfunction runServerBootstrapper(): void {\n if (_serverConfig?.bootstrapper) {\n _serverConfig.bootstrapper();\n }\n}\n\nfunction buildServerUrl(endpoint: string): string {\n const apiUrl = getServerApiUrl();\n return endpoint.startsWith(\"http\") ? endpoint : `${apiUrl}${endpoint}`;\n}\n\nexport async function ServerJsonApiGet(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n}): Promise<ApiResponseInterface> {\n runServerBootstrapper();\n const token = await getServerToken();\n\n const apiResponse = await serverRequest({\n method: \"GET\",\n url: buildServerUrl(params.endpoint),\n token,\n cache: params.classKey.cache,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _serverConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n paginationHandler: async (endpoint: string) =>\n ServerJsonApiGet({\n classKey: params.classKey,\n endpoint,\n companyId: params.companyId,\n language: params.language,\n }),\n });\n}\n\nexport async function ServerJsonApiPost(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n overridesJsonApiCreation?: boolean;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n}): Promise<ApiResponseInterface> {\n runServerBootstrapper();\n const token = await getServerToken();\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await serverRequest({\n method: \"POST\",\n url: buildServerUrl(params.endpoint),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _serverConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function ServerJsonApiPut(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n}): Promise<ApiResponseInterface> {\n runServerBootstrapper();\n const token = await getServerToken();\n\n let body = params.body;\n if (!body) {\n body = {};\n } else {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await serverRequest({\n method: \"PUT\",\n url: buildServerUrl(params.endpoint),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _serverConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function ServerJsonApiPatch(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n body?: any;\n files?: { [key: string]: File | Blob } | File | Blob;\n overridesJsonApiCreation?: boolean;\n responseType?: ApiRequestDataTypeInterface;\n language: string;\n}): Promise<ApiResponseInterface> {\n runServerBootstrapper();\n const token = await getServerToken();\n\n let body = params.body;\n if (!body) {\n body = {};\n } else if (params.overridesJsonApiCreation !== true) {\n body = JsonApiDataFactory.create(params.classKey, body);\n }\n\n const apiResponse = await serverRequest({\n method: \"PATCH\",\n url: buildServerUrl(params.endpoint),\n token,\n body,\n files: params.files,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _serverConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n\nexport async function ServerJsonApiDelete(params: {\n classKey: ApiRequestDataTypeInterface;\n endpoint: string;\n companyId?: string;\n language: string;\n responseType?: ApiRequestDataTypeInterface;\n}): Promise<ApiResponseInterface> {\n runServerBootstrapper();\n const token = await getServerToken();\n\n const apiResponse = await serverRequest({\n method: \"DELETE\",\n url: buildServerUrl(params.endpoint),\n token,\n companyId: params.companyId,\n language: params.language,\n additionalHeaders: _serverConfig?.additionalHeaders,\n });\n\n return translateResponse({\n classKey: params.responseType ?? params.classKey,\n apiResponse,\n companyId: params.companyId,\n language: params.language,\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -98,4 +98,4 @@ type ModuleFactory = (params: {
98
98
  }>;
99
99
  }) => ModuleWithPermissions;
100
100
 
101
- export { Action as A, type ModuleWithPermissions as M, type PageUrl as P, type ModuleFactory as a, type PermissionCheck as b, type ModulePermissionDefinition as c, type ModuleDefinition as d, type PermissionConfig as e, type PermissionModule as f, type PermissionUser as g };
101
+ export { Action as A, type ModuleFactory as M, type PageUrl as P, type ModuleWithPermissions as a, type PermissionCheck as b, type ModulePermissionDefinition as c, type ModuleDefinition as d, type PermissionConfig as e, type PermissionModule as f, type PermissionUser as g };
@@ -98,4 +98,4 @@ type ModuleFactory = (params: {
98
98
  }>;
99
99
  }) => ModuleWithPermissions;
100
100
 
101
- export { Action as A, type ModuleWithPermissions as M, type PageUrl as P, type ModuleFactory as a, type PermissionCheck as b, type ModulePermissionDefinition as c, type ModuleDefinition as d, type PermissionConfig as e, type PermissionModule as f, type PermissionUser as g };
101
+ export { Action as A, type ModuleFactory as M, type PageUrl as P, type ModuleWithPermissions as a, type PermissionCheck as b, type ModulePermissionDefinition as c, type ModuleDefinition as d, type PermissionConfig as e, type PermissionModule as f, type PermissionUser as g };
@@ -1,4 +1,4 @@
1
- import { P as PageUrl } from './types-bsm8Fp55.js';
1
+ import { P as PageUrl } from './types-Cn153-mH.js';
2
2
 
3
3
  declare function usePageUrlGenerator(): (params: {
4
4
  page?: PageUrl | string;
@@ -1,4 +1,4 @@
1
- import { P as PageUrl } from './types-Bq_UA8Lg.mjs';
1
+ import { P as PageUrl } from './types-D0uLYV2P.mjs';
2
2
 
3
3
  declare function usePageUrlGenerator(): (params: {
4
4
  page?: PageUrl | string;
@@ -1,4 +1,4 @@
1
- import { N as NotificationInterface } from './notification.interface-MnRt1Tjm.mjs';
1
+ import { N as NotificationInterface } from './notification.interface-P_pNfzZQ.mjs';
2
2
 
3
3
  interface UseSocketOptions {
4
4
  token: string;
@@ -1,4 +1,4 @@
1
- import { N as NotificationInterface } from './notification.interface-CjzV_KII.js';
1
+ import { N as NotificationInterface } from './notification.interface-BNCvnVCW.js';
2
2
 
3
3
  interface UseSocketOptions {
4
4
  token: string;
@@ -97,34 +97,4 @@ interface UserInterface extends ApiDataInterface {
97
97
  get modules(): ModuleInterface[];
98
98
  }
99
99
 
100
- type ContentInput = {
101
- id: string;
102
- name?: string;
103
- authorId?: string;
104
- editorIds?: string[];
105
- };
106
- interface ContentInterface extends ApiDataInterface {
107
- get contentType(): string | undefined;
108
- get name(): string;
109
- get abstract(): string | undefined;
110
- get tldr(): string | undefined;
111
- get aiStatus(): string;
112
- get relevance(): number | undefined;
113
- get author(): UserInterface;
114
- get editors(): UserInterface[];
115
- }
116
-
117
- type NotificationInput = {
118
- id: string;
119
- isRead: boolean;
120
- };
121
- interface NotificationInterface extends ApiDataInterface {
122
- get notificationType(): string;
123
- get isRead(): boolean;
124
- get message(): string | undefined;
125
- get actionUrl(): string | undefined;
126
- get actor(): ApiDataInterface | undefined;
127
- get subject(): ApiDataInterface | undefined;
128
- }
129
-
130
- export type { ContentInterface as C, NotificationInterface as N, RoleInput as R, UserInterface as U, ContentInput as a, CompanyInput as b, CompanyInterface as c, NotificationInput as d, RoleInterface as e, UserInput as f };
100
+ export type { CompanyInput as C, RoleInput as R, UserInterface as U, CompanyInterface as a, RoleInterface as b, UserInput as c };
@@ -97,34 +97,4 @@ interface UserInterface extends ApiDataInterface {
97
97
  get modules(): ModuleInterface[];
98
98
  }
99
99
 
100
- type ContentInput = {
101
- id: string;
102
- name?: string;
103
- authorId?: string;
104
- editorIds?: string[];
105
- };
106
- interface ContentInterface extends ApiDataInterface {
107
- get contentType(): string | undefined;
108
- get name(): string;
109
- get abstract(): string | undefined;
110
- get tldr(): string | undefined;
111
- get aiStatus(): string;
112
- get relevance(): number | undefined;
113
- get author(): UserInterface;
114
- get editors(): UserInterface[];
115
- }
116
-
117
- type NotificationInput = {
118
- id: string;
119
- isRead: boolean;
120
- };
121
- interface NotificationInterface extends ApiDataInterface {
122
- get notificationType(): string;
123
- get isRead(): boolean;
124
- get message(): string | undefined;
125
- get actionUrl(): string | undefined;
126
- get actor(): ApiDataInterface | undefined;
127
- get subject(): ApiDataInterface | undefined;
128
- }
129
-
130
- export type { ContentInterface as C, NotificationInterface as N, RoleInput as R, UserInterface as U, ContentInput as a, CompanyInput as b, CompanyInterface as c, NotificationInput as d, RoleInterface as e, UserInput as f };
100
+ export type { CompanyInput as C, RoleInput as R, UserInterface as U, CompanyInterface as a, RoleInterface as b, UserInput as c };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carlonicora/nextjs-jsonapi",
3
- "version": "3.24.2",
3
+ "version": "3.26.0",
4
4
  "description": "Next.js JSON:API client with server/client support and caching",
5
5
  "author": "Carlo Nicora",
6
6
  "license": "GPL-3.0-or-later",
@@ -67,6 +67,16 @@
67
67
  "types": "./dist/features/tokenusage/index.d.ts",
68
68
  "import": "./dist/features/tokenusage/index.mjs",
69
69
  "require": "./dist/features/tokenusage/index.js"
70
+ },
71
+ "./analytics": {
72
+ "types": "./dist/features/analytics/index.d.ts",
73
+ "import": "./dist/features/analytics/index.mjs",
74
+ "require": "./dist/features/analytics/index.js"
75
+ },
76
+ "./analytics/server": {
77
+ "types": "./dist/features/analytics/server.d.ts",
78
+ "import": "./dist/features/analytics/server.mjs",
79
+ "require": "./dist/features/analytics/server.js"
70
80
  }
71
81
  },
72
82
  "scripts": {
@@ -21,6 +21,7 @@ import { useRoleTableStructure } from "../features/role/hooks";
21
21
  import { useUserTableStructure } from "../features/user/hooks";
22
22
  import { useStripePriceTableStructure } from "../features/billing/stripe-price/hooks/useStripePriceTableStructure";
23
23
  import { useStripeProductTableStructure } from "../features/billing/stripe-product/hooks/useStripeProductTableStructure";
24
+ import { useWaitlistTableStructure } from "../features/waitlist/hooks/useWaitlistTableStructure";
24
25
  import { registerTableGenerator } from "../hooks";
25
26
 
26
27
  export * from "../features/content/hooks";
@@ -34,4 +35,5 @@ registerTableGenerator("users", useUserTableStructure);
34
35
  registerTableGenerator("companies", useCompanyTableStructure);
35
36
  registerTableGenerator("stripe-products", useStripeProductTableStructure);
36
37
  registerTableGenerator("stripe-prices", useStripePriceTableStructure);
38
+ registerTableGenerator("waitlists", useWaitlistTableStructure);
37
39
  // Note: Content registration moved to app-level to support app-specific cellTopic
@@ -19,7 +19,7 @@ export function PageContainer({ children, testId, className }: PageContainerProp
19
19
  <Header leftContent={headerLeftContent} logo={headerLogo} mobileChildren={headerMobileChildren}>
20
20
  {headerChildren}
21
21
  </Header>
22
- <main className={cn(`flex w-full flex-1 flex-col gap-y-4 pt-4 ps-4 pe-0`, className)}>{children}</main>
22
+ <main className={cn(`flex w-full flex-1 flex-col gap-y-4 pt-4 ps-4 pe-0 max-md:pe-4`, className)}>{children}</main>
23
23
  </div>
24
24
  );
25
25
  }
@@ -21,7 +21,13 @@ import { partitionTabs, Tab } from "@/components/containers";
21
21
  import { HEADER_ROW_MIN_H, RoundPageContainerTitle } from "@/components/containers/RoundPageContainerTitle";
22
22
  import { Header, MobileNavigationBar } from "@/components/navigations";
23
23
  import { MicroLabel } from "@/components/typography";
24
- import { useHeaderChildren, useHeaderLeftContent, useHeaderLogo, useHeaderMobileChildren } from "@/contexts";
24
+ import {
25
+ useHeaderChildren,
26
+ useHeaderLeftContent,
27
+ useHeaderLogo,
28
+ useHeaderMobileChildren,
29
+ useMobileNavigationItems,
30
+ } from "@/contexts";
25
31
  import { useUrlRewriter } from "@/hooks";
26
32
  import { cn, useIsMobile } from "@/index";
27
33
  import { useOptionalSidebar } from "@/shadcnui";
@@ -186,7 +192,15 @@ export function RoundPageContainer({
186
192
  const headerLogo = useHeaderLogo();
187
193
  const headerMobileChildren = useHeaderMobileChildren();
188
194
  const [showDetails, setShowDetailsState] = useState(defaultDetailsOpen);
195
+ // Phone-only open state for the details Sheet. It always starts closed and is
196
+ // never written to the cookie: on a phone the panel is a full-screen overlay,
197
+ // so a remembered "open" (set on desktop, where it is a side column) covered
198
+ // the page on every visit — the assistant's Conversations panel did exactly that.
199
+ const [showDetailsMobile, setShowDetailsMobile] = useState(false);
189
200
  const isMobile = useIsMobile();
201
+ // The bottom bar's "Menu" slot opens the same drawer as the header's sidebar
202
+ // toggle, so the header drops its toggle on a phone whenever the bar renders.
203
+ const hasMobileNavigation = useMobileNavigationItems().length > 0;
190
204
  // The desktop shell drops its start padding only where a sidebar sits against it. A page
191
205
  // without one (the customer portal) keeps the padding on both sides.
192
206
  const hasSidebar = useOptionalSidebar() !== null;
@@ -298,6 +312,21 @@ export function RoundPageContainer({
298
312
 
299
313
  const isReady = mounted;
300
314
 
315
+ // Phone, viewport-bound shell: the whole card becomes the single scroller, so
316
+ // the title row, command row, artwork and section picker scroll away with the
317
+ // content instead of staying pinned and leaving the content less than half the
318
+ // screen. That covers ordinary tabs AND document tabs (fill-height + constrained
319
+ // width: the entity's own editor, which then grows with its text). Only bare
320
+ // fill-height tabs (graph, map, recordings) and full-width pages (lists, canvas,
321
+ // run) keep the inner scroller, since they need a definite height. Desktop is
322
+ // untouched.
323
+ const activeDocumentTab = activeFillHeight && activeConstrainWidth;
324
+ const mobileFlow = isMobile && isFixed && (!activeFillHeight || activeDocumentTab) && !fullWidth;
325
+ const innerClip = mobileFlow ? `` : clip;
326
+ const innerScrollY = mobileFlow ? `` : scrollY;
327
+ // A single section needs no picker.
328
+ const showMobileSectionPicker = (tabs?.length ?? 0) > 1;
329
+
301
330
  if (!isReady) {
302
331
  return (
303
332
  <>
@@ -305,6 +334,7 @@ export function RoundPageContainer({
305
334
  leftContent={headerLeftContent}
306
335
  logo={headerLogo}
307
336
  mobileChildren={headerMobileChildren}
337
+ hideSidebarTriggerOnMobile={hasMobileNavigation}
308
338
  className="bg-sidebar border-0"
309
339
  >
310
340
  {headerChildren}
@@ -349,6 +379,7 @@ export function RoundPageContainer({
349
379
  leftContent={headerLeftContent}
350
380
  logo={headerLogo}
351
381
  mobileChildren={headerMobileChildren}
382
+ hideSidebarTriggerOnMobile={hasMobileNavigation}
352
383
  className="bg-sidebar border-0"
353
384
  >
354
385
  {headerChildren}
@@ -379,19 +410,21 @@ export function RoundPageContainer({
379
410
  the wrapper, leaving the bar no room and pushing it below the fold —
380
411
  it then appears only after scrolling to the very end of the page. */}
381
412
  <div className="bg-background flex min-h-0 w-full flex-1 rounded-lg border p-0">
382
- <div className="flex w-full flex-col">
413
+ {/* pb-16 under mobileFlow: room for the floating Help button, which
414
+ otherwise sits over the last lines of the page. */}
415
+ <div className={cn("flex w-full flex-col", mobileFlow && "min-h-0 overflow-y-auto pb-16")}>
383
416
  {(!fullWidth || forceHeader) && (
384
417
  <RoundPageContainerTitle
385
418
  module={module}
386
419
  details={details}
387
420
  detailsTitle={detailsTitle}
388
421
  detailsIcon={detailsIcon}
389
- showDetails={showDetails}
390
- setShowDetails={setShowDetails}
422
+ showDetails={isMobile ? showDetailsMobile : showDetails}
423
+ setShowDetails={isMobile ? setShowDetailsMobile : setShowDetails}
391
424
  fullWidth={fullWidth}
392
425
  />
393
426
  )}
394
- <div className={cn(`flex w-full`, isFixed && `h-full`, clip)}>
427
+ <div className={cn(`flex w-full`, isFixed && !mobileFlow && `h-full`, innerClip)}>
395
428
  {layout === "rail" && tabs ? (
396
429
  // Rail layout: the vertical-tab navigation is a flush-left
397
430
  // sidebar of the card and the content fills the full remaining
@@ -409,7 +442,11 @@ export function RoundPageContainer({
409
442
  // the shadcn root's default `data-[orientation=horizontal]:flex-col`
410
443
  // to keep the rail and content side by side.
411
444
  orientation="horizontal"
412
- className={cn(`flex min-w-0 grow data-[orientation=horizontal]:flex-row`, isFixed && `h-full`, clip)}
445
+ className={cn(
446
+ `flex min-w-0 grow data-[orientation=horizontal]:flex-row`,
447
+ isFixed && !mobileFlow && `h-full`,
448
+ innerClip,
449
+ )}
413
450
  >
414
451
  {/* Flush-left section rail — md and up */}
415
452
  <aside
@@ -454,14 +491,17 @@ export function RoundPageContainer({
454
491
  </aside>
455
492
 
456
493
  {/* Content — full width, fills the remaining space */}
457
- <div className={cn(`flex min-w-0 grow flex-col`, clip)}>
494
+ <div className={cn(`flex min-w-0 grow flex-col`, innerClip)}>
458
495
  {/* The rail is hidden below md, so its header would vanish with
459
496
  it — render it above the section Select instead, capped to
460
497
  the rail's width so a phone does not open on a full-bleed
461
498
  image. */}
462
- {railHeader && <div className="mx-auto w-full max-w-48 px-2 pt-2 md:hidden">{railHeader}</div>}
463
- {/* Section Select — below md */}
464
- <div data-testid="round-page-rail-select" className="p-2 md:hidden">
499
+ {railHeader && <div className="mx-auto w-full max-w-24 px-2 pt-2 md:hidden">{railHeader}</div>}
500
+ {/* Section Select — below md, and only when there is a choice to make */}
501
+ <div
502
+ data-testid="round-page-rail-select"
503
+ className={cn("p-2 md:hidden", !showMobileSectionPicker && "hidden")}
504
+ >
465
505
  <Select
466
506
  items={tabItems}
467
507
  value={activeTab}
@@ -492,7 +532,7 @@ export function RoundPageContainer({
492
532
  </Select>
493
533
  </div>
494
534
  <div
495
- className={cn(`min-w-0 grow`, activeFillHeight ? cn(`flex flex-col`, clip) : cn(scrollY, `p-4`))}
535
+ className={cn(`min-w-0 grow`, activeFillHeight ? cn(`flex flex-col`, innerClip) : cn(innerScrollY, `p-4`))}
496
536
  >
497
537
  {/* Centre and constrain rail content (like the non-rail
498
538
  layout). Fill-height tabs are full-bleed — a canvas, a
@@ -524,7 +564,7 @@ export function RoundPageContainer({
524
564
  className={cn(
525
565
  `grow`,
526
566
  isMobile ? `p-2` : `p-4`,
527
- activeFillHeight ? cn(`flex flex-col`, clip) : scrollY,
567
+ activeFillHeight ? cn(`flex flex-col`, innerClip) : innerScrollY,
528
568
  fullWidth && `p-0`,
529
569
  )}
530
570
  >
@@ -544,7 +584,7 @@ export function RoundPageContainer({
544
584
  onValueChange={handleTabChange}
545
585
  >
546
586
  {isMobile ? (
547
- <div className="p-0">
587
+ <div className={cn("p-0", !showMobileSectionPicker && "hidden")}>
548
588
  <Select
549
589
  items={tabItems}
550
590
  value={activeTab}
@@ -579,7 +619,7 @@ export function RoundPageContainer({
579
619
  className={cn(
580
620
  `flex w-full `,
581
621
  isMobile ? `` : `px-4`,
582
- activeFillHeight ? `flex-1 min-h-0` : scrollY,
622
+ activeFillHeight ? `flex-1 min-h-0` : innerScrollY,
583
623
  )}
584
624
  >
585
625
  {tabs.map((tab) => (
@@ -605,8 +645,12 @@ export function RoundPageContainer({
605
645
  </div>
606
646
  {details &&
607
647
  (isMobile ? (
608
- <Sheet open={showDetails} onOpenChange={setShowDetails}>
609
- <SheetContent side="end">
648
+ <Sheet open={showDetailsMobile} onOpenChange={setShowDetailsMobile}>
649
+ {/* Full width: the default 75% left a useless strip of page beside it. */}
650
+ <SheetContent
651
+ side="end"
652
+ className="data-[side=left]:w-full data-[side=right]:w-full data-[side=left]:sm:max-w-none data-[side=right]:sm:max-w-none"
653
+ >
610
654
  <SheetHeader>
611
655
  <SheetTitle>{detailsTitle ?? "Details"}</SheetTitle>
612
656
  </SheetHeader>
@@ -38,10 +38,74 @@ export function RoundPageContainerTitle({
38
38
  const { title } = useSharedContext();
39
39
  const isMobile = useIsMobile();
40
40
 
41
+ // A Tooltip on the toggle stays stuck on screen after a tap, so a phone gets
42
+ // the bare button. The aria-label carries the same text the tooltip shows,
43
+ // since the icon alone does not name the control.
44
+ const detailsLabel = `${showDetails ? "Hide" : "Show"} ${typeof detailsTitle === "string" ? detailsTitle : "details"}`;
45
+ const detailsToggle = details ? (
46
+ isMobile ? (
47
+ <Button
48
+ data-help="page.details"
49
+ variant={showDetails ? `ghost` : `default`}
50
+ onClick={() => setShowDetails(!showDetails)}
51
+ aria-label={detailsLabel}
52
+ className="shrink-0"
53
+ >
54
+ {detailsIcon ?? <InfoIcon />}
55
+ </Button>
56
+ ) : (
57
+ <Tooltip>
58
+ {/* render prop, not a Button nested inside the trigger: the trigger
59
+ renders its own <button>, and a button inside a button is invalid. */}
60
+ <TooltipTrigger
61
+ render={
62
+ <Button
63
+ data-help="page.details"
64
+ variant={showDetails ? `ghost` : `default`}
65
+ onClick={() => setShowDetails(!showDetails)}
66
+ aria-label={detailsLabel}
67
+ className={cn(`cursor-pointer`)}
68
+ />
69
+ }
70
+ >
71
+ {detailsIcon ?? <InfoIcon />}
72
+ </TooltipTrigger>
73
+ <TooltipContent>
74
+ {showDetails ? "Hide" : "Show"} {detailsTitle ?? "details"}
75
+ </TooltipContent>
76
+ </Tooltip>
77
+ )
78
+ ) : null;
79
+
41
80
  return (
42
81
  <div className="flex w-full flex-col border-b">
43
- <div className={cn(`flex w-full flex-row items-center gap-x-2 p-4 justify-between`, HEADER_ROW_MIN_H)}>
44
- {!isMobile ? (
82
+ {isMobile ? (
83
+ // Phone: "type · name" on one truncated line with the details toggle
84
+ // beside it; the page controls drop to their own wrapping line below,
85
+ // so they can never push the page wider than the screen.
86
+ <div className={cn(`flex w-full flex-col gap-y-2 p-4`, HEADER_ROW_MIN_H)}>
87
+ <div className="flex w-full min-w-0 items-center justify-between gap-x-2">
88
+ <div className="text-muted-foreground flex min-w-0 items-center gap-x-2 text-base font-light">
89
+ {title.titleActions}
90
+ {module && module.icon ? <module.icon className="text-primary h-5 w-5 shrink-0" /> : title.icon}
91
+ <span className="truncate">
92
+ {title.type}
93
+ {title.element && (
94
+ <>
95
+ <span aria-hidden> · </span>
96
+ <span className="text-primary font-semibold">{title.element}</span>
97
+ </>
98
+ )}
99
+ </span>
100
+ </div>
101
+ {detailsToggle}
102
+ </div>
103
+ {title.functions && (
104
+ <div className="flex w-full min-w-0 flex-wrap items-center justify-end gap-2">{title.functions}</div>
105
+ )}
106
+ </div>
107
+ ) : (
108
+ <div className={cn(`flex w-full flex-row items-center gap-x-2 p-4 justify-between`, HEADER_ROW_MIN_H)}>
45
109
  <div className="flex w-full gap-x-4">
46
110
  <div className={"text-muted-foreground flex items-center gap-x-2 text-lg font-light whitespace-nowrap"}>
47
111
  {title.titleActions}
@@ -50,43 +114,19 @@ export function RoundPageContainerTitle({
50
114
  </div>
51
115
  <div className={cn("text-primary w-full text-xl font-semibold")}>{title.element}</div>
52
116
  </div>
53
- ) : (
54
- <div className="text-muted-foreground flex min-w-0 items-center gap-x-2 text-base font-light">
55
- {title.titleActions}
56
- {module && module.icon ? <module.icon className="text-primary h-5 w-5 shrink-0" /> : title.icon}
57
- <span className="truncate">{title.type}</span>
58
- </div>
59
- )}
60
- {(title.functions || details) && (
61
- <div className="flex shrink-0 items-center gap-x-2">
62
- {title.functions}
63
- {details && (
64
- <Tooltip>
65
- <TooltipTrigger>
66
- <Button
67
- data-help="page.details"
68
- render={<div />}
69
- nativeButton={false}
70
- variant={showDetails ? `ghost` : `default`}
71
- onClick={() => setShowDetails(!showDetails)}
72
- className={cn(`cursor-pointer`)}
73
- >
74
- {detailsIcon ?? <InfoIcon />}
75
- </Button>
76
- </TooltipTrigger>
77
- <TooltipContent>
78
- {showDetails ? "Hide" : "Show"} {detailsTitle ?? "details"}
79
- </TooltipContent>
80
- </Tooltip>
81
- )}
82
- </div>
83
- )}
84
- </div>
117
+ {(title.functions || details) && (
118
+ <div className="flex shrink-0 items-center gap-x-2">
119
+ {title.functions}
120
+ {detailsToggle}
121
+ </div>
122
+ )}
123
+ </div>
124
+ )}
85
125
  {title.actionBar && (
86
126
  <div
87
127
  data-testid="round-page-action-bar"
88
128
  data-help="page.action-bar"
89
- className="flex w-full items-center gap-x-2 border-t px-4 py-2"
129
+ className="flex w-full items-center gap-x-2 border-t px-4 py-2 max-md:flex-wrap max-md:gap-y-2"
90
130
  >
91
131
  {/* The bar is the page's command row, so the commands inside it label
92
132
  themselves ("Edit", "Delete") instead of rendering the bare glyph
@@ -872,7 +872,12 @@ export default function BlockNoteEditor({
872
872
  // font-size: 16px }` explicitly; outside AI mode the form's text-sm
873
873
  // wins via cascade, but ForkYDocExtension re-evaluates the style
874
874
  // context on AI activation and the explicit 16px takes over.
875
- "[&_.bn-default-styles]:!text-sm",
875
+ // Phone: an editable field types at 16px, or iPhone Safari zooms the
876
+ // page in on focus. The two ranges never overlap, so neither depends on
877
+ // CSS source order to win.
878
+ onChange
879
+ ? "md:[&_.bn-default-styles]:!text-sm max-md:[&_.bn-default-styles]:!text-base"
880
+ : "[&_.bn-default-styles]:!text-sm",
876
881
  className,
877
882
  )}
878
883
  >
@@ -898,6 +903,13 @@ export default function BlockNoteEditor({
898
903
  // — and the surrounding EditorSheet form — to scroll instead of
899
904
  // scrolling internally.
900
905
  onChange && stretch && "[&.bn-container]:min-h-0 [&.bn-container]:overflow-y-auto",
906
+ // Phone — these reach the floating menus because BlockNote puts this
907
+ // className on its portal root too. The formatting toolbar (~450px)
908
+ // scrolls sideways instead of losing alignment and link off-screen;
909
+ // the slash menu is capped to the screen and drops its keyboard
910
+ // shortcut badges, which mean nothing on a touch screen.
911
+ "max-md:[&_.bn-formatting-toolbar]:max-w-[calc(100vw-1rem)] max-md:[&_.bn-formatting-toolbar]:overflow-x-auto max-md:[&_.bn-formatting-toolbar>*]:shrink-0",
912
+ "max-md:[&_.bn-suggestion-menu]:max-w-[calc(100vw-1rem)] max-md:[&_.bn-suggestion-menu_[role=option]>[data-position=right]]:hidden",
901
913
  size === "sm" && "small",
902
914
  )}
903
915
  >
@@ -225,7 +225,7 @@ export function EntityMultiSelector<T extends { id: string }>({
225
225
  <Popover open={disabled ? false : open} onOpenChange={disabled ? undefined : setOpen} modal>
226
226
  <PopoverTrigger className="w-full" disabled={disabled}>
227
227
  <div
228
- className={`bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] flex min-h-7 w-full items-center gap-2 rounded-md border px-2 text-sm md:text-xs/relaxed ${
228
+ className={`bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] flex min-h-7 max-md:min-h-10 w-full items-center gap-2 rounded-md border px-2 text-sm md:text-xs/relaxed ${
229
229
  disabled ? "cursor-not-allowed opacity-50" : ""
230
230
  }`}
231
231
  >
@@ -249,7 +249,7 @@ export function EntityMultiSelector<T extends { id: string }>({
249
249
  ref={searchInputRef}
250
250
  placeholder={placeholder}
251
251
  type="text"
252
- className="h-8 w-full pe-7 ps-7 text-xs"
252
+ className="h-8 w-full pe-7 ps-7 text-xs max-md:text-base"
253
253
  value={searchTerm}
254
254
  onChange={(e) => setSearchTerm(e.target.value)}
255
255
  />
@@ -263,7 +263,7 @@ export function EntityMultiSelector<T extends { id: string }>({
263
263
  </button>
264
264
  )}
265
265
  </div>
266
- <div className="max-h-52 overflow-y-auto p-1">
266
+ <div className="max-h-52 max-md:min-h-0 overflow-y-auto p-1">
267
267
  {sortedOptions.length === 0 ? (
268
268
  <div className="text-muted-foreground py-4 text-center text-xs">{emptyText}</div>
269
269
  ) : (
@@ -321,7 +321,7 @@ export function EntityMultiSelector<T extends { id: string }>({
321
321
  {!disabled && (
322
322
  <button
323
323
  type="button"
324
- className="text-muted-foreground hover:text-foreground rounded-sm p-0.5 transition-colors"
324
+ className="text-muted-foreground hover:text-foreground rounded-sm p-0.5 max-md:p-2 transition-colors"
325
325
  onClick={() => removeEntity(value.id)}
326
326
  >
327
327
  <XIcon className="size-3" />
@@ -165,7 +165,7 @@ export default function EntitySelector<T extends BaseEntity>({
165
165
  <Popover open={disabled ? false : open} onOpenChange={disabled ? undefined : setOpen} modal={true}>
166
166
  <PopoverTrigger className="w-full" disabled={disabled}>
167
167
  <div
168
- className={`bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] flex min-h-7 w-full items-center gap-2 rounded-md border px-2 py-0.5 text-sm md:text-xs/relaxed ${
168
+ className={`bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] flex min-h-7 max-md:min-h-10 w-full items-center gap-2 rounded-md border px-2 py-0.5 text-sm md:text-xs/relaxed ${
169
169
  hasValue(effectiveValue) ? "" : "text-muted-foreground"
170
170
  } ${disabled ? "cursor-not-allowed opacity-50" : ""}`}
171
171
  >
@@ -191,7 +191,7 @@ export default function EntitySelector<T extends BaseEntity>({
191
191
  <PopoverContent align="start" className="w-(--anchor-width) pointer-events-auto">
192
192
  <Command shouldFilter={false}>
193
193
  <div className="relative mb-2 w-full">
194
- <SearchIcon className="text-muted-foreground absolute top-2.5 start-2.5 h-4 w-4" />
194
+ <SearchIcon className="text-muted-foreground absolute top-2.5 max-md:top-3 start-2.5 h-4 w-4" />
195
195
  <Input
196
196
  placeholder={placeholder}
197
197
  type="text"
@@ -200,10 +200,10 @@ export default function EntitySelector<T extends BaseEntity>({
200
200
  value={searchTerm}
201
201
  />
202
202
  {isSearching ? (
203
- <RefreshCwIcon className="text-muted-foreground absolute top-2.5 end-2.5 h-4 w-4 animate-spin" />
203
+ <RefreshCwIcon className="text-muted-foreground absolute top-2.5 max-md:top-3 end-2.5 h-4 w-4 animate-spin" />
204
204
  ) : searchTermRef.current ? (
205
205
  <XIcon
206
- className="absolute top-2.5 end-2.5 h-4 w-4 cursor-pointer"
206
+ className="absolute top-2.5 max-md:top-3 end-2.5 h-4 w-4 cursor-pointer"
207
207
  onClick={() => {
208
208
  setSearchTerm("");
209
209
  search("");