cabloy 5.1.98 → 5.1.100

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 (141) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/hooks/contract-loop-gate.ts +151 -44
  3. package/CHANGELOG.md +28 -0
  4. package/CLAUDE.md +2 -0
  5. package/cabloy-docs/.vitepress/config.mjs +4 -0
  6. package/cabloy-docs/backend/controller-aop-guide.md +0 -1
  7. package/cabloy-docs/frontend/a-openapi-under-the-hood.md +1 -1
  8. package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +1 -1
  9. package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +1 -1
  10. package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +1 -1
  11. package/cabloy-docs/frontend/model-resource-owner-pattern.md +1 -1
  12. package/cabloy-docs/frontend/model-state-guide.md +3 -1
  13. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -2
  14. package/cabloy-docs/frontend/resource-list-page-deep-dive.md +1 -1
  15. package/cabloy-docs/frontend/rest-resource-source-reading-map.md +2 -2
  16. package/cabloy-docs/frontend/rest-resource-under-the-hood.md +4 -4
  17. package/cabloy-docs/frontend/use-state-data-best-practices.md +384 -0
  18. package/package.json +1 -1
  19. package/scripts/upgrade.ts +2 -0
  20. package/vona/packages-cli/cli/package.json +1 -1
  21. package/vona/packages-cli/cli-set-api/package.json +1 -1
  22. package/vona/packages-vona/vona/package.json +1 -1
  23. package/vona/packages-vona/vona-core/package.json +1 -1
  24. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +9 -9
  25. package/vona/packages-vona/vona-mock/package.json +1 -1
  26. package/vona/pnpm-lock.yaml +56 -56
  27. package/vona/pnpm-workspace.yaml +1 -1
  28. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +22 -0
  29. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  30. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  31. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  32. package/vona/src/suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts +47 -0
  33. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +13 -0
  34. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +13 -0
  35. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +16 -5
  36. package/vona/src/suite/a-training/modules/training-record/src/index.ts +2 -0
  37. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +1 -0
  38. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +34 -0
  39. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +209 -123
  40. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  41. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  42. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +18 -1
  43. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -2
  44. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  45. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +5 -3
  46. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +28 -0
  47. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +8 -0
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyRequest.ts +15 -0
  49. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +30 -0
  50. package/vona/src/suite-vendor/a-file/package.json +2 -2
  51. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  52. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +27 -0
  53. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +8 -0
  54. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyRequest.ts +15 -0
  55. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +27 -0
  56. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +1 -1
  57. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +1 -1
  58. package/vona/src/suite-vendor/a-image/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  61. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +24 -9
  62. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +87 -73
  63. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionActionByRoles.ts +27 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionUser.ts +20 -0
  65. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  66. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +4 -3
  67. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +5 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +0 -13
  71. package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +0 -7
  72. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  73. package/vona/src/suite-vendor/a-vona/modules/a-version/src/bean/meta.version.ts +2 -24
  74. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  75. package/zova/packages-utils/zova-jsx/package.json +2 -2
  76. package/zova/packages-zova/zova/package.json +3 -3
  77. package/zova/packages-zova/zova-core/package.json +1 -1
  78. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +9 -13
  79. package/zova/pnpm-lock.yaml +24 -9
  80. package/zova/src/suite/a-demo/modules/demo-basic/src/page/toolOne/controller.tsx +2 -2
  81. package/zova/src/suite/a-demo/modules/demo-todo/src/page/todo/controller.tsx +2 -2
  82. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +2 -2
  83. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +2 -2
  84. package/zova/src/suite/a-home/modules/home-login/src/page/login/controller.tsx +2 -2
  85. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +5 -1
  86. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +20 -15
  87. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +6 -1
  88. package/zova/src/suite/cabloy-basic/modules/basic-file/cli/openapi.config.ts +9 -0
  89. package/zova/src/suite/cabloy-basic/modules/basic-file/package.json +58 -0
  90. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/component/formFieldFile.ts +31 -0
  91. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +222 -0
  92. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/locales.ts +7 -0
  93. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/this.ts +2 -0
  94. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +121 -0
  95. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/baseURL.ts +5 -0
  96. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/index.ts +3 -0
  97. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +306 -0
  98. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +5718 -0
  99. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +40 -0
  100. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +187 -0
  101. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +534 -0
  102. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/en-us.ts +15 -0
  103. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/zh-cn.ts +15 -0
  104. package/zova/src/suite/cabloy-basic/modules/basic-file/src/index.ts +4 -0
  105. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +74 -0
  106. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/index.ts +1 -0
  107. package/zova/src/suite/cabloy-basic/modules/basic-file/src/model/file.ts +23 -0
  108. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/file.ts +12 -0
  109. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/index.ts +1 -0
  110. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.build.json +13 -0
  111. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.json +5 -0
  112. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +61 -0
  113. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +38 -1
  114. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +31 -0
  115. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +52 -0
  116. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +614 -1
  117. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +10 -0
  118. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +13 -0
  119. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +126 -53
  120. package/zova/src/suite/cabloy-basic/modules/basic-image/src/model/image.ts +23 -0
  121. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +44 -4
  122. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +17 -12
  123. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +2 -2
  124. package/zova/src/suite/cabloy-basic/modules/basic-resource/src/component/formFieldResourcePicker/controller.tsx +2 -2
  125. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +7 -2
  126. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +34 -33
  127. package/zova/src/suite/cabloy-basic/package.json +1 -0
  128. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  129. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +5 -3
  130. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +2 -2
  131. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +2 -2
  132. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  133. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  134. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +17 -17
  135. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  136. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +8 -3
  137. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  138. package/zova/src/suite-vendor/a-zova/package.json +4 -4
  139. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +0 -17
  140. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.userName.ts +0 -35
  141. /package/vona/patches/{zova-core@5.1.76.patch → zova-core@5.1.77.patch} +0 -0
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.98
1
+ 5.1.100
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, statSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { spawnSync, type SpawnSyncReturns } from 'node:child_process';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
@@ -21,6 +21,25 @@ interface SyncStateEntry {
21
21
  timestamp: number;
22
22
  }
23
23
 
24
+ interface CommandPlan {
25
+ args: readonly string[];
26
+ display: string;
27
+ }
28
+
29
+ interface EditionConfig {
30
+ id: 'basic' | 'start';
31
+ label: string;
32
+ reverseVonaContentMarkers: readonly string[];
33
+ reverseAutoSyncCommands: readonly CommandPlan[];
34
+ reverseWebBuildCommand: string;
35
+ }
36
+
37
+ type ReverseSyncOutcome =
38
+ | { kind: 'not-applicable'; message: string }
39
+ | { kind: 'skipped'; message: string }
40
+ | { kind: 'success'; message: string }
41
+ | { kind: 'failure'; message: string };
42
+
24
43
  type SyncState = Record<string, SyncStateEntry>;
25
44
 
26
45
  const SCRIPT_FILE = fileURLToPath(import.meta.url);
@@ -45,13 +64,10 @@ const FORWARD_PATH_MARKERS = ['/controller/', '/dto/', '/entity/'];
45
64
 
46
65
  const FORWARD_CONTENT_MARKERS = ['@Web.', '@Api.field', '@Api.body', 'v.openapi(', '@Dto<'];
47
66
 
48
- const REVERSE_VONA_CONTENT_MARKERS = [
49
- 'zova-rest-cabloy-basic-admin',
67
+ const SHARED_REVERSE_VONA_CONTENT_MARKERS = [
50
68
  'ZovaRender.',
51
69
  'tableActionRow(',
52
70
  'tableActionBulk(',
53
- 'ZovaRender.field(',
54
- 'ZovaRender.cell(',
55
71
  ];
56
72
 
57
73
  const REVERSE_ZOVA_PATH_MARKERS = ['/src/bean/', '/src/component/', '/src/.metadata/'];
@@ -64,6 +80,35 @@ const REVERSE_ZOVA_CONTENT_MARKERS = [
64
80
  '@Component<',
65
81
  ];
66
82
 
83
+ const REVERSE_AUTO_SYNC_COMMANDS: readonly CommandPlan[] = [
84
+ { args: ['run', 'build:zova:admin'], display: 'npm run build:zova:admin' },
85
+ { args: ['run', 'deps:vona'], display: 'npm run deps:vona' },
86
+ ];
87
+
88
+ const EDITION_CONFIGS: Record<'basic' | 'start', EditionConfig> = {
89
+ basic: {
90
+ id: 'basic',
91
+ label: 'Cabloy Basic',
92
+ reverseVonaContentMarkers: ['zova-rest-cabloy-basic-admin'],
93
+ reverseAutoSyncCommands: REVERSE_AUTO_SYNC_COMMANDS,
94
+ reverseWebBuildCommand: 'npm run build:zova:web',
95
+ },
96
+ start: {
97
+ id: 'start',
98
+ label: 'Cabloy Start',
99
+ reverseVonaContentMarkers: ['zova-rest-cabloy-start-admin'],
100
+ reverseAutoSyncCommands: REVERSE_AUTO_SYNC_COMMANDS,
101
+ reverseWebBuildCommand: 'npm run build:zova:web',
102
+ },
103
+ };
104
+
105
+ const ACTIVE_EDITION = resolveEdition();
106
+ const FALLBACK_REVERSE_VONA_CONTENT_MARKERS = [
107
+ ...SHARED_REVERSE_VONA_CONTENT_MARKERS,
108
+ ...EDITION_CONFIGS.basic.reverseVonaContentMarkers,
109
+ ...EDITION_CONFIGS.start.reverseVonaContentMarkers,
110
+ ];
111
+
67
112
  function toPosixPath(value: string): string {
68
113
  return value.split(path.sep).join('/');
69
114
  }
@@ -78,6 +123,21 @@ function normalizePath(value?: string): string | null {
78
123
  }
79
124
  }
80
125
 
126
+ function resolveEdition(): EditionConfig | null {
127
+ if (existsSync(path.resolve(ROOT, '__CABLOY_BASIC__'))) {
128
+ return EDITION_CONFIGS.basic;
129
+ }
130
+ if (existsSync(path.resolve(ROOT, '__CABLOY_START__'))) {
131
+ return EDITION_CONFIGS.start;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ function getReverseVonaContentMarkers(edition: EditionConfig | null): readonly string[] {
137
+ if (!edition) return FALLBACK_REVERSE_VONA_CONTENT_MARKERS;
138
+ return [...SHARED_REVERSE_VONA_CONTENT_MARKERS, ...edition.reverseVonaContentMarkers];
139
+ }
140
+
81
141
  function isCodeFile(filePath: string): boolean {
82
142
  return TARGET_PATTERNS.some(([prefix, suffix]) => filePath.includes(prefix) && filePath.endsWith(suffix));
83
143
  }
@@ -98,8 +158,12 @@ function detectForward(filePath: string, content: string): string | null {
98
158
  return null;
99
159
  }
100
160
 
101
- function detectReverse(filePath: string, content: string): string | null {
102
- if (filePath.includes('/vona/src/') && containsAny(content, REVERSE_VONA_CONTENT_MARKERS)) {
161
+ function detectReverse(
162
+ filePath: string,
163
+ content: string,
164
+ reverseVonaContentMarkers: readonly string[],
165
+ ): string | null {
166
+ if (filePath.includes('/vona/src/') && containsAny(content, reverseVonaContentMarkers)) {
103
167
  return 'Vona code is consuming frontend metadata or render resources.';
104
168
  }
105
169
  if (
@@ -112,10 +176,10 @@ function detectReverse(filePath: string, content: string): string | null {
112
176
  return null;
113
177
  }
114
178
 
115
- function analyze(filePath: string, content: string): AnalysisResult {
179
+ function analyze(filePath: string, content: string, edition: EditionConfig | null): AnalysisResult {
116
180
  return {
117
181
  forwardReason: detectForward(filePath, content),
118
- reverseReason: detectReverse(filePath, content),
182
+ reverseReason: detectReverse(filePath, content, getReverseVonaContentMarkers(edition)),
119
183
  };
120
184
  }
121
185
 
@@ -180,9 +244,9 @@ function markAutoSync(filePath: string): void {
180
244
  saveState(state);
181
245
  }
182
246
 
183
- function runNpm(args: string[]): SpawnSyncReturns<string> {
247
+ function runNpm(args: readonly string[]): SpawnSyncReturns<string> {
184
248
  const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
185
- return spawnSync(command, args, {
249
+ return spawnSync(command, [...args], {
186
250
  cwd: ROOT,
187
251
  encoding: 'utf8',
188
252
  });
@@ -205,22 +269,79 @@ function summarizeProcess(result: SpawnSyncReturns<string>): string {
205
269
  return `exit ${exitCode}: ${tail}`;
206
270
  }
207
271
 
208
- function autoSyncReverse(filePath: string): readonly [boolean, string] {
209
- const buildResult = runNpm(['run', 'build:zova:admin']);
210
- if (buildResult.status !== 0) {
211
- return [false, `Auto-sync failed during \`npm run build:zova:admin\`: ${summarizeProcess(buildResult)}`];
272
+ function autoSyncReverse(filePath: string, edition: EditionConfig): ReverseSyncOutcome {
273
+ for (const command of edition.reverseAutoSyncCommands) {
274
+ const result = runNpm(command.args);
275
+ if (result.status !== 0) {
276
+ return {
277
+ kind: 'failure',
278
+ message: `Auto-sync failed during \`${command.display}\`: ${summarizeProcess(result)}`,
279
+ };
280
+ }
281
+ }
282
+
283
+ markAutoSync(filePath);
284
+ const commands = edition.reverseAutoSyncCommands.map(command => `\`${command.display}\``).join(' and ');
285
+ return {
286
+ kind: 'success',
287
+ message: `Auto-sync ran ${commands} for this ${edition.label} reverse-chain edit.`,
288
+ };
289
+ }
290
+
291
+ function buildReverseGuidance(edition: EditionConfig | null): string {
292
+ if (!edition) {
293
+ return 'If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable, resolve the active Cabloy edition marker before choosing the relevant Zova build and generated-output path, and then run `npm run deps:vona`.';
294
+ }
295
+
296
+ const autoSyncCommands = edition.reverseAutoSyncCommands
297
+ .map(command => `\`${command.display}\``)
298
+ .join(', then ');
299
+ return `If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable, run ${autoSyncCommands}, and also run \`${edition.reverseWebBuildCommand}\` when the Web flavor is affected in this ${edition.label} repo.`;
300
+ }
301
+
302
+ function resolveReverseSyncOutcome(
303
+ filePath: string,
304
+ result: AnalysisResult,
305
+ edition: EditionConfig | null,
306
+ ): ReverseSyncOutcome {
307
+ if (!result.reverseReason) {
308
+ return {
309
+ kind: 'not-applicable',
310
+ message: '',
311
+ };
212
312
  }
213
313
 
214
- const depsResult = runNpm(['run', 'deps:vona']);
215
- if (depsResult.status !== 0) {
216
- return [false, `Auto-sync failed during \`npm run deps:vona\`: ${summarizeProcess(depsResult)}`];
314
+ if (!isHighConfidenceReverseSource(filePath)) {
315
+ return {
316
+ kind: 'not-applicable',
317
+ message:
318
+ 'Auto-sync did not run because this reverse-chain signal came from the consumer side rather than a high-confidence frontend source edit.',
319
+ };
217
320
  }
218
321
 
219
- markAutoSync(filePath);
220
- return [true, 'Auto-sync ran `npm run build:zova:admin` and `npm run deps:vona` for this reverse-chain edit.'];
322
+ if (shouldSkipAutoSync(filePath)) {
323
+ return {
324
+ kind: 'skipped',
325
+ message: 'Auto-sync skipped because the same reverse-source edit was already synced recently in this repo.',
326
+ };
327
+ }
328
+
329
+ if (!edition) {
330
+ return {
331
+ kind: 'not-applicable',
332
+ message:
333
+ 'Auto-sync did not run because the active Cabloy edition marker could not be resolved for this repo.',
334
+ };
335
+ }
336
+
337
+ return autoSyncReverse(filePath, edition);
221
338
  }
222
339
 
223
- function buildMessages(filePath: string, result: AnalysisResult): string {
340
+ function buildMessages(
341
+ result: AnalysisResult,
342
+ edition: EditionConfig | null,
343
+ reverseSyncOutcome: ReverseSyncOutcome,
344
+ ): string {
224
345
  const messages = ["Contract-loop gate: this change may affect Cabloy's bidirectional contract loop."];
225
346
 
226
347
  if (result.forwardReason) {
@@ -233,30 +354,15 @@ function buildMessages(filePath: string, result: AnalysisResult): string {
233
354
  }
234
355
 
235
356
  if (result.reverseReason) {
236
- messages.push(
237
- `Reverse chain: ${result.reverseReason} If backend tooling or backend metadata will consume this handoff, refresh generated metadata when applicable, run the relevant Zova build first, and then run \`npm run deps:vona\` for the Cabloy Basic path. Use \`npm run build:zova:admin\` for Admin changes, and also run \`npm run build:zova:web\` when the Web flavor is affected.`,
238
- );
239
- if (isHighConfidenceReverseSource(filePath)) {
240
- if (shouldSkipAutoSync(filePath)) {
241
- messages.push('Auto-sync skipped because the same reverse-source edit was already synced recently in this repo.');
242
- } else {
243
- const [ok, detail] = autoSyncReverse(filePath);
244
- messages.push(detail);
245
- if (!ok) {
246
- messages.push(
247
- 'Please review the failure before continuing. If generated artifacts already contain the expected changes but consumers still behave stale, suspect local dependency drift before making more source edits.',
248
- );
249
- return messages.join(' ');
250
- }
251
- }
252
- } else {
357
+ messages.push(`Reverse chain: ${result.reverseReason} ${buildReverseGuidance(edition)}`);
358
+ if (reverseSyncOutcome.message) {
359
+ messages.push(reverseSyncOutcome.message);
360
+ }
361
+ if (reverseSyncOutcome.kind === 'failure') {
253
362
  messages.push(
254
- 'Auto-sync did not run because this reverse-chain signal came from the consumer side rather than a high-confidence frontend source edit.',
363
+ 'Please review the failure before continuing. If generated artifacts already contain the expected changes but consumers still behave stale, suspect local dependency drift before making more source edits.',
255
364
  );
256
365
  }
257
- messages.push(
258
- 'For Cabloy Start, apply the same reverse-chain logic but resolve the Start-specific flavor names and generated-output paths from the active Start repo before executing edition-specific steps.',
259
- );
260
366
  }
261
367
 
262
368
  return messages.join(' ');
@@ -277,10 +383,11 @@ function runClaudeHook(): number {
277
383
  const content = readText(filePath);
278
384
  if (content === null) return 0;
279
385
 
280
- const result = analyze(filePath, content);
386
+ const result = analyze(filePath, content, ACTIVE_EDITION);
281
387
  if (!hasSignal(result)) return 0;
282
388
 
283
- const message = buildMessages(filePath, result);
389
+ const reverseSyncOutcome = resolveReverseSyncOutcome(filePath, result, ACTIVE_EDITION);
390
+ const message = buildMessages(result, ACTIVE_EDITION, reverseSyncOutcome);
284
391
  console.log(
285
392
  JSON.stringify({
286
393
  hookSpecificOutput: {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.100
4
+
5
+ ### Improvements
6
+
7
+ - Refresh the Zova lockfile after compensation rerun preparation.
8
+ - Refresh the Vona `zova-core` patch for v5.1.77.
9
+
10
+ ## 5.1.99
11
+
12
+ ### Features
13
+
14
+ - Add dossier files to training records.
15
+ - Derive dossier file policy from the file scene.
16
+
17
+ ### Improvements
18
+
19
+ - Rename and standardize `QueryAutoLoad` helpers.
20
+ - Use `QueryAutoLoad` for upload policy handling.
21
+ - Simplify the contract loop hook.
22
+ - Share Claude assets across editions.
23
+ - Split permission cache beans for clearer responsibility.
24
+ - Clarify permission action method names.
25
+ - Align default permission guard evaluation.
26
+ - Reuse derived upload policy state.
27
+ - Streamline upload policy queries.
28
+ - Derive image policy from the image scene.
29
+ - Generate the file SDK and move policy state into the model.
30
+
3
31
  ## 5.1.98
4
32
 
5
33
  ### Features
package/CLAUDE.md CHANGED
@@ -53,6 +53,8 @@ Before inventing a custom implementation path:
53
53
  - For frontend work, assume Cabloy Basic and Cabloy Start share a frontend engineering layer but may diverge in UI layer, frontend flavors, suite/module availability, SSR site baselines, project assets, and generated outputs.
54
54
  - For Zova frontend analysis, do not default to generic Vue reinterpretation first. Read the code through Zova’s controller / bean / IoC architecture before mapping it to Vue concepts.
55
55
  - For Zova source-reading or Vue-vs-Zova explanation tasks, start from the frontend reading guides and source-reading map in `cabloy-docs/frontend/` before doing framework-neutral reinterpretation.
56
+ - For frontend async state that affects rendering or interaction across consumers, prefer model-owned `$useStateData(...)` over controller-managed fetch/cache state.
57
+ - Default to establishing such query state during render. Use `disableSuspenseOnInit: true` only for relatively stable query-backed state when you want to skip the init-time `query.suspense()` kick; if strict readiness is needed later, wait explicitly at the interaction boundary.
56
58
  - Keep repo-wide AI rules in `CLAUDE.md` short and durable; put branching Zova analysis workflows in `.claude/skills/`.
57
59
  - For SSR theme-sensitive frontend work, detect the active edition marker and UI library before making assumptions. Cabloy Basic currently means DaisyUI + Tailwind CSS assumptions; Cabloy Start currently means Vuetify assumptions.
58
60
  - In Web SSR without cookie-backed theme resolution, do not treat server reads of `$theme.dark`, `$theme.darkMode`, or `$token` as final browser truth. Keep theme-sensitive SSR branching hydration-tolerant or defer final theme-sensitive decisions to the client.
@@ -485,6 +485,10 @@ export default defineConfig({
485
485
  text: 'Resource Models & CRUD',
486
486
  items: [
487
487
  { text: 'Model State Guide', link: '/frontend/model-state-guide' },
488
+ {
489
+ text: '`$useStateData` Best Practices',
490
+ link: '/frontend/use-state-data-best-practices',
491
+ },
488
492
  {
489
493
  text: 'Model Resource Owner Pattern',
490
494
  link: '/frontend/model-resource-owner-pattern',
@@ -93,7 +93,6 @@ Typical jobs include:
93
93
  ```typescript
94
94
  @Passport.public()
95
95
  @Passport.activated(false)
96
- @Passport.userName({ name: 'admin' })
97
96
  @Passport.roleName({ name: 'admin' })
98
97
  @Passport.admin()
99
98
  ```
@@ -131,7 +131,7 @@ The source confirms that:
131
131
 
132
132
  - bootstrap data is loaded first
133
133
  - permissions are then loaded through `getPermissions(resource)`
134
- - on the server, permissions are autoloaded as part of the bootstrap path
134
+ - on the server, permissions are ensured loaded as part of the bootstrap path
135
135
  - on the client, permission queries are refetched when needed
136
136
 
137
137
  That means bootstrap and permissions are part of the same lower-level OpenAPI/runtime ladder, not unrelated utilities.
@@ -121,7 +121,7 @@ Representative source facts:
121
121
 
122
122
  - the page resolves `$$modelResource` from the route resource
123
123
  - it derives `formMeta` from `formScene`
124
- - it autoloads `this.$$modelResource.getFormApiSchemas(this.formMeta)?.sdk`
124
+ - it ensures `this.$$modelResource.getFormApiSchemas(this.formMeta)?.sdk` is loaded
125
125
  - it derives `formSchema` from `this.$$modelResource.getFormSchema(this.formMeta)`
126
126
 
127
127
  This is the entry-shell boundary:
@@ -94,7 +94,7 @@ The clearest current list shell is:
94
94
  Representative source facts:
95
95
 
96
96
  - the page resolves `$$modelResource` from the route resource
97
- - `__init__()` autoloads `this.$$modelResource.apiSchemasSelect.sdk`
97
+ - `__init__()` ensures `this.$$modelResource.apiSchemasSelect.sdk` is loaded
98
98
  - render reads `schemaRow?.rest?.blocks`
99
99
 
100
100
  This is the list-shell boundary:
@@ -117,7 +117,7 @@ Primary anchors:
117
117
  What to prove:
118
118
 
119
119
  - the list route shell resolves the expected `resource`
120
- - `apiSchemasSelect.sdk` still autoloads where expected
120
+ - `apiSchemasSelect.sdk` is still ensured loaded where expected
121
121
  - `schemaRow`, `schemaFilter`, and list data flow are still exposed correctly
122
122
  - `basic-page:blockPage` still becomes the deeper list runtime owner
123
123
 
@@ -162,7 +162,7 @@ protected async __init__(resource: string) {
162
162
  And `_bootstrap()` uses:
163
163
 
164
164
  ```typescript
165
- const queryBootstrap = await $QueryAutoLoad(() => this.$sdk.getBootstrap(this.resource));
165
+ const queryBootstrap = await $QueryEnsureLoaded(() => this.$sdk.getBootstrap(this.resource));
166
166
  ```
167
167
 
168
168
  Then it resolves:
@@ -10,6 +10,8 @@ If you specifically want the scalable resource-facade pattern, continue with [Mo
10
10
 
11
11
  If you want the generic lower-level model runtime beneath these helper families, continue with [A-Model Under the Hood](/frontend/a-model-under-the-hood).
12
12
 
13
+ If your main question is how to design and review `$useStateData(...)` usage itself, continue with [`$useStateData` Best Practices](/frontend/use-state-data-best-practices).
14
+
13
15
  If you want to understand how that owner pattern expands into the whole `rest-resource` module runtime, continue with [Rest Resource Under the Hood](/frontend/rest-resource-under-the-hood), then [Rest Resource Source Reading Map](/frontend/rest-resource-source-reading-map).
14
16
 
15
17
  If you want to apply that pattern in your own module with a more uniform two-usage model, continue with [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide).
@@ -346,7 +348,7 @@ A plain CRUD helper usually forwards requests.
346
348
 
347
349
  It owns several resource-level concerns together:
348
350
 
349
- - bootstrap of resource metadata through `$QueryAutoLoad(...)`
351
+ - bootstrap of resource metadata through `$QueryEnsureLoaded(...)`
350
352
  - resolution of the final `resourceApi`
351
353
  - permissions lookup
352
354
  - OpenAPI schema access for view/create/update/select
@@ -110,7 +110,7 @@ Its main jobs are:
110
110
  - derive `formMeta`
111
111
  - resolve `formProvider`
112
112
  - resolve `formSchema`
113
- - autoload form API schemas
113
+ - ensure form API schemas are loaded
114
114
  - render `formSchema?.rest?.blocks`
115
115
 
116
116
  This is the first key architectural boundary:
@@ -154,7 +154,7 @@ Its main jobs are:
154
154
  - derive `formMeta` and `schemaScene`
155
155
  - derive `formProvider`, `formSchema`, and `formData`
156
156
  - prepare the shared JSX render context
157
- - autoload view/query data
157
+ - ensure view/query data is loaded
158
158
  - own submit flow through `submitData(...)`
159
159
  - update page meta through `setPageMeta(...)`
160
160
  - expose `$$pageEntry` into the render context for downstream blocks
@@ -102,7 +102,7 @@ zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controlle
102
102
  Its main jobs are:
103
103
 
104
104
  - resolve the current selector-backed `ModelResource`
105
- - autoload select API schemas
105
+ - ensure select API schemas are loaded
106
106
  - read `schemaRow?.rest?.blocks`
107
107
  - render those blocks through `ZovaJsx`
108
108
 
@@ -207,7 +207,7 @@ Read:
207
207
  This controller does three main things:
208
208
 
209
209
  1. resolves the selector-backed `ModelResource` instance from `this.$params.resource`
210
- 2. autoloads `apiSchemasSelect.sdk`
210
+ 2. ensures `apiSchemasSelect.sdk` is loaded
211
211
  3. reads `schemaRow?.rest?.blocks` and renders those blocks through `ZovaJsx`
212
212
 
213
213
  The key point is what it does **not** do.
@@ -239,7 +239,7 @@ It mainly owns:
239
239
  - `formMeta` derivation
240
240
  - `formProvider` lookup from `ModelResource`
241
241
  - `formSchema` lookup from `ModelResource`
242
- - autoload of the form API schemas
242
+ - ensuring the form API schemas are loaded
243
243
  - block rendering from `formSchema?.rest?.blocks`
244
244
 
245
245
  Again, the most important insight is what it does **not** fully own.
@@ -98,7 +98,7 @@ Route record
98
98
  └─ page-controller shell
99
99
  ├─ resolves current resource / id / formScene
100
100
  ├─ resolves selector-backed ModelResource
101
- ├─ autoloads top-level API schema surface
101
+ ├─ ensures top-level API schema surface is loaded
102
102
  └─ reads schemaRow.rest.blocks or formSchema.rest.blocks
103
103
 
104
104
 
@@ -255,7 +255,7 @@ zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controlle
255
255
  The main jobs of `ControllerPageResource` are:
256
256
 
257
257
  1. resolve the selector-backed model from `this.$params.resource`
258
- 2. autoload the select API schema through `this.$$modelResource.apiSchemasSelect.sdk`
258
+ 2. ensure the select API schema is loaded through `this.$$modelResource.apiSchemasSelect.sdk`
259
259
  3. read `this.schemaRow?.rest?.blocks`
260
260
  4. render those blocks through `ZovaJsx`
261
261
 
@@ -291,7 +291,7 @@ The main jobs of `ControllerPageEntry` are:
291
291
  - derive `formMeta`
292
292
  - expose `formProvider` from the model
293
293
  - expose `formSchema` from the model
294
- - autoload the form API schemas
294
+ - ensure the form API schemas are loaded
295
295
  - read `formSchema?.rest?.blocks`
296
296
  - render those blocks through `ZovaJsx`
297
297
 
@@ -366,7 +366,7 @@ That means one generic model class can serve many resources safely, because the
366
366
  Inside initialization, `_bootstrap()` calls:
367
367
 
368
368
  ```typescript
369
- $QueryAutoLoad(() => this.$sdk.getBootstrap(this.resource));
369
+ $QueryEnsureLoaded(() => this.$sdk.getBootstrap(this.resource));
370
370
  ```
371
371
 
372
372
  and then resolves: