openxiangda-skill-kit 2.0.0-alpha.13 → 2.0.0-alpha.130
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.
- package/README.md +6 -6
- package/dist/bin.js +0 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -62
- package/dist/index.js.map +1 -1
- package/dist/internal/skill-installer.d.ts +8 -0
- package/dist/internal/skill-installer.d.ts.map +1 -0
- package/dist/internal/skill-installer.js +51 -0
- package/dist/internal/skill-installer.js.map +1 -0
- package/package.json +3 -6
- package/skills/manifest.json +2 -32
- package/skills/openxiangda-v2/SKILL.md +57 -24
- package/skills/openxiangda-v2/agents/openai.yaml +1 -1
- package/skills/openxiangda-v2/references/appspec.md +67 -0
- package/skills/openxiangda-v2/references/architecture.md +9 -0
- package/skills/openxiangda-v2/references/backend.md +279 -0
- package/skills/openxiangda-v2/references/commands.md +21 -0
- package/skills/openxiangda-v2/references/data-authz.md +410 -0
- package/skills/openxiangda-v2/references/delivery.md +49 -0
- package/skills/openxiangda-v2/references/discovery.md +15 -0
- package/skills/openxiangda-v2/references/frontend.md +259 -0
- package/skills/openxiangda-v2/references/public-access.md +159 -0
- package/skills/openxiangda-v2/references/testing.md +74 -0
- package/skills/openxiangda-v2/references/workflow-events.md +279 -0
- package/skills/openxiangda-v2/references/workspace.md +48 -0
- package/docs/architecture/repository-and-release.md +0 -52
- package/docs/backend.md +0 -89
- package/docs/concepts.md +0 -34
- package/docs/data-authz.md +0 -100
- package/docs/delivery.md +0 -71
- package/docs/frontend.md +0 -47
- package/docs/getting-started.md +0 -120
- package/docs/index.md +0 -23
- package/docs/llms.txt +0 -12
- package/docs/reference/cli.md +0 -51
- package/docs/reference/mcp.md +0 -26
- package/docs/workflow-events.md +0 -63
- package/skills/openxiangda-v2-architecture/SKILL.md +0 -29
- package/skills/openxiangda-v2-architecture/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-backend/SKILL.md +0 -42
- package/skills/openxiangda-v2-backend/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-data-authz/SKILL.md +0 -44
- package/skills/openxiangda-v2-data-authz/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-delivery/SKILL.md +0 -63
- package/skills/openxiangda-v2-delivery/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-frontend/SKILL.md +0 -40
- package/skills/openxiangda-v2-frontend/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-workflow-events/SKILL.md +0 -39
- package/skills/openxiangda-v2-workflow-events/agents/openai.yaml +0 -4
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# OpenXiangda 2.0 Frontend
|
|
2
|
+
|
|
3
|
+
Read [Data and authorization](data-authz.md) for every resource or permission
|
|
4
|
+
change and keep the generated [workspace contract](workspace.md) authoritative.
|
|
5
|
+
|
|
6
|
+
Use the official Vite + React Router + Refine Core + Ant Design template. Import the platform-owned runtime from `openxiangda/react`, field controls from `openxiangda/field-kit` and clients/types from `openxiangda/core`. Do not copy these implementations into application source and do not add per-resource function wrappers.
|
|
7
|
+
|
|
8
|
+
Generated desktop lists already own row selection, atomic batch update/delete,
|
|
9
|
+
CSV/XLS/XLSX preview import, export, filters, column settings and density. Keep
|
|
10
|
+
imports at 100 rows per transaction, use exact declared labels or field codes,
|
|
11
|
+
and leave attachment values to the platform managed-file component.
|
|
12
|
+
|
|
13
|
+
Declare searchable and filterable fields on the resource and let the standard
|
|
14
|
+
CRUD renderer compose the search area. Keyword search spans the declared
|
|
15
|
+
searchable fields; the first two filters remain inline and additional filters
|
|
16
|
+
open in the platform's large modal. The renderer also preserves declaration
|
|
17
|
+
order for form fields and removes authorization/debug explanations from the
|
|
18
|
+
business UI. Do not create application-owned search toolbars, field ordering
|
|
19
|
+
maps, permission tips or per-resource CRUD pages for these standard behaviors.
|
|
20
|
+
|
|
21
|
+
Read the current user, complete application-role union and capabilities from the platform runtime authorization endpoint. There is no application-selected active role or identity switch. The standard Shell owns the optional Perspective selector. Use `hasReadCapability` for navigation, list/detail visibility and readable fields; continue to use `hasCapability` for create/update/delete, workflows and custom actions. The standard client sends the selected Perspective automatically, and the server repeats the projection for rows, fields and RLS. Never implement resource-specific frontend filters for it. Strip unauthorized fields from create and update payloads and never treat a disabled input as server authorization. Verify with `pnpm openxiangda check`.
|
|
22
|
+
|
|
23
|
+
Use the standard `Shell` unchanged. It owns the authenticated user's display
|
|
24
|
+
name, account identifier, safe avatar upload, localized application-role names,
|
|
25
|
+
theme controls, Perspective selection and navigation scroll restoration. It
|
|
26
|
+
also owns directory field requests under the current logged-in user's role
|
|
27
|
+
union. Never fetch identity for the header, display role codes, add a second
|
|
28
|
+
identity selector, or copy Shell and
|
|
29
|
+
directory-client implementations into application source.
|
|
30
|
+
|
|
31
|
+
For a custom application role-management page, use only the typed functions in
|
|
32
|
+
`openxiangda/core`: `loadRoleManagementCatalog`, `listRoleMemberships`,
|
|
33
|
+
`searchRoleManagementUsers`, the membership mutations, and the
|
|
34
|
+
role-management-grant mutations. The catalog's `roleManagement` projection
|
|
35
|
+
drives button visibility, while the platform repeats the check for every
|
|
36
|
+
request. The “Custom role-management pages” section in
|
|
37
|
+
[Data and authorization](data-authz.md) defines the delegation and CAS
|
|
38
|
+
contract. Do not use the developer control-plane client in browser code.
|
|
39
|
+
|
|
40
|
+
The application owns the editable admin information architecture through the
|
|
41
|
+
typed `defineAdminNavigation`, `adminNavigationGroup`, `adminResourcePage`,
|
|
42
|
+
and `adminOperationPage` helpers in `openxiangda/config`. The compiler emits
|
|
43
|
+
all reachable `adminPages`, but the Shell renders only pages explicitly
|
|
44
|
+
referenced by generated `adminNavigation`, then applies current-user access as
|
|
45
|
+
a final filter. Page existence, route reachability and menu visibility are
|
|
46
|
+
separate: detail/new/edit/dynamic/handoff pages never become menu entries by
|
|
47
|
+
discovery, and permission logic cannot create entries. Read
|
|
48
|
+
`openxiangda://workspace/contracts` or call `contract_describe`, then copy
|
|
49
|
+
`data.adminNavigationAuthoring.suggestion.expression` once into
|
|
50
|
+
`frontend.admin.navigation` with the listed `openxiangda/config` imports. The
|
|
51
|
+
proposal is deterministic and editable; the compiler/runtime never invokes it
|
|
52
|
+
or appends newly added resources later.
|
|
53
|
+
|
|
54
|
+
Generated desktop resource CRUD routes use the compiler-owned
|
|
55
|
+
`/admin/resources/<resourceCode>...` namespace and always render inside the
|
|
56
|
+
platform's one Shell. Their independent mobile admin surface is projected from
|
|
57
|
+
the same generated route catalog under `/m/admin/resources/<resourceCode>...`.
|
|
58
|
+
Generated pages consume that catalog for every list/create/detail/edit/back
|
|
59
|
+
navigation; do not reconstruct root resource paths in application code. Root
|
|
60
|
+
and ordinary `/m/...` paths remain available to explicit user routes. The
|
|
61
|
+
compiler rejects an explicit route whose canonical path shape conflicts with
|
|
62
|
+
any explicit or platform-generated route, including dynamic routes that differ
|
|
63
|
+
only by parameter name. Do not add aliases, redirects or route-order branches
|
|
64
|
+
for earlier alpha paths.
|
|
65
|
+
|
|
66
|
+
Declare the application-wide admin boundary at `frontend.admin.access` with
|
|
67
|
+
`allOf` and/or `anyOf` capability arrays. Import generated `adminAccess` and
|
|
68
|
+
pass it to `OpenXiangdaApplication`; the runtime evaluates that same immutable
|
|
69
|
+
expression before `/admin/**`, `/m/admin/**`, the Shell, generated resources or
|
|
70
|
+
admin contribution pages mount. Portal shortcuts may use the exported pure
|
|
71
|
+
`isAdminAccessAllowed` predicate, while Data/App/Workflow APIs remain the
|
|
72
|
+
server-side authority.
|
|
73
|
+
|
|
74
|
+
Declare custom routes in `openxiangda.config.ts`, import the generated
|
|
75
|
+
`appRoutes`, and bind every route key to exactly one local React page with
|
|
76
|
+
`defineApplicationContributions` from `openxiangda/react`. Pass the result to
|
|
77
|
+
`OpenXiangdaApplication`. The runtime registers `surface: 'admin'` routes inside
|
|
78
|
+
the one platform Shell; do not wrap them in another Shell. It renders
|
|
79
|
+
`surface: 'user'` routes without the admin Shell so mobile/user experiences can
|
|
80
|
+
own their page layout without creating another router, identity provider or
|
|
81
|
+
permission store. Only static admin routes explicitly referenced by
|
|
82
|
+
`frontend.admin.navigation` enter the menu. Hidden routes retain the same
|
|
83
|
+
`capability` or `access.allOf/anyOf`, including ancestor constraints. Keep admin
|
|
84
|
+
operation pages under `/admin/operations`; parameterized routes remain
|
|
85
|
+
reachable but cannot be menu references. `defineAdminContributions` remains an
|
|
86
|
+
admin-only helper and intentionally rejects `user` routes.
|
|
87
|
+
|
|
88
|
+
The platform also owns admin appearance. Wrap bespoke admin content in
|
|
89
|
+
`OpenXiangdaAdminPage`. Ant Design components inherit the existing
|
|
90
|
+
`ConfigProvider`; do not create another theme provider. Use
|
|
91
|
+
`useOpenXiangdaTheme()` for TypeScript-rendered values such as chart colors and
|
|
92
|
+
the public `--oxa-color-*`, `--oxa-shadow-surface`, and
|
|
93
|
+
`--oxa-radius-surface` variables for custom CSS. Never hard-code white, black,
|
|
94
|
+
or neutral greys as admin canvas, card, border, or text colors. Application
|
|
95
|
+
brand and categorical colors may remain explicit only when they are not used as
|
|
96
|
+
structural theme colors. Verify every custom admin page in light, dark, and
|
|
97
|
+
follow-system modes.
|
|
98
|
+
|
|
99
|
+
Declare application-owned login visuals with optional
|
|
100
|
+
`frontend.authentication`. The only supported account contract is
|
|
101
|
+
`existing-platform-users-only` with `registration.mode: 'reject'`; use exact
|
|
102
|
+
desktop `/login` and mobile `/m/login`, each pointing to a static protected user
|
|
103
|
+
default route. Import generated `authenticationSurfaces` and bind an exact
|
|
104
|
+
desktop/mobile renderer map alongside `appRoutes`:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
const contributions = defineApplicationContributions(
|
|
108
|
+
{ routes: appRoutes, authenticationSurfaces },
|
|
109
|
+
{
|
|
110
|
+
pages,
|
|
111
|
+
authentication: {
|
|
112
|
+
applicationLogin: DesktopLogin,
|
|
113
|
+
applicationLoginMobile: MobileLogin,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Renderers receive only `ApplicationLoginSurfaceProps`: safe method descriptors,
|
|
120
|
+
state/error metadata, normalized `returnTo`, and platform callbacks. They never
|
|
121
|
+
receive credentials, provider configuration, OAuth state, tokens, roles, or
|
|
122
|
+
authorization facts. The platform owns the one Router and authentication
|
|
123
|
+
boundary, tokenless v2 facade, Secure HttpOnly session/refresh family and
|
|
124
|
+
logout. Do not put login in `appRoutes`, call v1 auth APIs, persist tokens, or
|
|
125
|
+
translate network/5xx and authenticated 403 states into login. Generated
|
|
126
|
+
`platformAuthManifest` is the independent login QA denominator and does not
|
|
127
|
+
change protected user route counts.
|
|
128
|
+
|
|
129
|
+
For a deliberately anonymous user page, first read
|
|
130
|
+
[Anonymous public access](public-access.md), declare one static `surface: 'user'`
|
|
131
|
+
route and bind it through `frontend.publicAccess`. The policy must name one
|
|
132
|
+
resource, its writable/returnable fields and only the bounded operations needed
|
|
133
|
+
by the page (`draft.read`, `draft.update`, `validate`, `create`, `own.list`,
|
|
134
|
+
`own.read`). A public route must not also declare `capability` or `access`.
|
|
135
|
+
Pass generated `anonymousPublicAccess` to
|
|
136
|
+
`<OpenXiangdaApplication publicAccess={anonymousPublicAccess}>`, then use
|
|
137
|
+
`createAnonymousPublicClient({ routeCode })` from `openxiangda/react` inside
|
|
138
|
+
the page. This client owns bootstrap, current-draft CAS, named validation,
|
|
139
|
+
managed upload, idempotent submission and owner-scoped list/detail calls. Do
|
|
140
|
+
not call the general Native Data API, store an identity in local storage, or
|
|
141
|
+
derive ownership from IP, user-agent or a browser fingerprint. The HttpOnly
|
|
142
|
+
browser credential identifies only this browser profile; clearing it or using
|
|
143
|
+
another browser loses access by design.
|
|
144
|
+
|
|
145
|
+
This is also the whole-application composition contract: pass the resulting
|
|
146
|
+
`contributions` to `OpenXiangdaApplication` and let that component remain the
|
|
147
|
+
only owner of `BrowserRouter`, `RuntimeBoundary`, Refine, generated resource and
|
|
148
|
+
Workflow routes, and the admin Shell. A user page may render an independent PC
|
|
149
|
+
or mobile layout, but it must not add a nested router/Shell or copy generated
|
|
150
|
+
routes. Route parameters continue to come from React Router, and generated
|
|
151
|
+
capability plus ancestor access guards run before the component renders.
|
|
152
|
+
|
|
153
|
+
To expose the standard application-level todo center, declare
|
|
154
|
+
`frontend.user.applicationTodoCenter: true`. The compiler generates `/todos`;
|
|
155
|
+
the same runtime exposes the independent mobile `/m/todos` page. The page reads only the authenticated user's
|
|
156
|
+
Notification Hub recipient projection and deep-links to the platform-resolved
|
|
157
|
+
target. Do not query Notification Hub management endpoints or recreate a local
|
|
158
|
+
message state store.
|
|
159
|
+
|
|
160
|
+
When an application needs branded user-end composition, keep those generated
|
|
161
|
+
routes and contribute renderers from the application entry through the public
|
|
162
|
+
`openxiangda/react` contract:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import {
|
|
166
|
+
defineApplicationContributions,
|
|
167
|
+
type StandardApplicationTodoCenterProps,
|
|
168
|
+
type StandardUserPageFrameProps,
|
|
169
|
+
type StandardUserSurfaceContributions,
|
|
170
|
+
} from 'openxiangda/react';
|
|
171
|
+
|
|
172
|
+
const standardUserSurfaces = {
|
|
173
|
+
frame: {
|
|
174
|
+
desktop: DesktopUserFrame,
|
|
175
|
+
mobile: MobileUserFrame,
|
|
176
|
+
},
|
|
177
|
+
applicationTodoCenter: {
|
|
178
|
+
desktop: DesktopTodoCenter,
|
|
179
|
+
mobile: MobileTodoCenter,
|
|
180
|
+
},
|
|
181
|
+
} satisfies StandardUserSurfaceContributions;
|
|
182
|
+
|
|
183
|
+
export const applicationContributions = defineApplicationContributions(
|
|
184
|
+
{ routes: appRoutes, authenticationSurfaces },
|
|
185
|
+
{ pages, authentication, standardUserSurfaces },
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Both desktop/mobile members and both groups are mandatory once
|
|
190
|
+
`standardUserSurfaces` is present; partial families fail at compile time and
|
|
191
|
+
again at runtime. Omit the property to retain the platform defaults.
|
|
192
|
+
|
|
193
|
+
`StandardUserPageFrameProps` contains `pageKind`, `device`, `mobile`, rendered
|
|
194
|
+
`children`, safe `route` metadata, `canGoBack` and the platform-owned `back()`
|
|
195
|
+
callback. The frame wraps Todo and standard Workflow work-center, launch, task
|
|
196
|
+
and instance pages. It must not create a Router, resolve identity, redirect a
|
|
197
|
+
standard path or reinterpret route metadata as authorization.
|
|
198
|
+
|
|
199
|
+
`StandardApplicationTodoCenterProps` contains only the authenticated current
|
|
200
|
+
user's `items`, aggregate `counts`, `total`, `loading`, `loadingMore`, `error`,
|
|
201
|
+
immutable `query`, `hasMore`, and the bounded callbacks `setQuery`, `refresh`,
|
|
202
|
+
`loadMore`, `recordInteraction` and `openItem`. `setQuery` owns view, keyword,
|
|
203
|
+
unread and paged-offset changes; `loadMore` appends and de-duplicates the next
|
|
204
|
+
platform page; `openItem` records a click best-effort and uses the
|
|
205
|
+
platform-resolved desktop/mobile target. Render only these props. Never import
|
|
206
|
+
the private platform client, call Notification Hub endpoints, persist a Todo
|
|
207
|
+
copy, accept a user/token parameter, or navigate from an item object not
|
|
208
|
+
supplied by the current render. A renderer exception is contained by the
|
|
209
|
+
platform error boundary without replacing `RuntimeBoundary` or the Router.
|
|
210
|
+
|
|
211
|
+
The compiler also emits the required generated `routeManifest`. This is the
|
|
212
|
+
single desktop/mobile paired catalog for standard Workflow and Todo pages. Each
|
|
213
|
+
entry carries stable route codes, path parameters, access metadata and a
|
|
214
|
+
`requiresAuthentication: true` marker; the top-level digest binds the complete
|
|
215
|
+
catalog. Pass it directly to `OpenXiangdaApplication`. The runtime validates
|
|
216
|
+
user Surface pairs, parameter names and shared access metadata before registering
|
|
217
|
+
the Router. Applications must not recreate standard `/todos`, `/m/todos`, or
|
|
218
|
+
Workflow paths, add aliases/redirects, or maintain a second route state. The
|
|
219
|
+
compiler/platform preflight is the digest authority; the browser validates the
|
|
220
|
+
digest shape and fails closed on malformed or inconsistent entries.
|
|
221
|
+
|
|
222
|
+
For standard process submission, consume the typed browser helpers
|
|
223
|
+
`loadBusinessProcessReceipt(commandId)` and
|
|
224
|
+
`pollBusinessProcessCommand(commandId, afterRevision)` (or the matching Nest
|
|
225
|
+
`receipt`/`poll` methods). A first response may be `accepted`; follow
|
|
226
|
+
`nextPoll` until `terminal` instead of treating it as failure or issuing a
|
|
227
|
+
generic request to the platform endpoint.
|
|
228
|
+
|
|
229
|
+
For a small business action on a generated resource page, use the typed
|
|
230
|
+
`resources[resourceCode].toolbar`, `.row` or `.detail` slots accepted by
|
|
231
|
+
`defineApplicationContributions`. Declare a stable action code, label and
|
|
232
|
+
capability/access expression. The render context contains only the current
|
|
233
|
+
resource, already-authorized record or selection, and a bounded `refresh()`;
|
|
234
|
+
it does not own CRUD, revisions, fields or authorization. Invoke a guarded Nest
|
|
235
|
+
App Operation for cross-resource transactions, invariants or side effects and
|
|
236
|
+
leave ordinary CRUD on the Native Data API. Do not copy a generated page just
|
|
237
|
+
to insert a button.
|
|
238
|
+
|
|
239
|
+
Generated contracts intentionally emit each resource Surface once in
|
|
240
|
+
`resourceSurfaces`; `resourceDefinitions[code].surface` references that shared
|
|
241
|
+
object. Import the generated runtime definitions normally. Do not serialize,
|
|
242
|
+
inline or duplicate Surface literals in application code.
|
|
243
|
+
|
|
244
|
+
The standard Workflow detail renderers consume only the authoritative Surface:
|
|
245
|
+
`presentation.businessDetail`, `presentation.summary`, the typed timeline and
|
|
246
|
+
operation descriptors. Continue to use `SurfaceFieldValue` for complete field
|
|
247
|
+
semantics and the Workflow-scoped file client for attachments, rich-text images
|
|
248
|
+
and signatures. Keep desktop and mobile renderers structurally independent;
|
|
249
|
+
place decision actions in the fixed primary group, all low-frequency actions in
|
|
250
|
+
the single more-actions entry, and collect comments in the action confirmation
|
|
251
|
+
layer. The canonical desktop paths are `/tasks/:taskId` and
|
|
252
|
+
`/workflows/:instanceId`; they render as standalone full-screen pages outside
|
|
253
|
+
the admin Shell, while the platform alone replace-redirects historical admin
|
|
254
|
+
detail URLs. Filter `system: true` and internal fields, omit empty sections,
|
|
255
|
+
merge operation actor/action/reason/time into its vertical node, and never show
|
|
256
|
+
UUIDs or revision/event diagnostics. Treat `stale` as metadata and show the
|
|
257
|
+
friendly refresh prompt only after a real command token/CAS conflict. Do not
|
|
258
|
+
copy the standard page into an application merely to show full business fields
|
|
259
|
+
or rearrange platform-owned operations.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# OpenXiangda 2.0 Anonymous Public Access
|
|
2
|
+
|
|
3
|
+
Read this reference whenever the requirement includes an external person without
|
|
4
|
+
a platform account, anonymous or guest access, a public form/page, resumable
|
|
5
|
+
submission, duplicate checks, public attachment upload, or reading the visitor's
|
|
6
|
+
own submitted records.
|
|
7
|
+
|
|
8
|
+
## Decide the boundary first
|
|
9
|
+
|
|
10
|
+
Record these choices before editing:
|
|
11
|
+
|
|
12
|
+
- the one static public route and one Native resource it serves;
|
|
13
|
+
- the exact fields the visitor may write and receive;
|
|
14
|
+
- whether drafts are needed and their bounded inactivity lifetime;
|
|
15
|
+
- whether the page needs `own.list`, `own.read`, or both;
|
|
16
|
+
- every named duplicate validation and its exact field tuple;
|
|
17
|
+
- file fields and their normal resource-level type, count, size and MIME limits;
|
|
18
|
+
- same-browser-only acceptance and the negative browser/device cases.
|
|
19
|
+
|
|
20
|
+
This contract identifies possession of one browser profile, not a natural person.
|
|
21
|
+
Clearing the platform HttpOnly cookie, private browsing, another browser or another
|
|
22
|
+
device creates a new anonymous visitor. Do not promise recovery, merge or
|
|
23
|
+
cross-device continuity. WeChat, DingTalk, SMS/email verification and automatic
|
|
24
|
+
platform-account creation require later, separate identity contracts.
|
|
25
|
+
|
|
26
|
+
## Declare the one public policy
|
|
27
|
+
|
|
28
|
+
Declare the resource normally, then add one static `surface: 'user'` route and one
|
|
29
|
+
`frontend.publicAccess` policy in `openxiangda.config.ts`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export default defineOpenXiangdaApp({
|
|
33
|
+
// ...app, authz and data declarations...
|
|
34
|
+
frontend: {
|
|
35
|
+
root: 'apps/web',
|
|
36
|
+
routes: [
|
|
37
|
+
{
|
|
38
|
+
code: 'visitor-apply',
|
|
39
|
+
path: '/visitor/apply',
|
|
40
|
+
label: '访客预约',
|
|
41
|
+
surface: 'user',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
publicAccess: {
|
|
45
|
+
policies: [
|
|
46
|
+
{
|
|
47
|
+
code: 'visitor-apply-public',
|
|
48
|
+
routeCode: 'visitor-apply',
|
|
49
|
+
mode: 'anonymous',
|
|
50
|
+
resourceCode: 'visitor-requests',
|
|
51
|
+
operations: [
|
|
52
|
+
'draft.read',
|
|
53
|
+
'draft.update',
|
|
54
|
+
'validate',
|
|
55
|
+
'create',
|
|
56
|
+
'own.list',
|
|
57
|
+
'own.read',
|
|
58
|
+
],
|
|
59
|
+
fields: ['visitorName', 'phone', 'visitDate', 'photo'],
|
|
60
|
+
requiredFields: ['visitorName', 'phone', 'visitDate'],
|
|
61
|
+
ownRecordFields: ['visitorName', 'phone', 'visitDate', 'photo'],
|
|
62
|
+
draft: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
inactivityTtlSeconds: 2_592_000,
|
|
65
|
+
maxBytes: 262_144,
|
|
66
|
+
},
|
|
67
|
+
validations: [
|
|
68
|
+
{
|
|
69
|
+
code: 'phone-unused',
|
|
70
|
+
kind: 'duplicate',
|
|
71
|
+
fields: ['phone'],
|
|
72
|
+
result: 'availability',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The route must be static and must not also declare `capability` or `access`.
|
|
83
|
+
`fields`, `requiredFields`, `ownRecordFields` and validation fields must reference
|
|
84
|
+
declared fields on the same resource. Public create must cover every writable
|
|
85
|
+
required business field on that Native resource; `check` rejects an incomplete or
|
|
86
|
+
widened declaration.
|
|
87
|
+
|
|
88
|
+
Request only the operations the page uses:
|
|
89
|
+
|
|
90
|
+
- `draft.read` and `draft.update` resume the current browser's active draft;
|
|
91
|
+
- `validate` runs only named availability checks and never returns matching rows;
|
|
92
|
+
- `create` performs the final idempotent submission;
|
|
93
|
+
- `own.list` returns a bounded, server-ordered page of this browser's submitted
|
|
94
|
+
records;
|
|
95
|
+
- `own.read` returns one submitted record only after the server repeats the owner
|
|
96
|
+
and submitted-draft receipt checks.
|
|
97
|
+
|
|
98
|
+
## Use the generated browser client
|
|
99
|
+
|
|
100
|
+
The template already passes generated `anonymousPublicAccess` to
|
|
101
|
+
`OpenXiangdaApplication`. Bind the route through the normal generated `appRoutes`
|
|
102
|
+
contribution, then use only the dedicated client in that page:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { createAnonymousPublicClient } from 'openxiangda/react';
|
|
106
|
+
|
|
107
|
+
const publicClient = createAnonymousPublicClient({ routeCode: 'visitor-apply' });
|
|
108
|
+
|
|
109
|
+
const session = await publicClient.bootstrap();
|
|
110
|
+
const draft = session.draft ?? await publicClient.currentDraft();
|
|
111
|
+
const saved = await publicClient.saveDraft(draft.revision, {
|
|
112
|
+
visitorName,
|
|
113
|
+
phone,
|
|
114
|
+
visitDate,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const availability = await publicClient.validate('phone-unused', { phone });
|
|
118
|
+
const photo = await publicClient.upload('photo', file);
|
|
119
|
+
const withPhoto = await publicClient.saveDraft(saved.revision, { photo });
|
|
120
|
+
const receipt = await publicClient.submit(
|
|
121
|
+
withPhoto.revision,
|
|
122
|
+
crypto.randomUUID(),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const page = await publicClient.listOwn({ pageSize: 20 });
|
|
126
|
+
const detail = await publicClient.getOwn(receipt.recordId);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Call `bootstrap()` before other methods and reuse the same idempotency key when
|
|
130
|
+
retrying one uncertain submission. Draft writes use the returned revision as CAS;
|
|
131
|
+
on conflict, reload the current draft instead of overwriting it. Treat duplicate
|
|
132
|
+
preflight as UI feedback only: the platform repeats validation atomically during
|
|
133
|
+
submission.
|
|
134
|
+
|
|
135
|
+
Never call the general Native Data API from this page, construct `created_by`,
|
|
136
|
+
accept a caller-selected draft id, query arbitrary filters/order/count, expose an
|
|
137
|
+
object-store credential, or use a NestJS action as anonymous CRUD. Never store an
|
|
138
|
+
identity in local storage or derive ownership from IP, user-agent or a browser
|
|
139
|
+
fingerprint. IP may participate only in platform rate limiting.
|
|
140
|
+
|
|
141
|
+
## Acceptance
|
|
142
|
+
|
|
143
|
+
After `pnpm openxiangda check --json`, verify the exact preproduction route in real
|
|
144
|
+
browsers:
|
|
145
|
+
|
|
146
|
+
1. a fresh browser can bootstrap, save, reload and resume one draft;
|
|
147
|
+
2. declared file upload/preview and final submission succeed;
|
|
148
|
+
3. named validation returns only `available` or `duplicate`, and concurrent final
|
|
149
|
+
duplicate submissions cannot both succeed;
|
|
150
|
+
4. the same browser can list and open its submitted records;
|
|
151
|
+
5. another browser gets an empty list and the same record id returns 404;
|
|
152
|
+
6. clearing the browser credential loses draft/history access as documented;
|
|
153
|
+
7. undeclared routes, fields, operations, validations and ordinary Data API calls
|
|
154
|
+
remain denied;
|
|
155
|
+
8. production uses HTTPS and no public object-storage bucket or anonymous upload
|
|
156
|
+
whitelist was introduced.
|
|
157
|
+
|
|
158
|
+
Read [Data and authorization](data-authz.md), [Frontend](frontend.md) and
|
|
159
|
+
[Testing](testing.md) for the surrounding resource, page and delivery checks.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Testing and Acceptance
|
|
2
|
+
|
|
3
|
+
Run `pnpm openxiangda check --json` after declaration or code changes. It owns
|
|
4
|
+
generation plus a read-only Native compatibility preflight against the test
|
|
5
|
+
platform before static validation, unit tests or production builds. Use
|
|
6
|
+
`--environment production` only for an explicit production-target check.
|
|
7
|
+
Preserve the stable diagnostic and JSON pointer instead of bypassing a failing
|
|
8
|
+
stage. Read
|
|
9
|
+
`data.sealedArtifact` in the machine result. A successful check always reports
|
|
10
|
+
`state: "check-did-not-seal"`, `sealed: false` and
|
|
11
|
+
`usableForDeploy: false`; it may also describe an older package as
|
|
12
|
+
`previousArtifact`. Never treat an existing `.openxiangda/build/app-package.json`
|
|
13
|
+
as output from the current check. Follow the exact `nextCommand`: rerun check
|
|
14
|
+
when diagnostics fail, or run `openxiangda deploy` to build, seal and deploy.
|
|
15
|
+
|
|
16
|
+
For each changed resource, verify the real chain:
|
|
17
|
+
|
|
18
|
+
1. declaration compiles into one resource, Surface and capability set;
|
|
19
|
+
2. desktop and mobile render the declared field semantics;
|
|
20
|
+
3. create, read, update, delete, filtering, export and revision conflict use Native Data API;
|
|
21
|
+
4. an allowed role succeeds and a denied role fails for operation, field and row paths;
|
|
22
|
+
5. stored values and audit records match the declared shape;
|
|
23
|
+
6. PostgreSQL/RLS remains authoritative, including current-user and multi-role-union cases.
|
|
24
|
+
|
|
25
|
+
For `frontend.publicAccess`, add a separate anonymous-browser matrix. Verify draft
|
|
26
|
+
resume, declared file upload, named availability validation, idempotent submission,
|
|
27
|
+
and same-browser `own.list`/`own.read`. In a second browser verify an empty list and
|
|
28
|
+
404 for the first browser's record id. Clear the first browser credential and
|
|
29
|
+
confirm access is lost as documented. Also prove undeclared routes, fields,
|
|
30
|
+
operations, validations and ordinary Data API calls remain denied. A logged-in
|
|
31
|
+
admin path or a mocked client does not close anonymous public acceptance.
|
|
32
|
+
|
|
33
|
+
For platform generator changes, add a representative multi-resource fixture
|
|
34
|
+
(the compatibility corpus fixes 43 resources, complete list/form/detail/mobile
|
|
35
|
+
surfaces, Perspective/AuthZ/Workflow/Event and exactly 161 producers), assert
|
|
36
|
+
the real toolchain generator output remains byte-identical, and pass the same
|
|
37
|
+
fixture through the platform's exported Native validator. Then run the unchanged
|
|
38
|
+
Web dist budget gate. A budget failure is a
|
|
39
|
+
generator/runtime regression to fix; never raise the application budget or copy
|
|
40
|
+
generated contracts into a smaller application-local format. Browser acceptance
|
|
41
|
+
must also cover the large more-filters modal, declaration-order forms, hidden
|
|
42
|
+
developer-only authorization copy, real account/role labels, avatar update and
|
|
43
|
+
left-menu scroll preservation across route changes.
|
|
44
|
+
|
|
45
|
+
Mock and unit tests are useful but do not close remote acceptance. When real
|
|
46
|
+
identity acceptance is needed, create a short-lived local plan and run:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm openxiangda accept --plan .openxiangda/acceptance-plan.json --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The plan is explicit and preproduction-only:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"schemaVersion": "openxiangda.preproduction-acceptance-plan/v2",
|
|
57
|
+
"environmentKey": "preproduction",
|
|
58
|
+
"expiresInMinutes": 240,
|
|
59
|
+
"actors": [
|
|
60
|
+
{ "key": "allowed", "roleCodes": ["resource_admin"] },
|
|
61
|
+
{ "key": "denied", "roleCodes": ["resource_viewer"] }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The result returns real temporary users and one-time login URLs. Treat those
|
|
67
|
+
URLs as secrets, do not commit the plan/result, and let them expire. `accept`
|
|
68
|
+
is an additional manual test facility: it is never called by `check` or
|
|
69
|
+
`deploy`, never becomes a release gate, and a skipped run must not block a
|
|
70
|
+
release. When it is used, capture the exact AppVersion, environment Head,
|
|
71
|
+
positive/negative result, browser behavior and request identifiers. Production
|
|
72
|
+
promotion reuses the exact successful version; it is not another build.
|
|
73
|
+
|
|
74
|
+
For a new toolchain release, also create a completely fresh workspace using only the packed or published `openxiangda` package. Do not use repository-relative imports, unpublished workspace links or machine-installed legacy Skills.
|