@multiplatform.one/backoffice 7.28.1 → 7.29.1

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 (269) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/desk/DeskSurface.native.js.map +1 -1
  3. package/dist/cjs/form/DocFormScreen.cjs +167 -224
  4. package/dist/cjs/form/DocFormScreen.native.js +182 -259
  5. package/dist/cjs/form/DocFormScreen.native.js.map +1 -1
  6. package/dist/cjs/i18n/en.cjs +1 -0
  7. package/dist/cjs/i18n/en.native.js +1 -0
  8. package/dist/cjs/i18n/en.native.js.map +1 -1
  9. package/dist/cjs/list/ListBulkActionsBar.cjs +30 -18
  10. package/dist/cjs/list/ListBulkActionsBar.native.js +43 -31
  11. package/dist/cjs/list/ListBulkActionsBar.native.js.map +1 -1
  12. package/dist/cjs/list/sidebar-section.cjs +15 -7
  13. package/dist/cjs/list/sidebar-section.native.js +16 -7
  14. package/dist/cjs/list/sidebar-section.native.js.map +1 -1
  15. package/dist/cjs/report/DoctypeReportView.cjs +20 -374
  16. package/dist/cjs/report/DoctypeReportView.native.js +23 -455
  17. package/dist/cjs/report/DoctypeReportView.native.js.map +1 -1
  18. package/dist/cjs/report/QueryReportScreen.cjs +33 -23
  19. package/dist/cjs/report/QueryReportScreen.native.js +33 -23
  20. package/dist/cjs/report/QueryReportScreen.native.js.map +1 -1
  21. package/dist/cjs/report/queryReportColumns.cjs +3 -105
  22. package/dist/cjs/report/queryReportColumns.native.js +2 -129
  23. package/dist/cjs/report/queryReportColumns.native.js.map +1 -1
  24. package/dist/cjs/report/queryReportFilterBar.cjs +3 -4
  25. package/dist/cjs/report/queryReportFilterBar.native.js +3 -4
  26. package/dist/cjs/report/queryReportFilterBar.native.js.map +1 -1
  27. package/dist/cjs/report/queryReportUrlState.cjs +2 -2
  28. package/dist/cjs/report/queryReportUrlState.native.js +2 -2
  29. package/dist/cjs/report/queryReportUrlState.native.js.map +1 -1
  30. package/dist/cjs/report/report-grid.cjs +2 -426
  31. package/dist/cjs/report/report-grid.native.js +1 -479
  32. package/dist/cjs/report/report-grid.native.js.map +1 -1
  33. package/dist/cjs/report/report-toolbar.cjs +2 -228
  34. package/dist/cjs/report/report-toolbar.native.js +1 -274
  35. package/dist/cjs/report/report-toolbar.native.js.map +1 -1
  36. package/dist/cjs/report/reportShapes.cjs +12 -383
  37. package/dist/cjs/report/reportShapes.native.js +12 -501
  38. package/dist/cjs/report/reportShapes.native.js.map +1 -1
  39. package/dist/cjs/report/reportViewModel.cjs +20 -360
  40. package/dist/cjs/report/reportViewModel.native.js +19 -627
  41. package/dist/cjs/report/reportViewModel.native.js.map +1 -1
  42. package/dist/cjs/report/savedReports.cjs +2 -106
  43. package/dist/cjs/report/savedReports.native.js +1 -132
  44. package/dist/cjs/report/savedReports.native.js.map +1 -1
  45. package/dist/cjs/report/useQueryReport.cjs +11 -219
  46. package/dist/cjs/report/useQueryReport.native.js +11 -244
  47. package/dist/cjs/report/useQueryReport.native.js.map +1 -1
  48. package/dist/cjs/shared/measures.cjs +3 -5
  49. package/dist/cjs/shared/measures.native.js +3 -5
  50. package/dist/cjs/shared/measures.native.js.map +1 -1
  51. package/dist/cjs/views/ReportView.cjs +3 -256
  52. package/dist/cjs/views/ReportView.native.js +3 -382
  53. package/dist/cjs/views/ReportView.native.js.map +1 -1
  54. package/dist/cjs/views/report-controls.cjs +6 -6
  55. package/dist/cjs/views/report-controls.native.js +6 -6
  56. package/dist/cjs/views/report-controls.native.js.map +1 -1
  57. package/dist/esm/desk/DeskSurface.mjs.map +1 -1
  58. package/dist/esm/desk/DeskSurface.native.js.map +1 -1
  59. package/dist/esm/form/DocFormScreen.mjs +169 -226
  60. package/dist/esm/form/DocFormScreen.mjs.map +1 -1
  61. package/dist/esm/form/DocFormScreen.native.js +184 -261
  62. package/dist/esm/form/DocFormScreen.native.js.map +1 -1
  63. package/dist/esm/i18n/en.mjs +1 -0
  64. package/dist/esm/i18n/en.mjs.map +1 -1
  65. package/dist/esm/i18n/en.native.js +1 -0
  66. package/dist/esm/i18n/en.native.js.map +1 -1
  67. package/dist/esm/list/ListBulkActionsBar.mjs +31 -19
  68. package/dist/esm/list/ListBulkActionsBar.mjs.map +1 -1
  69. package/dist/esm/list/ListBulkActionsBar.native.js +37 -25
  70. package/dist/esm/list/ListBulkActionsBar.native.js.map +1 -1
  71. package/dist/esm/list/sidebar-section.mjs +16 -8
  72. package/dist/esm/list/sidebar-section.mjs.map +1 -1
  73. package/dist/esm/list/sidebar-section.native.js +17 -8
  74. package/dist/esm/list/sidebar-section.native.js.map +1 -1
  75. package/dist/esm/report/DoctypeReportView.mjs +21 -375
  76. package/dist/esm/report/DoctypeReportView.mjs.map +1 -1
  77. package/dist/esm/report/DoctypeReportView.native.js +24 -456
  78. package/dist/esm/report/DoctypeReportView.native.js.map +1 -1
  79. package/dist/esm/report/QueryReportScreen.mjs +26 -16
  80. package/dist/esm/report/QueryReportScreen.mjs.map +1 -1
  81. package/dist/esm/report/QueryReportScreen.native.js +26 -16
  82. package/dist/esm/report/QueryReportScreen.native.js.map +1 -1
  83. package/dist/esm/report/queryReportColumns.mjs +1 -103
  84. package/dist/esm/report/queryReportColumns.mjs.map +1 -1
  85. package/dist/esm/report/queryReportColumns.native.js +1 -117
  86. package/dist/esm/report/queryReportColumns.native.js.map +1 -1
  87. package/dist/esm/report/queryReportFilterBar.mjs +1 -2
  88. package/dist/esm/report/queryReportFilterBar.mjs.map +1 -1
  89. package/dist/esm/report/queryReportFilterBar.native.js +1 -2
  90. package/dist/esm/report/queryReportFilterBar.native.js.map +1 -1
  91. package/dist/esm/report/queryReportUrlState.mjs +1 -1
  92. package/dist/esm/report/queryReportUrlState.native.js +1 -1
  93. package/dist/esm/report/report-grid.mjs +1 -425
  94. package/dist/esm/report/report-grid.mjs.map +1 -1
  95. package/dist/esm/report/report-grid.native.js +1 -468
  96. package/dist/esm/report/report-grid.native.js.map +1 -1
  97. package/dist/esm/report/report-toolbar.mjs +2 -228
  98. package/dist/esm/report/report-toolbar.mjs.map +1 -1
  99. package/dist/esm/report/report-toolbar.native.js +2 -264
  100. package/dist/esm/report/report-toolbar.native.js.map +1 -1
  101. package/dist/esm/report/reportShapes.mjs +2 -373
  102. package/dist/esm/report/reportShapes.mjs.map +1 -1
  103. package/dist/esm/report/reportShapes.native.js +2 -491
  104. package/dist/esm/report/reportShapes.native.js.map +1 -1
  105. package/dist/esm/report/reportViewModel.mjs +2 -342
  106. package/dist/esm/report/reportViewModel.mjs.map +1 -1
  107. package/dist/esm/report/reportViewModel.native.js +2 -610
  108. package/dist/esm/report/reportViewModel.native.js.map +1 -1
  109. package/dist/esm/report/savedReports.mjs +1 -105
  110. package/dist/esm/report/savedReports.mjs.map +1 -1
  111. package/dist/esm/report/savedReports.native.js +1 -132
  112. package/dist/esm/report/savedReports.native.js.map +1 -1
  113. package/dist/esm/report/useQueryReport.mjs +2 -210
  114. package/dist/esm/report/useQueryReport.mjs.map +1 -1
  115. package/dist/esm/report/useQueryReport.native.js +2 -235
  116. package/dist/esm/report/useQueryReport.native.js.map +1 -1
  117. package/dist/esm/shared/measures.mjs +1 -3
  118. package/dist/esm/shared/measures.mjs.map +1 -1
  119. package/dist/esm/shared/measures.native.js +1 -3
  120. package/dist/esm/shared/measures.native.js.map +1 -1
  121. package/dist/esm/views/ReportView.mjs +3 -256
  122. package/dist/esm/views/ReportView.mjs.map +1 -1
  123. package/dist/esm/views/ReportView.native.js +3 -382
  124. package/dist/esm/views/ReportView.native.js.map +1 -1
  125. package/dist/esm/views/report-controls.mjs +3 -3
  126. package/dist/esm/views/report-controls.mjs.map +1 -1
  127. package/dist/esm/views/report-controls.native.js +3 -3
  128. package/dist/esm/views/report-controls.native.js.map +1 -1
  129. package/dist/jsx/desk/DeskSurface.mjs.map +1 -1
  130. package/dist/jsx/desk/DeskSurface.native.js.map +1 -1
  131. package/dist/jsx/form/DocFormScreen.mjs +169 -226
  132. package/dist/jsx/form/DocFormScreen.mjs.map +1 -1
  133. package/dist/jsx/form/DocFormScreen.native.js +182 -259
  134. package/dist/jsx/form/DocFormScreen.native.js.map +1 -1
  135. package/dist/jsx/i18n/en.mjs +1 -0
  136. package/dist/jsx/i18n/en.mjs.map +1 -1
  137. package/dist/jsx/i18n/en.native.js +1 -0
  138. package/dist/jsx/i18n/en.native.js.map +1 -1
  139. package/dist/jsx/list/ListBulkActionsBar.mjs +31 -19
  140. package/dist/jsx/list/ListBulkActionsBar.mjs.map +1 -1
  141. package/dist/jsx/list/ListBulkActionsBar.native.js +43 -31
  142. package/dist/jsx/list/ListBulkActionsBar.native.js.map +1 -1
  143. package/dist/jsx/list/sidebar-section.mjs +16 -8
  144. package/dist/jsx/list/sidebar-section.mjs.map +1 -1
  145. package/dist/jsx/list/sidebar-section.native.js +16 -7
  146. package/dist/jsx/list/sidebar-section.native.js.map +1 -1
  147. package/dist/jsx/report/DoctypeReportView.mjs +21 -375
  148. package/dist/jsx/report/DoctypeReportView.mjs.map +1 -1
  149. package/dist/jsx/report/DoctypeReportView.native.js +23 -455
  150. package/dist/jsx/report/DoctypeReportView.native.js.map +1 -1
  151. package/dist/jsx/report/QueryReportScreen.mjs +26 -16
  152. package/dist/jsx/report/QueryReportScreen.mjs.map +1 -1
  153. package/dist/jsx/report/QueryReportScreen.native.js +33 -23
  154. package/dist/jsx/report/QueryReportScreen.native.js.map +1 -1
  155. package/dist/jsx/report/queryReportColumns.mjs +1 -103
  156. package/dist/jsx/report/queryReportColumns.mjs.map +1 -1
  157. package/dist/jsx/report/queryReportColumns.native.js +2 -129
  158. package/dist/jsx/report/queryReportColumns.native.js.map +1 -1
  159. package/dist/jsx/report/queryReportFilterBar.mjs +1 -2
  160. package/dist/jsx/report/queryReportFilterBar.mjs.map +1 -1
  161. package/dist/jsx/report/queryReportFilterBar.native.js +3 -4
  162. package/dist/jsx/report/queryReportFilterBar.native.js.map +1 -1
  163. package/dist/jsx/report/queryReportUrlState.mjs +1 -1
  164. package/dist/jsx/report/queryReportUrlState.native.js +2 -2
  165. package/dist/jsx/report/queryReportUrlState.native.js.map +1 -1
  166. package/dist/jsx/report/report-grid.mjs +1 -425
  167. package/dist/jsx/report/report-grid.mjs.map +1 -1
  168. package/dist/jsx/report/report-grid.native.js +1 -479
  169. package/dist/jsx/report/report-grid.native.js.map +1 -1
  170. package/dist/jsx/report/report-toolbar.mjs +2 -228
  171. package/dist/jsx/report/report-toolbar.mjs.map +1 -1
  172. package/dist/jsx/report/report-toolbar.native.js +1 -274
  173. package/dist/jsx/report/report-toolbar.native.js.map +1 -1
  174. package/dist/jsx/report/reportShapes.mjs +2 -373
  175. package/dist/jsx/report/reportShapes.mjs.map +1 -1
  176. package/dist/jsx/report/reportShapes.native.js +12 -501
  177. package/dist/jsx/report/reportShapes.native.js.map +1 -1
  178. package/dist/jsx/report/reportViewModel.mjs +2 -342
  179. package/dist/jsx/report/reportViewModel.mjs.map +1 -1
  180. package/dist/jsx/report/reportViewModel.native.js +19 -627
  181. package/dist/jsx/report/reportViewModel.native.js.map +1 -1
  182. package/dist/jsx/report/savedReports.mjs +1 -105
  183. package/dist/jsx/report/savedReports.mjs.map +1 -1
  184. package/dist/jsx/report/savedReports.native.js +1 -132
  185. package/dist/jsx/report/savedReports.native.js.map +1 -1
  186. package/dist/jsx/report/useQueryReport.mjs +2 -210
  187. package/dist/jsx/report/useQueryReport.mjs.map +1 -1
  188. package/dist/jsx/report/useQueryReport.native.js +11 -244
  189. package/dist/jsx/report/useQueryReport.native.js.map +1 -1
  190. package/dist/jsx/shared/measures.mjs +1 -3
  191. package/dist/jsx/shared/measures.mjs.map +1 -1
  192. package/dist/jsx/shared/measures.native.js +3 -5
  193. package/dist/jsx/shared/measures.native.js.map +1 -1
  194. package/dist/jsx/views/ReportView.mjs +3 -256
  195. package/dist/jsx/views/ReportView.mjs.map +1 -1
  196. package/dist/jsx/views/ReportView.native.js +3 -382
  197. package/dist/jsx/views/ReportView.native.js.map +1 -1
  198. package/dist/jsx/views/report-controls.mjs +3 -3
  199. package/dist/jsx/views/report-controls.mjs.map +1 -1
  200. package/dist/jsx/views/report-controls.native.js +6 -6
  201. package/dist/jsx/views/report-controls.native.js.map +1 -1
  202. package/package.json +12 -12
  203. package/src/backofficeScreens.designLaw.spec.ts +0 -5
  204. package/src/desk/DeskSurface.tsx +4 -7
  205. package/src/form/DocFormScreen.chrome.spec.ts +77 -0
  206. package/src/form/DocFormScreen.guest.screen.spec.tsx +175 -0
  207. package/src/form/DocFormScreen.rights.spec.tsx +7 -3
  208. package/src/form/DocFormScreen.tsx +214 -315
  209. package/src/i18n/en.ts +1 -0
  210. package/src/list/ListBulkActionsBar.rights.spec.tsx +134 -0
  211. package/src/list/ListBulkActionsBar.screen.spec.tsx +7 -1
  212. package/src/list/ListBulkActionsBar.tsx +76 -57
  213. package/src/list/ListFilterBar.screen.spec.tsx +14 -13
  214. package/src/list/sidebar-section.tsx +11 -3
  215. package/src/report/DoctypeReportView.tsx +28 -550
  216. package/src/report/QueryReportScreen.spec.tsx +75 -1
  217. package/src/report/QueryReportScreen.tsx +38 -22
  218. package/src/report/queryReportColumns.tsx +4 -123
  219. package/src/report/queryReportFilterBar.tsx +3 -5
  220. package/src/report/queryReportUrlState.ts +1 -1
  221. package/src/report/report-grid.tsx +4 -529
  222. package/src/report/report-toolbar.tsx +4 -308
  223. package/src/report/reportShapes.ts +25 -613
  224. package/src/report/reportShim.spec.ts +162 -0
  225. package/src/report/reportViewModel.ts +32 -605
  226. package/src/report/savedReports.ts +4 -161
  227. package/src/report/useQueryReport.ts +9 -347
  228. package/src/shared/measures.ts +3 -11
  229. package/src/views/ReportView.screen.spec.tsx +25 -20
  230. package/src/views/ReportView.tsx +7 -439
  231. package/src/views/report-controls.tsx +12 -20
  232. package/types/desk/DeskSurface.d.ts.map +1 -1
  233. package/types/form/DocFormScreen.d.ts +2 -2
  234. package/types/form/DocFormScreen.d.ts.map +1 -1
  235. package/types/i18n/en.d.ts +1 -0
  236. package/types/i18n/en.d.ts.map +1 -1
  237. package/types/list/ListBulkActionsBar.d.ts.map +1 -1
  238. package/types/list/sidebar-section.d.ts.map +1 -1
  239. package/types/report/DoctypeReportView.d.ts.map +1 -1
  240. package/types/report/QueryReportScreen.d.ts +1 -1
  241. package/types/report/QueryReportScreen.d.ts.map +1 -1
  242. package/types/report/queryReportColumns.d.ts +2 -11
  243. package/types/report/queryReportColumns.d.ts.map +1 -1
  244. package/types/report/queryReportFilterBar.d.ts +1 -1
  245. package/types/report/queryReportFilterBar.d.ts.map +1 -1
  246. package/types/report/queryReportUrlState.d.ts +1 -1
  247. package/types/report/queryReportUrlState.d.ts.map +1 -1
  248. package/types/report/report-grid.d.ts +2 -19
  249. package/types/report/report-grid.d.ts.map +1 -1
  250. package/types/report/report-toolbar.d.ts +2 -29
  251. package/types/report/report-toolbar.d.ts.map +1 -1
  252. package/types/report/reportShapes.d.ts +2 -115
  253. package/types/report/reportShapes.d.ts.map +1 -1
  254. package/types/report/reportViewModel.d.ts +2 -135
  255. package/types/report/reportViewModel.d.ts.map +1 -1
  256. package/types/report/savedReports.d.ts +2 -18
  257. package/types/report/savedReports.d.ts.map +1 -1
  258. package/types/report/useQueryReport.d.ts +2 -35
  259. package/types/report/useQueryReport.d.ts.map +1 -1
  260. package/types/shared/measures.d.ts +1 -2
  261. package/types/shared/measures.d.ts.map +1 -1
  262. package/types/views/ReportView.d.ts +3 -22
  263. package/types/views/ReportView.d.ts.map +1 -1
  264. package/types/views/report-controls.d.ts +2 -5
  265. package/types/views/report-controls.d.ts.map +1 -1
  266. package/src/report/report-grid.spec.tsx +0 -175
  267. package/src/report/report-toolbar.spec.tsx +0 -90
  268. package/src/report/reportShapes.spec.ts +0 -391
  269. package/src/report/reportViewModel.spec.ts +0 -393
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/backoffice",
3
- "version": "7.28.1",
3
+ "version": "7.29.1",
4
4
  "description": "Embeddable Frappe desk recreation (backoffice) for the multiplatform.one stack",
5
5
  "keywords": [
6
6
  "backoffice",
@@ -71,21 +71,21 @@
71
71
  "access": "public"
72
72
  },
73
73
  "dependencies": {
74
- "@multiplatform.one/components": "7.28.1",
75
- "@multiplatform.one/forms": "7.28.1",
76
- "@multiplatform.one/frappe": "7.28.1",
77
- "@multiplatform.one/frappe-ui": "7.28.1",
78
- "@multiplatform.one/i18n": "7.28.1",
79
- "@multiplatform.one/platform": "7.28.1",
80
- "@multiplatform.one/store": "7.28.1",
81
- "@multiplatform.one/table": "7.28.1",
82
- "@multiplatform.one/theme": "7.28.1",
74
+ "@multiplatform.one/components": "7.29.1",
75
+ "@multiplatform.one/forms": "7.29.1",
76
+ "@multiplatform.one/frappe": "7.29.1",
77
+ "@multiplatform.one/frappe-ui": "7.29.1",
78
+ "@multiplatform.one/i18n": "7.29.1",
79
+ "@multiplatform.one/platform": "7.29.1",
80
+ "@multiplatform.one/store": "7.29.1",
81
+ "@multiplatform.one/table": "7.29.1",
82
+ "@multiplatform.one/theme": "7.29.1",
83
83
  "@phosphor-icons/react": "^2.1.10",
84
84
  "tamagui": "2.7.6"
85
85
  },
86
86
  "devDependencies": {
87
- "@multiplatform.one/config": "7.28.1",
88
- "@multiplatform.one/test-utils": "7.28.1",
87
+ "@multiplatform.one/config": "7.29.1",
88
+ "@multiplatform.one/test-utils": "7.29.1",
89
89
  "@tamagui/build": "2.7.6",
90
90
  "@testing-library/react": "^16.3.2",
91
91
  "@types/react-dom": "^19.2.3",
@@ -79,11 +79,6 @@ describe("Backoffice screens — design-law 100%", () => {
79
79
  expect(source).not.toContain("toLocaleString");
80
80
  });
81
81
 
82
- it("report toolbar filters are catalog Chip, not 999-radius wells", () => {
83
- expect(read("report/report-toolbar.tsx")).toContain("<Chip");
84
- expect(read("report/report-toolbar.tsx")).not.toMatch(/borderRadius=\{999\}/);
85
- });
86
-
87
82
  it("Workspace shortcut counts are catalog Chip; labels and titles weight 400", () => {
88
83
  const shortcut = read("workspace/WorkspaceShortcut.tsx");
89
84
  expect(shortcut).toContain("<Chip");
@@ -110,13 +110,10 @@ export function DeskSurface({
110
110
  <NewDocSurface doctype={target.doctype} defaults={target.defaults} readOnly={readOnly} />
111
111
  );
112
112
  case "form":
113
- // MPO-15: DocFormScreen is the strong form today. The engine
114
- // (formLayout + useFormEngine) lives in frappe-ui and FrappeForm now
115
- // reads the one lossless meta model, but it still maps fields itself
116
- // instead of through buildFormTabs, so it does not render permlevel,
117
- // fetch_from, set_only_once or the *_depends_on chains yet. When it
118
- // does, the exported FrappeForm becomes this branch's renderer. Swap
119
- // here, nowhere else.
113
+ // DocFormScreen is desk chrome over FrappeForm (MPO-15): the fields
114
+ // are the exported FrappeForm's, on the controller useFrappeForm
115
+ // returns, so this branch and a bare <FrappeForm> resolve a doctype
116
+ // the same way.
120
117
  return <DocFormScreen doctype={target.doctype} name={target.name} readOnly={readOnly} />;
121
118
  case "report":
122
119
  return <QueryReportScreen reportName={target.reportName} />;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * MPO-15 AC2: DocFormScreen is page chrome over FrappeForm.
3
+ *
4
+ * The desk form page owns its toolbar, menu, dialogs, sidebar, timeline and
5
+ * banners. Which fields render, which are required, read-only or hidden, and
6
+ * how they lay out into tabs is FrappeForm's job, so the exported component
7
+ * and the desk can never resolve the same doctype differently again. Any
8
+ * DocField key name in the page, or an import of the layout engine, means the
9
+ * page has started resolving fields of its own.
10
+ */
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { describe, expect, it } from "vitest";
14
+
15
+ const source = fs.readFileSync(path.resolve(__dirname, "DocFormScreen.tsx"), "utf8");
16
+
17
+ const docFieldKeys = [
18
+ "fieldname",
19
+ "fieldtype",
20
+ "permlevel",
21
+ "depends_on",
22
+ "mandatory_depends_on",
23
+ "read_only_depends_on",
24
+ "collapsible_depends_on",
25
+ "set_only_once",
26
+ "fetch_from",
27
+ "reqd",
28
+ "hidden",
29
+ "read_only",
30
+ "allow_on_submit",
31
+ "in_list_view",
32
+ "options",
33
+ ];
34
+
35
+ const fieldResolution = [
36
+ "buildFormTabs",
37
+ "buildPermLevelMatrix",
38
+ "buildCopyValues",
39
+ "buildCreateDefaults",
40
+ "isPromptNamed",
41
+ "SchemaForm",
42
+ "useFormEngine",
43
+ "sessionDocRights",
44
+ ];
45
+
46
+ function importedNames(from: string): string[] {
47
+ const names: string[] = [];
48
+ const pattern = /import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+"([^"]+)"/g;
49
+ for (const match of source.matchAll(pattern)) {
50
+ if (match[2] !== from) continue;
51
+ for (const part of match[1].split(",")) {
52
+ const name = part
53
+ .trim()
54
+ .replace(/^type\s+/, "")
55
+ .split(/\s+as\s+/)[0];
56
+ if (name) names.push(name);
57
+ }
58
+ }
59
+ return names;
60
+ }
61
+
62
+ describe("DocFormScreen is chrome over FrappeForm (MPO-15 AC2)", () => {
63
+ it.each(docFieldKeys)("names no DocField key: %s", (key) => {
64
+ expect(source).not.toMatch(new RegExp(`\\b${key}\\b`));
65
+ });
66
+
67
+ it.each(fieldResolution)("does not resolve fields itself: %s", (name) => {
68
+ expect(source).not.toMatch(new RegExp(`\\b${name}\\b`));
69
+ });
70
+
71
+ it("renders the field body through FrappeForm and its controller", () => {
72
+ const frappeUi = importedNames("@multiplatform.one/frappe-ui");
73
+ expect(frappeUi).toContain("FrappeForm");
74
+ expect(frappeUi).toContain("useFrappeForm");
75
+ expect(source).toMatch(/<FrappeForm(?:<\w+>)?\s[^>]*\bform=\{form\}/s);
76
+ });
77
+ });
@@ -0,0 +1,175 @@
1
+ // LC-96 on the desk form (MPO-473). Signed out, pokemon's bench refuses
2
+ // getdoctype to Guest while Guest can still read the record, and the desk form
3
+ // stopped at its own "Could not load Pokemon" before FrappeForm could lay the
4
+ // record out. The bench below answers getdoctype the way Frappe's
5
+ // is_whitelisted refuses Guest (one message, a <details> summary and body) and
6
+ // answers or refuses the record per test.
7
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
8
+ import { fireEvent, screen, waitFor } from "@testing-library/react";
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
10
+ import { BackofficeProvider } from "../config/BackofficeProvider";
11
+ import { createMemoryNavigator } from "../navigation/navigator";
12
+ import { DocFormScreen } from "./DocFormScreen";
13
+
14
+ const doctype = "Poke Move";
15
+
16
+ const tackle = {
17
+ name: "a1b2c3",
18
+ doctype,
19
+ owner: "Administrator",
20
+ modified: "2026-09-29 10:00:00",
21
+ docstatus: 0,
22
+ idx: 0,
23
+ move_name: "tackle",
24
+ power: 40,
25
+ };
26
+
27
+ const refusal = {
28
+ message:
29
+ "<details><summary>You are not permitted to access this resource. Login to access</summary>Function <strong>frappe.desk.form.load.getdoctype</strong> is not whitelisted.</details>",
30
+ title: "Method Not Allowed",
31
+ indicator: "red",
32
+ raise_exception: 1,
33
+ };
34
+
35
+ const settle = { timeout: 8000 };
36
+
37
+ let run = 0;
38
+ let metaAnswer: "refused" | "broken" = "refused";
39
+ let recordReadable = true;
40
+ let metaReads = 0;
41
+
42
+ function json(body: unknown, status = 200) {
43
+ return new Response(JSON.stringify(body), {
44
+ status,
45
+ headers: { "content-type": "application/json" },
46
+ });
47
+ }
48
+
49
+ function refused() {
50
+ return json(
51
+ {
52
+ exc_type: "PermissionError",
53
+ exception: `frappe.exceptions.PermissionError: ${refusal.message}`,
54
+ _server_messages: JSON.stringify([JSON.stringify(refusal)]),
55
+ },
56
+ 403,
57
+ );
58
+ }
59
+
60
+ beforeEach(() => {
61
+ run += 1;
62
+ metaAnswer = "refused";
63
+ recordReadable = true;
64
+ metaReads = 0;
65
+ vi.stubGlobal("fetch", async (input: string | URL | Request) => {
66
+ const url = new URL(String(input instanceof Request ? input.url : input));
67
+ const method = url.pathname.replace(/^\/api\/method\//, "");
68
+ if (method === "frappe.desk.form.load.getdoctype") {
69
+ metaReads += 1;
70
+ return metaAnswer === "refused"
71
+ ? refused()
72
+ : json({ exc_type: "ValidationError", exception: "Poke Move is being migrated" }, 417);
73
+ }
74
+ if (method.startsWith("frappe.")) return refused();
75
+ const resource = `/api/resource/${encodeURIComponent(doctype)}`;
76
+ if (url.pathname === resource || url.pathname.startsWith(`${resource}/`)) {
77
+ if (!recordReadable) {
78
+ return json({ exc_type: "PermissionError", exception: "Not permitted" }, 403);
79
+ }
80
+ return url.pathname === resource ? json({ data: [tackle] }) : json({ data: tackle });
81
+ }
82
+ return json({ data: [], message: [] });
83
+ });
84
+ });
85
+
86
+ afterEach(() => {
87
+ vi.restoreAllMocks();
88
+ vi.unstubAllGlobals();
89
+ });
90
+
91
+ function renderForm(name: string) {
92
+ return renderWithProviders(
93
+ <BackofficeProvider
94
+ frappe={{ baseURL: `https://desk-form-guest-${run}.spec.test` }}
95
+ navigation={createMemoryNavigator("/backoffice")}
96
+ >
97
+ <DocFormScreen doctype={doctype} name={name} />
98
+ </BackofficeProvider>,
99
+ );
100
+ }
101
+
102
+ function inputOf(label: string): HTMLInputElement {
103
+ const field = screen.getByLabelText(label, { exact: false });
104
+ return (
105
+ field instanceof HTMLInputElement ? field : field.querySelector("input")
106
+ ) as HTMLInputElement;
107
+ }
108
+
109
+ function button(label: string): HTMLElement | undefined {
110
+ return Array.from(document.querySelectorAll('[role="button"], button')).find(
111
+ (node) => node.textContent?.trim() === label,
112
+ ) as HTMLElement | undefined;
113
+ }
114
+
115
+ describe("DocFormScreen with DocType meta refused (LC-96)", () => {
116
+ it("lays out the record's own fields read-only instead of stopping at 'Could not load'", async () => {
117
+ renderForm("a1b2c3");
118
+ await waitFor(() => expect(inputOf("Power").value).toBe("40"), settle);
119
+ expect(inputOf("Power").disabled || inputOf("Power").readOnly).toBe(true);
120
+ expect(inputOf("Move Name").value).toBe("tackle");
121
+ expect(screen.queryByText(/Could not load/)).toBeNull();
122
+ expect(screen.queryByLabelText("Owner", { exact: true })).toBeNull();
123
+ expect(button("Save")).toBeUndefined();
124
+ expect(metaReads).toBe(1);
125
+ });
126
+
127
+ it("offers nothing that needs the refused meta or docinfo", async () => {
128
+ renderForm("a1b2c3");
129
+ await waitFor(() => expect(inputOf("Power").value).toBe("40"), settle);
130
+ expect(screen.queryByText("Activity")).toBeNull();
131
+ fireEvent.click(screen.getByTestId("form-toolbar-menu"));
132
+ await waitFor(
133
+ () => expect(document.querySelectorAll('[role="menuitem"]').length).toBeGreaterThan(0),
134
+ settle,
135
+ );
136
+ const items = Array.from(document.querySelectorAll('[role="menuitem"]')).map(
137
+ (node) => node.textContent ?? "",
138
+ );
139
+ expect(items.some((label) => label.startsWith("Duplicate"))).toBe(false);
140
+ expect(items.some((label) => label.startsWith("Delete"))).toBe(false);
141
+ });
142
+
143
+ it("says the reader can't open a record refused too, with no retry", async () => {
144
+ recordReadable = false;
145
+ renderForm("a1b2c3");
146
+ const state = await screen.findByTestId("frappe-form-not-permitted", {}, settle);
147
+ expect(state.textContent).toContain(`You can't open ${doctype} a1b2c3`);
148
+ expect(screen.queryByText(/Could not load/)).toBeNull();
149
+ expect(screen.queryByRole("button", { name: /retry/i })).toBeNull();
150
+ });
151
+
152
+ it("says the reader can't create, with each server message on its own line", async () => {
153
+ renderForm("new");
154
+ const state = await screen.findByTestId("frappe-form-not-permitted", {}, settle);
155
+ expect(state.textContent).toContain(`You can't create ${doctype}`);
156
+ const lines = Array.from(state.querySelectorAll("p")).map((node) => node.textContent);
157
+ expect(lines).toContain("You are not permitted to access this resource. Login to access");
158
+ expect(lines).toContain("Function frappe.desk.form.load.getdoctype is not whitelisted.");
159
+ expect(lines.some((line) => line?.includes("accessFunction"))).toBe(false);
160
+ expect(button("Save")).toBeUndefined();
161
+ });
162
+
163
+ it("keeps a meta read that failed for another reason a retryable error", async () => {
164
+ metaAnswer = "broken";
165
+ renderForm("a1b2c3");
166
+ await waitFor(
167
+ () => expect(screen.getByText(/Poke Move is being migrated/)).toBeTruthy(),
168
+ settle,
169
+ );
170
+ expect(screen.queryByTestId("frappe-form-not-permitted")).toBeNull();
171
+ const retry = screen.getByRole("button", { name: /retry/i });
172
+ fireEvent.click(retry);
173
+ await waitFor(() => expect(metaReads).toBe(2), settle);
174
+ });
175
+ });
@@ -2,6 +2,8 @@
2
2
  // usable permissions (a new record, or a token-auth bench answering
3
3
  // {"null": 0}) the screen used to union every role's DocPerm rows, so any
4
4
  // reader got an editable form and Save. It now resolves the session's roles.
5
+ // The form body is FrappeForm's (MPO-15 AC2), so a reader asking for a new
6
+ // record meets FrappeForm's not-permitted state, as desk refuses new_doc.
5
7
  import { renderWithProviders } from "@multiplatform.one/test-utils";
6
8
  import { act, screen, waitFor } from "@testing-library/react";
7
9
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -111,13 +113,15 @@ describe("DocFormScreen rights without docinfo permissions", () => {
111
113
  expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(false);
112
114
  });
113
115
 
114
- it("gives a reader a read-only new record with no Save", async () => {
116
+ it("tells a reader they can't create one, with no Save", async () => {
115
117
  roles = ["Guest"];
116
118
  renderForm("new");
117
- await waitFor(() => expect(moveNameInput()).toBeTruthy(), { timeout: 5000 });
119
+ await waitFor(() => expect(screen.getByTestId("frappe-form-not-permitted")).toBeTruthy(), {
120
+ timeout: 5000,
121
+ });
118
122
  await settle();
119
123
  expect(saveButton()).toBeUndefined();
120
- expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(true);
124
+ expect(screen.queryByLabelText("Move Name", { exact: false })).toBeNull();
121
125
  });
122
126
 
123
127
  it("gives a reader an existing record read-only", async () => {