openxiangda 1.0.33 → 1.0.35

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 (52) hide show
  1. package/README.md +16 -0
  2. package/lib/cli.js +409 -0
  3. package/lib/workspace-init.js +1 -0
  4. package/openxiangda-skills/SKILL.md +7 -0
  5. package/openxiangda-skills/references/architecture-patterns.md +2 -2
  6. package/openxiangda-skills/references/best-practices.md +42 -12
  7. package/openxiangda-skills/references/connector-resources.md +3 -0
  8. package/openxiangda-skills/references/data-views.md +217 -0
  9. package/openxiangda-skills/references/forms/component-registry.md +4 -2
  10. package/openxiangda-skills/references/forms/form-schema.md +37 -0
  11. package/openxiangda-skills/references/pages/page-sdk.md +43 -0
  12. package/openxiangda-skills/references/pages/workspace-structure.md +1 -0
  13. package/openxiangda-skills/references/workspace-state.md +9 -0
  14. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +8 -1
  15. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +1 -0
  16. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +1 -1
  17. package/package.json +1 -1
  18. package/packages/sdk/dist/components/index.cjs +944 -765
  19. package/packages/sdk/dist/components/index.cjs.map +1 -1
  20. package/packages/sdk/dist/components/index.d.mts +18 -2
  21. package/packages/sdk/dist/components/index.d.ts +18 -2
  22. package/packages/sdk/dist/components/index.mjs +938 -761
  23. package/packages/sdk/dist/components/index.mjs.map +1 -1
  24. package/packages/sdk/dist/runtime/index.cjs +47 -0
  25. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  26. package/packages/sdk/dist/runtime/index.d.mts +18 -1
  27. package/packages/sdk/dist/runtime/index.d.ts +18 -1
  28. package/packages/sdk/dist/runtime/index.mjs +47 -0
  29. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  30. package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +1 -0
  31. package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +1 -1
  32. package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +12 -6
  33. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.test.ts +24 -7
  34. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.ts +13 -0
  35. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/types.ts +8 -6
  36. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.test.ts +5 -5
  37. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.ts +7 -5
  38. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.test.ts +1 -1
  39. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.ts +3 -3
  40. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.test.ts +2 -2
  41. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.ts +8 -8
  42. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/types.ts +12 -10
  43. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/schema.ts +58 -6
  44. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/schema.ts +9 -0
  45. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/schema.ts +72 -16
  46. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketDetailDrawer.tsx +6 -3
  47. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketTableActions.tsx +6 -4
  48. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/form-groups/service-ticket-college.json +2 -2
  49. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/roles.json +2 -2
  50. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/service-ticket.ts +4 -4
  51. package/templates/sy-lowcode-app-workspace/examples/forms/customer/schema.ts +1 -0
  52. package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +1 -0
@@ -42,8 +42,10 @@ Most business processes are status lifecycles, not approval workflows.
42
42
  Use a normal form plus:
43
43
 
44
44
  - a `status` field
45
- - responsibility fields such as `ownerUserId`, `ownerDeptId`, `collegeId`,
46
- `classId`
45
+ - user-facing responsibility fields such as personnel, department, enum, or
46
+ SDK-backed select fields
47
+ - hidden permission scope keys, when form permission groups require scalar
48
+ matching
47
49
  - an action log form
48
50
  - a pure state machine in `domain/<feature>/state-machine.ts`
49
51
  - a service method that changes status and writes the log in one path
@@ -103,20 +105,48 @@ For apps with dynamic roles:
103
105
 
104
106
  1. Create an app role maintenance form, such as `app-role`.
105
107
  2. Use automation / JS_CODE to sync role records to platform roles.
106
- 3. Add redundant ownership fields to business forms, for example:
107
- - `collegeId`
108
- - `classId`
109
- - `ownerDeptId`
110
- - `ownerUserId`
111
- - `roleCode`
112
- 4. Create page permission groups for entry visibility.
113
- 5. Create form permission groups with condition-based data permissions for real
108
+ 3. Model visible scope fields with maintainable controls:
109
+ - personnel and departments use platform personnel/department fields
110
+ - enum scopes use `SelectField` / `RadioField` with `options`
111
+ - class, college, project, customer, and similar maintained records use
112
+ `SelectField` with `optionSource.type: "linkedForm"` so the runtime queries
113
+ source form records through the SDK and builds dropdown options
114
+ - when the source form can have many records, set `remoteSearch: true`,
115
+ `searchFieldId`, and a reasonable `pageSize` so typing in the dropdown
116
+ triggers remote search
117
+ 4. Add hidden derived scope keys only when platform form permission conditions
118
+ require scalar matching, for example:
119
+ - `collegeScopeKey`
120
+ - `classScopeKey`
121
+ - `ownerDeptScopeKey`
122
+ - `ownerUserScopeKey`
123
+ 5. Create page permission groups for entry visibility.
124
+ 6. Create form permission groups with condition-based data permissions for real
114
125
  data isolation.
115
126
 
116
127
  Frontend button hiding is only user experience. It is not permission control.
117
128
  Every sensitive action must still be protected by platform role/form permission
118
129
  groups or backend-side JS_CODE checks.
119
130
 
131
+ ## Form Copy And Field Visibility
132
+
133
+ - Every visible form field should have a short, user-facing placeholder.
134
+ - Do not add tips to every field. Tips are only for special constraints,
135
+ unusual formats, compliance notes, or non-obvious business rules.
136
+ - Use select/radio controls for enums. For values maintained by other forms, use
137
+ `SelectField` with SDK-backed `optionSource` options. Do not ask users to type
138
+ raw IDs.
139
+ - Hide permission scope keys, computed, sync, and developer/internal fields with
140
+ `behavior: "HIDDEN"` when they must exist in the schema. Scope keys should be
141
+ derived from visible select/person/department fields, not typed by
142
+ users. For select-derived scalar keys, use `valueSync`.
143
+ - Do not add separate creator, updater, creator department, updater department,
144
+ created time, or updated time fields unless the user explicitly needs a
145
+ separate business field. The platform creates system metadata for every form.
146
+ - All visible copy must be written for end users. Do not put implementation
147
+ notes, schema descriptions, or development explanations in sections, cards,
148
+ labels, tips, helper text, or empty states.
149
+
120
150
  ## Query Performance
121
151
 
122
152
  - Always use paginated APIs with `currentPage`, `pageSize`, sort, and structured
@@ -166,8 +196,8 @@ groups or backend-side JS_CODE checks.
166
196
  - `service-ticket-ops`: custom data management page based on
167
197
  `DataManagementList`, split into page, components, hook, query builder, and
168
198
  detail drawer.
169
- - `role-governance`: role maintenance form, role sync JS_CODE, redundant
170
- ownership fields, and permission-group resource examples.
199
+ - `role-governance`: role maintenance form, role sync JS_CODE, maintainable
200
+ scope fields, hidden permission keys, and permission-group resource examples.
171
201
  - `pc-portal-shell`: app-shell PC portal with routes, modules, components, and
172
202
  services.
173
203
  - `mobile-portal-shell`: app-shell mobile portal with mobile-only components
@@ -10,6 +10,7 @@ Common folders:
10
10
  - `menus`
11
11
  - `workflows`
12
12
  - `automations`
13
+ - `data-views`
13
14
  - `permissions/page-groups`
14
15
  - `permissions/form-groups`
15
16
  - `settings/forms`
@@ -27,6 +28,8 @@ Connector manifests use stable `code` values. The platform maps connector `code`
27
28
 
28
29
  Notification manifests live under `src/resources/notifications/` and contain `templates` plus `typeConfigs`. See `notifications.md` before generating reminders or message templates.
29
30
 
31
+ Data view manifests live under `src/resources/data-views/` and define read-only materialized views for repeated multi-form joins. Use them for joined list/report/lookup sources where refresh lag is acceptable. Do not use them for single-form CRUD, simple linkedForm selects, real-time writes, or write-back. See `data-views.md` before generating one.
32
+
30
33
  ```json
31
34
  {
32
35
  "code": "crm",
@@ -0,0 +1,217 @@
1
+ # Data View Resources
2
+
3
+ Data views are OpenXiangda-managed read-only PostgreSQL materialized views. They turn repeated multi-form joins into a published resource under `src/resources/data-views/`.
4
+
5
+ Use a data view when the app needs read-only joined data from multiple forms, such as:
6
+
7
+ - Ticket list with customer name, owner, SLA, and status fields.
8
+ - Order list with product, customer, and payment fields.
9
+ - Project dashboard combining project, member, task, and risk forms.
10
+ - Reusable lookup or report data consumed by several pages or automations.
11
+ - Large list pages where repeated client-side cross-form joins would be slow or inconsistent.
12
+
13
+ Do not use a data view for:
14
+
15
+ - Single-form CRUD. Use `sdk.form.advancedSearch`, form pages, or `DataManagementList`.
16
+ - Simple one-form dropdown options. Use `SelectField` with `optionSource.type: "linkedForm"`.
17
+ - Writes or write-back. Data views are read-only.
18
+ - Strong real-time views after every form update. Data views update after manual or scheduled refresh.
19
+ - Raw SQL, incremental refresh, source-table trigger refresh, or group-by aggregation. v1 does not support them.
20
+
21
+ ## Authoring
22
+
23
+ Place manifests in `src/resources/data-views/*.json`. Use logical `formCode` values in source files. The CLI resolves them to the current profile's `formUuid` during `resource publish`.
24
+
25
+ Minimal example:
26
+
27
+ ```json
28
+ {
29
+ "code": "ticket_with_customer",
30
+ "name": "Ticket With Customer",
31
+ "base": { "formCode": "service_ticket", "alias": "ticket" },
32
+ "joins": [
33
+ {
34
+ "type": "left",
35
+ "formCode": "customer",
36
+ "alias": "customer",
37
+ "on": [
38
+ {
39
+ "left": "ticket.customer.value",
40
+ "op": "=",
41
+ "right": "customer.form_instance_id"
42
+ }
43
+ ]
44
+ }
45
+ ],
46
+ "select": [
47
+ { "field": "ticket.form_instance_id", "as": "ticketId" },
48
+ { "field": "ticket.title", "as": "ticketTitle" },
49
+ { "field": "customer.name", "as": "customerName" }
50
+ ],
51
+ "indexes": [{ "fields": ["ticketId"], "unique": true }],
52
+ "refresh": { "mode": "scheduled", "cron": "0 */10 * * * *" },
53
+ "permissionGroups": [
54
+ {
55
+ "code": "ticket_query",
56
+ "name": "Ticket Query",
57
+ "roles": ["manager"],
58
+ "operations": ["query"]
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Field reference rules:
65
+
66
+ - Use `alias.field`, for example `ticket.title`.
67
+ - Use system fields directly, such as `form_instance_id`, `created_at`, `updated_at`, `created_by`, and `tenant_id`.
68
+ - For option-like JSON fields that store `{ label, value }`, use `.value` for joins and `.label` for display when needed.
69
+ - Every `select` item must have an explicit output alias in `as`.
70
+ - Runtime `fields`, `filters`, `order`, indexes, field permissions, and row permissions reference output aliases, not source field references.
71
+
72
+ Join rules:
73
+
74
+ - v1 supports `left` and `inner`.
75
+ - Join operators are `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`.
76
+ - The platform automatically constrains sources to the same tenant.
77
+
78
+ Filters:
79
+
80
+ - Definition `where` filters source rows before materialization and uses source references such as `ticket.status`.
81
+ - Runtime query filters use output aliases such as `ticketTitle`.
82
+ - Supported operators include `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`, `contains`, `notContains`, `in`, `isEmpty`, and `isNotEmpty`, with aliases such as `eq`, `neq`, `gte`, `lte`, `like`, `is_null`, and `is_not_null`.
83
+
84
+ Refresh:
85
+
86
+ - `manual` means refresh only when an administrator runs refresh or the resource is recreated.
87
+ - `scheduled` uses cron, for example `0 */10 * * * *`.
88
+ - Query results may be stale. Show or inspect `lastRefreshedAt` when freshness matters.
89
+
90
+ Indexes:
91
+
92
+ - Index output aliases that pages filter or sort by.
93
+ - Use `unique: true` only when the output field combination is truly unique.
94
+
95
+ ## Permissions
96
+
97
+ Management APIs require `app:data-view:manage`.
98
+
99
+ Runtime page queries use data view permission groups unless the user has app manage or data view manage runtime bypass permission.
100
+
101
+ Permission group behavior:
102
+
103
+ - `operations` can include `query` and `refresh`; omitted operations default to `query`.
104
+ - Empty or omitted `roles` match all logged-in users.
105
+ - Missing or empty `fieldPermissions` means all output fields are visible.
106
+ - When multiple permission groups match, field permissions are most permissive: a field is visible if any matched group allows it.
107
+ - `dataPermission` is a row condition over output aliases.
108
+ - Multiple matched row conditions are ORed.
109
+ - If any matched group has no row condition, rows are unrestricted.
110
+
111
+ ## Commands
112
+
113
+ Use the standard resource workflow:
114
+
115
+ ```bash
116
+ openxiangda resource validate --profile dev
117
+ openxiangda resource plan --profile dev
118
+ openxiangda resource publish --profile dev
119
+ openxiangda resource pull --profile dev
120
+ ```
121
+
122
+ Diagnostic commands:
123
+
124
+ ```bash
125
+ openxiangda data-view list --profile dev
126
+ openxiangda data-view status ticket_with_customer --profile dev
127
+ openxiangda data-view refresh ticket_with_customer --profile dev
128
+ openxiangda data-view query ticket_with_customer --profile dev --fields ticketId,customerName
129
+ openxiangda data-view query ticket_with_customer --profile dev --query-json query.json
130
+ ```
131
+
132
+ ## Runtime SDK
133
+
134
+ Direct query:
135
+
136
+ ```ts
137
+ const response = await sdk.dataView.query("ticket_with_customer", {
138
+ fields: ["ticketId", "ticketTitle", "customerName"],
139
+ filters: [
140
+ { field: "customerName", operator: "contains", value: keyword },
141
+ ],
142
+ order: [{ field: "ticketTitle", isAsc: "y" }],
143
+ currentPage: 1,
144
+ pageSize: 20,
145
+ })
146
+ ```
147
+
148
+ The response data is:
149
+
150
+ ```ts
151
+ {
152
+ data: unknown[]
153
+ totalCount: number
154
+ currentPage: number
155
+ pageSize: number
156
+ lastRefreshedAt?: string | null
157
+ }
158
+ ```
159
+
160
+ Page data source descriptor:
161
+
162
+ ```ts
163
+ export default {
164
+ dataSources: [
165
+ {
166
+ key: "tickets",
167
+ type: "dataView.query",
168
+ code: "ticket_with_customer",
169
+ fields: ["ticketId", "ticketTitle", "customerName"],
170
+ defaultFilter: [
171
+ { field: "ticketTitle", operator: "isNotEmpty" }
172
+ ]
173
+ }
174
+ ]
175
+ }
176
+
177
+ const response = await sdk.dataSource.run("tickets", {
178
+ filters: [
179
+ { field: "customerName", operator: "contains", value: keyword }
180
+ ],
181
+ pageSize: 20,
182
+ })
183
+ ```
184
+
185
+ ## Common Patterns
186
+
187
+ List page:
188
+
189
+ - Define one data view for the list row shape.
190
+ - Select only fields shown in the table, filters, and row actions.
191
+ - Add indexes for common filters and sorts.
192
+ - Query with `sdk.dataView.query`.
193
+
194
+ Dashboard:
195
+
196
+ - Use one or more data views as read-optimized sources.
197
+ - Keep refresh scheduled.
198
+ - Show `lastRefreshedAt` when business users care about freshness.
199
+
200
+ Reusable lookup:
201
+
202
+ - Use a data view if the display label depends on multiple forms.
203
+ - Keep the output small, for example `id`, `label`, `status`, and `owner`.
204
+ - For simple one-form lookup, keep using linkedForm instead.
205
+
206
+ Automation diagnosis:
207
+
208
+ - Query the data view with `openxiangda data-view query --query-json`.
209
+ - Refresh manually after bulk imports or test data resets.
210
+
211
+ ## Troubleshooting
212
+
213
+ - `formCode 未绑定`: publish or bind the source forms first so `.openxiangda/state.json` has their `formUuid`.
214
+ - Empty joined fields: check whether the source field stores a scalar or `{ label, value }`; linked/select fields usually need `.value`.
215
+ - Query field rejected: runtime filters and `fields` must use output aliases.
216
+ - User sees no data: inspect role codes, permission groups, `fieldPermissions`, and `dataPermission`.
217
+ - Data is stale: check `lastRefreshedAt`, scheduled cron, and refresh status.
@@ -21,7 +21,6 @@ Common field components:
21
21
  | `ImageField` | Image upload |
22
22
  | `AddressField` | Address |
23
23
  | `CascadeSelectField` | Cascading select |
24
- | `AssociationFormField` | Select records from another form when a linked-form picker is required |
25
24
  | `LocationField` | Location |
26
25
  | `EditorField` | Rich text |
27
26
  | `JSONField` | Structured JSON |
@@ -35,7 +34,10 @@ Rules:
35
34
  - Avoid generated random field IDs after initial creation.
36
35
  - Use field-level `rules` for validation. Required fields should set both `required: true` and `rules: [{ required: true, message: "..." }]` when a custom message is needed.
37
36
  - For option components (`SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, `CascadeSelectField`), always provide an `options` array. Use `options: [{ value: "stable_code", label: "显示名" }]`; if options will be loaded elsewhere later, still emit `options: []` so the runtime does not crash on `options.map(...)`.
38
- - Do not rely on custom `relation` metadata alone for normal form pages. A `SelectField` with `relation` but no `options` can white-screen with `Cannot read properties of undefined (reading 'map')`. Use static `options`, `AssociationFormField`, or a custom code page that resolves linked records.
37
+ - Do not rely on custom `relation` metadata alone for normal form pages. A `SelectField` with `relation` but no `options` can white-screen with `Cannot read properties of undefined (reading 'map')`. Use static `options`, or use `SelectField` with `optionSource.type: "linkedForm"` so the runtime queries source form records through the SDK.
38
+ - Do not model business enums or data-source references as free text IDs. Use `SelectField` / `RadioField` with `options` for enums. For records maintained by another form, use `SelectField` with `options: []` and `optionSource.linkedForm`; set `remoteSearch: true`, `searchFieldId`, and a modest `pageSize` when the source data can be large.
39
+ - If a selected option value must also be stored as a hidden scalar key for permission conditions, add `valueSync: [{ targetFieldId: "scopeKey", valuePath: "value" }]` to the visible `SelectField`.
40
+ - `AssociationFormField` is kept only for backward compatibility. Do not use it for new form pages.
39
41
  - For `NumberField`, prefer explicit `min`, `max`, `precision`, and `unit` when the business meaning is constrained.
40
42
  - For `DateField`, use `mode: "date"` or `mode: "datetime"` and a stable `format` such as `YYYY-MM-DD` or `YYYY-MM-DD HH:mm`.
41
43
  - For `CascadeDateField`, store a date range and document whether the value is inclusive.
@@ -35,6 +35,7 @@ export default defineFormSchema({
35
35
  fieldId: "customer_type",
36
36
  componentName: "SelectField",
37
37
  label: "客户类型",
38
+ placeholder: "请选择客户类型",
38
39
  options: [
39
40
  { value: "enterprise", label: "企业客户" },
40
41
  { value: "individual", label: "个人客户" },
@@ -55,11 +56,47 @@ export default defineFormSchema({
55
56
 
56
57
  - Each persisted field needs a stable `fieldId`.
57
58
  - Field labels should be user-facing Chinese names when the app is Chinese.
59
+ - Each visible field should include a concise user-facing `placeholder`. Hidden/internal fields do not need placeholders.
60
+ - Use `tips` only for special constraints, unusual formats, compliance notes, or non-obvious business rules. Do not add tips to every field.
61
+ - Use `SelectField` / `RadioField` for enum values and always provide `options`.
62
+ - Use `SelectField` with `optionSource.type: "linkedForm"` for values maintained in another form or data source, such as class, college, customer, project, category, or asset. The runtime queries source form data through the SDK and builds `{ label, value }` options. For large source forms, set `remoteSearch: true`, an explicit `searchFieldId`, and a modest `pageSize` so typing in the dropdown triggers remote search. Do not use `AssociationFormField` for new form pages, and do not ask users to type raw IDs in `TextField`.
63
+ - Keep only user-needed fields visible. If a scalar key is needed for permissions, synchronization, computed state, or internal logic, derive it from a visible select/person/department field and keep it in the schema with `behavior: "HIDDEN"`. For `SelectField`, use `valueSync` when the selected option value should be copied into a hidden scalar field.
64
+ - Do not create duplicate platform system fields such as creator, updater, creator department, updater department, created time, or updated time unless the user explicitly needs a distinct business field. The platform already creates system metadata for every form.
65
+ - Labels, placeholders, tips, section titles, descriptions, and empty states must be end-user-facing. Do not write developer-facing implementation explanations into visible form copy.
58
66
  - Use platform-supported field components from `component-registry.md`.
59
67
  - Do not generate fields that are only visual layout containers.
60
68
  - Put validation rules on fields as `field.rules`. Do not put validation objects in top-level `schema.rules`.
61
69
  - For option components (`SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, `CascadeSelectField`), always include `options`. If a linked lookup is not ready, use `options: []` instead of omitting it.
62
70
 
71
+ Linked form dropdown example:
72
+
73
+ ```ts
74
+ {
75
+ fieldId: "college",
76
+ componentName: "SelectField",
77
+ label: "所属学院",
78
+ placeholder: "请选择所属学院",
79
+ showSearch: true,
80
+ allowClear: true,
81
+ options: [],
82
+ valueSync: [{ targetFieldId: "collegeScopeKey", valuePath: "value" }],
83
+ optionSource: {
84
+ type: "linkedForm",
85
+ linkedForm: {
86
+ formUuid: "FORM_COLLEGE_PROFILE",
87
+ fieldId: "collegeName",
88
+ labelFieldId: "collegeName",
89
+ valueFieldId: "collegeCode",
90
+ searchFieldId: "collegeName",
91
+ sortField: "collegeName",
92
+ pageSize: 20,
93
+ remoteSearch: true,
94
+ deduplicate: true,
95
+ },
96
+ },
97
+ }
98
+ ```
99
+
63
100
  ## Effects vs Validation
64
101
 
65
102
  Top-level `schema.rules` is reserved for `FormEffect[]`, not validation. A `FormEffect` must have both `when` and `then`:
@@ -6,6 +6,8 @@ Guidelines:
6
6
 
7
7
  - Do not hardcode `/openxiangda-api` calls inside end-user page components unless the page is explicitly an admin tool.
8
8
  - Prefer SDK modules for form data, user context, permissions, and platform navigation.
9
+ - Use `sdk.dataView.query` for published read-only multi-form data views. Data view runtime queries can filter, sort, paginate, and select only output aliases.
10
+ - Use `sdk.dataSource.run()` with a page data source descriptor when the page config should own the data view code, default fields, or default filters.
9
11
  - Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
10
12
  - Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
11
13
  - For the current user's department hierarchy, use `sdk.department.getCurrentUserParentDepartments()`; do not hardcode `GET /department/:id/parentDepartments` in page code.
@@ -13,3 +15,44 @@ Guidelines:
13
15
  - Treat user context and tenant context as runtime-provided values.
14
16
 
15
17
  When the SDK lacks a capability, document the fallback and keep it isolated.
18
+
19
+ Data view query:
20
+
21
+ ```ts
22
+ const response = await sdk.dataView.query("ticket_with_customer", {
23
+ fields: ["ticketId", "ticketTitle", "customerName"],
24
+ filters: [
25
+ { field: "customerName", operator: "contains", value: keyword },
26
+ ],
27
+ order: [{ field: "ticketTitle", isAsc: "y" }],
28
+ currentPage: 1,
29
+ pageSize: 20,
30
+ })
31
+ ```
32
+
33
+ Data view data source descriptor:
34
+
35
+ ```ts
36
+ export default {
37
+ dataSources: [
38
+ {
39
+ key: "tickets",
40
+ type: "dataView.query",
41
+ code: "ticket_with_customer",
42
+ fields: ["ticketId", "ticketTitle", "customerName"],
43
+ defaultFilter: [
44
+ { field: "ticketTitle", operator: "isNotEmpty" }
45
+ ]
46
+ }
47
+ ]
48
+ }
49
+
50
+ const response = await sdk.dataSource.run("tickets", {
51
+ filters: [
52
+ { field: "customerName", operator: "contains", value: keyword }
53
+ ],
54
+ pageSize: 20,
55
+ })
56
+ ```
57
+
58
+ Data view filters and fields use output aliases such as `customerName`, not source references such as `customer.name`. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
@@ -38,3 +38,4 @@ Rules:
38
38
  - Shared components can live under `src/components/` when reused.
39
39
  - Build artifacts under `dist/` are generated and should not be hand edited.
40
40
  - Publish through `openxiangda workspace publish --profile <name>`.
41
+ - Visible page copy must be written for end users. Do not show implementation notes, schema explanations, or developer-only guidance inside page sections, cards, alerts, tooltips, or empty states.
@@ -31,6 +31,13 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
31
31
  "menus": {},
32
32
  "roles": {},
33
33
  "connectors": {},
34
+ "dataViews": {
35
+ "ticket_with_customer": {
36
+ "dataViewId": "DV_XXX",
37
+ "materializedViewName": "mv_dv_abc123",
38
+ "status": "active"
39
+ }
40
+ },
34
41
  "notifications": {
35
42
  "templates": {},
36
43
  "typeConfigs": {}
@@ -52,6 +59,8 @@ Tokens never belong in the project. User tokens live in `~/.openxiangda/profiles
52
59
  - Do not search platform apps or reuse similar names unless the user explicitly asks to reuse an existing app or provides an `appType`.
53
60
  - Local resource keys are logical codes.
54
61
  - Live IDs and lightweight runtime aliases are nested under the profile that produced them.
62
+ - `resources.dataViews` is keyed by data view `code` and stores only profile-local platform metadata such as `dataViewId`, `materializedViewName`, and last known `status`.
63
+ - Data view definitions, refresh config, permissions, and source `formCode` references belong in `src/resources/data-views/*.json`, not in state.
55
64
  - Do not store business configuration or secrets in `.openxiangda/state.json`; store those in `src/resources/`.
56
65
  - A prod publish must not read dev IDs.
57
66
  - Before publishing to another platform, run `openxiangda workspace bind --profile <name> --app-type <APP_XXX>`.
@@ -74,8 +74,15 @@ Read these references only when writing or reviewing schema:
74
74
  ## Rules
75
75
 
76
76
  - Prefer deterministic `formCode` as local key; bind live `formUuid` under the active profile.
77
+ - Every visible field should have a concise, user-facing `placeholder` that tells the user what to enter or select.
78
+ - Use `tips` sparingly. Add tips only for special constraints, unusual formats, or non-obvious business rules; ordinary fields should not have tips.
79
+ - Use `SelectField` or `RadioField` for enumerable business values. Do not model enums as `TextField` values that users must type manually.
80
+ - When a value is maintained by another form or data source, such as class, college, customer, project, category, or asset, use `SelectField` with `optionSource.type: "linkedForm"`. The runtime queries the source form through the SDK, converts records to `{ label, value }` options, and can use `remoteSearch: true` plus `searchFieldId` for large datasets. Do not use `AssociationFormField` for new form work, and do not make users maintain raw ID text fields for those values.
81
+ - Display only fields the user needs to interact with. Permission scope keys, computed fields, sync fields, and developer/internal fields should normally be derived from visible select/person/department fields and stay in the schema with `behavior: "HIDDEN"` instead of appearing on the form page. For select-derived scalar keys, use `valueSync`.
82
+ - Do not create separate fields for platform system metadata such as creator, updater, creator department, updater department, created time, or updated time unless the user explicitly asks for a separate business concept. The platform creates those system fields for every form.
83
+ - All labels, placeholders, tips, section titles, empty states, and helper text must be end-user-facing business copy. Do not write developer explanations or implementation notes into visible page copy.
77
84
  - For business lifecycles, model status with normal form fields (`status`, owner/responsibility fields, action-log relation fields) unless the scenario has real approval tasks. Do not create workflow forms for ordinary status transitions.
78
- - For permission isolation, add redundant ownership fields such as `collegeId`, `classId`, `ownerDeptId`, and `ownerUserId` at schema time so form permission groups can use structured conditions later.
85
+ - For permission isolation, collect user-facing ownership with select, personnel, or department fields. If form permission groups require scalar matching, add hidden derived keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, and `ownerUserScopeKey`; never expose those keys as editable text inputs.
79
86
  - For multi-platform publishing, create or bind the form separately for each profile.
80
87
  - Do not copy `formUuid` from dev to prod unless the target platform explicitly already uses that ID.
81
88
  - Keep `schema.ts` and `page.tsx` as the source for fields/layout/rules and presentation; generated build output is not the source of truth.
@@ -71,6 +71,7 @@ Read these references only when editing page code:
71
71
  - Formal user-facing entries such as admin consoles, PC portals, and mobile portals must be app-shell code pages. Declare `entry: { mode: "app-shell", hidePlatformNav: true, defaultRoute: "<home-route>" }` in `page.config.ts`.
72
72
  - Do not generate single-file large pages. Split complex code pages into `domain/`, `shared/services/`, `shared/hooks/`, shared/page-local `components/`, route/config files, and `styles.css` as described in `../../references/best-practices.md`.
73
73
  - Keep view code thin. Page components call hooks/services; business rules, state transition rules, permission predicates, and query builders live outside TSX and are reusable by PC and mobile pages.
74
+ - All visible page copy must be end-user-facing business text. Do not put developer explanations, implementation notes, schema descriptions, or "this module is generated by..." text into sections, cards, alerts, empty states, tooltips, or helper copy.
74
75
  - Store live `pageId`, `routeKey`, and `legacyFormUuid` under the current profile only.
75
76
  - Use `openxiangda/runtime` for platform data access instead of hardcoding backend URLs in page code.
76
77
  - For reminders, alerts, and business messages, declare `src/resources/notifications/` first and call `sdk.notification`; do not hardcode notification API URLs.
@@ -34,7 +34,7 @@ Use role codes in permission group JSON. Bind existing role IDs only inside the
34
34
  openxiangda permission role-bind sales --role-id <id> --profile dev
35
35
  ```
36
36
 
37
- For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form, sync it to platform roles with automation / JS_CODE, and add redundant ownership fields to business forms (`collegeId`, `classId`, `ownerDeptId`, `ownerUserId`, `roleCode`). Use page permission groups for entry visibility and form permission groups with condition-based data permissions for real data isolation.
37
+ For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form and sync it to platform roles with automation / JS_CODE. Visible scope fields should be maintainable controls: personnel/department fields for people and orgs, `SelectField` / `RadioField` for enums, and `SelectField` with `optionSource.type: "linkedForm"` for records maintained by other forms. When the source form can have many records, set `remoteSearch: true` and `searchFieldId` so typing in the dropdown triggers an SDK query instead of loading every record. If form permission groups need scalar matching, derive hidden keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, `ownerUserScopeKey`, and `roleCode`; do not expose raw ID text fields to users. Use page permission groups for entry visibility and form permission groups with condition-based data permissions for real data isolation.
38
38
 
39
39
  ## Page Permission Groups
40
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {