@multiplatform.one/backoffice 7.20.0 → 7.21.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 (194) hide show
  1. package/dist/cjs/form/DocFormScreen.cjs +27 -4
  2. package/dist/cjs/form/DocFormScreen.native.js +28 -5
  3. package/dist/cjs/form/DocFormScreen.native.js.map +1 -1
  4. package/dist/cjs/list/DoctypeListScreen.cjs +18 -10
  5. package/dist/cjs/list/DoctypeListScreen.native.js +21 -11
  6. package/dist/cjs/list/DoctypeListScreen.native.js.map +1 -1
  7. package/dist/cjs/list/listFields.cjs +1 -1
  8. package/dist/cjs/list/listFields.native.js +1 -1
  9. package/dist/cjs/list/listFields.native.js.map +1 -1
  10. package/dist/cjs/shared/useUserRoles.cjs +6 -1
  11. package/dist/cjs/shared/useUserRoles.native.js +6 -1
  12. package/dist/cjs/shared/useUserRoles.native.js.map +1 -1
  13. package/dist/esm/chrome/BackofficeAwesomebar.native.js.map +1 -1
  14. package/dist/esm/chrome/CommandPalette.native.js.map +1 -1
  15. package/dist/esm/chrome/HelpMenu.native.js.map +1 -1
  16. package/dist/esm/chrome/NotificationsButton.native.js.map +1 -1
  17. package/dist/esm/chrome/UserMenu.native.js.map +1 -1
  18. package/dist/esm/chrome/awesomebar-data.native.js.map +1 -1
  19. package/dist/esm/chrome/command-palette-model.native.js.map +1 -1
  20. package/dist/esm/chrome/help-about-dialog.native.js.map +1 -1
  21. package/dist/esm/chrome/help-shortcuts-dialog.native.js.map +1 -1
  22. package/dist/esm/chrome/notifications-data.native.js.map +1 -1
  23. package/dist/esm/chrome/notifications-panel.native.js.map +1 -1
  24. package/dist/esm/chrome/notifications-utils.native.js.map +1 -1
  25. package/dist/esm/chrome/shortcuts.native.js.map +1 -1
  26. package/dist/esm/chrome/user-session.native.js.map +1 -1
  27. package/dist/esm/config/BackofficeProvider.native.js.map +1 -1
  28. package/dist/esm/config/capabilities.native.js.map +1 -1
  29. package/dist/esm/config/events.native.js.map +1 -1
  30. package/dist/esm/config/slots.native.js.map +1 -1
  31. package/dist/esm/desk/Desk.native.js.map +1 -1
  32. package/dist/esm/desk/DeskSurface.native.js.map +1 -1
  33. package/dist/esm/desk/resolveDeskPath.native.js.map +1 -1
  34. package/dist/esm/desk/scope.native.js.map +1 -1
  35. package/dist/esm/desk/target.native.js.map +1 -1
  36. package/dist/esm/form/DocFormScreen.mjs +28 -5
  37. package/dist/esm/form/DocFormScreen.mjs.map +1 -1
  38. package/dist/esm/form/DocFormScreen.native.js +29 -6
  39. package/dist/esm/form/DocFormScreen.native.js.map +1 -1
  40. package/dist/esm/form/FormConnections.native.js.map +1 -1
  41. package/dist/esm/form/FormDialogs.native.js.map +1 -1
  42. package/dist/esm/form/FormErrorBoundary.native.js.map +1 -1
  43. package/dist/esm/form/FormHeaderCluster.native.js.map +1 -1
  44. package/dist/esm/form/FormSidebarPanel.native.js.map +1 -1
  45. package/dist/esm/form/FormTimeline.native.js.map +1 -1
  46. package/dist/esm/form/FormToolbar.native.js.map +1 -1
  47. package/dist/esm/form/connections-data.native.js.map +1 -1
  48. package/dist/esm/form/createPrefill.native.js.map +1 -1
  49. package/dist/esm/form/dependsOn.native.js.map +1 -1
  50. package/dist/esm/form/dirtyTracking.native.js.map +1 -1
  51. package/dist/esm/form/docinfo.native.js.map +1 -1
  52. package/dist/esm/form/fetchFrom.native.js.map +1 -1
  53. package/dist/esm/form/fieldPermissions.native.js.map +1 -1
  54. package/dist/esm/form/formActions.native.js.map +1 -1
  55. package/dist/esm/form/formLayout.native.js.map +1 -1
  56. package/dist/esm/form/platformHooks.native.js.map +1 -1
  57. package/dist/esm/form/sidebar-attachments.native.js.map +1 -1
  58. package/dist/esm/form/sidebar-dialogs.native.js.map +1 -1
  59. package/dist/esm/form/sidebar-people.native.js.map +1 -1
  60. package/dist/esm/form/sidebar-rpc.native.js.map +1 -1
  61. package/dist/esm/form/sidebar-utils.native.js.map +1 -1
  62. package/dist/esm/form/timeline-entries.native.js.map +1 -1
  63. package/dist/esm/form/timeline-utils.native.js.map +1 -1
  64. package/dist/esm/form/useFormEngine.native.js.map +1 -1
  65. package/dist/esm/i18n/en.native.js.map +1 -1
  66. package/dist/esm/i18n/index.native.js.map +1 -1
  67. package/dist/esm/index.native.js.map +1 -1
  68. package/dist/esm/list/AddTagsDialog.native.js.map +1 -1
  69. package/dist/esm/list/AssignToDialog.native.js.map +1 -1
  70. package/dist/esm/list/DoctypeListScreen.mjs +19 -11
  71. package/dist/esm/list/DoctypeListScreen.mjs.map +1 -1
  72. package/dist/esm/list/DoctypeListScreen.native.js +22 -12
  73. package/dist/esm/list/DoctypeListScreen.native.js.map +1 -1
  74. package/dist/esm/list/ListBulkActionsBar.native.js.map +1 -1
  75. package/dist/esm/list/ListFilterBar.native.js.map +1 -1
  76. package/dist/esm/list/ListSidebar.native.js.map +1 -1
  77. package/dist/esm/list/bulk-edit-dialog.native.js.map +1 -1
  78. package/dist/esm/list/bulk-export.native.js.map +1 -1
  79. package/dist/esm/list/bulk-submit-cancel.native.js.map +1 -1
  80. package/dist/esm/list/bulkActions.native.js.map +1 -1
  81. package/dist/esm/list/filter-chips.native.js.map +1 -1
  82. package/dist/esm/list/filter-editor.native.js.map +1 -1
  83. package/dist/esm/list/filter-fields.native.js.map +1 -1
  84. package/dist/esm/list/filter-saved.native.js.map +1 -1
  85. package/dist/esm/list/filter-sort.native.js.map +1 -1
  86. package/dist/esm/list/filter-standard.native.js.map +1 -1
  87. package/dist/esm/list/filterConversion.native.js.map +1 -1
  88. package/dist/esm/list/list-tools-sheet.native.js.map +1 -1
  89. package/dist/esm/list/listColumns.native.js.map +1 -1
  90. package/dist/esm/list/listFields.mjs +1 -1
  91. package/dist/esm/list/listFields.mjs.map +1 -1
  92. package/dist/esm/list/listFields.native.js +1 -1
  93. package/dist/esm/list/listFields.native.js.map +1 -1
  94. package/dist/esm/list/listFormat.native.js.map +1 -1
  95. package/dist/esm/list/listUrlState.native.js.map +1 -1
  96. package/dist/esm/list/sidebar-section.native.js.map +1 -1
  97. package/dist/esm/list/sidebar-stats.native.js.map +1 -1
  98. package/dist/esm/list/statusColors.native.js.map +1 -1
  99. package/dist/esm/navigation/navigator.native.js.map +1 -1
  100. package/dist/esm/navigation/paths.native.js.map +1 -1
  101. package/dist/esm/oneAdapter/index.native.js.map +1 -1
  102. package/dist/esm/quickEntry/QuickEntryDialog.native.js.map +1 -1
  103. package/dist/esm/quickEntry/quickEntryPlan.native.js.map +1 -1
  104. package/dist/esm/report/DoctypeReportView.native.js.map +1 -1
  105. package/dist/esm/report/QueryReportScreen.native.js.map +1 -1
  106. package/dist/esm/report/queryReportColumns.native.js.map +1 -1
  107. package/dist/esm/report/queryReportFilterBar.native.js.map +1 -1
  108. package/dist/esm/report/queryReportUrlState.native.js.map +1 -1
  109. package/dist/esm/report/report-grid.native.js.map +1 -1
  110. package/dist/esm/report/report-toolbar.native.js.map +1 -1
  111. package/dist/esm/report/reportShapes.native.js.map +1 -1
  112. package/dist/esm/report/reportViewModel.native.js.map +1 -1
  113. package/dist/esm/report/savedReports.native.js.map +1 -1
  114. package/dist/esm/report/useQueryReport.native.js.map +1 -1
  115. package/dist/esm/shared/deskLinks.native.js.map +1 -1
  116. package/dist/esm/shared/listNavContext.native.js.map +1 -1
  117. package/dist/esm/shared/types.native.js.map +1 -1
  118. package/dist/esm/shared/useDocinfo.native.js.map +1 -1
  119. package/dist/esm/shared/useDoctypeMeta.native.js.map +1 -1
  120. package/dist/esm/shared/useFrappeApp.native.js.map +1 -1
  121. package/dist/esm/shared/useRecentVisit.native.js.map +1 -1
  122. package/dist/esm/shared/useSessionUserId.native.js.map +1 -1
  123. package/dist/esm/shared/useSlugTarget.native.js.map +1 -1
  124. package/dist/esm/shared/useUserRoles.mjs +7 -2
  125. package/dist/esm/shared/useUserRoles.mjs.map +1 -1
  126. package/dist/esm/shared/useUserRoles.native.js +7 -2
  127. package/dist/esm/shared/useUserRoles.native.js.map +1 -1
  128. package/dist/esm/shell/BackofficeShell.native.js.map +1 -1
  129. package/dist/esm/shell/HomeRedirect.native.js.map +1 -1
  130. package/dist/esm/shell/NotFoundScreen.native.js.map +1 -1
  131. package/dist/esm/shell/SlugScreen.native.js.map +1 -1
  132. package/dist/esm/shell/WorkspaceSidebar.native.js.map +1 -1
  133. package/dist/esm/shell/useWorkspaces.native.js.map +1 -1
  134. package/dist/esm/shell/workspaceIcons.native.js.map +1 -1
  135. package/dist/esm/views/CalendarView.native.js.map +1 -1
  136. package/dist/esm/views/DashboardView.native.js.map +1 -1
  137. package/dist/esm/views/DoctypeViewScreen.native.js.map +1 -1
  138. package/dist/esm/views/ImageView.native.js.map +1 -1
  139. package/dist/esm/views/KanbanView.native.js.map +1 -1
  140. package/dist/esm/views/ReportView.native.js.map +1 -1
  141. package/dist/esm/views/TreeView.native.js.map +1 -1
  142. package/dist/esm/views/ViewHeader.native.js.map +1 -1
  143. package/dist/esm/views/ViewStates.native.js.map +1 -1
  144. package/dist/esm/views/ViewSwitcherMenu.native.js.map +1 -1
  145. package/dist/esm/views/calendar-field-selector.native.js.map +1 -1
  146. package/dist/esm/views/connection.native.js.map +1 -1
  147. package/dist/esm/views/dashboard-data.native.js.map +1 -1
  148. package/dist/esm/views/deskViewConfigs.native.js.map +1 -1
  149. package/dist/esm/views/kanban-board-controls.native.js.map +1 -1
  150. package/dist/esm/views/kanban-field-picker.native.js.map +1 -1
  151. package/dist/esm/views/kanbanBoardOps.native.js.map +1 -1
  152. package/dist/esm/views/report-controls.native.js.map +1 -1
  153. package/dist/esm/views/viewAvailability.native.js.map +1 -1
  154. package/dist/esm/views/viewMeta.native.js.map +1 -1
  155. package/dist/esm/workspace/WorkspaceChartBlock.native.js.map +1 -1
  156. package/dist/esm/workspace/WorkspaceGrid.native.js.map +1 -1
  157. package/dist/esm/workspace/WorkspaceLinkCard.native.js.map +1 -1
  158. package/dist/esm/workspace/WorkspaceLinkDirectory.native.js.map +1 -1
  159. package/dist/esm/workspace/WorkspaceNumberCard.native.js.map +1 -1
  160. package/dist/esm/workspace/WorkspaceQuickList.native.js.map +1 -1
  161. package/dist/esm/workspace/WorkspaceRecents.native.js.map +1 -1
  162. package/dist/esm/workspace/WorkspaceScreen.native.js.map +1 -1
  163. package/dist/esm/workspace/WorkspaceShortcut.native.js.map +1 -1
  164. package/dist/esm/workspace/parseContent.native.js.map +1 -1
  165. package/dist/esm/workspace/types.native.js.map +1 -1
  166. package/dist/esm/workspace/useDashboardChart.native.js.map +1 -1
  167. package/dist/esm/workspace/useWorkspaceDoc.native.js.map +1 -1
  168. package/dist/jsx/form/DocFormScreen.mjs +28 -5
  169. package/dist/jsx/form/DocFormScreen.mjs.map +1 -1
  170. package/dist/jsx/form/DocFormScreen.native.js +28 -5
  171. package/dist/jsx/form/DocFormScreen.native.js.map +1 -1
  172. package/dist/jsx/list/DoctypeListScreen.mjs +19 -11
  173. package/dist/jsx/list/DoctypeListScreen.mjs.map +1 -1
  174. package/dist/jsx/list/DoctypeListScreen.native.js +21 -11
  175. package/dist/jsx/list/DoctypeListScreen.native.js.map +1 -1
  176. package/dist/jsx/list/listFields.mjs +1 -1
  177. package/dist/jsx/list/listFields.mjs.map +1 -1
  178. package/dist/jsx/list/listFields.native.js +1 -1
  179. package/dist/jsx/shared/useUserRoles.mjs +7 -2
  180. package/dist/jsx/shared/useUserRoles.mjs.map +1 -1
  181. package/dist/jsx/shared/useUserRoles.native.js +6 -1
  182. package/dist/jsx/shared/useUserRoles.native.js.map +1 -1
  183. package/package.json +12 -12
  184. package/src/form/DocFormScreen.rights.spec.tsx +130 -0
  185. package/src/form/DocFormScreen.tsx +34 -6
  186. package/src/list/DoctypeListScreen.rights.spec.tsx +113 -0
  187. package/src/list/DoctypeListScreen.tsx +76 -52
  188. package/src/list/listFields.spec.ts +37 -1
  189. package/src/list/listFields.ts +1 -1
  190. package/src/shared/useUserRoles.ts +7 -6
  191. package/types/form/DocFormScreen.d.ts.map +1 -1
  192. package/types/list/DoctypeListScreen.d.ts.map +1 -1
  193. package/types/shared/useUserRoles.d.ts +4 -3
  194. package/types/shared/useUserRoles.d.ts.map +1 -1
@@ -0,0 +1,130 @@
1
+ // DocFormScreen's permission fallback (MPO-390). When docinfo carries no
2
+ // usable permissions (a new record, or a token-auth bench answering
3
+ // {"null": 0}) the screen used to union every role's DocPerm rows, so any
4
+ // reader got an editable form and Save. It now resolves the session's roles.
5
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
6
+ import { act, screen, waitFor } from "@testing-library/react";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { BackofficeProvider } from "../config/BackofficeProvider";
9
+ import { createMemoryNavigator } from "../navigation/navigator";
10
+ import { DocFormScreen } from "./DocFormScreen";
11
+
12
+ const doctype = "Poke Move";
13
+
14
+ const meta = {
15
+ name: doctype,
16
+ doctype: "DocType",
17
+ title_field: "move_name",
18
+ fields: [{ fieldname: "move_name", fieldtype: "Data", label: "Move Name" }],
19
+ permissions: [
20
+ { role: "Pokemon Editor", read: 1, write: 1, create: 1, delete: 1 },
21
+ { role: "Guest", read: 1 },
22
+ ],
23
+ };
24
+
25
+ const tackle = {
26
+ name: "a1b2c3",
27
+ doctype,
28
+ owner: "Administrator",
29
+ modified: "2026-09-27 00:00:00.000000",
30
+ docstatus: 0,
31
+ move_name: "tackle",
32
+ };
33
+
34
+ let run = 0;
35
+ let roles: string[] = [];
36
+ let sessionAnswered = false;
37
+
38
+ function json(body: unknown, status = 200) {
39
+ return new Response(JSON.stringify(body), {
40
+ status,
41
+ headers: { "content-type": "application/json" },
42
+ });
43
+ }
44
+
45
+ beforeEach(() => {
46
+ run += 1;
47
+ sessionAnswered = false;
48
+ vi.stubGlobal("fetch", async (input: string | URL | Request) => {
49
+ const url = new URL(String(input instanceof Request ? input.url : input));
50
+ const method = url.pathname.replace(/^\/api\/method\//, "");
51
+ if (method === "frappe.auth.get_logged_user") return json({ message: "user@pokemon.test" });
52
+ if (method === "frappe.core.doctype.user.user.get_roles") {
53
+ sessionAnswered = true;
54
+ return json({ message: roles });
55
+ }
56
+ if (method === "frappe.desk.form.load.getdoctype") return json({ docs: [meta] });
57
+ if (method === "frappe.desk.form.load.getdoc") {
58
+ return json({ docs: [tackle], docinfo: { permissions: { null: 0 } } });
59
+ }
60
+ if (method === "frappe.desk.form.load.get_docinfo") {
61
+ return json({ docinfo: { permissions: { null: 0 } } });
62
+ }
63
+ const resource = `/api/resource/${encodeURIComponent(doctype)}`;
64
+ if (url.pathname === `${resource}/a1b2c3`) return json({ data: tackle });
65
+ if (url.pathname === resource) return json({ data: [tackle] });
66
+ return json({ data: [], message: [] });
67
+ });
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.restoreAllMocks();
72
+ vi.unstubAllGlobals();
73
+ });
74
+
75
+ function renderForm(name: string) {
76
+ return renderWithProviders(
77
+ <BackofficeProvider
78
+ frappe={{ baseURL: `https://form-rights-${run}.spec.test` }}
79
+ navigation={createMemoryNavigator("/backoffice")}
80
+ >
81
+ <DocFormScreen doctype={doctype} name={name} />
82
+ </BackofficeProvider>,
83
+ );
84
+ }
85
+
86
+ function saveButton(): HTMLElement | undefined {
87
+ return Array.from(document.querySelectorAll('[role="button"], button')).find(
88
+ (node) => node.textContent?.trim() === "Save",
89
+ ) as HTMLElement | undefined;
90
+ }
91
+
92
+ function moveNameInput(): HTMLInputElement {
93
+ const field = screen.getByLabelText("Move Name", { exact: false });
94
+ return (
95
+ field instanceof HTMLInputElement ? field : field.querySelector("input")
96
+ ) as HTMLInputElement;
97
+ }
98
+
99
+ async function settle() {
100
+ await waitFor(() => expect(sessionAnswered).toBe(true), { timeout: 5000 });
101
+ await act(async () => {
102
+ await new Promise((resolve) => setTimeout(resolve, 50));
103
+ });
104
+ }
105
+
106
+ describe("DocFormScreen rights without docinfo permissions", () => {
107
+ it("lets an editor save a new record", async () => {
108
+ roles = ["Pokemon Editor", "Guest"];
109
+ renderForm("new");
110
+ await waitFor(() => expect(saveButton()).toBeTruthy(), { timeout: 5000 });
111
+ expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(false);
112
+ });
113
+
114
+ it("gives a reader a read-only new record with no Save", async () => {
115
+ roles = ["Guest"];
116
+ renderForm("new");
117
+ await waitFor(() => expect(moveNameInput()).toBeTruthy(), { timeout: 5000 });
118
+ await settle();
119
+ expect(saveButton()).toBeUndefined();
120
+ expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(true);
121
+ });
122
+
123
+ it("gives a reader an existing record read-only", async () => {
124
+ roles = ["Guest"];
125
+ renderForm("a1b2c3");
126
+ await waitFor(() => expect(moveNameInput().value).toBe("tackle"), { timeout: 5000 });
127
+ await settle();
128
+ expect(moveNameInput().disabled || moveNameInput().readOnly).toBe(true);
129
+ });
130
+ });
@@ -30,7 +30,12 @@ import {
30
30
  type TabsItem,
31
31
  } from "@multiplatform.one/components";
32
32
  import { Input } from "@multiplatform.one/forms";
33
- import { SchemaForm, useFrappeConfig } from "@multiplatform.one/frappe-ui";
33
+ import {
34
+ SchemaForm,
35
+ sessionDocRights,
36
+ useFrappeConfig,
37
+ type DocShareGrant,
38
+ } from "@multiplatform.one/frappe-ui";
34
39
  import { useLayoutSizeClass, useResolvedKnobs } from "@multiplatform.one/theme";
35
40
  import { ListBulletsIcon } from "@phosphor-icons/react";
36
41
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -222,10 +227,14 @@ export function DocFormScreen({
222
227
  }, [modifiedStamp, docName, doctype, reloadDocinfo]);
223
228
 
224
229
  // -----------------------------------------------------------------------
225
- // Permissions: per-doc flags from docinfo when usable, else the meta role
226
- // matrix (union across roles — the session's role set is not exposed
227
- // client-side; the desk itself relies on docinfo.permissions).
230
+ // Permissions: per-doc flags from docinfo when usable, else the session's
231
+ // roles against meta's DocPerm rows (sessionDocRights). Fixture mode has no
232
+ // session, so it keeps the union of every role's rows.
228
233
  // -----------------------------------------------------------------------
234
+ const { roles, user: sessionUser } = useUserRoles();
235
+ const fixtureMode = Boolean(config?.fixtures);
236
+ const recordOwner =
237
+ !isCreate && typeof engine.baseline?.owner === "string" ? engine.baseline.owner : undefined;
229
238
  const perms = useMemo(() => {
230
239
  const docPerms = normalizeDocPermissions(
231
240
  docinfo?.permissions as Record<string, number> | undefined,
@@ -242,6 +251,26 @@ export function DocFormScreen({
242
251
  print: !!docPerms.print,
243
252
  };
244
253
  }
254
+ if (!fixtureMode) {
255
+ const rights = sessionDocRights({
256
+ fixtures: false,
257
+ meta,
258
+ roles,
259
+ user: sessionUser,
260
+ owner: recordOwner,
261
+ shares: docinfo?.shared as DocShareGrant[] | undefined,
262
+ });
263
+ return {
264
+ write: rights.write,
265
+ create: rights.create,
266
+ delete: rights.delete,
267
+ submit: rights.submit,
268
+ cancel: rights.cancel,
269
+ amend: rights.amend,
270
+ email: rights.email,
271
+ print: rights.print,
272
+ };
273
+ }
245
274
  const rules = meta?.permissions ?? [];
246
275
  const anyRole = (key: keyof (typeof rules)[number]) =>
247
276
  rules.some((rule) => !rule.permlevel && !!rule[key]);
@@ -255,7 +284,7 @@ export function DocFormScreen({
255
284
  email: rules.length === 0 || anyRole("email"),
256
285
  print: rules.length === 0 || anyRole("print"),
257
286
  };
258
- }, [docinfo?.permissions, meta?.permissions]);
287
+ }, [docinfo?.permissions, docinfo?.shared, meta, fixtureMode, roles, sessionUser, recordOwner]);
259
288
 
260
289
  const isSubmittable = !!meta?.is_submittable;
261
290
  const formReadOnly =
@@ -270,7 +299,6 @@ export function DocFormScreen({
270
299
  // -----------------------------------------------------------------------
271
300
  // Permlevel matrix: session roles × meta.permissions (desk perm.js). Roles
272
301
  // stay undefined in fixture mode → coarse permlevel>0 read-only fallback.
273
- const { roles } = useUserRoles();
274
302
  const permLevels = useMemo(
275
303
  () => buildPermLevelMatrix(meta?.permissions, roles),
276
304
  [meta?.permissions, roles],
@@ -0,0 +1,113 @@
1
+ // The backoffice list offers New and the write/delete bulk actions only as
2
+ // far as the session's roles grant them on the doctype (MPO-390), measured
3
+ // against a live-shaped bench: getdoctype for meta, the session RPCs for the
4
+ // user and roles, and empty answers for everything else the list asks.
5
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
6
+ import { act, fireEvent, screen, waitFor } from "@testing-library/react";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { BackofficeProvider } from "../config/BackofficeProvider";
9
+ import { createMemoryNavigator } from "../navigation/navigator";
10
+ import { DoctypeListScreen } from "./DoctypeListScreen";
11
+
12
+ const doctype = "Poke Move";
13
+
14
+ const meta = {
15
+ name: doctype,
16
+ doctype: "DocType",
17
+ title_field: "move_name",
18
+ fields: [{ fieldname: "move_name", fieldtype: "Data", label: "Move Name", in_list_view: 1 }],
19
+ permissions: [
20
+ { role: "Pokemon Editor", read: 1, write: 1, create: 1, delete: 1 },
21
+ { role: "Guest", read: 1 },
22
+ ],
23
+ };
24
+
25
+ let run = 0;
26
+ let roles: string[] = [];
27
+ let sessionAnswered = false;
28
+
29
+ function json(body: unknown, status = 200) {
30
+ return new Response(JSON.stringify(body), {
31
+ status,
32
+ headers: { "content-type": "application/json" },
33
+ });
34
+ }
35
+
36
+ beforeEach(() => {
37
+ run += 1;
38
+ sessionAnswered = false;
39
+ vi.stubGlobal("fetch", async (input: string | URL | Request) => {
40
+ const url = new URL(String(input instanceof Request ? input.url : input));
41
+ const method = url.pathname.replace(/^\/api\/method\//, "");
42
+ if (method === "frappe.auth.get_logged_user") return json({ message: "user@pokemon.test" });
43
+ if (method === "frappe.core.doctype.user.user.get_roles") {
44
+ sessionAnswered = true;
45
+ return json({ message: roles });
46
+ }
47
+ if (method === "frappe.desk.form.load.getdoctype") return json({ docs: [meta] });
48
+ if (method === "frappe.client.get_count") return json({ message: 1 });
49
+ if (url.pathname === `/api/resource/${encodeURIComponent(doctype)}`) {
50
+ return json({ data: [{ name: "a1b2c3", doctype, move_name: "tackle" }] });
51
+ }
52
+ return json({ data: [], message: [] });
53
+ });
54
+ });
55
+
56
+ afterEach(() => {
57
+ vi.restoreAllMocks();
58
+ vi.unstubAllGlobals();
59
+ });
60
+
61
+ function renderList() {
62
+ return renderWithProviders(
63
+ <BackofficeProvider
64
+ frappe={{ baseURL: `https://list-rights-${run}.spec.test` }}
65
+ navigation={createMemoryNavigator("/backoffice")}
66
+ >
67
+ <DoctypeListScreen doctype={doctype} />
68
+ </BackofficeProvider>,
69
+ );
70
+ }
71
+
72
+ async function settle() {
73
+ await waitFor(() => expect(sessionAnswered).toBe(true));
74
+ await act(async () => {
75
+ await new Promise((resolve) => setTimeout(resolve, 50));
76
+ });
77
+ }
78
+
79
+ async function selectRow() {
80
+ const checkbox = await waitFor(() => screen.getAllByLabelText(/^Select row a1b2c3$/)[0], {
81
+ timeout: 5000,
82
+ });
83
+ fireEvent.click(checkbox as HTMLElement);
84
+ }
85
+
86
+ function bulkActionLabels(): string[] {
87
+ return Array.from(document.querySelectorAll('[role="button"], button'), (node) =>
88
+ node.textContent?.trim(),
89
+ ).filter((label): label is string => label === "Bulk Edit" || label === "Delete");
90
+ }
91
+
92
+ describe("DoctypeListScreen rights", () => {
93
+ it("offers an editor New, Bulk Edit and Delete", async () => {
94
+ roles = ["Pokemon Editor", "Guest"];
95
+ renderList();
96
+ await waitFor(() => expect(screen.getByLabelText(`New ${doctype}`)).toBeTruthy());
97
+ await selectRow();
98
+ await waitFor(() => expect(bulkActionLabels()).toEqual(["Bulk Edit", "Delete"]));
99
+ });
100
+
101
+ it("offers a reader no New, no Bulk Edit and no Delete", async () => {
102
+ roles = ["Guest"];
103
+ renderList();
104
+ await waitFor(() => expect(screen.getByRole("heading", { name: doctype })).toBeTruthy());
105
+ await settle();
106
+ expect(screen.queryByLabelText(`New ${doctype}`)).toBeNull();
107
+ await selectRow();
108
+ await act(async () => {
109
+ await new Promise((resolve) => setTimeout(resolve, 50));
110
+ });
111
+ expect(bulkActionLabels()).toEqual([]);
112
+ });
113
+ });
@@ -33,6 +33,7 @@ import {
33
33
  import {
34
34
  FrappeTable,
35
35
  docFieldsToFilterFields,
36
+ sessionDocRights,
36
37
  useFrappeAggregate,
37
38
  useFrappeConfig,
38
39
  } from "@multiplatform.one/frappe-ui";
@@ -222,8 +223,17 @@ export function DoctypeListScreen({
222
223
  if (metaError && !meta) emitEvent({ type: "error", error: metaError, context: "list-meta" });
223
224
  }, [metaError, meta, emitEvent]);
224
225
 
225
- // "New" affordances: host prop + the newDoc capability.
226
- const showNew = !hideNewButton && capabilities.newDoc.enabled;
226
+ // Session roles × meta.permissions: the doctype-level rights (desk
227
+ // get_role_permissions) gate New and the write/delete bulk actions; fixture
228
+ // mode has no session and withholds nothing.
229
+ const { roles, user: rolesUser } = useUserRoles();
230
+ const rights = useMemo(
231
+ () => sessionDocRights({ fixtures: fixtureMode, meta, roles, user: rolesUser }),
232
+ [fixtureMode, meta, roles, rolesUser],
233
+ );
234
+
235
+ // "New" affordances: host prop + the newDoc capability + the create right.
236
+ const showNew = !hideNewButton && capabilities.newDoc.enabled && rights.create;
227
237
 
228
238
  // ONE FILTER SYSTEM (MOD-04): `baseFilters` is the single writable filter
229
239
  // state for this screen. Every filter surface — quick standard filters,
@@ -405,7 +415,6 @@ export function DoctypeListScreen({
405
415
  // fields above the roles' read level are pruned (desk hides them; the
406
416
  // server strips their values anyway). Undefined (fixtures / roles RPC
407
417
  // unavailable) prunes nothing — never hides more than desk would.
408
- const { roles } = useUserRoles();
409
418
  const permMatrix = useMemo(
410
419
  () => buildPermLevelMatrix(meta?.permissions, roles),
411
420
  [meta?.permissions, roles],
@@ -690,14 +699,19 @@ export function DoctypeListScreen({
690
699
  },
691
700
  ] as BulkAction<ListRow>[])
692
701
  : []),
693
- {
694
- id: "edit",
695
- label: t("list.editTitle", "Bulk Edit"),
696
- onExecute: (items, clearSelection) => {
697
- const names = items.map((item) => item.name).filter(Boolean) as string[];
698
- if (names.length > 0) setPendingDialog({ type: "edit", names, clear: clearSelection });
699
- },
700
- },
702
+ ...(rights.write
703
+ ? ([
704
+ {
705
+ id: "edit",
706
+ label: t("list.editTitle", "Bulk Edit"),
707
+ onExecute: (items, clearSelection) => {
708
+ const names = items.map((item) => item.name).filter(Boolean) as string[];
709
+ if (names.length > 0)
710
+ setPendingDialog({ type: "edit", names, clear: clearSelection });
711
+ },
712
+ },
713
+ ] as BulkAction<ListRow>[])
714
+ : []),
701
715
  {
702
716
  id: "export",
703
717
  label: t("list.export", "Export CSV"),
@@ -706,7 +720,7 @@ export function DoctypeListScreen({
706
720
  if (names.length > 0) await handleBulkExport(names);
707
721
  },
708
722
  },
709
- ...(isSubmittable
723
+ ...(isSubmittable && rights.submit
710
724
  ? ([
711
725
  {
712
726
  id: "submit",
@@ -724,6 +738,10 @@ export function DoctypeListScreen({
724
738
  clearSelection();
725
739
  },
726
740
  },
741
+ ] as BulkAction<ListRow>[])
742
+ : []),
743
+ ...(isSubmittable && rights.cancel
744
+ ? ([
727
745
  {
728
746
  id: "cancel",
729
747
  label: t("list.cancel", "Cancel"),
@@ -742,47 +760,51 @@ export function DoctypeListScreen({
742
760
  },
743
761
  ] as BulkAction<ListRow>[])
744
762
  : []),
745
- {
746
- id: "delete",
747
- label: t("list.delete", "Delete"),
748
- variant: "destructive",
749
- requiresConfirmation: true,
750
- confirmTitle: t("list.deleteTitle", "Delete documents"),
751
- confirmMessage: t("list.deleteConfirm", {
752
- defaultValue: "Permanently delete the selected {{doctype}} documents?",
753
- doctype,
754
- }),
755
- onExecute: async (items, clearSelection) => {
756
- if (!app) return;
757
- const names = items.map((item) => item.name).filter(Boolean) as string[];
758
- if (names.length === 0) return;
759
- const result = await bulkDelete(app, doctype, names);
760
- if (result.failed.length > 0) {
761
- notify({
762
- severity: "warning",
763
- scope: "page",
764
- body: t("list.deletePartial", {
765
- count: result.succeeded.length,
766
- total: names.length,
767
- names: result.failed.map((failure) => failure.name).join(", "),
768
- defaultValue: "Deleted {{count}} of {{total}} documents; failed: {{names}}",
769
- }),
770
- });
771
- } else {
772
- notify({
773
- severity: "success",
774
- scope: "page",
775
- body: t("list.deleteDone", {
776
- count: result.succeeded.length,
777
- defaultValue_one: "Deleted {{count}} document",
778
- defaultValue_other: "Deleted {{count}} documents",
763
+ ...(rights.delete
764
+ ? ([
765
+ {
766
+ id: "delete",
767
+ label: t("list.delete", "Delete"),
768
+ variant: "destructive",
769
+ requiresConfirmation: true,
770
+ confirmTitle: t("list.deleteTitle", "Delete documents"),
771
+ confirmMessage: t("list.deleteConfirm", {
772
+ defaultValue: "Permanently delete the selected {{doctype}} documents?",
773
+ doctype,
779
774
  }),
780
- });
781
- }
782
- clearSelection();
783
- count.refresh();
784
- },
785
- },
775
+ onExecute: async (items, clearSelection) => {
776
+ if (!app) return;
777
+ const names = items.map((item) => item.name).filter(Boolean) as string[];
778
+ if (names.length === 0) return;
779
+ const result = await bulkDelete(app, doctype, names);
780
+ if (result.failed.length > 0) {
781
+ notify({
782
+ severity: "warning",
783
+ scope: "page",
784
+ body: t("list.deletePartial", {
785
+ count: result.succeeded.length,
786
+ total: names.length,
787
+ names: result.failed.map((failure) => failure.name).join(", "),
788
+ defaultValue: "Deleted {{count}} of {{total}} documents; failed: {{names}}",
789
+ }),
790
+ });
791
+ } else {
792
+ notify({
793
+ severity: "success",
794
+ scope: "page",
795
+ body: t("list.deleteDone", {
796
+ count: result.succeeded.length,
797
+ defaultValue_one: "Deleted {{count}} document",
798
+ defaultValue_other: "Deleted {{count}} documents",
799
+ }),
800
+ });
801
+ }
802
+ clearSelection();
803
+ count.refresh();
804
+ },
805
+ },
806
+ ] as BulkAction<ListRow>[])
807
+ : []),
786
808
  ...hostListActions,
787
809
  ],
788
810
  [
@@ -796,6 +818,7 @@ export function DoctypeListScreen({
796
818
  capabilities.assignments.enabled,
797
819
  capabilities.tags.enabled,
798
820
  hostListActions,
821
+ rights,
799
822
  ],
800
823
  );
801
824
 
@@ -1060,6 +1083,7 @@ export function DoctypeListScreen({
1060
1083
  {...(bulkActionsEnabled ? { bulkActions } : {})}
1061
1084
  onRowClick={handleRowClick}
1062
1085
  onStateChange={handleStateChange}
1086
+ rights={rights}
1063
1087
  layout="list"
1064
1088
  />
1065
1089
  )}
@@ -86,7 +86,7 @@ describe("listFieldsToFetch permlevel pruning", () => {
86
86
  const matrix = buildPermLevelMatrix(meta.permissions, ["Desk User"]);
87
87
  const fields = listFieldsToFetch(meta, matrix);
88
88
  expect(fields).toEqual(
89
- expect.arrayContaining(["name", "modified", "full_name", "enabled", "_comment_count"]),
89
+ expect.arrayContaining(["name", "modified", "full_name", "enabled", "_comments"]),
90
90
  );
91
91
  expect(fields).not.toContain("role_profile_name");
92
92
  expect(fields).not.toContain("api_secret_hint");
@@ -99,6 +99,42 @@ describe("listFieldsToFetch permlevel pruning", () => {
99
99
  });
100
100
  });
101
101
 
102
+ // frappe/model/__init__.py default_fields and optional_fields: the only names
103
+ // frappe.desk.reportview.validate_fields lets through without a DocField.
104
+ // Anything else that isn't a field of the doctype answers 417 "Field not
105
+ // permitted in query" for every user without the query bypass (MPO-389).
106
+ const frappeStandardFields = new Set([
107
+ "doctype",
108
+ "name",
109
+ "owner",
110
+ "creation",
111
+ "modified",
112
+ "modified_by",
113
+ "docstatus",
114
+ "idx",
115
+ "_user_tags",
116
+ "_comments",
117
+ "_assign",
118
+ "_liked_by",
119
+ "_seen",
120
+ ]);
121
+
122
+ describe("listFieldsToFetch asks only for columns Frappe will select", () => {
123
+ it("names nothing but the doctype's fields and Frappe's standard columns", () => {
124
+ const fieldnames = new Set(meta.fields.map((field) => field.fieldname));
125
+ const rejected = listFieldsToFetch(meta, undefined).filter(
126
+ (name) => !fieldnames.has(name) && !frappeStandardFields.has(name),
127
+ );
128
+ expect(rejected).toEqual([]);
129
+ });
130
+
131
+ it("reads the comment count from _comments instead of the computed _comment_count", () => {
132
+ const fields = listFieldsToFetch(meta, undefined);
133
+ expect(fields).toContain("_comments");
134
+ expect(fields).not.toContain("_comment_count");
135
+ });
136
+ });
137
+
102
138
  describe("listDocumentColumnIds desk list anatomy", () => {
103
139
  it("is title, in_list_view values, then the activity cluster — no extra ID/modified", () => {
104
140
  expect(listDocumentColumnIds(meta)).toEqual([
@@ -24,7 +24,7 @@ export function listFieldsToFetch(meta?: DoctypeMeta, permMatrix?: PermLevelMatr
24
24
  "_liked_by",
25
25
  "_assign",
26
26
  "_user_tags",
27
- "_comment_count",
27
+ "_comments",
28
28
  ]);
29
29
  if (meta?.title_field) fields.add(meta.title_field);
30
30
  for (const field of meta?.fields ?? []) {
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Session user's role list for backoffice surfaces, read off the ambient
3
3
  * FrappeProvider. The fetch itself lives in frappe-ui's form engine
4
- * (`useSessionRoles`) so FrappeForm and the desk resolve permlevel from one
5
- * source; `roles` stays undefined while loading, in fixture mode, or when
6
- * the RPC fails.
4
+ * (`useFrappeSession`) so FrappeForm and the desk resolve permlevel and doc
5
+ * rights from one source; `roles` and `user` stay undefined while loading,
6
+ * in fixture mode, or when the RPC fails.
7
7
  */
8
8
 
9
- import { useFrappeConfig, useSessionRoles } from "@multiplatform.one/frappe-ui";
9
+ import { useFrappeConfig, useFrappeSession } from "@multiplatform.one/frappe-ui";
10
10
 
11
- export function useUserRoles(): { roles?: string[] } {
11
+ export function useUserRoles(): { roles?: string[]; user?: string } {
12
12
  const config = useFrappeConfig();
13
- return { roles: useSessionRoles(config) };
13
+ const { roles, user } = useFrappeSession(config);
14
+ return { roles, user };
14
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DocFormScreen.d.ts","sourceRoot":"","sources":["../../src/form/DocFormScreen.tsx"],"names":[],"mappings":"AAqCA,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAqCtC,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,wDAAwD;IACxD,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAID,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,SAAS,EACT,eAAe,GAChB,EAAE,kBAAkB,2CAq+BpB"}
1
+ {"version":3,"file":"DocFormScreen.d.ts","sourceRoot":"","sources":["../../src/form/DocFormScreen.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAqCtC,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,wDAAwD;IACxD,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAID,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,SAAS,EACT,eAAe,GAChB,EAAE,kBAAkB,2CA4/BpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"DoctypeListScreen.d.ts","sourceRoot":"","sources":["../../src/list/DoctypeListScreen.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAEV,eAAe,EAIhB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAS1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAoB5E,OAAO,EAAgB,KAAK,OAAO,EAAoB,MAAM,eAAe,CAAC;AAY7E,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,8EAA8E;IAC9E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACtF,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,cAAc,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C;AAsBD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,KAAK,EAAE,cAAc,EACrB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,aAAa,EACb,cAAc,GACf,EAAE,sBAAsB,2CA86BxB"}
1
+ {"version":3,"file":"DoctypeListScreen.d.ts","sourceRoot":"","sources":["../../src/list/DoctypeListScreen.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAEV,eAAe,EAIhB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAOL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAS1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,0BAA0B,CAAC;AAoB5E,OAAO,EAAgB,KAAK,OAAO,EAAoB,MAAM,eAAe,CAAC;AAY7E,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,8EAA8E;IAC9E,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACtF,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gFAAgF;IAChF,cAAc,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C;AAsBD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,KAAK,EAAE,cAAc,EACrB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,aAAa,EACb,cAAc,GACf,EAAE,sBAAsB,2CAq8BxB"}
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Session user's role list for backoffice surfaces, read off the ambient
3
3
  * FrappeProvider. The fetch itself lives in frappe-ui's form engine
4
- * (`useSessionRoles`) so FrappeForm and the desk resolve permlevel from one
5
- * source; `roles` stays undefined while loading, in fixture mode, or when
6
- * the RPC fails.
4
+ * (`useFrappeSession`) so FrappeForm and the desk resolve permlevel and doc
5
+ * rights from one source; `roles` and `user` stay undefined while loading,
6
+ * in fixture mode, or when the RPC fails.
7
7
  */
8
8
  export declare function useUserRoles(): {
9
9
  roles?: string[];
10
+ user?: string;
10
11
  };
11
12
  //# sourceMappingURL=useUserRoles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUserRoles.d.ts","sourceRoot":"","sources":["../../src/shared/useUserRoles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,YAAY,IAAI;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAGnD"}
1
+ {"version":3,"file":"useUserRoles.d.ts","sourceRoot":"","sources":["../../src/shared/useUserRoles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,YAAY,IAAI;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAIlE"}