cabloy 5.1.134 → 5.1.136

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 (185) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-backend-scaffold/SKILL.md +9 -0
  3. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +24 -1
  4. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +12 -0
  5. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +28 -0
  6. package/.github/workflows/playwright-e2e.yml +2 -2
  7. package/.github/workflows/vona-test-pg.yml +1 -1
  8. package/.github/workflows/vona-test-sqlite3.yml +1 -1
  9. package/CHANGELOG.md +38 -0
  10. package/CLAUDE.md +1 -0
  11. package/cabloy-docs/.vitepress/config.mjs +7 -0
  12. package/cabloy-docs/backend/markdown-guide.md +189 -0
  13. package/cabloy-docs/frontend/component-guide.md +6 -0
  14. package/cabloy-docs/frontend/css-in-js-guide.md +6 -0
  15. package/cabloy-docs/frontend/markdown-guide.md +258 -0
  16. package/cabloy-docs/frontend/page-guide.md +48 -0
  17. package/cabloy-docs/frontend/page-params-guide.md +4 -2
  18. package/cabloy-docs/frontend/page-route-guide.md +2 -2
  19. package/cabloy-docs/frontend/zova-router-under-the-hood.md +2 -0
  20. package/cabloy-docs/frontend/zova-source-reading-map.md +53 -9
  21. package/cabloy-docs/fullstack/a-pay-payment-suite.md +333 -0
  22. package/cabloy-docs/fullstack/payment-sandbox-configuration.md +285 -0
  23. package/e2e/specs/a-commerce/commerce.spec.ts +503 -47
  24. package/e2e/specs/cabloy-basic/basic.spec.ts +30 -0
  25. package/package.json +1 -1
  26. package/vona/env/.env +2 -0
  27. package/vona/packages-cli/cli/package.json +1 -1
  28. package/vona/packages-cli/cli-set-api/cli/templates/init/error/boilerplate/config/errors.ts +6 -1
  29. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
  30. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
  31. package/vona/packages-cli/cli-set-api/package.json +1 -1
  32. package/vona/packages-utils/localeutil/package.json +1 -1
  33. package/vona/packages-utils/localeutil/src/util.ts +1 -1
  34. package/vona/packages-vona/vona/package.json +1 -1
  35. package/vona/packages-vona/vona-core/package.json +1 -1
  36. package/vona/packages-vona/vona-mock/package.json +1 -1
  37. package/vona/patches/{zova-core@5.1.82.patch → zova-core@5.1.84.patch} +2 -2
  38. package/vona/pnpm-lock.yaml +826 -270
  39. package/vona/pnpm-workspace.yaml +1 -1
  40. package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +1 -1
  41. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +99 -1
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.index.ts +1 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +19 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/product.ts +3 -3
  47. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentUpdate.tsx +14 -0
  48. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentView.tsx +19 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +48 -3
  50. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicDetail.tsx +15 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +48 -3
  52. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +52 -3
  53. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/productContent.tsx +36 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +6 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/productContent.ts +22 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +82 -6
  57. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +271 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productPresentation.test.ts +81 -2
  59. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
  60. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +32 -15
  61. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +8 -2
  62. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +8 -2
  63. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/locales.ts +18 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +3 -1
  65. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/en-us.ts +3 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/zh-cn.ts +3 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +13 -2
  69. package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/providerReference.ts +18 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +43 -8
  71. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +74 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-trade/test/providerReference.test.ts +33 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +73 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +2 -0
  75. package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +30 -0
  76. package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +7 -2
  77. package/vona/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +4 -1
  78. package/vona/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +4 -1
  79. package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +4 -4
  80. package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +2 -2
  81. package/vona/src/suite/a-home/modules/home-user/src/entity/user.ts +8 -1
  82. package/vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts +6 -2
  83. package/vona/src/suite/a-home/modules/home-user/test/passportTest.test.ts +1 -0
  84. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +4 -4
  85. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +64 -0
  86. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +54 -0
  87. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/this.ts +2 -0
  88. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/bean.markdown.ts +137 -0
  89. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/index.ts +1 -0
  90. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.build.json +11 -0
  91. package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.json +7 -0
  92. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  93. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/bean/bean.ssr.ts +17 -0
  94. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +11 -3
  95. package/vona/src/suite-vendor/a-cabloy/package.json +2 -1
  96. package/vona/src/suite-vendor/a-cabloy/tsconfig.json +3 -0
  97. package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
  98. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +1 -0
  99. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +8 -1
  100. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
  101. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/providerOperationFailure.ts +8 -0
  102. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +187 -29
  103. package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +4 -0
  104. package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
  105. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
  106. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +0 -6
  107. package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
  108. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +2 -1
  109. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +458 -22
  110. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
  111. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/lib/stripeGateway.ts +85 -0
  112. package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +2 -0
  113. package/vona/src/suite-vendor/a-pay/package.json +5 -5
  114. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  115. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  116. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -0
  117. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  118. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/bean.passport.ts +14 -0
  119. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.passport.ts +4 -0
  120. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -2
  121. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/user.ts +4 -0
  122. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  123. package/zova/packages-utils/logger/package.json +2 -2
  124. package/zova/packages-utils/zova-jsx/package.json +2 -2
  125. package/zova/packages-zova/zova/package.json +3 -3
  126. package/zova/packages-zova/zova-core/package.json +3 -3
  127. package/zova/packages-zova/zova-core/src/core/sys/resource.ts +1 -1
  128. package/zova/packages-zova/zova-core/src/core/sys/util.ts +8 -0
  129. package/zova/pnpm-lock.yaml +570 -11
  130. package/zova/src/front/config/config/config.cabloyCommerce.ts +0 -6
  131. package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +3 -1
  132. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/schemas.ts +236 -36
  133. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/types.ts +4962 -2402
  134. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
  135. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
  136. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +8 -3
  137. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +6 -7
  138. package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +1 -3
  139. package/zova/src/suite/a-commerce/modules/commerce-member/src/routes.ts +2 -3
  140. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +13 -20
  141. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +3 -9
  142. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +9 -11
  143. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +0 -1
  144. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +6 -4
  145. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +78 -17
  146. package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +10 -13
  147. package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +3 -3
  148. package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +31 -1
  149. package/zova/src/suite/a-home/modules/home-api/src/api/homeBaseSiteCatalog.ts +24 -0
  150. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +329 -17
  151. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +5866 -1829
  152. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBaseSiteCatalog.ts +13 -0
  153. package/zova/src/suite/a-home/modules/home-base/src/component/page/controller.tsx +1 -0
  154. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +0 -1
  155. package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/detail.ts +1 -0
  156. package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +61 -0
  157. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/formFieldMarkdown.ts +36 -0
  158. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/markdownHtml.ts +36 -0
  159. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +147 -0
  160. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/this.ts +2 -0
  161. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +111 -0
  162. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +40 -0
  163. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/style.ts +13 -0
  164. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/controller.tsx +17 -0
  165. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/render.tsx +9 -0
  166. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/style.ts +13 -0
  167. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
  168. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +142 -0
  169. package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.build.json +13 -0
  170. package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.json +5 -0
  171. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +25 -3
  172. package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +1 -0
  173. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/pageEntry.ts +1 -0
  174. package/zova/src/suite/cabloy-basic/package.json +1 -0
  175. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  176. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +7 -0
  177. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  178. package/zova/src/suite-vendor/a-zova/modules/a-logger/package.json +2 -2
  179. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  180. package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/sys.router.ts +3 -2
  181. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  182. package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrProfile.test.ts +88 -0
  183. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  184. package/zova/src/suite-vendor/a-zova/package.json +5 -5
  185. package/.claude/scheduled_tasks.lock +0 -1
@@ -0,0 +1,258 @@
1
+ # Markdown Guide
2
+
3
+ This guide explains how Markdown works across the Cabloy Basic frontend and backend. The practical contract is deliberately split into two values:
4
+
5
+ - **Markdown** is the editable source value.
6
+ - **HTML** is a server-generated presentation projection.
7
+
8
+ The Zova frontend provides the editor and the trusted-HTML display component. The Vona backend owns Markdown parsing, HTML rendering, and sanitization.
9
+
10
+ Use this page together with:
11
+
12
+ - [Form Guide](/frontend/form-guide)
13
+ - [Component Guide](/frontend/component-guide)
14
+ - [SSR ClientOnly](/frontend/ssr-client-only)
15
+ - [Backend Markdown Guide](/backend/markdown-guide)
16
+
17
+ > [!TIP]
18
+ > **Choose the component by data contract**
19
+ >
20
+ > - Use `basic-markdown:formFieldMarkdown` or `ZFormFieldMarkdown` to edit a Markdown string in a schema-driven form.
21
+ > - Use `basic-markdown:markdownHtml` or `ZMarkdownHtml` to display HTML that was generated and sanitized by the server.
22
+ > - Do not pass arbitrary user HTML, browser-generated HTML, or HTML from an unknown pipeline to `ZMarkdownHtml`.
23
+
24
+ ## The Markdown data contract
25
+
26
+ A typical business resource keeps the authoring value and the rendered value separate:
27
+
28
+ | Surface | Owner | Purpose | Public use |
29
+ | --------------------- | --------------------------------- | ------------------------------ | ------------------------------------------------ |
30
+ | `descriptionMarkdown` | Form and backend business service | Editable source text | Returned to an authorized editor |
31
+ | `descriptionHtml` | Backend business service | Server-derived HTML projection | Returned to a public read model when appropriate |
32
+ | `ZMarkdownHtml` | Zova presentation layer | Displays the HTML projection | Receives trusted HTML only |
33
+
34
+ The frontend editor never needs to submit HTML. On every relevant write path, the backend should derive HTML again from the submitted Markdown. This prevents a caller from choosing a different HTML representation than the server's rendering and sanitization policy.
35
+
36
+ The Commerce Catalog product content flow is the reference implementation:
37
+
38
+ ```text
39
+ Admin Markdown field
40
+ → descriptionMarkdown
41
+ → Vona service calls bean.markdown.renderHtml(...)
42
+ → descriptionMarkdown + descriptionHtml are persisted
43
+ → public detail exposes descriptionHtml
44
+ → ZMarkdownHtml displays the server projection
45
+ ```
46
+
47
+ The backend companion page documents the rendering and sanitizer contract in detail: [Backend Markdown Guide](/backend/markdown-guide).
48
+
49
+ ## Configure the Markdown form field
50
+
51
+ The schema-driven renderer is selected with `ZovaRender.field`:
52
+
53
+ ```typescript
54
+ import { Api, v } from 'vona-module-a-openapiutils';
55
+ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
56
+
57
+ export class EntityArticleContent {
58
+ @Api.field(v.optional(), ZovaRender.field('basic-markdown:formFieldMarkdown'))
59
+ bodyMarkdown?: string;
60
+ }
61
+ ```
62
+
63
+ The current resource options type does not add business-specific options. The renderer identifier is the important part of the contract:
64
+
65
+ ```typescript
66
+ ZovaRender.field('basic-markdown:formFieldMarkdown');
67
+ ```
68
+
69
+ The field value remains Markdown. It is not the editor's HTML output, and it should not be replaced with `innerHTML` or a browser DOM serialization.
70
+
71
+ A field can keep the derived HTML hidden from the editor-facing form while still exposing it in a separate public DTO. The Commerce Catalog entity follows this shape:
72
+
73
+ ```typescript
74
+ @Api.field(
75
+ v.optional(),
76
+ ZovaRender.field('basic-markdown:formFieldMarkdown'),
77
+ )
78
+ descriptionMarkdown?: string;
79
+
80
+ @Api.field(v.optional(), ZovaRender.visible(false))
81
+ descriptionHtml?: string;
82
+ ```
83
+
84
+ The exact persistence and DTO design belongs to the business module. Markdown does not require a paired HTML column, but a persisted projection can avoid repeating rendering work for public reads.
85
+
86
+ ## What `ZFormFieldMarkdown` does
87
+
88
+ `ZFormFieldMarkdown` is a Zova component with the normal Controller/Render/Style split.
89
+
90
+ | Zova role | Responsibility in this component |
91
+ | ---------- | ------------------------------------------------------------------------------------------- |
92
+ | Controller | Owns the TipTap `Editor`, Markdown `value`, `readonly` state, form callbacks, and lifecycle |
93
+ | Render | Composes the standard `ZFormField`, field shell, `ClientOnly`, and `EditorContent` |
94
+ | Style | Creates the shared rich-text CSS class used by the editor and HTML display component |
95
+
96
+ This is a Markdown editor integrated with `ZFormField`; it is not a generic HTML editor API.
97
+
98
+ ### Editor initialization
99
+
100
+ The Controller creates the browser-dependent TipTap editor in `$controllerMounted`. Its extension set includes:
101
+
102
+ - Markdown
103
+ - StarterKit
104
+ - nested `TaskList` and `TaskItem`
105
+ - Image
106
+ - TableKit
107
+ - Highlight
108
+
109
+ The initial value is interpreted as Markdown through:
110
+
111
+ ```typescript
112
+ content: this.value,
113
+ contentType: 'markdown',
114
+ editable: !this.readonly,
115
+ ```
116
+
117
+ The editor DOM receives the current component style class and a minimum-height/padding class. The outer field shell uses Cabloy Basic's DaisyUI-oriented classes and adds an error border when the host form field is invalid.
118
+
119
+ ### Editor-to-form flow
120
+
121
+ When TipTap emits an update, the controller calls:
122
+
123
+ ```typescript
124
+ editor.getMarkdown();
125
+ ```
126
+
127
+ and forwards the Markdown string to the host form field's `setValue()` method. When the editor loses focus, the controller forwards the event to the host form field's `handleBlur()` method, so the normal form validation and touched-state path remains in control.
128
+
129
+ There are no component-specific Vue `emit()` events. The effective interaction boundary is the standard form-field contract plus the editor's update and blur callbacks.
130
+
131
+ ### Form-to-editor flow
132
+
133
+ The render bean binds the current `propsBucket.value` and `propsBucket.readonly` values from `ZFormField`. If the form value changes externally, the controller updates the editor with:
134
+
135
+ ```typescript
136
+ this.editor.commands.setContent(value, { contentType: 'markdown' });
137
+ ```
138
+
139
+ A private syncing flag prevents that programmatic update from immediately writing the same value back to the form. When `readonly` changes, the controller calls `editor.setEditable(!readonly)`.
140
+
141
+ When the component is disposed, the controller calls `editor.destroy()` so TipTap's DOM and event resources are released.
142
+
143
+ ### SSR and `ClientOnly`
144
+
145
+ TipTap's `EditorContent` is browser-dependent. The editor is therefore inside Zova's `ClientOnly` component:
146
+
147
+ - SSR renders a minimum-height placeholder;
148
+ - the browser creates the editor after mounting;
149
+ - the editor should not be described as an SSR-rendered interactive control.
150
+
151
+ The read-only HTML display path is different: it does not need TipTap or `ClientOnly` because it receives already-generated HTML.
152
+
153
+ ## Display server-generated HTML with `ZMarkdownHtml`
154
+
155
+ Use `ZMarkdownHtml` when a page has an HTML projection from a trusted backend rendering path:
156
+
157
+ ```tsx
158
+ import { ZMarkdownHtml } from 'zova-module-basic-markdown';
159
+
160
+ export class ControllerArticlePage {
161
+ public render() {
162
+ const html = this.article?.bodyHtml;
163
+ if (!html) return null;
164
+
165
+ return <ZMarkdownHtml class="article-content" html={html} />;
166
+ }
167
+ }
168
+ ```
169
+
170
+ `ZMarkdownHtml` accepts `class?: string` and `html?: string`. Its render bean produces one `div`, applies the shared rich-text class, and assigns the `html` prop through `innerHTML`.
171
+
172
+ > [!WARNING]
173
+ > `ZMarkdownHtml` does **not** sanitize its input. It does not parse Markdown. Its caller is responsible for providing HTML generated by `bean.markdown.renderHtml(...)` or by an equivalent, explicitly trusted sanitizer pipeline. Treating this component as a general-purpose safe HTML sink is incorrect.
174
+
175
+ The shared rich-text style covers headings, paragraphs, ordered and unordered lists, nested task-list structure, blockquotes, inline and block code, highlight classes, links, images, horizontal rules, and tables. The style is a presentation layer; it does not change the sanitizer or URL policy.
176
+
177
+ A public product page uses this boundary:
178
+
179
+ ```text
180
+ publicProduct.descriptionHtml
181
+ → product page controller
182
+ → <ZMarkdownHtml html={descriptionHtml} />
183
+ ```
184
+
185
+ The public response should not expose the editable Markdown unless that is an intentional access-controlled contract.
186
+
187
+ ## Supported Markdown features
188
+
189
+ The current frontend and backend extension sets are aligned around these features:
190
+
191
+ - StarterKit document basics: headings, paragraphs, emphasis, strong text, strike-through, blockquotes, horizontal rules, lists, and code;
192
+ - GitHub-flavored Markdown parsing on the backend;
193
+ - nested task lists;
194
+ - images;
195
+ - tables;
196
+ - highlight support in the TipTap document/rendering extensions.
197
+
198
+ The backend Markdown manager is configured with two-space indentation. Treat this as the current parser/serializer convention, not as a reason to reject every inbound document that uses another indentation style.
199
+
200
+ A representative input is:
201
+
202
+ ````markdown
203
+ # Product details
204
+
205
+ This is **durable** content.
206
+
207
+ - [ ] Draft the description
208
+ - [x] Review the link
209
+
210
+ | Field | Value |
211
+ | ------ | ----- |
212
+ | Status | Ready |
213
+
214
+ ```ts
215
+ const ready = true;
216
+ ```
217
+
218
+ [Project home](https://example.com)
219
+
220
+ ![Product image](https://example.com/product.png)
221
+ ````
222
+
223
+ The exact HTML output is governed by the backend renderer and sanitizer. Do not infer support for arbitrary raw HTML, iframe embeds, inline styles, `data:` URLs, or custom classes from the presence of a TipTap extension.
224
+
225
+ ## Common mistakes
226
+
227
+ 1. **Storing editor HTML as the form value**
228
+ - Keep the form value as Markdown. The editor uses `getMarkdown()` and `setContent(..., { contentType: 'markdown' })` for that reason.
229
+
230
+ 2. **Trusting a client-supplied HTML field**
231
+ - Ignore or overwrite submitted HTML. Rebuild it on the server from Markdown.
232
+
233
+ 3. **Passing unknown HTML to `ZMarkdownHtml`**
234
+ - The component uses `innerHTML` and has no frontend sanitizer.
235
+
236
+ 4. **Expecting task checkboxes to be interactive**
237
+ - The backend transforms rendered task inputs into disabled checkboxes for display.
238
+
239
+ 5. **Expecting a toolbar or upload workflow from this module**
240
+ - The module supplies an editor surface, not a toolbar specification, image upload service, remote image importer, or configurable plugin registry.
241
+
242
+ 6. **Treating browser validation or presentation CSS as security**
243
+ - The backend renderer/sanitizer is the relevant content boundary. External image availability, privacy, CSP, and application authorization remain separate concerns.
244
+
245
+ ## Frontend checklist
246
+
247
+ Before integrating Markdown into a Zova feature, verify:
248
+
249
+ 1. The editable field is a string containing Markdown.
250
+ 2. The field metadata uses `basic-markdown:formFieldMarkdown`.
251
+ 3. The form value is updated through the editor's Markdown API, not HTML.
252
+ 4. Blur and readonly behavior still pass through the standard `ZFormField` contract.
253
+ 5. The interactive editor is inside the expected `ClientOnly` boundary.
254
+ 6. Public display uses `ZMarkdownHtml` only with server-derived sanitized HTML.
255
+ 7. Public DTOs do not expose editable Markdown unless that is intentional.
256
+ 8. The backend write path regenerates the HTML projection for every create, update, import, or backfill path that can change Markdown.
257
+
258
+ For the backend API and security details, continue with [Backend Markdown Guide](/backend/markdown-guide).
@@ -164,6 +164,54 @@ class StylePageCounter extends BeanStyleBase {
164
164
  }
165
165
  ```
166
166
 
167
+ ### Companion-member access in a split page
168
+
169
+ The three files have distinct primary ownership:
170
+
171
+ - **Controller** owns state, actions, and lifecycle work.
172
+ - **Render** owns TSX composition.
173
+ - **Style** owns scoped CSS-in-JS setup.
174
+
175
+ Within one split page or component, ordinary access to a companion member uses `this.member`; it does not require `@Use()` or a container lookup. Runtime lookup proceeds in this order:
176
+
177
+ | Current bean | Direct member lookup order |
178
+ | ------------ | ----------------------------------- |
179
+ | Controller | Controller only |
180
+ | Style | Style, then Controller |
181
+ | Render | Render, then Controller, then Style |
182
+
183
+ A member declared on the current bean shadows the next fallback surface. Keep names distinct when a Style class name and a Controller state/action could otherwise collide.
184
+
185
+ For example, the Render bean can read Controller state and actions directly, while it receives the generated Style class through the same direct access surface:
186
+
187
+ ```typescript
188
+ @Render()
189
+ class RenderPageCounter extends BeanRenderBase {
190
+ public render() {
191
+ return <div class={this.cTextCenter}>count: {this.count}</div>;
192
+ }
193
+ }
194
+ ```
195
+
196
+ The Style bean can likewise read Controller state while owning the generated class name:
197
+
198
+ ```typescript
199
+ @Style()
200
+ class StylePageCounter extends BeanStyleBase {
201
+ cTextCenter: string;
202
+
203
+ protected async __init__() {
204
+ this.cTextCenter = this.$style({
205
+ opacity: this.count > 0 ? 1 : 0.6,
206
+ });
207
+ }
208
+ }
209
+ ```
210
+
211
+ Use explicit `@Use()` or bean/container lookup when the task needs a specific named instance, selector or scope boundary, identity passing, lifecycle control, or other interop. Those mechanisms are not the default for same-component companion access.
212
+
213
+ For the generated typing and runtime evidence behind this rule, see [Zova Source Reading Map](/frontend/zova-source-reading-map#3-split-controller-render-and-style-companion-access).
214
+
167
215
  ### More-file growth
168
216
 
169
217
  When the page continues to grow, you can keep splitting responsibilities instead of forcing one large controller or render bean to absorb everything.
@@ -30,7 +30,9 @@ Because route params arrive as strings at the URL level, the schema is also wher
30
30
 
31
31
  ## Route record requirements
32
32
 
33
- One important rule is that if a page supports params, the route should expose the proper route structure and route name.
33
+ A page route that declares or consumes dynamic params **must** define `route.name`. Do not use an unnamed route with params.
34
+
35
+ The route name is required because Zova uses the named route identity to generate and resolve the page's typed `params` schema at runtime. This initializes the controller's `$params` surface during SSR and client navigation. A params schema declaration alone does not make `$params` available if the route is unnamed.
34
36
 
35
37
  Representative route idea:
36
38
 
@@ -82,6 +84,6 @@ this.$router.push(url);
82
84
  When adding or editing param-driven page behavior:
83
85
 
84
86
  1. use the Zova refactor command when possible
85
- 2. update the route record and route name deliberately
87
+ 2. define `route.name` whenever the route has dynamic params
86
88
  3. regenerate metadata when route typing depends on it
87
89
  4. use `this.$params` and typed router helpers instead of manual parsing
@@ -42,7 +42,7 @@ This matters because path generation is modular by default.
42
42
 
43
43
  ## `name`
44
44
 
45
- If a page uses params, the route name becomes especially important because typed param-aware routing depends on it.
45
+ A route with dynamic params must define `name`. Typed param-aware routing uses the named route identity to generate and resolve the page's runtime params schema; an unnamed parameterized route does not provide the required `$params` contract.
46
46
 
47
47
  ## `component`
48
48
 
@@ -145,7 +145,7 @@ When editing page routing, do not only change the URL string.
145
145
 
146
146
  It should also check whether the route change affects:
147
147
 
148
- - params typing
148
+ - params typing and the required `route.name` for dynamic params
149
149
  - auth behavior
150
150
  - layout behavior
151
151
  - whether `meta.locale` should participate in URL-locale behavior
@@ -417,6 +417,8 @@ When `_initControllerRoute(...)` updates a page controller, it resolves the rout
417
417
  - `pageNameSchemas` for named routes
418
418
  - `pagePathSchemas` for path-keyed routes
419
419
 
420
+ For page routes with dynamic params, define `route.name` and use the named-route metadata path. Do not rely on an unnamed parameterized route: the generated path-keyed record does not establish the typed controller `$params` contract.
421
+
420
422
  Those schema records are generated into module metadata.
421
423
 
422
424
  Representative generator source:
@@ -107,7 +107,51 @@ Use this path when you are asking questions like:
107
107
  - `component.ts` shows how render is patched toward controller/render beans
108
108
  - the concrete component controller shows the public authoring pattern for the case you care about
109
109
 
110
- ## 3. Bean lifecycle, instance scope, and helper APIs
110
+ ## 3. Split Controller, Render, and Style companion access
111
+
112
+ Use this path when you are asking questions like:
113
+
114
+ - why can a Render Bean read Controller state with `this.member`?
115
+ - why can a Style Bean read Controller state, or a Render Bean read a generated Style class?
116
+ - does generated type augmentation create the runtime fallback behavior?
117
+ - when is `@Use()` or an explicit bean lookup actually needed?
118
+
119
+ ### Read the docs first
120
+
121
+ - [Page Guide](/frontend/page-guide#companion-member-access-in-a-split-page)
122
+ - [Component Guide](/frontend/component-guide#controllerref-and-internal-companion-access)
123
+ - [CSS-in-JS Guide](/frontend/css-in-js-guide#style-beans-in-a-split-controllerrenderstyle-component)
124
+
125
+ ### Then read source in this order
126
+
127
+ 1. `zova/src/suite/a-home/modules/home-layoutadmin/src/.metadata/component/layoutAdmin.ts`
128
+ 2. `zova/packages-zova/zova-core/src/composables/useController.ts`
129
+ 3. `zova/packages-zova/zova-core/src/bean/beanStyleBase.ts`
130
+ 4. `zova/packages-zova/zova-core/src/bean/beanControllerLike.ts`
131
+ 5. `zova/packages-zova/zova-core/src/bean/beanRenderLike.ts`
132
+ 6. `zova/packages-zova/zova-core/src/bean/beanContainer.ts`
133
+ 7. `zova/packages-zova/zova-core/src/core/context/component.ts`
134
+
135
+ ### What each file clarifies
136
+
137
+ - `layoutAdmin.ts` shows generated type augmentation: Style extends Controller and Render extends Style, so TypeScript exposes the companion members.
138
+ - `useController.ts` creates the Controller first, then Style, then Render in one component context.
139
+ - `beanStyleBase.ts` and `beanControllerLike.ts` show Style's runtime fallback from Style to Controller.
140
+ - `beanRenderLike.ts` extends that fallback so Render resolves Render, then Controller, then Style.
141
+ - `beanContainer.ts` shows that proxy handling keeps locally declared members local and asks `__get__`/`__set__` only for a missing member; this produces own-member shadowing.
142
+ - `component.ts` shows that the runtime uses the Controller's render when it has one and otherwise selects the Render Bean.
143
+
144
+ The source-confirmed direct lookup order is:
145
+
146
+ | Current bean | Direct member lookup order |
147
+ | ------------ | ----------------------------------- |
148
+ | Controller | Controller only |
149
+ | Style | Style, then Controller |
150
+ | Render | Render, then Controller, then Style |
151
+
152
+ Use `this.member` for normal same-component access. Use explicit `@Use()` or a bean/container lookup only when you need a named or specific instance, selector or scope boundary, identity passing, lifecycle control, or other interop. Generated type augmentation makes this authoring surface type-safe; the proxy fallback is the separate runtime mechanism that supplies missing companion members.
153
+
154
+ ## 4. Bean lifecycle, instance scope, and helper APIs
111
155
 
112
156
  Use this path when you are asking questions like:
113
157
 
@@ -134,7 +178,7 @@ Use this path when you are asking questions like:
134
178
  - `beanContainer.ts` shows init, inject, and dispose flow
135
179
  - `context/util.ts` shows how instance scope is applied around framework operations
136
180
 
137
- ## 4. Page routing, params, query, and layout-oriented route behavior
181
+ ## 5. Page routing, params, query, and layout-oriented route behavior
138
182
 
139
183
  Use this path when you are asking questions like:
140
184
 
@@ -180,9 +224,9 @@ If your next question becomes any of these:
180
224
  then continue immediately with:
181
225
 
182
226
  - [Router View Hosts Guide](/frontend/router-view-hosts-guide)
183
- - section 5 on this page for the compact source-reading path
227
+ - section 6 on this page for the compact source-reading path
184
228
 
185
- ## 5. Router-view hosts, routertabs, and routerstack
229
+ ## 6. Router-view hosts, routertabs, and routerstack
186
230
 
187
231
  Use this path when you are asking questions like:
188
232
 
@@ -225,7 +269,7 @@ If your next question becomes specifically about task-level title, dirty state,
225
269
  - `model/stack.ts` shows the fullPath-based stack identity model and recency-based pruning
226
270
  - active layout consumers show how the current shell turns host state into visible Admin or Web behavior
227
271
 
228
- ## 6. Model state, cache-oriented state, and broader data ownership
272
+ ## 7. Model state, cache-oriented state, and broader data ownership
229
273
 
230
274
  Use this path when you are asking questions like:
231
275
 
@@ -253,7 +297,7 @@ Use this path when you are asking questions like:
253
297
  - built-in model beans show how the architecture is used in nontrivial cases
254
298
  - consuming code shows whether the state really belongs in the model or only uses the model result
255
299
 
256
- ## 7. Command scene and command-bean invocation
300
+ ## 8. Command scene and command-bean invocation
257
301
 
258
302
  Use this path when you are asking questions like:
259
303
 
@@ -318,7 +362,7 @@ Use this path when you are asking questions like:
318
362
  - `controller.tsx` shows app-level meta setup, behavior-holder initialization, and behavior-wrapped `RouterView` render
319
363
  - `config.ts` shows the root behavior injection point through `scope.config.behaviors`
320
364
 
321
- ## 8. Behavior scene and render-time interception
365
+ ## 9. Behavior scene and render-time interception
322
366
 
323
367
  Use this path when you are asking questions like:
324
368
 
@@ -345,7 +389,7 @@ Use this path when you are asking questions like:
345
389
  - composer/service files show how behavior chains are normalized and executed
346
390
  - host dependencies show why host-scoped injection is part of the behavior design
347
391
 
348
- ## 9. SSR runtime and hydration handoff
392
+ ## 10. SSR runtime and hydration handoff
349
393
 
350
394
  Use this path when you are asking questions like:
351
395
 
@@ -374,7 +418,7 @@ Use this path when you are asking questions like:
374
418
  - page/controller/model files show whether the bug is actually page-level logic
375
419
  - the Vona side shows whether the problem happens before the Zova runtime is even entered
376
420
 
377
- ## 10. Modules, suites, and architectural placement
421
+ ## 11. Modules, suites, and architectural placement
378
422
 
379
423
  Use this path when you are asking questions like:
380
424