cabloy 5.1.97 → 5.1.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +27 -0
- package/cabloy-docs/.vitepress/config.mjs +7 -0
- package/cabloy-docs/backend/image-guide.md +544 -0
- package/cabloy-docs/frontend/image-guide.md +512 -0
- package/cabloy-docs/frontend/introduction.md +2 -0
- package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
- package/cabloy-docs/frontend/model-architecture.md +2 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/cabloy-docs/frontend/model-state-guide.md +2 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
- package/cabloy-docs/frontend/system-startup-guide.md +2 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
- package/cabloy-docs/fullstack/image-workflow.md +403 -0
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/package.json +1 -1
- package/vona/package.original.json +0 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +424 -47
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +66 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +196 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +167 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/package.json +14 -0
- package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +59 -11
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +24 -54
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +13 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/pnpm-lock.yaml +389 -397
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +0 -9
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# State Architecture for Vue Developers
|
|
2
|
+
|
|
3
|
+
This guide is for Vue developers who already know common Vue state patterns and want the shortest docs-style explanation of how Zova/Cabloy organizes large-project state differently.
|
|
4
|
+
|
|
5
|
+
It does **not** argue that Vue's usual tools are wrong.
|
|
6
|
+
It explains why large projects often need a stronger architecture for state ownership, sharing boundaries, persistence, cache semantics, and SSR behavior.
|
|
7
|
+
|
|
8
|
+
Use this page together with:
|
|
9
|
+
|
|
10
|
+
- [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
11
|
+
- [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison)
|
|
12
|
+
- [IoC and Beans](/frontend/ioc-and-beans)
|
|
13
|
+
- [Model Architecture](/frontend/model-architecture)
|
|
14
|
+
- [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
15
|
+
|
|
16
|
+
## Why this page exists
|
|
17
|
+
|
|
18
|
+
Many Vue teams do not struggle because they picked one obviously bad tool.
|
|
19
|
+
|
|
20
|
+
They struggle because a large codebase gradually accumulates many valid state mechanisms at the same time:
|
|
21
|
+
|
|
22
|
+
- component-local reactive state
|
|
23
|
+
- composables
|
|
24
|
+
- `provide/inject`
|
|
25
|
+
- store layers
|
|
26
|
+
- query cache
|
|
27
|
+
- local persistence
|
|
28
|
+
- SSR-specific state rules
|
|
29
|
+
|
|
30
|
+
Each mechanism can solve a real local problem.
|
|
31
|
+
|
|
32
|
+
The large-project difficulty usually appears later, when the team can no longer answer these questions cleanly:
|
|
33
|
+
|
|
34
|
+
- who owns this state?
|
|
35
|
+
- how far should it be shared?
|
|
36
|
+
- which lifecycle rules apply to it?
|
|
37
|
+
- who owns cache identity and invalidation?
|
|
38
|
+
- is this only a value, or part of a larger resource boundary?
|
|
39
|
+
|
|
40
|
+
That is the architectural problem this page addresses.
|
|
41
|
+
|
|
42
|
+
## The shortest accurate summary
|
|
43
|
+
|
|
44
|
+
If you only remember one paragraph, remember this one:
|
|
45
|
+
|
|
46
|
+
> Large Vue projects rarely become hard because they need one more state tool. They usually become hard because ownership, sharing boundaries, persistence, cache semantics, and SSR behavior stop composing into one coherent system.
|
|
47
|
+
|
|
48
|
+
Zova's answer is to push those concerns back under a more explicit architecture:
|
|
49
|
+
|
|
50
|
+
- **Controller / Bean** makes ownership more explicit
|
|
51
|
+
- **IoC scopes** make sharing boundaries more explicit
|
|
52
|
+
- **Model** unifies several state families under one model-owned boundary
|
|
53
|
+
- **Resource Owner** keeps query, schema, permissions, forms, and invalidation closer to one resource-level owner
|
|
54
|
+
|
|
55
|
+
## Start with boundary questions, not only tool questions
|
|
56
|
+
|
|
57
|
+
In many Vue projects, state discussions begin with questions like these:
|
|
58
|
+
|
|
59
|
+
- should this go into a composable?
|
|
60
|
+
- should this move to a store?
|
|
61
|
+
- should we add a query layer?
|
|
62
|
+
- should we persist this in localStorage?
|
|
63
|
+
|
|
64
|
+
Those are reasonable questions.
|
|
65
|
+
|
|
66
|
+
But in a large system, the more important questions usually come first:
|
|
67
|
+
|
|
68
|
+
1. **Ownership** — which framework-managed object should own this state?
|
|
69
|
+
2. **Sharing boundary** — is it local, parent/child shared, app-level, or system-level?
|
|
70
|
+
3. **State family** — is it query-style remote state, in-memory state, local persistence, cookie-backed state, or async persisted state?
|
|
71
|
+
4. **Lifecycle** — what should survive page disposal, refresh, route transitions, or SSR request boundaries?
|
|
72
|
+
5. **Resource boundary** — does this state belong to a larger resource owner that also owns schema, permissions, forms, and invalidation?
|
|
73
|
+
|
|
74
|
+
Zova tries to answer those questions structurally instead of leaving them spread across unrelated mechanisms.
|
|
75
|
+
|
|
76
|
+
## 1. Ownership: make the state host explicit
|
|
77
|
+
|
|
78
|
+
A common Vue mental model starts with local reactive primitives and composition logic inside `setup()`.
|
|
79
|
+
|
|
80
|
+
Zova keeps Vue's reactive foundation, but changes the visible business-facing host. The most important state often lives on framework-managed Controller or Bean instances instead of starting life as scattered local variables.
|
|
81
|
+
|
|
82
|
+
That changes the reading question from:
|
|
83
|
+
|
|
84
|
+
- which composable or local variable holds this?
|
|
85
|
+
|
|
86
|
+
into:
|
|
87
|
+
|
|
88
|
+
- which Controller, Bean, or Model owns this?
|
|
89
|
+
|
|
90
|
+
That is a better scaling question because ownership drift is often the first step toward cache drift, persistence drift, and SSR drift.
|
|
91
|
+
|
|
92
|
+
For the broader authoring-model shift, read [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers) and [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison).
|
|
93
|
+
|
|
94
|
+
## 2. Sharing: treat scope as a first-class architectural decision
|
|
95
|
+
|
|
96
|
+
Large Vue codebases often express different sharing ranges through different mechanisms:
|
|
97
|
+
|
|
98
|
+
- component-local state
|
|
99
|
+
- parent-child props or `provide/inject`
|
|
100
|
+
- app-level stores
|
|
101
|
+
- long-lived globals or imported singletons
|
|
102
|
+
|
|
103
|
+
Zova tries to keep more of those cases inside one bean-and-container architecture.
|
|
104
|
+
|
|
105
|
+
A compact comparison is:
|
|
106
|
+
|
|
107
|
+
| Sharing range | Common Vue pattern | Zova framing |
|
|
108
|
+
| ------------------ | ------------------------------------------------------- | --------------------------------------------------- |
|
|
109
|
+
| component-internal | local state or component-local composables | `ctx`-scoped bean ownership |
|
|
110
|
+
| between-components | props/emits, parent-owned composables, `provide/inject` | hierarchical injection such as `host` or `skipSelf` |
|
|
111
|
+
| app-global | store or app-level provided state | `app`-scoped bean ownership |
|
|
112
|
+
| system-level | module singleton or long-lived imported state | `sys`-scoped bean ownership |
|
|
113
|
+
|
|
114
|
+
The key idea is simple:
|
|
115
|
+
|
|
116
|
+
> Do not start by switching mechanisms. Start by choosing the sharing boundary.
|
|
117
|
+
|
|
118
|
+
This distinction becomes especially important in SSR, where app/request state and system-level state should not be mixed casually.
|
|
119
|
+
|
|
120
|
+
Read together with [IoC and Beans](/frontend/ioc-and-beans) and [System Startup Guide](/frontend/system-startup-guide).
|
|
121
|
+
|
|
122
|
+
## 3. Model: unify multiple state families under one model boundary
|
|
123
|
+
|
|
124
|
+
In many stacks, remote cache, in-memory shared state, local persistence, cookie state, and async persistent state are handled by separate tools or utilities.
|
|
125
|
+
|
|
126
|
+
Zova Model tries to organize those families under one broader model-owned runtime.
|
|
127
|
+
|
|
128
|
+
The current model layer exposes five main state families:
|
|
129
|
+
|
|
130
|
+
- `data`
|
|
131
|
+
- `mem`
|
|
132
|
+
- `local`
|
|
133
|
+
- `cookie`
|
|
134
|
+
- `db`
|
|
135
|
+
|
|
136
|
+
This means Model is not only a request wrapper and not only another store.
|
|
137
|
+
|
|
138
|
+
Its architectural role is to give several important state categories a shared home for concerns such as:
|
|
139
|
+
|
|
140
|
+
- cache identity
|
|
141
|
+
- invalidation
|
|
142
|
+
- restore behavior
|
|
143
|
+
- persistence strategy
|
|
144
|
+
- SSR-aware hydration behavior
|
|
145
|
+
|
|
146
|
+
Read together with [Model Architecture](/frontend/model-architecture), [Model State Guide](/frontend/model-state-guide), and [Server Data](/frontend/server-data).
|
|
147
|
+
|
|
148
|
+
## 4. Resource Owner: keep resource semantics together
|
|
149
|
+
|
|
150
|
+
A large frontend resource usually needs more than a query call.
|
|
151
|
+
|
|
152
|
+
The frontend often also needs:
|
|
153
|
+
|
|
154
|
+
- schema
|
|
155
|
+
- permissions
|
|
156
|
+
- forms
|
|
157
|
+
- list and item queries
|
|
158
|
+
- create/update/delete mutations
|
|
159
|
+
- invalidation rules
|
|
160
|
+
|
|
161
|
+
If those concerns are split across pages, form helpers, store logic, and query callbacks, the resource boundary becomes hard to explain.
|
|
162
|
+
|
|
163
|
+
Zova's resource-owner direction tries to keep more of that semantic bundle under one stable owner.
|
|
164
|
+
|
|
165
|
+
A useful shorthand is:
|
|
166
|
+
|
|
167
|
+
> the page consumes resource semantics, while the model owns query and resource semantics.
|
|
168
|
+
|
|
169
|
+
That is why the Resource Owner pattern matters in large applications: it improves not only reuse, but also boundary stability.
|
|
170
|
+
|
|
171
|
+
Read together with [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern) and [Using ModelResource in Your Module](/frontend/model-resource-usage-guide).
|
|
172
|
+
|
|
173
|
+
## One comparison table
|
|
174
|
+
|
|
175
|
+
| Dimension | Common large-Vue drift | Zova architectural answer |
|
|
176
|
+
| ------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
177
|
+
| State host | state starts locally, then moves outward case by case | assign ownership first to Controller / Bean / Model |
|
|
178
|
+
| Sharing | sharing range often changes the mechanism | sharing is framed through IoC scopes |
|
|
179
|
+
| Persistence | local, cookie, and async persistence drift into utilities | persistence lives inside model-state families |
|
|
180
|
+
| Cache identity | keys and refresh rules drift across pages and callbacks | model identity and model policy own more of the cache semantics |
|
|
181
|
+
| SSR | request-level and long-lived state can blur together | `app` and `sys` make the distinction structural |
|
|
182
|
+
| Resource semantics | schema, forms, permissions, and invalidation spread across layers | Resource Owner pulls them back under one owner |
|
|
183
|
+
|
|
184
|
+
## When this architecture matters most
|
|
185
|
+
|
|
186
|
+
This architecture is most valuable when the frontend is:
|
|
187
|
+
|
|
188
|
+
- long-lived
|
|
189
|
+
- maintained by multiple developers
|
|
190
|
+
- SSR-aware
|
|
191
|
+
- cache-heavy
|
|
192
|
+
- persistence-heavy
|
|
193
|
+
- resource-heavy
|
|
194
|
+
|
|
195
|
+
For small or short-lived projects, a lighter scattered approach may still be good enough.
|
|
196
|
+
|
|
197
|
+
The point is not that every Vue project must adopt the same architecture.
|
|
198
|
+
The point is that as complexity rises, boundary control becomes more important than adding one more tool.
|
|
199
|
+
|
|
200
|
+
## Recommended reading path
|
|
201
|
+
|
|
202
|
+
If this page matches your question, continue in this order:
|
|
203
|
+
|
|
204
|
+
1. [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
205
|
+
2. [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison)
|
|
206
|
+
3. [IoC and Beans](/frontend/ioc-and-beans)
|
|
207
|
+
4. [Model Architecture](/frontend/model-architecture)
|
|
208
|
+
5. [Model State Guide](/frontend/model-state-guide)
|
|
209
|
+
6. [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
210
|
+
7. [System Startup Guide](/frontend/system-startup-guide)
|
|
211
|
+
|
|
212
|
+
## Final takeaway
|
|
213
|
+
|
|
214
|
+
The most important practical idea is simple:
|
|
215
|
+
|
|
216
|
+
> Large Vue projects usually do not need more state tools first. They need a state architecture that can keep ownership, scope, persistence, cache semantics, SSR behavior, and resource boundaries coherent as the system grows.
|
|
217
|
+
|
|
218
|
+
That is the main reason to read Zova's frontend architecture through Controller / Bean ownership, IoC scopes, Model, and Resource Owner, instead of mapping every state problem back to one more isolated Vue mechanism.
|
|
@@ -167,6 +167,8 @@ The legacy system-start docs used route registration as the clearest example bou
|
|
|
167
167
|
|
|
168
168
|
That distinction is especially important in SSR-capable systems, where app lifecycles can repeat while system-level route and config wiring should not.
|
|
169
169
|
|
|
170
|
+
If your main question is how this `app` versus `sys` lifecycle split participates in the broader state architecture of a large Vue application, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) together with [IoC and Beans](/frontend/ioc-and-beans).
|
|
171
|
+
|
|
170
172
|
A practical reading sequence is:
|
|
171
173
|
|
|
172
174
|
1. [Environment and Config Guide](/frontend/environment-config-guide)
|
|
@@ -12,6 +12,7 @@ The goal is to explain two things clearly:
|
|
|
12
12
|
Use this page together with:
|
|
13
13
|
|
|
14
14
|
- [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
15
|
+
- [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers)
|
|
15
16
|
- [Zova Reactivity Under the Hood](/frontend/zova-reactivity-under-the-hood)
|
|
16
17
|
- [Zova Source Reading Map](/frontend/zova-source-reading-map)
|
|
17
18
|
|
|
@@ -40,16 +41,16 @@ The big difference is the **business-facing programming model**.
|
|
|
40
41
|
|
|
41
42
|
## Side-by-side comparison table
|
|
42
43
|
|
|
43
|
-
| Dimension
|
|
44
|
-
|
|
|
45
|
-
| Reactive host
|
|
46
|
-
| Derived state
|
|
47
|
-
| Main wiring surface
|
|
48
|
-
| Route state access
|
|
49
|
-
| Sharing model
|
|
50
|
-
| Render organization
|
|
51
|
-
| Growth path
|
|
52
|
-
| Architectural emphasis | explicit object roles, scope ownership, and runtime-managed surfaces
|
|
44
|
+
| Dimension | Zova | Vue 3 default mental model |
|
|
45
|
+
| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
46
|
+
| Reactive host | controller or bean instance | local `ref` / `reactive` values inside `setup()` |
|
|
47
|
+
| Derived state | `$computed()` often assigned to instance fields | `computed()` usually assigned to local variables |
|
|
48
|
+
| Main wiring surface | `__init__` and bean lifecycle | `setup()` and composition hooks |
|
|
49
|
+
| Route state access | page-controller surface such as `$route`, `$params`, `$query` | composables such as `useRoute()` |
|
|
50
|
+
| Sharing model | IoC containers and bean scopes unify more sharing patterns | composables, props/emits, `provide/inject`, and store layers often coexist |
|
|
51
|
+
| Render organization | controller-oriented; render can stay in controller or move to render beans | component-oriented; template/render/setup stay the obvious center |
|
|
52
|
+
| Growth path | split into controller, render, style, service, model, or other beans | split into composables, child components, and store or helper layers |
|
|
53
|
+
| Architectural emphasis | explicit object roles, scope ownership, and runtime-managed surfaces | flexible local composition with more developer-managed assembly |
|
|
53
54
|
|
|
54
55
|
## 1. Reactive host
|
|
55
56
|
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Fullstack Image Workflow
|
|
2
|
+
|
|
3
|
+
This page explains the fullstack image workflow in Cabloy Basic with a practical, contract-chain path.
|
|
4
|
+
|
|
5
|
+
In this page, **resolved relation field** means the DTO-side field such as `image` or `sceneImages` that carries `DtoImageView` data for frontend preview and display.
|
|
6
|
+
|
|
7
|
+
It fills the gap between the layer-specific image guides:
|
|
8
|
+
|
|
9
|
+
- [Backend Image Guide](/backend/image-guide)
|
|
10
|
+
- [Frontend Image Guide](/frontend/image-guide)
|
|
11
|
+
|
|
12
|
+
Use this page when the real question is not only backend image policy and not only frontend image rendering, but the bridge between the two.
|
|
13
|
+
|
|
14
|
+
Use this page together with:
|
|
15
|
+
|
|
16
|
+
- [Contract Loop Playbook](/fullstack/contract-loop-playbook)
|
|
17
|
+
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
18
|
+
- [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
|
|
19
|
+
- [Backend Image Guide](/backend/image-guide)
|
|
20
|
+
- [Frontend Image Guide](/frontend/image-guide)
|
|
21
|
+
- [Upload Guide](/backend/upload-guide)
|
|
22
|
+
- [Serialization Guide](/backend/serialization-guide)
|
|
23
|
+
- [Form Guide](/frontend/form-guide)
|
|
24
|
+
- [Table Guide](/frontend/table-guide)
|
|
25
|
+
|
|
26
|
+
> [!TIP]
|
|
27
|
+
> **Image workflow docs path**
|
|
28
|
+
>
|
|
29
|
+
> - **[Fullstack Image Workflow](/fullstack/image-workflow)** — learn the cross-stack chain from backend image field contract and scene policy to frontend `basic-image` consumption
|
|
30
|
+
> - **[Backend Image Guide](/backend/image-guide)** — learn image scenes, providers, serializer transforms, delivery policy, and backend lifecycle behavior
|
|
31
|
+
> - **[Frontend Image Guide](/frontend/image-guide)** — learn `basic-image:formFieldImage`, `basic-image:image`, relation previews, and client-side image-field behavior
|
|
32
|
+
>
|
|
33
|
+
> Use this page when the question is about how one image field travels across the full stack rather than only one layer.
|
|
34
|
+
|
|
35
|
+
## Why this page exists
|
|
36
|
+
|
|
37
|
+
The repository now has a backend image guide and a frontend image guide.
|
|
38
|
+
|
|
39
|
+
What contributors and AI workflows often still need is one continuous answer to this narrower question:
|
|
40
|
+
|
|
41
|
+
> how does one image field travel from backend entity and DTO metadata to frontend form and table rendering?
|
|
42
|
+
|
|
43
|
+
This page answers that question by treating image handling as one contract chain rather than as disconnected snippets.
|
|
44
|
+
|
|
45
|
+
## The shortest correct mental model
|
|
46
|
+
|
|
47
|
+
If you only remember one idea, remember this one:
|
|
48
|
+
|
|
49
|
+
> backend code owns the image field contract, image scene policy, and DTO-side image view resolution, while frontend `basic-image` resources consume that resolved contract for upload, preview, and display.
|
|
50
|
+
|
|
51
|
+
That means an image field usually has two connected surfaces:
|
|
52
|
+
|
|
53
|
+
- a stored identity field such as `imageId` or `sceneImageIds`
|
|
54
|
+
- a resolved relation field such as `image` or `sceneImages`
|
|
55
|
+
|
|
56
|
+
The fullstack workflow works best when both surfaces are authored deliberately and kept aligned.
|
|
57
|
+
|
|
58
|
+
## The fullstack chain in one view
|
|
59
|
+
|
|
60
|
+
A practical image workflow usually looks like this:
|
|
61
|
+
|
|
62
|
+
1. the backend entity stores image identity data such as `imageId` or `sceneImageIds`
|
|
63
|
+
2. backend field metadata points to the frontend image resources with `ZovaRender.field(...)` and `ZovaRender.cell(...)`
|
|
64
|
+
3. the backend image scene defines upload policy such as MIME type, size, and multiple-image rules
|
|
65
|
+
4. the backend DTO resolves image IDs into `DtoImageView` or `DtoImageView[]` through `a-image:resolveView` or `a-image:resolveViews`
|
|
66
|
+
5. the shared contract exposes both the stored ID field and the resolved relation field to frontend consumers
|
|
67
|
+
6. frontend `basic-image:formFieldImage` binds the stored ID field while reusing the resolved relation field for preview cards and dialogs
|
|
68
|
+
7. frontend `basic-image:image` uses the same resolved relation field to render thumbnails in list pages
|
|
69
|
+
|
|
70
|
+
This is not only a forward chain and not only a reverse chain.
|
|
71
|
+
|
|
72
|
+
Instead, it is one concrete business thread where backend contract truth, shared metadata, and frontend resource consumption cooperate.
|
|
73
|
+
|
|
74
|
+
For the broader decision model, read [Contract Loop Playbook](/fullstack/contract-loop-playbook).
|
|
75
|
+
|
|
76
|
+
## Specimen A: single image on Student
|
|
77
|
+
|
|
78
|
+
The Student resource is the best single-image specimen because it shows the complete chain with the smallest number of moving parts.
|
|
79
|
+
|
|
80
|
+
### Backend entity field
|
|
81
|
+
|
|
82
|
+
The Student entity stores one image identity field and points to the built-in image resources:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
@Api.field(
|
|
86
|
+
v.title($locale('StudentImage')),
|
|
87
|
+
v.optional(),
|
|
88
|
+
ZovaRender.field('basic-image:formFieldImage', {
|
|
89
|
+
imageScene: 'training-student:studentImage',
|
|
90
|
+
relationName: 'image',
|
|
91
|
+
accept: ['image/png', 'image/jpeg', 'image/webp'],
|
|
92
|
+
maxSize: 2 * 1024 * 1024,
|
|
93
|
+
enableCrop: true,
|
|
94
|
+
cropAspectRatio: 1,
|
|
95
|
+
resize: {
|
|
96
|
+
width: 512,
|
|
97
|
+
height: 512,
|
|
98
|
+
fit: 'cover',
|
|
99
|
+
format: 'jpeg',
|
|
100
|
+
quality: 90,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
ZovaRender.cell('basic-image:image', { relationName: 'image' }),
|
|
104
|
+
v.tableIdentity(),
|
|
105
|
+
)
|
|
106
|
+
imageId?: TableIdentity;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This already defines two important truths:
|
|
110
|
+
|
|
111
|
+
- the stored field is `imageId`
|
|
112
|
+
- the frontend resource identities are `basic-image:formFieldImage` and `basic-image:image`
|
|
113
|
+
|
|
114
|
+
### Backend image scene
|
|
115
|
+
|
|
116
|
+
The Student image scene defines the upload policy:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
@ImageScene({
|
|
120
|
+
upload: {
|
|
121
|
+
maxSize: 2 * 1024 * 1024,
|
|
122
|
+
mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
export class ImageSceneStudentImage extends BeanBase {}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This means the backend owns the true upload policy even when the frontend also performs convenience validation.
|
|
129
|
+
|
|
130
|
+
### DTO-side image resolution
|
|
131
|
+
|
|
132
|
+
The Student DTO resolves the stored ID into a resolved relation field:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
@Api.field(
|
|
136
|
+
ZovaRender.visible(false),
|
|
137
|
+
v.optional(),
|
|
138
|
+
v.serializerTransform('a-image:resolveView', {
|
|
139
|
+
fieldName: 'imageId',
|
|
140
|
+
imageScene: 'training-student:studentImage',
|
|
141
|
+
}),
|
|
142
|
+
v.object(DtoImageView),
|
|
143
|
+
)
|
|
144
|
+
image?: DtoImageView;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This is the bridge that turns:
|
|
148
|
+
|
|
149
|
+
- stored value: `imageId`
|
|
150
|
+
- resolved preview data: `image?: DtoImageView`
|
|
151
|
+
|
|
152
|
+
The same list-page branch is also visible in `DtoStudentSelectResItem`, where the row DTO resolves `image` for table usage.
|
|
153
|
+
|
|
154
|
+
### Frontend consumption
|
|
155
|
+
|
|
156
|
+
On the frontend side, the built-in resources consume that contract shape:
|
|
157
|
+
|
|
158
|
+
- `basic-image:formFieldImage` binds the ID field and reuses the `image` resolved relation field for preview cards
|
|
159
|
+
- `basic-image:image` uses the same resolved relation field to render thumbnails in tables
|
|
160
|
+
|
|
161
|
+
This is the cleanest single-image chain in the repo.
|
|
162
|
+
|
|
163
|
+
## Specimen B: multiple images on Record
|
|
164
|
+
|
|
165
|
+
The Record resource shows the plural branch.
|
|
166
|
+
|
|
167
|
+
### Backend entity field
|
|
168
|
+
|
|
169
|
+
The Record entity stores an array of image IDs and points to the same built-in resources:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
@Api.field(
|
|
173
|
+
v.title($locale('ScenePhotos')),
|
|
174
|
+
v.optional(),
|
|
175
|
+
ZovaRender.field('basic-image:formFieldImage', {
|
|
176
|
+
imageScene: 'training-record:sceneImage',
|
|
177
|
+
multiple: true,
|
|
178
|
+
maxCount: 9,
|
|
179
|
+
relationName: 'sceneImages',
|
|
180
|
+
accept: ['image/png', 'image/jpeg', 'image/webp'],
|
|
181
|
+
maxSize: 2 * 1024 * 1024,
|
|
182
|
+
enableCrop: false,
|
|
183
|
+
resize: {
|
|
184
|
+
width: 1280,
|
|
185
|
+
height: 1280,
|
|
186
|
+
fit: 'contain',
|
|
187
|
+
format: 'jpeg',
|
|
188
|
+
quality: 90,
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
ZovaRender.cell('basic-image:image', { relationName: 'sceneImages' }),
|
|
192
|
+
v.array(v.tableIdentity()),
|
|
193
|
+
)
|
|
194
|
+
sceneImageIds?: TableIdentity[];
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Backend image scene
|
|
198
|
+
|
|
199
|
+
The matching scene enables multi-image upload policy:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
@ImageScene({
|
|
203
|
+
upload: {
|
|
204
|
+
maxSize: 2 * 1024 * 1024,
|
|
205
|
+
mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
206
|
+
multiple: true,
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
export class ImageSceneSceneImage extends BeanBase {}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### DTO-side image resolution
|
|
213
|
+
|
|
214
|
+
The DTO resolves many IDs into many preview objects:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
@Api.field(
|
|
218
|
+
ZovaRender.visible(false),
|
|
219
|
+
v.optional(),
|
|
220
|
+
v.serializerTransform('a-image:resolveViews', {
|
|
221
|
+
fieldName: 'sceneImageIds',
|
|
222
|
+
imageScene: 'training-record:sceneImage',
|
|
223
|
+
}),
|
|
224
|
+
v.array(DtoImageView),
|
|
225
|
+
)
|
|
226
|
+
sceneImages?: DtoImageView[];
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The same pattern also appears in `DtoRecordSelectResItem`, which makes the relation data available for list-page thumbnails.
|
|
230
|
+
|
|
231
|
+
### Why this branch matters
|
|
232
|
+
|
|
233
|
+
This branch proves that the same workflow scales from:
|
|
234
|
+
|
|
235
|
+
- one ID -> one relation object
|
|
236
|
+
- many IDs -> many relation objects
|
|
237
|
+
|
|
238
|
+
without changing the overall contract-chain model.
|
|
239
|
+
|
|
240
|
+
## Backend authoring responsibilities
|
|
241
|
+
|
|
242
|
+
The backend owns the contract truth.
|
|
243
|
+
|
|
244
|
+
For image workflows, that usually means:
|
|
245
|
+
|
|
246
|
+
- the entity field defines the stored data shape
|
|
247
|
+
- `ZovaRender.field(...)` chooses the form-side frontend resource identity
|
|
248
|
+
- `ZovaRender.cell(...)` chooses the table-side frontend resource identity
|
|
249
|
+
- the image scene bean defines upload policy
|
|
250
|
+
- the DTO serializer transform resolves stored IDs into preview-ready image view objects
|
|
251
|
+
|
|
252
|
+
A practical rule is:
|
|
253
|
+
|
|
254
|
+
- when the business meaning of the image field changes, start by changing backend truth first
|
|
255
|
+
|
|
256
|
+
For the backend-authoring details, read [Backend Image Guide](/backend/image-guide).
|
|
257
|
+
|
|
258
|
+
## What crosses the contract boundary
|
|
259
|
+
|
|
260
|
+
The most important thing that crosses the fullstack boundary is not only the raw image ID.
|
|
261
|
+
|
|
262
|
+
What really crosses the boundary is the pairing of:
|
|
263
|
+
|
|
264
|
+
- stored identity field
|
|
265
|
+
- resolved preview field
|
|
266
|
+
|
|
267
|
+
In the single-image branch, that pair is usually:
|
|
268
|
+
|
|
269
|
+
- `imageId`
|
|
270
|
+
- `image?: DtoImageView`
|
|
271
|
+
|
|
272
|
+
In the multi-image branch, that pair is usually:
|
|
273
|
+
|
|
274
|
+
- `sceneImageIds`
|
|
275
|
+
- `sceneImages?: DtoImageView[]`
|
|
276
|
+
|
|
277
|
+
This is why the DTO serializer step matters so much.
|
|
278
|
+
|
|
279
|
+
It turns backend-owned identity data into frontend-friendly preview data without changing the persistence model.
|
|
280
|
+
|
|
281
|
+
## Frontend consumption responsibilities
|
|
282
|
+
|
|
283
|
+
The frontend does not redefine the image field contract.
|
|
284
|
+
|
|
285
|
+
Instead, it consumes the contract through the built-in image resources.
|
|
286
|
+
|
|
287
|
+
### `basic-image:formFieldImage`
|
|
288
|
+
|
|
289
|
+
The form-side resource is responsible for:
|
|
290
|
+
|
|
291
|
+
- selecting files
|
|
292
|
+
- performing frontend validation
|
|
293
|
+
- optionally cropping and resizing in the browser
|
|
294
|
+
- requesting an upload token
|
|
295
|
+
- uploading multipart data
|
|
296
|
+
- binding the stored image ID field
|
|
297
|
+
- synchronizing the resolved relation field for immediate UI refresh
|
|
298
|
+
|
|
299
|
+
### `basic-image:image`
|
|
300
|
+
|
|
301
|
+
The table-side resource is responsible for:
|
|
302
|
+
|
|
303
|
+
- resolving relation-backed preview data first
|
|
304
|
+
- falling back to URL-style values when needed
|
|
305
|
+
- rendering thumbnails
|
|
306
|
+
- opening the shared preview dialog
|
|
307
|
+
|
|
308
|
+
### Why `relationName` matters
|
|
309
|
+
|
|
310
|
+
The built-in frontend image resources work best when the resolved relation field is named predictably.
|
|
311
|
+
|
|
312
|
+
Common pairings are:
|
|
313
|
+
|
|
314
|
+
- `imageId` -> `image`
|
|
315
|
+
- `sceneImageIds` -> `sceneImages`
|
|
316
|
+
|
|
317
|
+
When your field names are nonstandard, set `relationName` explicitly so backend DTO resolution and frontend preview lookup stay aligned.
|
|
318
|
+
|
|
319
|
+
For the frontend runtime details, read [Frontend Image Guide](/frontend/image-guide).
|
|
320
|
+
|
|
321
|
+
## Single-image vs multi-image differences
|
|
322
|
+
|
|
323
|
+
The contract chain stays the same, but three details change.
|
|
324
|
+
|
|
325
|
+
### 1. Stored value shape
|
|
326
|
+
|
|
327
|
+
- single image: scalar ID such as `imageId`
|
|
328
|
+
- multiple images: ID array such as `sceneImageIds`
|
|
329
|
+
|
|
330
|
+
### 2. DTO serializer transform
|
|
331
|
+
|
|
332
|
+
- single image: `a-image:resolveView`
|
|
333
|
+
- multiple images: `a-image:resolveViews`
|
|
334
|
+
|
|
335
|
+
### 3. Relation preview shape
|
|
336
|
+
|
|
337
|
+
- single image: one `DtoImageView`
|
|
338
|
+
- multiple images: `DtoImageView[]`
|
|
339
|
+
|
|
340
|
+
That is the main branching point the fullstack doc should make explicit.
|
|
341
|
+
|
|
342
|
+
## Where this page sits in the contract loop
|
|
343
|
+
|
|
344
|
+
This workflow is easiest to understand when you keep the contract-loop vocabulary precise.
|
|
345
|
+
|
|
346
|
+
- backend field metadata and DTO/image-scene changes belong to backend contract truth
|
|
347
|
+
- image DTO resolution is part of the backend-authored handoff
|
|
348
|
+
- frontend `basic-image` resources are consumers of that truth
|
|
349
|
+
- frontend-specific render behavior is not a reason to duplicate the backend contract
|
|
350
|
+
|
|
351
|
+
That is why this page sits between:
|
|
352
|
+
|
|
353
|
+
- [Contract Loop Playbook](/fullstack/contract-loop-playbook)
|
|
354
|
+
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
355
|
+
- [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
|
|
356
|
+
|
|
357
|
+
It is a bridge page for one concrete business thread rather than a general direction guide.
|
|
358
|
+
|
|
359
|
+
## Verification workflow
|
|
360
|
+
|
|
361
|
+
When writing or changing image workflow docs, verify in this order.
|
|
362
|
+
|
|
363
|
+
1. confirm the entity field still points to:
|
|
364
|
+
- `basic-image:formFieldImage`
|
|
365
|
+
- `basic-image:image`
|
|
366
|
+
2. confirm the image scene still matches the specimen field
|
|
367
|
+
3. confirm the DTO still uses:
|
|
368
|
+
- `a-image:resolveView` for single-image fields
|
|
369
|
+
- `a-image:resolveViews` for multi-image fields
|
|
370
|
+
4. confirm the list-row DTOs still expose the same resolved relation fields for table usage
|
|
371
|
+
5. confirm the frontend image resources still consume the same relation-name pattern
|
|
372
|
+
6. confirm the page stays bridge-focused rather than duplicating the backend or frontend guides
|
|
373
|
+
7. run the docs build and confirm the page appears under `Fullstack / Architecture & Integration`
|
|
374
|
+
|
|
375
|
+
## Read together with
|
|
376
|
+
|
|
377
|
+
Use the surrounding docs based on which layer now needs deeper attention.
|
|
378
|
+
|
|
379
|
+
### Backend policy and authoring details
|
|
380
|
+
|
|
381
|
+
- [Backend Image Guide](/backend/image-guide)
|
|
382
|
+
- [Upload Guide](/backend/upload-guide)
|
|
383
|
+
- [Serialization Guide](/backend/serialization-guide)
|
|
384
|
+
- [Controller Guide](/backend/controller-guide)
|
|
385
|
+
|
|
386
|
+
### Frontend runtime and rendering details
|
|
387
|
+
|
|
388
|
+
- [Frontend Image Guide](/frontend/image-guide)
|
|
389
|
+
- [Form Guide](/frontend/form-guide)
|
|
390
|
+
- [Table Guide](/frontend/table-guide)
|
|
391
|
+
- [TableCell Authoring Cookbook](/frontend/table-cell-cookbook)
|
|
392
|
+
|
|
393
|
+
### Broader fullstack bridge and contract-loop decisions
|
|
394
|
+
|
|
395
|
+
- [Contract Loop Playbook](/fullstack/contract-loop-playbook)
|
|
396
|
+
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
397
|
+
- [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
|
|
398
|
+
|
|
399
|
+
A good final question is:
|
|
400
|
+
|
|
401
|
+
- am I debugging one layer, or am I debugging the handoff between layers?
|
|
402
|
+
|
|
403
|
+
If the answer is the handoff, this page is the right place to start.
|
|
@@ -46,6 +46,7 @@ Use this path when the task is about how backend and frontend stay aligned insid
|
|
|
46
46
|
- [Vona + Zova Integration](/fullstack/vona-zova-integration)
|
|
47
47
|
- [Contract Loop Playbook](/fullstack/contract-loop-playbook)
|
|
48
48
|
- [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
|
|
49
|
+
- [Fullstack Image Workflow](/fullstack/image-workflow)
|
|
49
50
|
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
50
51
|
- [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend)
|
|
51
52
|
|