cabloy 5.1.207 → 5.1.209
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 +13 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +9 -9
- package/repo-agent-governance/policies/repository.md +1 -1
- package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +4 -2
- package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +13 -1
- package/repo-docs/backend/unit-testing.md +1 -1
- package/repo-docs/frontend/form-guide.md +17 -1
- package/repo-docs/frontend/model-architecture.md +4 -0
- package/repo-docs/frontend/model-state-guide.md +23 -2
- package/repo-docs/frontend/resource-entry-page-deep-dive.md +119 -0
- package/repo-docs/frontend/scripts.md +3 -3
- package/repo-docs/frontend/zova-form-under-the-hood.md +5 -0
- package/repo-docs/fullstack/parallel-worktree-environment.md +3 -3
- package/repo-docs/fullstack/quickstart.md +11 -1
- package/repo-docs/reference/repo-scripts.md +19 -5
- package/repo-e2e/specs/cabloy-basic.spec.ts +1577 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-home/modules/home-user/src/controller/passport.ts +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/dto/passport.ts +4 -0
- package/vona/src/suite/a-home/modules/home-user/test/passport.test.ts +172 -0
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/rest.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
- package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +9 -0
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +17 -17
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +68 -52
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +524 -343
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +45 -11
- package/zova/src/suite/a-home/modules/home-passport/test/lib/persistenceIdentity.test.ts +229 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/package.json +9 -1
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/.metadata/index.ts +81 -5
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/bean/bean.draftCoordinator.ts +158 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +11 -3
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +438 -8
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/config.ts +11 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/en-us.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/zh-cn.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/lib/draftSchema.ts +649 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/model/draft.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/monkey.ts +11 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/draft.ts +49 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftDate.test.ts +262 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLifecycle.test.ts +1073 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLoader.mjs +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftModel.test.ts +572 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchema.test.ts +427 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchemaSafety.test.ts +200 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +6 -1
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +86 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +88 -13
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/schemaRefresh.ts +751 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefresh.test.ts +637 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefreshLifecycle.test.ts +994 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +331 -87
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts +11 -6
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +17 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/modelPersistence.ts +204 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/persistenceQueue.ts +50 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.ts +5 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/service/storage.ts +47 -2
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +3 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelLoader.mjs +34 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelPersistence.test.ts +727 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceQueue.test.ts +137 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceTransition.test.ts +129 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/monkey.ts +7 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/bean.router.ts +14 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/lib/routeDiscard.ts +26 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/service/routerGuards.ts +6 -3
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/routeDiscard.ts +24 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/test/lib/routeDiscard.test.ts +105 -0
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +202 -30
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabs.test.ts +524 -0
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabsLoader.mjs +35 -0
- package/zova/src/suite-vendor/a-zova/package.json +6 -6
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.209
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.209
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add draft recovery.
|
|
8
|
+
|
|
9
|
+
## 5.1.208
|
|
10
|
+
|
|
11
|
+
### Improvements
|
|
12
|
+
|
|
13
|
+
- Update the README and quickstart documentation.
|
|
14
|
+
- Update the changelog for version 5.1.207.
|
|
15
|
+
|
|
3
16
|
## 5.1.207
|
|
4
17
|
|
|
5
18
|
### Features
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
{
|
|
257
257
|
"adapter": "codex",
|
|
258
258
|
"category": "skill",
|
|
259
|
-
"sha256": "
|
|
259
|
+
"sha256": "900d9c74bd44f746990046c3ea142d653da740b156a648c573992134b5cc1b9f",
|
|
260
260
|
"source": "skills/cabloy-workflow/evals/evals.json",
|
|
261
261
|
"target": ".agents/skills/cabloy-workflow/evals/evals.json"
|
|
262
262
|
},
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
{
|
|
278
278
|
"adapter": "codex",
|
|
279
279
|
"category": "skill",
|
|
280
|
-
"sha256": "
|
|
280
|
+
"sha256": "b9414b99bc7ad83d4db2dccfd14e9dfa5281bdd78dd2df524080129dd032b750",
|
|
281
281
|
"source": "skills/cabloy-workflow/SKILL.md",
|
|
282
282
|
"target": ".agents/skills/cabloy-workflow/SKILL.md"
|
|
283
283
|
},
|
|
@@ -592,7 +592,7 @@
|
|
|
592
592
|
{
|
|
593
593
|
"adapter": "claude",
|
|
594
594
|
"category": "skill",
|
|
595
|
-
"sha256": "
|
|
595
|
+
"sha256": "900d9c74bd44f746990046c3ea142d653da740b156a648c573992134b5cc1b9f",
|
|
596
596
|
"source": "skills/cabloy-workflow/evals/evals.json",
|
|
597
597
|
"target": ".claude/skills/cabloy-workflow/evals/evals.json"
|
|
598
598
|
},
|
|
@@ -613,7 +613,7 @@
|
|
|
613
613
|
{
|
|
614
614
|
"adapter": "claude",
|
|
615
615
|
"category": "skill",
|
|
616
|
-
"sha256": "
|
|
616
|
+
"sha256": "b9414b99bc7ad83d4db2dccfd14e9dfa5281bdd78dd2df524080129dd032b750",
|
|
617
617
|
"source": "skills/cabloy-workflow/SKILL.md",
|
|
618
618
|
"target": ".claude/skills/cabloy-workflow/SKILL.md"
|
|
619
619
|
},
|
|
@@ -683,7 +683,7 @@
|
|
|
683
683
|
{
|
|
684
684
|
"adapter": "cursor",
|
|
685
685
|
"category": "policy",
|
|
686
|
-
"sha256": "
|
|
686
|
+
"sha256": "2948516f964a13242178b4f5969756d469f60cc1766616a53e899c5551cf443d",
|
|
687
687
|
"source": "policies/repository.md",
|
|
688
688
|
"target": ".cursor/rules/cabloy-governance.mdc"
|
|
689
689
|
},
|
|
@@ -942,7 +942,7 @@
|
|
|
942
942
|
{
|
|
943
943
|
"adapter": "cursor",
|
|
944
944
|
"category": "skill",
|
|
945
|
-
"sha256": "
|
|
945
|
+
"sha256": "900d9c74bd44f746990046c3ea142d653da740b156a648c573992134b5cc1b9f",
|
|
946
946
|
"source": "skills/cabloy-workflow/evals/evals.json",
|
|
947
947
|
"target": ".cursor/skills/cabloy-workflow/evals/evals.json"
|
|
948
948
|
},
|
|
@@ -963,7 +963,7 @@
|
|
|
963
963
|
{
|
|
964
964
|
"adapter": "cursor",
|
|
965
965
|
"category": "skill",
|
|
966
|
-
"sha256": "
|
|
966
|
+
"sha256": "b9414b99bc7ad83d4db2dccfd14e9dfa5281bdd78dd2df524080129dd032b750",
|
|
967
967
|
"source": "skills/cabloy-workflow/SKILL.md",
|
|
968
968
|
"target": ".cursor/skills/cabloy-workflow/SKILL.md"
|
|
969
969
|
},
|
|
@@ -1005,14 +1005,14 @@
|
|
|
1005
1005
|
{
|
|
1006
1006
|
"adapter": "codex",
|
|
1007
1007
|
"category": "policy",
|
|
1008
|
-
"sha256": "
|
|
1008
|
+
"sha256": "f4f6c2584bd58846e315ecf2c0772a47735ef0d1f24a72213d2d6ea13673db71",
|
|
1009
1009
|
"source": "policies/repository.md",
|
|
1010
1010
|
"target": "AGENTS.md"
|
|
1011
1011
|
},
|
|
1012
1012
|
{
|
|
1013
1013
|
"adapter": "claude",
|
|
1014
1014
|
"category": "policy",
|
|
1015
|
-
"sha256": "
|
|
1015
|
+
"sha256": "f4f6c2584bd58846e315ecf2c0772a47735ef0d1f24a72213d2d6ea13673db71",
|
|
1016
1016
|
"source": "policies/repository.md",
|
|
1017
1017
|
"target": "CLAUDE.md"
|
|
1018
1018
|
}
|
|
@@ -71,7 +71,7 @@ Before inventing a custom implementation path:
|
|
|
71
71
|
- Treat hooks, permissions, local settings, credentials, MCP registration, command discovery, and automatic command execution as platform-specific integrations, not portable rule or skill capabilities.
|
|
72
72
|
- After implementation, do not launch broad reviews automatically; when review is needed, scope it to the current diff and report only high-confidence issues.
|
|
73
73
|
- Do not modify shared environment identity or ports merely to bypass a busy resource. For intentional parallel worktree setup, follow `repo-docs/fullstack/parallel-worktree-environment.md`; otherwise wait for the shared resource or ask the user. Create or change worktree-local environment overrides only through the explicitly invoked `cabloy-worktree-environment` skill and its confirmation phase, and only in `vona/env/.env.local` and `zova/env/.env.local`; never modify flavor-, mode-, app-mode-, or runtime-specific `.env.*.local` files. The skill derives the standard `APP_NAME`, `SERVER_LISTEN_PORT`, `DEV_SERVER_PORT`, `DEV_SERVER_HMR_PORT`, and API-derived `API_BASE_URL` tuple only from Git worktree metadata and fixed port baselines; never read or expose `.env*` content while recommending values. Admin and Web are alternative commands using this shared tuple and must not run concurrently in one worktree; use another linked worktree for concurrent use. Detect the active edition before choosing scripts, and never run `npm run init` as an automatic follow-up.
|
|
74
|
-
- In Cabloy Basic, root `npm run test` and `npm run test:e2e`
|
|
74
|
+
- In Cabloy Basic and Cabloy Start, root `npm run test` and `npm run test:e2e` allocate an isolated same-checkout test run: disposable run-specific database and Redis namespaces, runtime/public paths, and listener. Ordinary same-checkout dev-process presence alone is not a blocker; do not require dev shutdown, another worktree, or environment changes solely to run these tests. Judge conflicts by actual overlap with effective test resources or shared preparation writes, not process names. Builds and dependency installation/generation remain shared prerequisites; coordinate overlapping writes, and check custom external resources separately. This guarantee does not cover raw test commands or authorize manual resets, process killing, or shared configuration changes. See `repo-docs/reference/repo-scripts.md`.
|
|
75
75
|
- For isolated E2E through Vona integrated SSR, do not edit Zova `API_BASE_URL` environment values or rebuild solely to match the test port: Vona supplies runtime server/browser API origins. On an origin mismatch, check explicit SSR Site and module-specific API URL overrides first; follow the active repository's scripts for changed SSR/REST artifacts and dependency synchronization. Do not generalize this handoff to Zova standalone SSR or SPA. See [Runtime API origins](repo-docs/fullstack/ssr-entry-modes.md#runtime-api-origins-in-vona-integrated-ssr).
|
|
76
76
|
- 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.
|
|
77
77
|
- 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.
|
|
@@ -105,9 +105,11 @@ For deeper reference material, read:
|
|
|
105
105
|
|
|
106
106
|
The reason is simple: these files are where Cabloy already encodes its real workflows.
|
|
107
107
|
|
|
108
|
-
###
|
|
108
|
+
### Isolated tests versus parallel worktree environment setup
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
In Cabloy Basic and Cabloy Start, root `npm run test` and `npm run test:e2e` isolate their runtime and framework-managed data from ordinary dev in the same checkout. A running Vona or Zova dev process alone is not a blocker and does not require dev shutdown, another worktree, or `/cabloy-worktree-environment`. For tests-only requests, use the active edition's root scripts and the [test-isolation guidance](../../../repo-docs/reference/repo-scripts.md#test-isolation-and-development-data); do not route them into environment setup. Check actual overlap with runner-owned test resources, coordinate shared build/dependency writes, and assess custom external resources separately. Process names alone do not establish a conflict.
|
|
111
|
+
|
|
112
|
+
When a request actually involves a second ordinary development runtime or worktree-local environment configuration, classify it as fullstack workflow setup. Read [Parallel Worktree Environment](../../../repo-docs/fullstack/parallel-worktree-environment.md), the canonical shared recipe maintained in Cabloy Basic for both editions.
|
|
111
113
|
|
|
112
114
|
This routing skill provides read-only guidance only. Do not infer an `APP_NAME` or ports, edit local overrides, or run `npm run init` from a generic worktree request. When the user wants confirmation-gated local environment setup, ask them to explicitly invoke `/cabloy-worktree-environment`.
|
|
113
115
|
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
{
|
|
47
47
|
"id": 8,
|
|
48
48
|
"prompt": "I created a linked Cabloy Basic worktree and only need backend tests. What should I do for local environment isolation?",
|
|
49
|
-
"expected_output": "
|
|
49
|
+
"expected_output": "Recognizes that Basic root npm run test already allocates isolated runtime and framework-managed data, including in a linked worktree. Does not require /cabloy-worktree-environment, local environment changes, another worktree, or dev shutdown for tests alone. Points to Repo Scripts, checks actual test-resource overlap and custom external resources, and coordinates any shared build/dependency preparation writes.",
|
|
50
50
|
"files": []
|
|
51
51
|
},
|
|
52
52
|
{
|
|
@@ -54,6 +54,18 @@
|
|
|
54
54
|
"prompt": "I am in a Cabloy checkout where repo-docs-internal exists, but the named architecture record I expected is not available in this edition. I need shared agent guidance and some maintainer rationale. Where should each part go?",
|
|
55
55
|
"expected_output": "Recognizes repo-docs-internal as the internal documentation home, routes shared operational guidance to public docs and Claude rules or skills, and treats the missing named record as non-blocking rather than inventing a replacement or moving rationale into public docs automatically.",
|
|
56
56
|
"files": []
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": 10,
|
|
60
|
+
"prompt": "Vona dev is running in this Cabloy Basic checkout. Can I run root npm run test:e2e here, or must I stop dev or create another worktree?",
|
|
61
|
+
"expected_output": "Explains that root managed E2E allocates run-owned database and Redis namespaces, runtime/public paths, and a listener, so ordinary same-checkout dev presence alone is not a blocker. Does not stop dev, change ports/env, request a new worktree, or invoke environment setup. Coordinates actual shared build/dependency writes and checks real test-resource or custom external-resource overlap without treating process names as proof. Does not extend isolation to raw test commands or infer reset/cleanup authorization.",
|
|
62
|
+
"files": []
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": 11,
|
|
66
|
+
"prompt": "In Cabloy Start, Vona dev and Zova Web dev are already active. I only need root npm run test and npm run test:e2e. Is their presence an isolation conflict?",
|
|
67
|
+
"expected_output": "Applies the same root managed-test isolation guarantee as Cabloy Basic while selecting Start's actual scripts and artifact prerequisites. Ordinary dev presence alone is not a conflict and does not require another worktree, dev shutdown, or environment changes. Distinguishes actual test-owned resource collisions, shared preparation writes, and custom external resources; preserves ownership and authorization checks.",
|
|
68
|
+
"files": []
|
|
57
69
|
}
|
|
58
70
|
]
|
|
59
71
|
}
|
|
@@ -53,7 +53,7 @@ A typical Vona test flow includes:
|
|
|
53
53
|
5. run the test files
|
|
54
54
|
6. close the shared application after the test run
|
|
55
55
|
|
|
56
|
-
In Cabloy Basic, root `npm run test` delegates to `vona/`'s `npm run test`; both allocate an isolated test identity, database and Redis namespace, Vona generated/runtime/public paths, and loopback listener. They can run in the same checkout as an ordinary development server without resetting or mutating that server's business data. Even a targeted run performs the test-mode setup. The Vona `npm run cov` script uses the same isolation; raw `:bin:test` commands do not. Shared build/dependency preparation may still write to shared artifacts, and custom external resources need separate isolation. See [Repo Scripts](/reference/repo-scripts#test-isolation-and-development-data
|
|
56
|
+
In Cabloy Basic and Cabloy Start, root `npm run test` delegates to `vona/`'s `npm run test`; both allocate an isolated test identity, database and Redis namespace, Vona generated/runtime/public paths, and loopback listener. They can run in the same checkout as an ordinary development server without resetting or mutating that server's business data. Even a targeted run performs the test-mode setup. The Vona `npm run cov` script uses the same isolation; raw `:bin:test` commands do not. Shared build/dependency preparation may still write to shared artifacts, and custom external resources need separate isolation. See [Repo Scripts](/reference/repo-scripts#test-isolation-and-development-data).
|
|
57
57
|
|
|
58
58
|
The runner owns this application lifecycle. Test files import the shared app from `vona-mock` and must not create or close it themselves:
|
|
59
59
|
|
|
@@ -239,7 +239,15 @@ The callback receives the current `ControllerFormField` instance. It is a field-
|
|
|
239
239
|
Zova invokes it in two important situations:
|
|
240
240
|
|
|
241
241
|
1. after the field controller has created its field binding and finished its initialization;
|
|
242
|
-
2. after a schema-
|
|
242
|
+
2. after a material schema or field-configuration change updates the field options and calls `resetField(this.name)`. Parent validation changes, such as `required[]`, count even when the field property itself is unchanged.
|
|
243
|
+
|
|
244
|
+
Field refresh always resets field state; there is no blanket reset-suppression option. A form can opt into `localePresentationRefresh` with a stable owner identity when supported schema annotations are display-only for that form. Generic Basic resource entry forms provide their resource/record/scene identity automatically. On a compatible locale transition, the field installs current validators, resets, silently restores its current raw live value, refreshes applicable local validation, and only then invokes `onFieldStateReady`.
|
|
245
|
+
|
|
246
|
+
Compatibility is conservative: resolved value/validation truth, defaults, references, canonical bindings, and the field's own renderer/options/readonly metadata must remain unchanged. Only recognized schema titles/descriptions, validation-message strings, and titles in the known Basic Form Layout tab/group grammar can differ. Unknown metadata, explicit effects or opaque validation do not authorize restoration. Unrelated static sibling renderer labels do not invalidate an independent field; a field whose own renderer options change still follows normal reset semantics. The known Basic Details renderer owns a nested form, including its exact hidden `_name` auxiliary row schema; descendant behavior stays within that boundary. Arbitrary hidden fields and direct cross-field effects do not receive this exception. This policy assumes permitted annotations are presentation-only, not that arbitrary custom renderers or CEL programs have been proven equivalent.
|
|
247
|
+
|
|
248
|
+
Replacing an inline `slotDefault` or `onFieldStateReady` closure during ordinary rendering is not itself a material schema change. The latest readiness callback is used when a real refresh occurs. Custom slots remain opaque for automatic value replay; the canonical Basic Input renderer's internal binding slot is a narrowly supported exception, not permission for arbitrary custom slots.
|
|
249
|
+
|
|
250
|
+
Cold schema loading does not consume the previous comparison facts; a remounted field finalizes after its binding is mounted and behavior initialization is complete. Explicit `reset(...)`, incoming-data replacement, or owner/scene changes invalidate preservation. No stored draft is read or replayed during a locale refresh, and standalone forms retain ordinary reset behavior unless they opt in. TanStack's ordinary reset may retain a value when neither the field nor form has a defined default; ineligibility means no extra value replay, not guaranteed erasure.
|
|
243
251
|
|
|
244
252
|
The second timing is important: write the external value in the callback **after** the reset, because a write made before `resetField(...)` can be overwritten. If an external API later creates or refreshes the value, synchronize that API result explicitly as well; `onFieldStateReady` does not fetch, retry, or observe external resources for you.
|
|
245
253
|
|
|
@@ -722,6 +730,14 @@ This Student pattern is recommended because:
|
|
|
722
730
|
|
|
723
731
|
This is also why Cabloy can provide complete CRUD-style form pages with a strong schema-driven surface instead of requiring every page to hand-build form wiring from scratch.
|
|
724
732
|
|
|
733
|
+
### Unsaved resource drafts
|
|
734
|
+
|
|
735
|
+
In Cabloy Basic's generic resource entry pages, create and edit forms enable IndexedDB draft recovery by default. After a full browser reload, Recover applies eligible unsaved input to the form without submitting or writing a business record. Successful submission, explicit discard, ordinary Back and task/workspace close delete the corresponding draft; switching between still-open tasks retains it. Standalone `ZForm` forms are not automatically opted into this resource capability.
|
|
736
|
+
|
|
737
|
+
Draft policy comes from the current schema: normal declared editable fields are included, while readonly, sensitive, file/upload and unsupported values are excluded. Use `@Api.field(ZovaRender.draft(false))` for additional sensitive fields or subtrees, or a scene-specific exclusion such as `ZovaRender.draft(false, 'form-create')`. Resource/global opt-outs and a configurable 24-hour default retention are available.
|
|
738
|
+
|
|
739
|
+
See [Resource Entry Page Deep Dive: Recovering unsaved create/edit drafts](/frontend/resource-entry-page-deep-dive#recovering-unsaved-create-edit-drafts) for the configuration examples, lifecycle, nested-detail behavior and browser-storage boundaries. Draft storage is not encryption, and two browser tabs with the same resource/task identity share a draft key rather than receiving independent draft sessions.
|
|
740
|
+
|
|
725
741
|
Read together with:
|
|
726
742
|
|
|
727
743
|
- [Tutorial 2: Create Your First CRUD](/fullstack/tutorial-2-first-crud)
|
|
@@ -236,6 +236,10 @@ Representative source files:
|
|
|
236
236
|
|
|
237
237
|
A key design detail is that persistence is treated as an extension of model-owned query state rather than as a separate unrelated subsystem.
|
|
238
238
|
|
|
239
|
+
DB records have a physical application/environment namespace: application name plus `[instanceId, siteId, userId]`. Passport supplies instance/user identity using the same rule on server and client; before a Passport user exists, both values are null. The site value comes independently from `SITE_ID`, and missing values normalize to null. This is persistence isolation, not a change to business query keys or sync local/cookie storage.
|
|
240
|
+
|
|
241
|
+
The app-owned `$modelPersistence` context also coordinates DB-backed query state on an identity change. It invalidates obsolete work and clears old in-memory values in place while keeping live observers usable. Initial SSR-hydrated query data can be adopted once; a later login does not promote old or pre-user data into the new scope. `$clear()` remains a full-store clear. See [IndexedDB environment isolation](./model-state-guide.md#indexeddb-environment-isolation) for the lifecycle and upgrade boundaries.
|
|
242
|
+
|
|
239
243
|
## SSR behavior
|
|
240
244
|
|
|
241
245
|
Zova Model is SSR-aware.
|
|
@@ -284,7 +284,7 @@ This is useful for larger persisted client state that should outlive a page sess
|
|
|
284
284
|
|
|
285
285
|
### Initialize and ensure async-local state
|
|
286
286
|
|
|
287
|
-
Assign the state property before awaiting it. When later initialization depends on the restored value, establish an explicit restore boundary with `$ensureStateLocalAsync(...)`. It waits for the pending
|
|
287
|
+
Assign the state property before awaiting it. When later initialization depends on the restored value, establish an explicit restore boundary with `$ensureStateLocalAsync(...)`. It waits for the pending restore to settle; it does not force another load and does not guarantee a defined result:
|
|
288
288
|
|
|
289
289
|
```typescript
|
|
290
290
|
protected async __init__() {
|
|
@@ -302,7 +302,9 @@ protected async __init__() {
|
|
|
302
302
|
}
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
`meta.defaultData` is a fallback: it initializes the state only when no persisted value is restored. If neither provides a value, the ensured result can still be `undefined`.
|
|
305
|
+
`meta.defaultData` is a fallback: it initializes the state only when no persisted value is restored. If neither provides a value, the ensured result can still be `undefined`. For async-local state, prefer a factory such as `defaultData: () => []` for mutable defaults that must be fresh on each restore. Do not carry that function into an SSR-dehydrated memory query: query metadata is transferred with its state and must remain serializable. Use a serializable default value for that branch.
|
|
306
|
+
|
|
307
|
+
Readiness is not permanent: an IndexedDB persistence-scope reset can make an already initialized async-local state pending again. Consumers must wait for the current restore before acting on its value; `$ensureStateLocalAsync(...)` does not pin an earlier scope or turn future reads into synchronous ones. For synchronous render consumers, keep the persisted backing state separate from a neutral current-scope projection and coordinate restore completion in the owning model. Do not persist that neutral projection or retain the previous scope's value while waiting. The router-tabs model uses this boundary for its writable array facade and route initialization.
|
|
306
308
|
|
|
307
309
|
Persisted local and async-local state should be updated by replacing or assigning the model field so its custom-ref setter can update query state and persist the new value:
|
|
308
310
|
|
|
@@ -351,6 +353,25 @@ Representative behaviors include:
|
|
|
351
353
|
|
|
352
354
|
This is why model helpers are better understood as state-runtime helpers, not only convenience wrappers.
|
|
353
355
|
|
|
356
|
+
### IndexedDB environment isolation
|
|
357
|
+
|
|
358
|
+
DB persistence automatically namespaces each physical record by application name and the environment tuple `[instanceId, siteId, userId]`, followed by the model-owned query identity. This applies to `$useStateLocalAsync(...)` and to remote queries whose effective persister uses `db`. Selectors and custom/simplified storage keys remain business keys; they cannot bypass the environment namespace. Logical Query Cache keys and SSR hydration keys do not acquire these dimensions. Sync `local` and `cookie` storage, `mem`, and `persister: false` keep their existing behavior.
|
|
359
|
+
|
|
360
|
+
The Passport model supplies instance and user identity through the neutral `$modelPersistence` context. Both server and client use the same rule:
|
|
361
|
+
|
|
362
|
+
- before Passport supplies a user, `instanceId` and `userId` are `null`;
|
|
363
|
+
- after Passport supplies a user, use the response's top-level `instanceId` and that user's `id`;
|
|
364
|
+
- `siteId` comes from the current SSR site's `SITE_ID`, independently of Passport;
|
|
365
|
+
- absent values normalize to `null`; non-null IDs normalize to strings, including valid zero values.
|
|
366
|
+
|
|
367
|
+
The backend composes Passport `instanceId` from the active Vona instance. No separate bootstrap request or special instance-only SSR handoff is needed. A null scope is usable immediately, not a pending state. A business that needs instance isolation before Passport has a user must include that extra distinction in its own key.
|
|
368
|
+
|
|
369
|
+
A real tuple change invalidates old DB-backed in-memory state as well as changing disk keys. Existing query observers stay attached, active remote queries refetch under the new identity, and async-local state reloads on its next read/ensure. Obsolete asynchronous work cannot publish into the new identity. A same-scope Passport refresh does not reset state. The initial scope installation preserves eligible query data transferred through SSR; later login or identity switches do not adopt old data. Do not copy pre-user state into an authenticated scope implicitly.
|
|
370
|
+
|
|
371
|
+
`$clear()` deliberately still clears the **entire** shared IndexedDB store, not only the current tuple. Accepted writes are ordered before clear and obsolete pending persistence work cannot repopulate it. This is useful for logout in a user-facing browser, but is not a server-side authorization boundary or cross-tab session protocol.
|
|
372
|
+
|
|
373
|
+
Unscoped legacy DB records are not read or automatically migrated. After upgrading, previously persisted DB state starts fresh; changing a business selector likewise does not migrate its old records. Keep using stable resource inputs in logical keys rather than repeating the environment tuple there.
|
|
374
|
+
|
|
354
375
|
## SSR-sensitive state choices
|
|
355
376
|
|
|
356
377
|
Model state choices can affect SSR behavior.
|
|
@@ -228,6 +228,125 @@ Inside `blockPageEntry`:
|
|
|
228
228
|
|
|
229
229
|
This is why the entry-page runtime should be read together with [Form Scene to Page Meta Guide](/frontend/form-scene-to-page-meta-guide) and [Page Meta Guide](/frontend/page-meta-guide).
|
|
230
230
|
|
|
231
|
+
## Recovering unsaved create/edit drafts
|
|
232
|
+
|
|
233
|
+
Cabloy Basic's generic resource entry runtime enables draft recovery by default for **create and edit** forms. View forms do not save drafts, and this capability does not automatically extend to standalone `ZForm` usages such as login or password-change forms.
|
|
234
|
+
|
|
235
|
+
Eligible input is saved silently through a Zova Model using asynchronous IndexedDB persistence: ordinary saves do not show a saved-draft banner. On page entry or after a browser reload, an initial persisted candidate offers **Recover** and **Discard** controls once client hydration and form initialization have finished. This is a one-shot recovery decision, not an active-draft toolbar. Successful recovery dismisses the banner and changes the live form only: it never creates or updates a business record and never submits the form automatically.
|
|
236
|
+
|
|
237
|
+
The ownership boundaries remain separate:
|
|
238
|
+
|
|
239
|
+
- `ModelResource` owns current server data, schema and mutations.
|
|
240
|
+
- `basic-pageentry`'s selector-backed `ModelDraft` owns each local snapshot and its persistence status.
|
|
241
|
+
- `BeanDraftCoordinator` owns draft identities, session invalidation and explicit route/task-close cleanup. Client application initialization registers this coordinator without preloading draft Models or browser snapshots, so even unopened inactive tasks can be cleaned up.
|
|
242
|
+
- `ZForm` owns editable values and validation.
|
|
243
|
+
- `blockPageEntry` coordinates recovery, submission and explicit abandonment through the coordinator.
|
|
244
|
+
|
|
245
|
+
### Draft lifecycle
|
|
246
|
+
|
|
247
|
+
| Action | Draft behavior |
|
|
248
|
+
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
249
|
+
| Edit eligible fields | Silently save the complete current local unsaved snapshot. |
|
|
250
|
+
| Enter new parent-form input with a candidate | Dismiss the candidate and replace it with the current form snapshot, without merging old candidate values. |
|
|
251
|
+
| Recover a candidate successfully | Apply it to the live form and dismiss the banner; subsequent saves stay silent. |
|
|
252
|
+
| Switch between still-open tasks | Retain input and draft state without reopening a dismissed banner. |
|
|
253
|
+
| Reload the browser | Retain the draft and offer recovery on returning to the same form. |
|
|
254
|
+
| Ordinary Back | Delete the outgoing form's draft. |
|
|
255
|
+
| Explicitly close a task or its workspace | Delete the drafts of the removed tasks, including inactive tasks. |
|
|
256
|
+
| Successfully submit | Delete the corresponding create/edit draft. |
|
|
257
|
+
| Validation or submission fails | Retain the draft. |
|
|
258
|
+
| Discard a recovery candidate | Delete that candidate and leave the current baseline form unchanged. |
|
|
259
|
+
| Component unmount or cache pruning | Not, by itself, an explicit draft discard. |
|
|
260
|
+
|
|
261
|
+
Reloading while the list or another task is active preserves the inactive form's draft too. Returning through its restored create/edit task offers recovery after the current schema and page route are ready; restored workspace metadata does not restore the live form values or imply automatic recovery.
|
|
262
|
+
|
|
263
|
+
A new parent `onChanged` event takes over an undecided recovery candidate. The next snapshot contains the complete current live form, not a field-by-field merge with the old candidate. Input entered before asynchronous candidate discovery wins too: discovery must not reopen the old banner or replace that newer input. To recover the replacement later, reload and explicitly choose Recover.
|
|
264
|
+
|
|
265
|
+
There is no active-draft Discard button, toolbar or other UI entry after recovery or new-input takeover. The internal `discardDraft()` reset method remains available to the runtime; candidate Discard is the visible recovery action, and ordinary Back or explicit task/workspace close still performs abandonment cleanup.
|
|
266
|
+
|
|
267
|
+
Edit recovery checks the current accessible record at the interaction boundary: a changed, deleted or inaccessible record must not receive stale recovered values. Recovery errors remain with the candidate controls; storage failures use a separate compact inline status rather than a saved-draft banner. Expired or structurally incompatible snapshots are not applied. Locale-only label changes do not invalidate compatible drafts.
|
|
268
|
+
|
|
269
|
+
### Locale refresh is not draft recovery
|
|
270
|
+
|
|
271
|
+
The resource form enables `ZForm`'s narrow `localePresentationRefresh` policy with a stable resource/record/scene identity. Schema refresh still updates field options and **always resets field state**. A compatible language change can then restore the current live value before the readiness callback; it does not read IndexedDB, replay a recovery candidate, preserve old validation errors, or skip new validation rules.
|
|
272
|
+
|
|
273
|
+
Compatibility includes complete resolved validation truth and strict field renderer/options metadata. For example, Student `name` can survive a label/message translation, while Student `level` has localized renderer options and follows normal reset behavior rather than receiving extra value replay. Supported Basic Form Layout tab/group titles are display-only; arbitrary renderer metadata is not. Explicit reset, data replacement, record/scene changes, changed constraints/defaults, and opaque effects invalidate preservation. Read [Form Guide](/frontend/form-guide#initialize-external-state-with-onfieldstateready) for the policy boundaries.
|
|
274
|
+
|
|
275
|
+
Draft projection supports `anyOf` and `oneOf` unions whose branches are all safe primitives or null, including Student `level` (`1 | 2 | 3`). Eligible branches declare string, boolean, number, integer or null types (including nonempty arrays of these types), or provide an untyped primitive/null `const` or nonempty primitive/null `enum`; defaults and renderer options do not establish eligibility. Safe unfinished values are persisted without enum-membership validation, coercion or branch selection.
|
|
276
|
+
|
|
277
|
+
Every branch is checked, including null branches, reference wrappers and resolved targets. A sensitive, readonly, write-only, file/upload/binary or `draft: false` branch, an unresolved or cyclic reference, or any unsupported branch excludes the **entire field**; unsafe alternatives are never dropped to persist the remainder. Multi-member unions containing objects or arrays remain excluded, as do `allOf` and simultaneous composition operators. Nullable objects and arrays with a single non-null branch retain their existing nested projection support.
|
|
278
|
+
|
|
279
|
+
Explicit draft recovery can therefore restore stored Student `level: 2` over server baseline `1`. A later locale refresh with incompatible translated renderer options still resets the live level normally, and an already-dirty draft then saves that current reset value. Primitive-union support does not weaken the locale reset policy. Union operators and every branch's structural contract participate in the draft signature; old incompatible snapshots are rejected rather than migrated or silently replayed.
|
|
280
|
+
|
|
281
|
+
After field readiness callbacks settle, `ZForm` coalesces an `onSchemaRefreshed` notification using current live values. `blockForm` forwards it to the entry owner, which updates an already-dirty draft so values changed by normal field reset do not leave a stale snapshot. This notification does not mark a clean form dirty, manufacture an input-change event, or overwrite an undecided recovery candidate.
|
|
282
|
+
|
|
283
|
+
### Disable recovery or change retention
|
|
284
|
+
|
|
285
|
+
The `basic-pageentry` module configuration defaults to:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
draft: {
|
|
289
|
+
enabled: true,
|
|
290
|
+
maxAge: 24 * 60 * 60 * 1000,
|
|
291
|
+
resources: {},
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Use the application's `config.modules` overrides to disable recovery globally, disable it for selected resources, or change the retention period:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
config.modules = {
|
|
299
|
+
'basic-pageentry': {
|
|
300
|
+
draft: {
|
|
301
|
+
enabled: true,
|
|
302
|
+
maxAge: 24 * 60 * 60 * 1000,
|
|
303
|
+
resources: {
|
|
304
|
+
'training-student:student': false,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Set `enabled: false` to disable the capability globally. A resource-entry `basic-pageentry:blockPageEntry` block can also specify `draft: false`, and root form `rest.draft: false` metadata disables the whole form. These are opt-outs from the default capability, not a requirement to manually enable every resource.
|
|
312
|
+
|
|
313
|
+
### Control which fields are persisted
|
|
314
|
+
|
|
315
|
+
Declared, editable, supported fields are included by default, including nested objects and arrays. The current form scene's effective schema controls projection: create uses `form-create`, while edit uses `form`. Unknown fields and unsafe runtime values are excluded.
|
|
316
|
+
|
|
317
|
+
Automatic exclusions include readonly fields, passwords and write-only values, obvious secret/key/token/credential/verification-code fields, file/upload/binary fields and unsupported objects such as `File`, `Blob` or DOM nodes. Drafts may hold incomplete safe input without passing submit validation. Nested detail recovery preserves existing row identity and deletion/order semantics rather than turning existing rows into new inserts; excluded server-owned values come from the current baseline.
|
|
318
|
+
|
|
319
|
+
#### Date-time fields and nested detail dialogs
|
|
320
|
+
|
|
321
|
+
For an eligible schema field declared as `type: 'string'` with `format: 'date-time'`, a valid runtime `Date` is projected to its ISO string. This is a schema-directed `Date` → ISO conversion, not a general object serializer or submit-time validation: incomplete safe primitive input is preserved without date parsing or coercion. Invalid `Date` instances, `Date` values on ordinary non-date-time fields, and unsupported non-date objects remain excluded.
|
|
322
|
+
|
|
323
|
+
The draft recovery projection intentionally restores the ISO **string**, without adding a Date reviver. The existing model persistence parser may revive ISO strings before recovery; the projection normalizes eligible Dates back to ISO. Basic Date accepts both runtime Dates and ISO strings, so reopening a recovered Training Record dialog displays the stored Training Time through the same native date control. Its date-only input represents midnight in the browser's local timezone; the projected ISO value expresses that instant in UTC. Clearing an optional date preserves the explicit absence so recovery does not silently restore the server's date.
|
|
324
|
+
|
|
325
|
+
A nested Details dialog has its own local form. Edits enter the parent resource form—and therefore its draft—only after the dialog's **OK** action. Cancel does not copy them into the parent. Dialog OK is not a business-resource submission: draft persistence and recovery do not create or update server records. Incompatible old snapshots are rejected through the schema signature rather than migrated or revived.
|
|
326
|
+
|
|
327
|
+
For business-sensitive content that cannot be reliably identified by automatic metadata, declare an explicit exclusion on the backend contract:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
@Api.field(ZovaRender.draft(false))
|
|
331
|
+
privateNote: string;
|
|
332
|
+
|
|
333
|
+
// Exclude this field only in create-form drafts.
|
|
334
|
+
@Api.field(ZovaRender.draft(false, 'form-create'))
|
|
335
|
+
verificationCode: string;
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Applied to an object or array field, `draft(false)` excludes the entire subtree. Scene-specific metadata follows the existing shared-form and exact-scene overlay rules. An explicit `draft(true)` does not override automatic sensitive, readonly or unsupported-value exclusions.
|
|
339
|
+
|
|
340
|
+
`ZovaRender` is generated from frontend-owned metadata. After changing that authoring surface, follow the [Contract Loop Playbook](/fullstack/contract-loop-playbook): build the affected paired SSR/REST flavor outputs before synchronizing Vona dependencies; do not patch generated consumers manually.
|
|
341
|
+
|
|
342
|
+
### Browser storage boundaries
|
|
343
|
+
|
|
344
|
+
Drafts use the generic [Model IndexedDB environment isolation](./model-state-guide.md#indexeddb-environment-isolation): application name and `[instanceId, siteId, userId]` belong to the persistence layer, not the draft business selector. The selector contains only `[resource, mode, id ?? null, path]`. Passport supplies the authoritative instance/user identity; draft discovery still requires an authenticated, non-anonymous user. A captured scope/epoch token invalidates old editors and queued cleanup after an instance, site or user switch, including switching away and back. Logout deliberately clears the entire shared Model DB store; it does not transfer another account's drafts into the new form. There is no draft-specific database adapter or legacy-key migration.
|
|
345
|
+
|
|
346
|
+
IndexedDB is **not encryption**. Automatic exclusions are defensive defaults, not a guarantee that every business-sensitive field can be recognized; exclude additional fields or disable sensitive resources deliberately. Storage failure is nonfatal and does not prevent normal editing or submission, but unfinished writes cannot be guaranteed durable if the browser terminates immediately.
|
|
347
|
+
|
|
348
|
+
There are no application-level draft-count or byte limits. Browser storage availability and native quota still apply. The same resource/task identity in two browser tabs shares one draft key; independent simultaneous create drafts and cross-tab conflict coordination are not provided.
|
|
349
|
+
|
|
231
350
|
## What this guide does not re-explain
|
|
232
351
|
|
|
233
352
|
This page does **not** fully re-explain:
|
|
@@ -68,7 +68,7 @@ npm run deps:vona
|
|
|
68
68
|
npm run test:e2e cabloy-basic home-user-account
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The sole managed `npm run test:e2e` command starts a fresh local Vona worker with `--workers=1 --flavor=normal --mode=test`, then runs the selected specs against Vona integrated SSR. Test-mode startup initializes test resources without a separate `db:reset`; browser commands consume already-built artifacts and do not rebuild them.
|
|
71
|
+
The sole managed `npm run test:e2e` command starts a fresh local Vona worker with `--workers=1 --flavor=normal --mode=test`, then runs the selected specs against Vona integrated SSR. Test-mode startup initializes test resources without a separate `db:reset`; browser commands consume already-built artifacts and do not rebuild them. In both Cabloy Basic and Cabloy Start, the root wrapper allocates run-owned test identity, database and Redis namespaces, generated/runtime/public paths, and a loopback listener independently of ordinary dev. A running same-checkout dev process alone is not a blocker. Check actual overlap with those test resources and custom external resources, and coordinate overlapping shared build/dependency writes; do not require dev shutdown or worktree/environment setup solely for these tests. See [Test isolation and development data](/reference/repo-scripts#test-isolation-and-development-data) for the ownership and preparation boundaries.
|
|
72
72
|
|
|
73
73
|
Place flat spec basenames directly after the npm script name. Use npm's `--` delimiter before Playwright options. With no names, every spec in `repo-e2e/specs` runs. Native `--grep` and `--grep-invert` remain available, and repeatable `--tag` values require all listed tags:
|
|
74
74
|
|
|
@@ -95,7 +95,7 @@ Cabloy Start does not expose the Basic Commerce wrapper family.
|
|
|
95
95
|
|
|
96
96
|
## Start SSR browser acceptance
|
|
97
97
|
|
|
98
|
-
The Start Web and Admin browser baseline exercises Vona integrated SSR dispatch at the
|
|
98
|
+
The Start Web and Admin browser baseline exercises Vona integrated SSR dispatch at the managed run's dynamically allocated loopback listener, independently of the ordinary dev listener. Prepare current SSR and REST artifacts, then use the managed local command:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
101
|
npm run build:zova
|
|
@@ -118,7 +118,7 @@ npm run test:e2e cabloy-admin -- --tag @admin --tag @cabloy-admin
|
|
|
118
118
|
npm run test:e2e cabloy-start -- --grep ATP-START-FLOW-01
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
Start does not support externally managed E2E targets: `E2E_BASE_URL` must be unset.
|
|
121
|
+
Start does not support externally managed E2E targets: `E2E_BASE_URL` must be unset. Like Basic, its root wrapper allocates run-specific test identity, database and Redis namespaces, generated/runtime/public paths, and a loopback listener, then starts one Vona worker with `--workers=1 --flavor=normal --mode=test`; Playwright does not reuse an existing process. It can run alongside ordinary same-checkout dev without requiring the configured dev port to be free. Test-mode startup initializes test state without a separate `db:reset`. Apply the same [test-resource ownership and shared-preparation checks](/reference/repo-scripts#test-isolation-and-development-data); the command does not replace the paired build/dependency-sync prerequisite or authorize shared environment/port changes.
|
|
122
122
|
|
|
123
123
|
## Zova script model
|
|
124
124
|
|
|
@@ -318,11 +318,16 @@ Its callback receives the current `ControllerFormField<TParentData>` instance. T
|
|
|
318
318
|
_getFormFieldOptions()
|
|
319
319
|
-> _formField.api.update(options)
|
|
320
320
|
-> form.resetField(name)
|
|
321
|
+
-> optional compatible-locale live-value restoration and current validation
|
|
321
322
|
-> onFieldStateReady(formField)
|
|
322
323
|
```
|
|
323
324
|
|
|
324
325
|
The callback must follow `resetField(...)`. A custom field may hold a value outside the form controller—for example, a widget value or an externally created challenge object—and a value written before the reset can be replaced by the reset's default value. The callback provides the replay point where the custom field can write its current external value back through `formField.setValue(...)` or the owning form setter.
|
|
325
326
|
|
|
327
|
+
The form owns detached, last-ready comparison facts keyed by canonical field name. Fields observe the current resolved form contract as well as their declared configuration, so parent `required[]` or reference-target changes refresh validators even when a projected property is unchanged. Loading gaps do not overwrite those facts. A previously known field remount defers finalization until its binding has mounted and its behavior initialization has completed; provisional creation does not seed over an existing empty value.
|
|
328
|
+
|
|
329
|
+
`localePresentationRefresh` is an explicit presentation-only policy, not permission to skip reset. Eligible refreshes restore only the current raw value, including `undefined`, without firing input listeners or `onChanged`; old errors and field metadata are never replayed. Explicit reset/data replacement changes the preservation epoch. The compatibility helper resolves current references and compares complete value/validation truth, with strict field renderer/options and ancestor behavior checks. See [Form Guide](/frontend/form-guide#initialize-external-state-with-onfieldstateready) for the supported display exceptions and conservative limits.
|
|
330
|
+
|
|
326
331
|
This lifecycle is separate from external data changes. If an API creates or refreshes the external value later, synchronize that result in the API completion path too. Likewise, user input should continue to update the field through `formField.setValue(...)`, while value-change effects belong to the normal field effect/input surfaces. `onFieldStateReady` is not a per-value-change listener and does not fetch, retry, or wait for a renderer DOM node.
|
|
327
332
|
|
|
328
333
|
For the public authoring shape and a renderer-neutral example, see [Initialize external state with `onFieldStateReady`](/frontend/form-guide#initialize-external-state-with-onfieldstateready).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Parallel Worktree Environment
|
|
2
2
|
|
|
3
|
-
Use worktree-local environment overrides when two Cabloy Basic or Cabloy Start worktrees need to run ordinary local development at the same time. In Cabloy Basic, root `npm run test` and `npm run test:e2e` already isolate their test-mode runtime and data on every run, including alongside ordinary dev in the **same** checkout; they do not require this worktree setup for that purpose.
|
|
3
|
+
Use worktree-local environment overrides when two Cabloy Basic or Cabloy Start worktrees need to run ordinary local development at the same time. In Cabloy Basic and Cabloy Start, root `npm run test` and `npm run test:e2e` already isolate their test-mode runtime and data on every run, including alongside ordinary dev in the **same** checkout; they do not require this worktree setup for that purpose.
|
|
4
4
|
|
|
5
5
|
This Cabloy Basic page is the canonical shared recipe for both editions. Detect the active edition before selecting commands, but keep the same two-file environment model for worktree-local development.
|
|
6
6
|
|
|
@@ -82,11 +82,11 @@ Use commands from the active repository root.
|
|
|
82
82
|
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
83
83
|
| Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
84
84
|
|
|
85
|
-
Run one frontend command, not both, in each worktree. In Cabloy Basic, root `npm run test` and `npm run test:e2e` allocate a fresh run-specific identity, test database and Redis namespace, generated/runtime/public paths, and loopback listener independently of the worktree's development environment. Managed E2E starts a fresh Vona test-mode worker, and startup initializes test resources and module seeds without a separate dev-mode reset.
|
|
85
|
+
Run one frontend development command, not both, in each worktree. In Cabloy Basic and Cabloy Start, root `npm run test` and `npm run test:e2e` allocate a fresh run-specific identity, test database and Redis namespace, generated/runtime/public paths, and loopback listener independently of the worktree's development environment. Managed E2E starts a fresh Vona test-mode worker, and startup initializes test resources and module seeds without a separate dev-mode reset. Ordinary dev presence alone is not a test-resource conflict and does not require another worktree, dev shutdown, or environment changes. Coordinate actual shared build/dependency writes and assess custom external resources separately; see [Test isolation and development data](/reference/repo-scripts#test-isolation-and-development-data). Leave `E2E_BASE_URL` unset: externally managed targets are unsupported. Do not change shared identity or ports merely to bypass a busy development resource.
|
|
86
86
|
|
|
87
87
|
## What this isolates
|
|
88
88
|
|
|
89
|
-
A unique worktree `APP_NAME` separates the identity of ordinary development resources across worktrees, including framework-managed development database names and Redis-related key prefixes. Worktree-local generated files, runtime files, build output, and coverage output are already separated by their worktree paths. Cabloy Basic root tests use a separate run-specific identity even when invoked inside a worktree; they do not clean its ordinary development business data.
|
|
89
|
+
A unique worktree `APP_NAME` separates the identity of ordinary development resources across worktrees, including framework-managed development database names and Redis-related key prefixes. Worktree-local generated files, runtime files, build output, and coverage output are already separated by their worktree paths. Cabloy Basic and Cabloy Start root tests use a separate run-specific identity even when invoked inside a worktree; they do not clean its ordinary development business data.
|
|
90
90
|
|
|
91
91
|
The generated Vona, Zova development HTTP, and Zova HMR ports let worktrees run their ordinary local processes concurrently. The matching Zova API URL prevents the frontend in one worktree from calling the Vona process in another. Builds, dependency generation, and other shared preparation writes remain accepted prerequisites rather than part of test data cleanup.
|
|
92
92
|
|
|
@@ -63,12 +63,22 @@ pnpm_config_minimum_release_age=0 npm create cabloy
|
|
|
63
63
|
|
|
64
64
|
## 3. Start Vona integrated SSR
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Before the first start, run the root command below to prepare the development databases and seed data:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run db:reset
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`npm run db:reset` is destructive: it clears application-owned Redis prefixes and drops and recreates the managed development databases, then runs migrations, initialization, and seeds. Use it only when you intend to reset those resources. Do not run it while a development server is using the same databases and Redis namespace.
|
|
73
|
+
|
|
74
|
+
Then start the Vona server:
|
|
67
75
|
|
|
68
76
|
```bash
|
|
69
77
|
npm run dev
|
|
70
78
|
```
|
|
71
79
|
|
|
80
|
+
For subsequent starts, run `npm run dev` directly. It reuses existing databases and does not create or seed missing databases; if a required database is missing, run `npm run db:reset` explicitly before starting again.
|
|
81
|
+
|
|
72
82
|
In the Cabloy Basic default environment, Vona listens on `7102` and serves the following SSR sites:
|
|
73
83
|
|
|
74
84
|
| SSR Site | URL |
|