@nocobase/app-template-default 1.0.0-beta.88 → 1.0.0-beta.90

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 (103) hide show
  1. package/.agents/rules/components.md +3 -1
  2. package/.agents/rules/shell.md +12 -2
  3. package/AGENTS.md +1 -1
  4. package/LICENSE.txt +129 -0
  5. package/client/components/back-button.tsx +37 -12
  6. package/client/components/brand-loading-mark.tsx +5 -2
  7. package/client/components/brand-mark-geometry.ts +3 -3
  8. package/client/components/breadcrumbs.tsx +47 -3
  9. package/client/components/data-table/data-table.tsx +90 -19
  10. package/client/components/data-table/pagination.tsx +17 -2
  11. package/client/components/list-feedback.tsx +31 -22
  12. package/client/components/loading.tsx +127 -16
  13. package/client/components/pending-button.tsx +111 -0
  14. package/client/components/role-list.tsx +7 -0
  15. package/client/components/route-child-page.tsx +72 -1
  16. package/client/components/route-overlay.tsx +149 -15
  17. package/client/components/task-progress.tsx +155 -0
  18. package/client/components/use-route-overlay.ts +8 -11
  19. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +62 -32
  20. package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +2 -0
  21. package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +1 -0
  22. package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +1 -0
  23. package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +1 -0
  24. package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +1 -0
  25. package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +2 -0
  26. package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +1 -0
  27. package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +1 -0
  28. package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +1 -0
  29. package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +1 -0
  30. package/client/extensions/nocobase-workflow-management/README.md +5 -5
  31. package/client/extensions/nocobase-workflow-management/definition-list.tsx +2 -9
  32. package/client/extensions/nocobase-workflow-management/execution-list.tsx +2 -4
  33. package/client/layouts/app-layout.tsx +0 -4
  34. package/client/lib/navigation-transform.ts +29 -1
  35. package/client/locales/system/en-US.ts +23 -0
  36. package/client/locales/system/zh-CN.ts +23 -0
  37. package/client/pages/home/index.tsx +66 -51
  38. package/client/pages/settings/database-explorer/en-US.ts +33 -0
  39. package/client/pages/settings/database-explorer/index.tsx +358 -0
  40. package/client/pages/settings/database-explorer/routes.ts +14 -0
  41. package/client/pages/settings/database-explorer/zh-CN.ts +30 -0
  42. package/client/pages/settings/default-access.tsx +1 -1
  43. package/client/pages/settings/roles.tsx +27 -27
  44. package/client/pages/settings/routes.ts +15 -7
  45. package/client/pages/settings/user-detail.tsx +3 -17
  46. package/client/pages/settings/user-form.tsx +9 -15
  47. package/client/pages/settings/users.tsx +11 -27
  48. package/client/plugins.ts +8 -1
  49. package/client/routes.ts +2 -1
  50. package/client/routing/client-route.tsx +199 -37
  51. package/client/routing/navigation-destination.ts +42 -0
  52. package/client/routing/route-leave-boundary.tsx +13 -1
  53. package/client/styles.css +18 -1
  54. package/config.example.yml +17 -11
  55. package/index.html +8 -11
  56. package/package.json +27 -27
  57. package/server/config/auth.ts +1 -1
  58. package/server/config/index.ts +3 -0
  59. package/server/config/jobs.ts +16 -8
  60. package/server/config/queue.ts +14 -13
  61. package/server/config/realtime.ts +4 -0
  62. package/tests/client/auth-i18n.test.tsx +1 -1
  63. package/tests/client/authenticated-client-fixture.test.tsx +37 -0
  64. package/tests/client/back-button.test.tsx +34 -0
  65. package/tests/client/client-route-recovery.test.tsx +93 -1
  66. package/tests/client/client-routes.test.ts +5 -3
  67. package/tests/client/database-explorer-integration.test.tsx +30 -0
  68. package/tests/client/database-explorer.test.tsx +152 -0
  69. package/tests/client/foundation-integration.test.ts +42 -29
  70. package/tests/client/home.test.tsx +26 -85
  71. package/tests/client/layout-i18n.test.tsx +83 -2
  72. package/tests/client/list-feedback.test.tsx +5 -5
  73. package/tests/client/list-loading.test.tsx +397 -0
  74. package/tests/client/loading.test.tsx +20 -11
  75. package/tests/client/manual-list-retry.test.tsx +2 -2
  76. package/tests/client/navigation-parent.test.tsx +172 -0
  77. package/tests/client/pending-button.test.tsx +106 -0
  78. package/tests/client/permission-management-tabs.test.tsx +48 -12
  79. package/tests/client/primitive-labels.test.tsx +2 -2
  80. package/tests/client/role-return.test.tsx +21 -3
  81. package/tests/client/roles.test.tsx +84 -3
  82. package/tests/client/route-leave-recheck.test.tsx +74 -0
  83. package/tests/client/route-presentation.test.tsx +557 -0
  84. package/tests/client/session-preparation.test.tsx +20 -17
  85. package/tests/client/settings-continuity.test.tsx +35 -11
  86. package/tests/client/settings-home.test.tsx +4 -2
  87. package/tests/client/settings-idle-list.test.tsx +124 -0
  88. package/tests/client/settings-navigation.test.ts +20 -0
  89. package/tests/client/user-drawer.test.tsx +3 -4
  90. package/tests/fixtures/dialog-footer.html +12 -0
  91. package/tests/fixtures/dialog-footer.tsx +215 -0
  92. package/tests/helpers/authenticated-client.tsx +77 -22
  93. package/tests/playwright/dialog-footer.test.ts +227 -0
  94. package/tests/project/bootstrap-loading.test.ts +60 -0
  95. package/tests/server/app-server.test.ts +18 -12
  96. package/tests/server/auth-email-config.test.ts +21 -0
  97. package/tests/server/backends-config.test.ts +118 -0
  98. package/tests/server/concurrency.test.ts +1 -1
  99. package/tests/server/config.test.ts +22 -14
  100. package/tests/server/lifecycle.test.ts +2 -2
  101. package/tests/server/realtime-config.test.ts +114 -0
  102. package/client/pages/home/capabilities.ts +0 -62
  103. package/client/pages/home/prompt-dialog.tsx +0 -122
@@ -15,7 +15,7 @@ yes n | pnpm exec shadcn add card
15
15
  pnpm exec prettier --write client/components/ui/card.tsx
16
16
  ```
17
17
 
18
- Build your own components by composing these primitives, and put them in `client/components/`. A page below another one — a covering child page, a record's own page — leaves by the header's breadcrumb when its route declares `breadcrumb` or the page declares its trail with `usePageBreadcrumb`, and otherwise by `BackButton` (`client/components/back-button.tsx`) above its heading; never both.
18
+ Build your own components by composing these primitives, and put them in `client/components/`. A covering child page, independent detail or edit declares its complete navigation with `breadcrumb` or `usePageBreadcrumb`; the shell renders desktop breadcrumbs and the mobile parent arrow. Declare dynamic/logic parents before loading succeeds and keep them on failure. Use `BackButton` only without that shell navigation, with route/access context for target validation; never show duplicate return entries.
19
19
 
20
20
  **Look components up in the shadcn skill.** The development Skill carries the shadcn/ui skill, unchanged from shadcn 4.21.0, in `.agents/skills/nocobase-app-development/references/frontend/shadcn/`. Read [the shadcn guide](../skills/nocobase-app-development/references/frontend/references/shadcn.md) first: it routes to the skill's rules and states where this application departs from them, among others `pnpm exec shadcn` instead of `npx shadcn@latest`, no `shadcn apply` or `--preset`, and create, edit and detail as `RouteDialog` or `RouteDrawer` child routes rather than a `Sheet`. `pnpm exec shadcn docs <name>` gives one primitive's documentation and examples for the Base UI version. For a whole page, start from the worked example that [the example index](../skills/nocobase-app-development/references/frontend/references/example.md) indexes.
21
21
 
@@ -31,6 +31,8 @@ Style with the semantic Tailwind tokens — `bg-background`, `text-foreground`,
31
31
 
32
32
  The default brand palette and density live in `client/theme/themes/*.css`; shared hover/cursor and Badge geometry live in `client/styles.css`, outside generated shadcn primitives. For semantic status labels, use `StatusBadge` from `client/components/status-badge.tsx` with `neutral`, `info`, `warning`, or `success`; choose the tone by meaning and keep the visible status text. A badge beside a title or on a dense card is the small one, `size='sm'` on `StatusBadge` or `data-size='sm'` on `Badge`. Reuse these shared components and tokens in new pages so template upgrades can carry the design consistently.
33
33
 
34
+ Dialog and AlertDialog footer actions use the shared semantic-slot sizing rule in `client/styles.css`, including linked Buttons and RouteOverlay's composed DialogFooter. Keep route links and pending guards; avoid page height/padding patches. The [footer contract](../skills/nocobase-app-development/references/frontend/references/overlay.md#footer-ownership) explains precedence, wrapping and the source upgrade path for existing applications.
35
+
34
36
  **Follow the UI guidelines.** [The UI guidelines](../skills/nocobase-app-development/references/frontend/ui-guidelines.md) set the conventions every page follows, among them the page header and the single placement of the primary action, empty states, row actions behind one "…" menu, dates picked from a `Calendar` in a `Popover` rather than a native date input, and medium-width drawers; the frontend workflow says how much of it to read for a change.
35
37
 
36
38
  ## Standard process management
@@ -4,6 +4,8 @@ Read this before changing the layouts, header, sidebar, breadcrumbs, account men
4
4
 
5
5
  ## Breadcrumbs and layouts
6
6
 
7
+ Both automatic and explicit parents pass through `client/routing/navigation-destination.ts`, using the shell's existing route/access context. Keep permission loading fail-closed, check every target ancestor, and remove denied parent names. Desktop breadcrumbs and the mobile parent arrow use the same resolved levels; mobile keeps the current title and omits the duplicate brand. Follow the [detail/edit generation contract](../skills/nocobase-app-development/references/frontend/references/navigation.md), including stable parents during record failures, source-tab state and direct-entry defaults. Do not add a second page BackButton under the shell.
8
+
7
9
  Layouts own breadcrumb route context and render the current page's trail in the header after the sidebar toggle (`client/components/breadcrumbs.tsx`): the route trail by default, or the trail a page declares with `usePageBreadcrumb` from `@nocobase/app-client` when it names records. Pages do not render breadcrumbs themselves. `AppRouter` selects routes and layouts. See [pages and routes](../skills/nocobase-app-development/references/frontend/references/page.md) for each layout's scope.
8
10
 
9
11
  The route-tree provider remembers each visited ancestor's query while its descendants are open. Default breadcrumbs and back links restore that query, including an empty one, instead of applying detail-only parameters to the list. Keep this provider mounted above page navigation. Explicit breadcrumb/back destinations remain application-owned. See the linked pages-and-routes reference for deep-link and reload fallbacks.
@@ -24,13 +26,13 @@ Theme lives in the account menu as a submenu beside Language, rather than a sepa
24
26
 
25
27
  The account menu checks Better Auth sign-out results before refreshing the session and shows a localized error toast for API or network failures. Preserve this behavior when upgrading the shell; navigation alone does not revoke a session.
26
28
 
27
- The authorization provider clears the permission snapshot before rendering a new session. Route navigation and page guards subscribe to the authorization revision; preserve these checks when customizing the shell so account changes and permission updates take effect without a reload. Keep `RouteLeaveBoundary` in `AppLayout`, outside all page Activities including nested routes, so `useSettingsLeave` navigation guards, confirmation UI and `beforeunload` survive permission checks. Clear retained guards on denial and session changes. Restored Activity Effects re-run existing data requests. Pending checks hide protected content with React Activity while retaining drafts; failed checks unmount it and deny access. Body portals must detach in an Effect cleanup so they also disappear while the page is hidden; use `BodyPortal` from `#components/body-portal` for application-owned content mounted on `document.body`.
29
+ The authorization provider clears the permission snapshot before rendering a new session. Route navigation and page guards subscribe to the authorization revision; preserve these checks when customizing the shell so account changes and permission updates take effect without a reload. Keep `RouteLeaveBoundary` in `AppLayout`, outside all page Activities including nested routes, so `useSettingsLeave` navigation guards, confirmation UI and `beforeunload` survive permission checks. Clear retained guards on denial and session changes. Restored Activity Effects re-run existing data requests. Keep each resolved route module for the same loader and retry attempt when Activity reconnects; namespace-wrapping loaders may return a new component type on every invocation and discard form state if invoked again. Pending checks hide protected content with React Activity while retaining drafts; failed checks unmount it and deny access. Body portals must detach in an Effect cleanup so they also disappear while the page is hidden; use `BodyPortal` from `#components/body-portal` for application-owned content mounted on `document.body`.
28
30
 
29
31
  The account menu language control in `client/layouts/components/language-switcher.tsx` uses a shadcn submenu with radio items. Render it inside `DropdownMenuContent` to preserve menu keyboard navigation and selection semantics.
30
32
 
31
33
  ## Branded loading
32
34
 
33
- Use `Loading` for page or panel waits and `Spinner` (the shadcn-compatible adapter to `BrandSpinner`) for inline pending actions, uploads, and refresh indicators. Both share the stable NocoBase mark and orbit animation; do not introduce independent Lucide spinners for application loading. Keep skeletons for content placeholders and progress bars for determinate progress. `index.html` has the matching pre-JavaScript mark and must stay visually consistent. All animations respect reduced motion.
35
+ Follow [pending feedback](../skills/nocobase-app-development/references/pending-feedback.md) for shared 300ms display and 10s long-wait guidance. Use `PendingButton` with real `submitting` for native form actions; preserve the shared submission lock. Use `Loading` for page or panel waits and `Spinner` (the shadcn-compatible adapter to `BrandSpinner`) for inline pending actions, uploads, and refresh indicators. Both share the stable NocoBase mark and orbit animation; do not introduce independent Lucide spinners for application loading. Keep skeletons for content placeholders and progress bars for determinate progress. `index.html` has the matching pre-JavaScript mark and must stay visually consistent. All animations respect reduced motion.
34
36
 
35
37
  The shared list of shell behaviors to keep when customizing is in [customizing the shell](../skills/nocobase-app-development/references/frontend/references/shell.md).
36
38
 
@@ -39,3 +41,11 @@ The signed-in region waits in `client/routing/session-preparation.tsx` before mo
39
41
  ## List context
40
42
 
41
43
  Keep the identity-keyed `SessionPreparation` boundary: mounted private list conditions and ancestor query memory must be discarded when the signed-in user changes. Public list state uses `useTableUrlState`; tabbed datasets use `useListTabs`, with complete owned parameter keys and permission-filtered tabs. Follow the synchronized [list navigation rules](../skills/nocobase-app-development/references/frontend/references/child-routes.md#list-tab-breadcrumbs-and-private-conditions). Default back links use an empty ancestor query when no source visit exists; explicit return destinations remain application-owned.
44
+
45
+ ## Page data freshness
46
+
47
+ Follow the synchronized [page data freshness rules](../skills/nocobase-app-development/references/frontend/references/page-freshness.md): ordinary pages have no persistent Refresh action. Preserve contextual read retries, write invalidation and live progress updates. Dependency upgrades do not rewrite application-owned pages or installed Registry source; reconcile them through the template upgrade workflow.
48
+
49
+ ## Features and extensions
50
+
51
+ Features and extensions belongs in System settings under the System category, alongside Database Explorer. Use the permission-filtered settings catalog rather than a separate sidebar footer entry. Its detail breadcrumb includes System settings and returns to the filtered catalog. The homepage retains its discovery link.
package/AGENTS.md CHANGED
@@ -63,7 +63,7 @@ A bare create-app shows Home to signed-in users and, only to unrestricted admini
63
63
 
64
64
  ### Pages and routes
65
65
 
66
- Declare every page in `client/routes.ts` with `defineAppRoutes()` and keep its component behind a lazy `componentLoader` whose module default-exports it. Never write the deployment base path such as `/main` into a route — the runtime restores it. `auth` controls browser navigation only and is not server security. Declare `navigation` on the route to put it in a menu, and always declare `authz` on the first page of every path; the page id identifies stored page grants, so changing it requires migrating the grants that reference it. Read [pages and routes](.agents/skills/nocobase-app-development/references/frontend/references/page.md), and [overlays](.agents/skills/nocobase-app-development/references/frontend/references/overlay.md) before implementing dialogs, drawers or close guards.
66
+ Declare every page in `client/routes.ts` with `defineAppRoutes()` and keep its component behind a lazy `componentLoader` whose module default-exports it. Never write the deployment base path such as `/main` into a route — the runtime restores it. `auth` controls browser navigation only and is not server security. Declare `navigation` on the route to put it in a menu, and always declare `authz` on the first page of every path; the page id identifies stored page grants, so changing it requires migrating the grants that reference it. Every new independent detail/edit page follows the [navigation pattern](.agents/skills/nocobase-app-development/references/frontend/references/navigation.md), including a parent during loading/errors and mobile return; omit duplicate page back links under the shell. Read [pages and routes](.agents/skills/nocobase-app-development/references/frontend/references/page.md), and [overlays](.agents/skills/nocobase-app-development/references/frontend/references/overlay.md) before implementing dialogs, drawers or close guards.
67
67
 
68
68
  Every route listed as a page permission needs a localized display name: use its existing navigation/breadcrumb title, or add its application route name to `client/pages/settings/page-permission-titles.ts` for an overlay or other page with no title metadata. Do not add breadcrumbs/menus just to label an overlay. Run `tests/client/page-permission-titles.test.ts`; it requires non-empty English/Chinese labels and rejects missing or stale mappings. Never rename route/permission ids or change `authz` to fix display text.
69
69
 
package/LICENSE.txt ADDED
@@ -0,0 +1,129 @@
1
+ Updated Date: November 7, 2026
2
+
3
+ NocoBase License Agreement
4
+
5
+ NOCOBASE PTE. LTD., a Singaporean Exempt Private Company Limited by Shares with its principal place of business located at 112 ROBINSON ROAD, #03-01, SINGAPORE ("The Company") https://www.nocobase.com/ issues this License Agreement ("Agreement") to you. You, as an individual or a company ("The User"), will be deemed to voluntarily accept all terms of this Agreement by using NocoBase (including but not limited to obtaining NocoBase source code, installation package, or image in any form, installing and using NocoBase, developing Applications with NocoBase, purchasing NocoBase commercial license and services). If the User does not agree to any term of this Agreement, or cannot accurately understand our interpretation of the relevant terms, please stop using it immediately.
6
+
7
+ This Agreement applies to any use, quotation, contract, invoice, and all software delivered by the Company. The User and the Company or NocoBase's agents need not sign a separate license agreement for the sale and delivery of the software.
8
+
9
+ This Agreement applies to NocoBase 3 and later versions. NocoBase 2 and earlier versions are independent products and remain subject to their original license agreement. Where a NocoBase 2 Commercial Edition user uses NocoBase 3 or later versions under a policy announced by the Company, that use is subject to this Agreement.
10
+
11
+ The Company reserves the right to formulate and modify this Agreement from time to time as needed. If there are changes, the Company will announce them in the form of website announcements, without further individual notification. The changed Agreement will automatically take effect once it is announced, becoming part of this Agreement.
12
+
13
+ ==============
14
+ 1. Definitions
15
+ ==============
16
+
17
+ 1.1 "Software" refers to NocoBase, consisting of Core, Studio, and Commercial Plugins, including their source code, installation packages, images, and all their modifications, updates, and upgrades.
18
+
19
+ 1.2 "Core" refers to NocoBase Core, the open source application foundation released by the Company, including the runtime, plugins, application templates, development standards, and development skills placed in the same code repository.
20
+
21
+ 1.3 "Studio" refers to NocoBase Studio, the governance platform for people and agents building software together, provided in the Commercial Edition.
22
+
23
+ 1.4 "Commercial Plugins" refers to the plugins provided in the Commercial Edition that extend the capabilities of Core, such as advanced approvals and enterprise SSO.
24
+
25
+ 1.5 "Community Edition" refers to the free version of the Software provided to the User through public channels, namely Core.
26
+
27
+ 1.6 "Commercial Edition" refers to the paid version of the Software purchased by the User from the Company or its agents, downloaded through exclusive channels, and includes additional benefits. In addition to Core, it includes Studio and Commercial Plugins, and consists of two versions: Professional Edition and Enterprise Edition.
28
+
29
+ 1.7 "Application" refers to a specific business use case application serving internal or external customers of the User, developed based on the Software, such as ERP/CRM. Studio is a development tool and is not part of any Application.
30
+
31
+ 1.8 "Customer" refers to the clients who commission the User to develop Applications or purchase the User's Applications.
32
+
33
+ 1.9 "Agent" refers to an AI agent that works with the Software, including the coding agents the User uses to develop Applications and the business agents in Applications.
34
+
35
+ 1.10 "Third-Party Services" refers to the large models, coding agents, cloud services, and other third-party products and services that the User chooses to use with the Software.
36
+
37
+ 1.11 "Third-Party Open Source Software" refers to open source software provided with the Software. They are licensed through various published open source software licenses or copyright notices accompanying such software.
38
+
39
+ ===================================
40
+ 2. Intellectual Property Protection
41
+ ===================================
42
+
43
+ Except for Third-Party Open Source Software, the Company owns all copyrights, trademark rights, patent rights, trade secrets, and other intellectual property rights of the Software, and has registered and protected them in relevant countries and regions according to the "Paris Convention" or "TRIPS Agreement", ensuring that the intellectual property rights of the Software are internationally recognized and protected.
44
+
45
+ The Company claims no intellectual property rights in the business code written by the User or by Agents when developing Applications, or in the User's business data, skills, and knowledge bases. The parts of the Software contained in an Application remain the intellectual property of the Company and are subject to this Agreement.
46
+
47
+ =============
48
+ 3. Disclaimer
49
+ =============
50
+
51
+ 3.1 The User shall not use the Software to engage in activities that contravene applicable laws and regulations or offend against public order or religious prohibitions. All legal liabilities and consequences arising from the User's use shall be borne by the User.
52
+
53
+ 3.2 The Company shall not be liable for any direct, indirect, special, incidental, or consequential damages (including but not limited to loss of profits, business interruption, data loss, or business information disclosure) caused by the User's use of the Software, even if it has been previously informed of the possibility of such damages.
54
+
55
+ 3.3 Code written, content generated, and actions taken by Agents shall be reviewed and accepted by the User before being used in production. All consequences arising from the use of the work of Agents shall be borne by the User.
56
+
57
+ 3.4 Third-Party Services are chosen and paid for by the User and are subject to their own terms. The Company shall not be liable for the availability, output, or data handling of Third-Party Services.
58
+
59
+ ===============
60
+ 4. License Type
61
+ ===============
62
+
63
+ 4.1 This Agreement serves as the unified license agreement for NocoBase Software, applying to both the Community Edition and the Commercial Editions.
64
+
65
+ 4.2 Core is open source software. This Agreement incorporates and references the full text of the Apache License, Version 2.0 ("Apache-2.0", available at: https://www.apache.org/licenses/LICENSE-2.0 ). Users of Core must comply with the Apache-2.0 License as well as the supplementary terms set forth in this Agreement. In case of any inconsistency between Apache-2.0 and this Agreement, the supplementary terms of this Agreement shall prevail.
66
+
67
+ 4.3 Studio and Commercial Plugins are commercial software of the Company and are not licensed under Apache-2.0. They can only be used under the Commercial License and in accordance with this Agreement.
68
+
69
+ ================================================
70
+ 5. Rights and Obligations of Open Source License
71
+ ================================================
72
+
73
+ 5.1 The Community Edition can be used for commercial purposes, including developing Applications for Customers.
74
+
75
+ 5.2 It is not allowed to remove or change the NocoBase statement "AI + NocoBase" and "Build reliable business systems", or its link to https://www.nocobase.com/, at the bottom left of the interface of any Application, including Applications delivered to Customers, even though the code that displays the statement is part of the Application. If the default layout is replaced, the statement shall be kept at an equally visible position on the main interface of the Application. Pages that can be accessed without signing in and pages embedded in other systems are not subject to this requirement. When the sidebar is collapsed, showing the statement as an icon with a tooltip, as provided by default, meets this requirement. The main LOGO in the upper left corner of the page, the version number, and other information on the interface can be changed.
76
+
77
+ 5.3 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
78
+
79
+ 5.4 It is not allowed to provide to the public any form of AI application development platform or agent development platform SaaS/PaaS products using the original or modified Software.
80
+
81
+ ===============================
82
+ 6. Rights of Commercial License
83
+ ===============================
84
+
85
+ 6.1 Obtain a permanent commercial license of the Software.
86
+
87
+ 6.2 Can develop Applications for Customers, deploy the completed Applications, including the Commercial Plugins they use, on Customers' servers, or host them and provide them to Customers as a service. This does not include Studio.
88
+
89
+ 6.3 Customers can use the Commercial Plugins in the Applications delivered to them, within those Applications only, and can continue to develop those Applications on their own or through others.
90
+
91
+ 6.4 Get software upgrades and exclusive technical support during the upgrade validity period. Upgrades and technical support are provided to the User only, and the User can upgrade the Commercial Plugins in Applications delivered to Customers during this period.
92
+
93
+ 6.5 The licensed Software can be used for commercial purposes with no restrictions on the number of Applications, Customers, users, and Agents.
94
+
95
+ 6.6 Can remove or change the NocoBase statement described in 5.2, and the brand, name, link, and other information about NocoBase on the interface of Applications.
96
+
97
+ 6.7 If there are other agreements in the contract for the above rights, the contract agreement shall prevail.
98
+
99
+ ====================================
100
+ 7. Obligations of Commercial License
101
+ ====================================
102
+
103
+ 7.1 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
104
+
105
+ 7.2 It is not allowed to sell, transfer, lease, share, gift, or distribute the Commercial License. Deploying Applications for Customers under 6.2 is not considered sharing the Commercial License, but the license key, access token, or other credentials of the Commercial License shall not be provided to Customers for any purpose other than deploying and running the Applications delivered to them.
106
+
107
+ 7.3 It is not allowed to sell, transfer, lease, share, or distribute any form of AI application development platform, agent development platform, or developer tools developed based on Software.
108
+
109
+ 7.4 It is not allowed to provide any form of AI application development platform or agent development platform SaaS/PaaS products to the public using the original or modified Software.
110
+
111
+ 7.5 Studio can only be used by the User holding the Commercial License, on servers owned or rented by the User. It is not allowed to deploy Studio for Customers, or to provide Studio to Customers or any third party in any form, including as a hosted service. Customers that need Studio shall purchase their own Commercial License.
112
+
113
+ 7.6 Customer personnel can take part in requirement confirmation, acceptance, and release approval in the User's Studio for the Applications the User develops for them. This is not considered providing Studio to Customers, but it is not allowed to let Customers use Studio to develop their own projects.
114
+
115
+ 7.7 If there is a violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility.
116
+
117
+ 7.8 If there are other agreements in the contract for the above obligations, the contract agreement shall prevail.
118
+
119
+ =============================================================
120
+ 8. Legal Jurisdiction, Interpretation, and Dispute Resolution
121
+ =============================================================
122
+
123
+ 8.1 Except for Mainland China, the interpretation, application, and all matters related to this agreement are subject to the jurisdiction of Singapore law.
124
+
125
+ 8.2 Any dispute related to this Agreement should first be resolved through friendly negotiation. If the negotiation fails to resolve the dispute, the dispute should be submitted to the International Chamber of Commerce (ICC) for arbitration. The arbitration venue should be Singapore, conducted in English.
126
+
127
+ 8.3 All terms and conditions of this Agreement shall be deemed enforceable to the maximum extent permitted by applicable law. If any term of this Agreement is deemed invalid by any applicable law, the invalidity of that term does not affect the validity of any other term of this Agreement, and it should be deemed that the invalid term has been modified as much as possible to make it valid and enforceable, or if the term cannot be modified, it should be deemed to have been deleted from this Agreement.
128
+
129
+ 8.4 The arbitration award is final, binding on both parties, and can be enforced in any court with jurisdiction.
@@ -1,16 +1,21 @@
1
1
  import { useTranslation } from '@nocobase/i18n/client';
2
2
  import { ArrowLeftIcon } from 'lucide-react';
3
3
  import type { ReactElement, ReactNode } from 'react';
4
- import { Link, type To, useResolvedPath } from 'react-router';
4
+ import { Link, type To, parsePath, useResolvedPath } from 'react-router';
5
5
 
6
- import { useRouteSearches } from '../routing/route-context.js';
6
+ import { navigationDestination } from '../routing/navigation-destination.js';
7
+ import {
8
+ useRouteTree,
9
+ useRouteAccess,
10
+ useRouteSearches,
11
+ } from '../routing/route-context.js';
7
12
 
8
13
  import { cn } from 'cn';
9
14
 
10
15
  export interface BackButtonProps {
11
16
  /**
12
17
  * Where it leads. Defaults to the parent route with its last visited query; a directly opened detail falls back
13
- * to the default parent list. An explicit destination is kept unchanged.
18
+ * to the default parent list. An explicit destination must match an authorized application page.
14
19
  */
15
20
  readonly to?: To;
16
21
  /** The label, "Back" by default. */
@@ -31,11 +36,36 @@ export function BackButton({
31
36
  children,
32
37
  className,
33
38
  to,
34
- }: BackButtonProps = {}): ReactElement {
39
+ }: BackButtonProps = {}): ReactElement | null {
35
40
  const { t } = useTranslation();
36
41
  const parent = useResolvedPath('..');
42
+ const resolved = useResolvedPath(to ?? '..');
43
+ const routes = useRouteTree();
44
+ const access = useRouteAccess();
37
45
  const searches = useRouteSearches();
38
46
 
47
+ const fallback = {
48
+ ...parent,
49
+ search: searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ?? '',
50
+ };
51
+ const rawPath =
52
+ typeof to === 'string' ? parsePath(to).pathname : to?.pathname;
53
+ const unsafe =
54
+ rawPath &&
55
+ (/^(?:[a-z][a-z\d+.-]*:|\/\/)/iu.test(rawPath) ||
56
+ rawPath.includes('\\') ||
57
+ [...rawPath].some((character) => character.charCodeAt(0) <= 32));
58
+ const primary = unsafe
59
+ ? undefined
60
+ : navigationDestination(
61
+ to && rawPath?.startsWith('/') ? to : to ? resolved : fallback,
62
+ routes,
63
+ access,
64
+ );
65
+ const destination =
66
+ primary ?? navigationDestination(fallback, routes, access);
67
+ if (!destination) return null;
68
+
39
69
  return (
40
70
  <Link
41
71
  replace
@@ -43,16 +73,11 @@ export function BackButton({
43
73
  'inline-flex w-fit items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground',
44
74
  className,
45
75
  )}
46
- to={
47
- to ?? {
48
- pathname: '..',
49
- search:
50
- searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ?? '',
51
- }
52
- }
76
+ to={destination}
53
77
  >
54
78
  <ArrowLeftIcon className='size-4' />
55
- {children ?? t('navigation.back', { defaultValue: 'Back' })}
79
+ {(primary ? children : undefined) ??
80
+ t('navigation.back', { defaultValue: 'Back' })}
56
81
  </Link>
57
82
  );
58
83
  }
@@ -1,7 +1,10 @@
1
1
  import type { ComponentPropsWithoutRef, ReactElement } from 'react';
2
2
  import { cn } from 'cn';
3
3
 
4
- import { BRAND_MARK_PATH, BRAND_MARK_TRANSLATE } from './brand-mark-geometry';
4
+ import {
5
+ BRAND_MARK_PATH,
6
+ BRAND_MARK_TRANSLATE,
7
+ } from './brand-mark-geometry.js';
5
8
 
6
9
  /** Keep the brand recognizable; the orbit, rather than the bricks, conveys progress. */
7
10
  export function BrandLoadingMark({
@@ -25,7 +28,7 @@ export function BrandLoadingMark({
25
28
  strokeWidth='2'
26
29
  opacity='.12'
27
30
  />
28
- <g className='nocobase-loading-orbit'>
31
+ <g className='origin-center motion-safe:animate-spin'>
29
32
  <path
30
33
  d='M32 4a28 28 0 0 1 28 28'
31
34
  stroke='currentColor'
@@ -1,7 +1,7 @@
1
1
  /** Official Logomark vector from the supplied logos/Transparent/Vectors asset.
2
2
  * Keep geometry unchanged; color, size and decorative animation belong to consumers.
3
3
  */
4
- export const BRAND_MARK_VIEW_BOX = '0 0 100 87.19720458984375';
5
- export const BRAND_MARK_TRANSLATE = 'translate(0 -6.401886463165283)';
6
- export const BRAND_MARK_PATH =
4
+ export const BRAND_MARK_VIEW_BOX: string = '0 0 100 87.19720458984375';
5
+ export const BRAND_MARK_TRANSLATE: string = 'translate(0 -6.401886463165283)';
6
+ export const BRAND_MARK_PATH: string =
7
7
  'M99.876 55.369c-.025-.045-.076-.072-.109-.111-.066-.078-.125-.168-.217-.221l-.014-.008-.001-.002h-.001L80.368 43.648v-24.67a.92.92 0 0 0-.124-.459c-.026-.045-.075-.072-.108-.111-.066-.08-.126-.168-.218-.221l-.014-.008-.001-.002h-.002L60.286 6.531a.92.92 0 0 0-.94 0L20.099 29.834h-.002l-.001.002-.014.006c-.092.055-.151.143-.218.221-.033.041-.082.068-.108.113a.907.907 0 0 0-.124.457v1.361L.466 43.373H.465l-.001.002c-.098.055-.162.143-.23.227-.034.041-.084.068-.109.113a.898.898 0 0 0-.125.457v25.195c0 .324.171.625.45.791l39.264 23.311c.002.002.006 0 .008.002l.007.008a.924.924 0 0 0 .911 0c.003-.002.004-.006.008-.008.001-.002.005 0 .007-.002l19.161-11.377 19.162 11.377c.002.002.006 0 .008.002.004.002.004.006.007.008a.924.924 0 0 0 .911 0c.003-.002.004-.006.008-.008.002-.002.005 0 .007-.002L99.55 81.812a.916.916 0 0 0 .45-.791V55.828a.92.92 0 0 0-.124-.459zM41.104 65.867V42.812l37.424-22.219v23.055L41.104 65.867zM59.815 8.391l17.83 10.588L59.82 29.561 40.185 41.219l-17.83-10.586 37.46-22.242zM39.264 42.812V65.866L21.472 55.305V32.248l17.792 10.564z';
@@ -1,6 +1,7 @@
1
1
  import { usePageBreadcrumbLevels } from '@nocobase/app-client';
2
2
  import type { PageBreadcrumbLevel } from '@nocobase/app-client';
3
3
  import { useTranslation } from '@nocobase/i18n/client';
4
+ import { ArrowLeftIcon } from 'lucide-react';
4
5
  import { Fragment, type ReactElement } from 'react';
5
6
  import { Link } from 'react-router';
6
7
 
@@ -23,10 +24,13 @@ import { settingsHomeRoute } from '#lib/navigation-transform';
23
24
  import { cn } from 'cn';
24
25
 
25
26
  import {
27
+ useRouteTree,
28
+ useRouteAccess,
26
29
  useRouteTrail,
27
30
  useRouteSearches,
28
31
  type RouteTrailEntry,
29
32
  } from '../routing/route-context.js';
33
+ import { navigationDestination } from '../routing/navigation-destination.js';
30
34
  import { routeKey } from '../routing/route-navigation.js';
31
35
 
32
36
  export interface BreadcrumbsProps {
@@ -45,7 +49,7 @@ export interface BreadcrumbsProps {
45
49
  * `usePageBreadcrumb` from `@nocobase/app-client`. Earlier levels link where a page sits behind them; the last is the
46
50
  * current page.
47
51
  *
48
- * On a phone only the last level shows, beside the brand; on a medium screen a trail of three or more levels keeps its
52
+ * On a phone the current title and a link to the nearest authorized parent show; on a medium screen a trail of three or more levels keeps its
49
53
  * first and last and folds the middle into a menu.
50
54
  */
51
55
  export function Breadcrumbs({
@@ -53,10 +57,18 @@ export function Breadcrumbs({
53
57
  denied,
54
58
  }: BreadcrumbsProps = {}): ReactElement | null {
55
59
  const trail = useRouteTrail();
60
+ const routes = useRouteTree();
61
+ const contextAccess = useRouteAccess();
62
+ const access = denied
63
+ ? {
64
+ ...contextAccess,
65
+ denied: new Set([...contextAccess.denied, ...denied]),
66
+ }
67
+ : contextAccess;
56
68
  const searches = useRouteSearches();
57
69
  const declared = usePageBreadcrumbLevels();
58
70
  const { t } = useTranslation();
59
- const trailLevels =
71
+ const candidates =
60
72
  declared ??
61
73
  trail.flatMap((entry): PageBreadcrumbLevel[] => {
62
74
  const title = routeLevelTitle(entry);
@@ -93,6 +105,19 @@ export function Breadcrumbs({
93
105
  ];
94
106
  });
95
107
 
108
+ const trailLevels = candidates.flatMap(
109
+ (level, index): PageBreadcrumbLevel[] => {
110
+ if (!level.to) return [level];
111
+ const to = navigationDestination(level.to, routes, access);
112
+ // Keep the current title during permission checks, but never expose an inaccessible ancestor's name.
113
+ return to
114
+ ? [{ ...level, to }]
115
+ : index === candidates.length - 1
116
+ ? [{ label: level.label }]
117
+ : [];
118
+ },
119
+ );
120
+
96
121
  if (trailLevels.length === 0) return null;
97
122
  // A level is told apart by its depth and label; two levels may share either alone.
98
123
  const levels = trailLevels.map((level, depth) => ({
@@ -101,6 +126,7 @@ export function Breadcrumbs({
101
126
  }));
102
127
  const last = levels.length - 1;
103
128
  const folded = levels.slice(1, last);
129
+ const parent = levels.slice(0, last).findLast((level) => level.to);
104
130
 
105
131
  return (
106
132
  <Breadcrumb
@@ -108,6 +134,21 @@ export function Breadcrumbs({
108
134
  className={cn('min-w-0', className)}
109
135
  >
110
136
  <BreadcrumbList className='flex-nowrap'>
137
+ {parent ? (
138
+ <BreadcrumbItem className='shrink-0 md:hidden'>
139
+ <BreadcrumbLink
140
+ aria-label={t('navigation.backTo', {
141
+ label: parent.label,
142
+ defaultValue: `Back to ${parent.label}`,
143
+ })}
144
+ title={parent.label}
145
+ className='inline-flex size-9 items-center justify-center rounded-sm focus-visible:outline-2 focus-visible:outline-ring'
146
+ render={<Link to={parent.to!} replace />}
147
+ >
148
+ <ArrowLeftIcon aria-hidden='true' className='size-4' />
149
+ </BreadcrumbLink>
150
+ </BreadcrumbItem>
151
+ ) : null}
111
152
  {levels.map((level, index) => (
112
153
  <Fragment key={level.key}>
113
154
  {index === 1 && folded.length > 0 ? (
@@ -154,7 +195,10 @@ export function Breadcrumbs({
154
195
  )}
155
196
  >
156
197
  {index === last ? (
157
- <BreadcrumbPage className='truncate font-medium'>
198
+ <BreadcrumbPage
199
+ title={level.label}
200
+ className='truncate font-medium'
201
+ >
158
202
  {level.label}
159
203
  </BreadcrumbPage>
160
204
  ) : level.to ? (
@@ -1,5 +1,6 @@
1
1
  import type { ListState } from '@nocobase/app-client';
2
2
  import { ListFeedback, ListFeedbackActions } from '#components/list-feedback';
3
+ import { RefreshIndicator } from '#components/loading';
3
4
  import {
4
5
  type ColumnDef,
5
6
  type PaginationState,
@@ -17,7 +18,14 @@ import {
17
18
  getSortedRowModel,
18
19
  useReactTable,
19
20
  } from '@tanstack/react-table';
20
- import { type ReactElement, type ReactNode, useMemo, useState } from 'react';
21
+ import {
22
+ type ReactElement,
23
+ type ReactNode,
24
+ useEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState,
28
+ } from 'react';
21
29
 
22
30
  import { DataTablePagination } from './pagination.js';
23
31
  import {
@@ -41,7 +49,18 @@ export interface DataTableProps<TData, TValue = unknown> {
41
49
  readonly sortingState?: SortingState;
42
50
  readonly onSortingChange?: OnChangeFn<SortingState>;
43
51
  readonly showSelectedCount?: boolean;
52
+ /**
53
+ * The current request is in flight. With rows of the current query and permission scope this is a background
54
+ * refresh: rows stay and a delayed refresh indicator appears. Without them the body shows the loading state.
55
+ * Explicit false pauses loading feedback for an unresolved, inactive query; omitting it uses listState.
56
+ */
44
57
  readonly loading?: boolean;
58
+ /**
59
+ * The current query's stable identity, such as its request path. When it
60
+ * changes the loading and refresh feedback restart their display delay;
61
+ * the table, its rows' geometry and its controls stay mounted.
62
+ */
63
+ readonly loadingIdentity?: unknown;
45
64
  readonly listState?: ListState;
46
65
  readonly onRetry?: () => void;
47
66
  readonly onClearFilters?: () => void;
@@ -95,7 +114,8 @@ export function DataTable<TData, TValue = unknown>({
95
114
  sortingState,
96
115
  onSortingChange,
97
116
  showSelectedCount = true,
98
- loading = false,
117
+ loading,
118
+ loadingIdentity,
99
119
  listState,
100
120
  onRetry,
101
121
  onClearFilters,
@@ -176,6 +196,37 @@ export function DataTable<TData, TValue = unknown>({
176
196
  rows.length > 0 && (state === 'ready' || state === 'refresh-failed');
177
197
  const showFeedback = !showRows || state === 'refresh-failed';
178
198
  const feedbackColumnCount = Math.max(1, table.getVisibleLeafColumns().length);
199
+ const showPagination =
200
+ pagination &&
201
+ (state === 'ready' || (state === 'refresh-failed' && rows.length > 0));
202
+ // A new query hides the previous rows at once; keep their body and footer
203
+ // geometry while it loads so the page below does not jump.
204
+ const bodyRef = useRef<HTMLTableSectionElement>(null);
205
+ const footerRef = useRef<HTMLDivElement>(null);
206
+ const [reserved, setReserved] = useState<{
207
+ readonly body: number;
208
+ readonly footer: number;
209
+ }>();
210
+ if (!showRows && state !== 'loading' && reserved !== undefined)
211
+ setReserved(undefined);
212
+ useEffect(() => {
213
+ const body = bodyRef.current;
214
+ if (!showRows || !body || typeof ResizeObserver === 'undefined') return;
215
+ const footer = footerRef.current;
216
+ const observer = new ResizeObserver(() => {
217
+ const next = {
218
+ body: body.offsetHeight,
219
+ footer: footer?.offsetHeight ?? 0,
220
+ };
221
+ setReserved((old) =>
222
+ old?.body === next.body && old.footer === next.footer ? old : next,
223
+ );
224
+ });
225
+ observer.observe(body);
226
+ if (footer) observer.observe(footer);
227
+ return () => observer.disconnect();
228
+ }, [showRows, showPagination]);
229
+ const reserving = state === 'loading' && reserved !== undefined;
179
230
 
180
231
  return (
181
232
  <div className={cn('flex flex-col gap-4', className)}>
@@ -194,8 +245,14 @@ export function DataTable<TData, TValue = unknown>({
194
245
  />
195
246
  </div>
196
247
  ) : null}
197
- <div className='overflow-hidden rounded-lg border'>
198
- <Table aria-busy={loading}>
248
+ <div className='relative overflow-hidden rounded-lg border'>
249
+ {/* A retry after a refresh failure is announced by its own toolbar button. */}
250
+ <RefreshIndicator
251
+ pending={Boolean(loading && showRows && state !== 'refresh-failed')}
252
+ identity={loadingIdentity}
253
+ />
254
+ {/* Only retained rows are busy; a loading body owns its own busy region and status. */}
255
+ <Table aria-busy={loading && showRows}>
199
256
  <TableHeader>
200
257
  {table.getHeaderGroups().map((headerGroup) => (
201
258
  <TableRow key={headerGroup.id}>
@@ -224,15 +281,27 @@ export function DataTable<TData, TValue = unknown>({
224
281
  </TableRow>
225
282
  ))}
226
283
  </TableHeader>
227
- <TableBody>
284
+ <TableBody ref={bodyRef}>
228
285
  {showFeedback ? (
229
286
  <TableRow>
230
- <TableCell colSpan={feedbackColumnCount} className='p-0'>
231
- <ListFeedback
232
- state={state}
233
- empty={emptyMessage}
234
- emptyAction={emptyAction}
235
- />
287
+ <TableCell
288
+ colSpan={feedbackColumnCount}
289
+ className='p-0'
290
+ style={
291
+ reserving && reserved.body > 0
292
+ ? { height: reserved.body }
293
+ : undefined
294
+ }
295
+ >
296
+ {/* Readiness may stay unresolved while the parent query is paused. */}
297
+ {state !== 'loading' || loading !== false ? (
298
+ <ListFeedback
299
+ state={state}
300
+ identity={loadingIdentity}
301
+ empty={emptyMessage}
302
+ emptyAction={emptyAction}
303
+ />
304
+ ) : null}
236
305
  </TableCell>
237
306
  </TableRow>
238
307
  ) : null}
@@ -258,14 +327,16 @@ export function DataTable<TData, TValue = unknown>({
258
327
  </TableBody>
259
328
  </Table>
260
329
  </div>
261
- {pagination &&
262
- (state === 'ready' || (state === 'refresh-failed' && rows.length > 0)) ? (
263
- <DataTablePagination
264
- table={table}
265
- pageSizeOptions={pageSizeOptions}
266
- showSelectedCount={showSelectedCount}
267
- disabled={loading}
268
- />
330
+ {showPagination || (pagination && reserving) ? (
331
+ <div ref={footerRef} data-slot='data-table-footer'>
332
+ <DataTablePagination
333
+ table={table}
334
+ pageSizeOptions={pageSizeOptions}
335
+ showSelectedCount={showSelectedCount}
336
+ disabled={loading || !showPagination}
337
+ countPending={!showPagination}
338
+ />
339
+ </div>
269
340
  ) : null}
270
341
  </div>
271
342
  );