@finesoft/front 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +4 -4
  2. package/dist/Outlet.svelte +39 -0
  3. package/dist/Outlet.svelte.d.ts +7 -0
  4. package/dist/browser-DIU6Sxl3.mjs +1237 -0
  5. package/dist/browser-kFMjlLGT.d.mts +262 -0
  6. package/dist/browser.d.mts +7 -2
  7. package/dist/browser.mjs +9 -1
  8. package/dist/controller-types-CgmJ6-le.d.mts +16 -0
  9. package/dist/cookies-Bpf9VayB.d.mts +779 -0
  10. package/dist/fetch-policy-BHT8RtrL.mjs +82 -0
  11. package/dist/host-guard-DDWxLpFL.mjs +222 -0
  12. package/dist/http-B6CJqDyf.d.mts +46 -0
  13. package/dist/http-CaxrMD1A.d.mts +1 -0
  14. package/dist/http-D70PL72H.mjs +257 -0
  15. package/dist/http.d.mts +3 -0
  16. package/dist/http.mjs +2 -0
  17. package/dist/index-node.d.mts +15 -0
  18. package/dist/index-node.mjs +15 -0
  19. package/dist/index.d.mts +48 -697
  20. package/dist/index.mjs +44 -261
  21. package/dist/load-node.d.mts +5 -0
  22. package/dist/load-node.mjs +10 -0
  23. package/dist/load-portable.d.mts +5 -0
  24. package/dist/load-portable.mjs +8 -0
  25. package/dist/lru-map-BKoUAySU.mjs +50 -0
  26. package/dist/messages-CAt2QdGr.mjs +140 -0
  27. package/dist/native-contract-DuR25hYB.d.mts +14 -0
  28. package/dist/native-contract.d.mts +2 -0
  29. package/dist/native-contract.mjs +1 -0
  30. package/dist/node-D9hB4dsz.d.mts +35 -0
  31. package/dist/node.d.mts +2 -0
  32. package/dist/node.mjs +59 -0
  33. package/dist/path-CGFl2w7D.mjs +113 -0
  34. package/dist/path-CXT6xGPO.d.mts +261 -0
  35. package/dist/portable-CaxrMD1A.d.mts +1 -0
  36. package/dist/portable.d.mts +11 -0
  37. package/dist/portable.mjs +12 -0
  38. package/dist/proxy-1SphZ7x7.mjs +436 -0
  39. package/dist/proxy-2dSWO-Xw.d.mts +53 -0
  40. package/dist/public-types-BcJM-AYc.mjs +835 -0
  41. package/dist/react-DhwBRw01.d.mts +16 -0
  42. package/dist/react.d.mts +3 -0
  43. package/dist/react.mjs +29 -0
  44. package/dist/rolldown-runtime-B4iAMlE-.mjs +35 -0
  45. package/dist/secure-fetch-Xlht2jd7.d.mts +30 -0
  46. package/dist/server-controller-proxy-BkVuVWVD.d.mts +38 -0
  47. package/dist/session-DnB4ZC3x.d.mts +1279 -0
  48. package/dist/src-Ftl_0rhu.mjs +28 -0
  49. package/dist/src-qwx7Vw8g.mjs +3807 -0
  50. package/dist/ssr-BEUNDvbj.d.mts +210 -0
  51. package/dist/ssr-C8xnYXoY.mjs +357 -0
  52. package/dist/ssr.d.mts +3 -0
  53. package/dist/ssr.mjs +3 -0
  54. package/dist/svelte-Dr5to3SE.d.mts +16 -0
  55. package/dist/svelte.d.mts +3 -0
  56. package/dist/svelte.mjs +13 -0
  57. package/dist/typegen-C-WeJCtf.d.mts +12 -0
  58. package/dist/typegen-cli.d.mts +1 -0
  59. package/dist/typegen-cli.mjs +11 -0
  60. package/dist/typegen.d.mts +3 -0
  61. package/dist/typegen.mjs +2 -0
  62. package/dist/types-BuaZHRG7.mjs +402 -0
  63. package/dist/undici-CPfL25Hr.mjs +22262 -0
  64. package/dist/vite-Cj4SPA8D.d.mts +277 -0
  65. package/dist/vite.d.mts +4 -0
  66. package/dist/vite.mjs +2354 -0
  67. package/dist/vue-DGmzuKho.d.mts +32 -0
  68. package/dist/vue.d.mts +3 -0
  69. package/dist/vue.mjs +56 -0
  70. package/dist/web.d.mts +6 -0
  71. package/dist/web.mjs +8 -0
  72. package/dist/worker.d.mts +2 -0
  73. package/dist/worker.mjs +2 -0
  74. package/docs/01-getting-started.md +67 -199
  75. package/docs/02-routing-and-controllers.md +163 -241
  76. package/docs/03-middleware.md +10 -212
  77. package/docs/04-rendering-and-hydration.md +6 -333
  78. package/docs/05-i18n.md +7 -237
  79. package/docs/06-http-client.md +20 -263
  80. package/docs/07-di-container.md +23 -257
  81. package/docs/08-observability.md +6 -286
  82. package/docs/09-server-and-deployment.md +59 -219
  83. package/docs/10-features-platform-pwa.md +7 -231
  84. package/docs/11-navigation.md +143 -288
  85. package/docs/12-session-restoration.md +6 -214
  86. package/docs/README.md +7 -7
  87. package/docs/advanced/custom-action-handler.md +29 -229
  88. package/docs/advanced/custom-adapter.md +7 -259
  89. package/docs/advanced/custom-event-recorder.md +7 -312
  90. package/docs/advanced/inline-proxy-codegen.md +6 -185
  91. package/docs/advanced/multi-tenant-scopes.md +10 -323
  92. package/docs/engineering/ci-release-flow.md +35 -222
  93. package/docs/engineering/project-structure.md +34 -277
  94. package/docs/engineering/testing.md +8 -310
  95. package/docs/pitfalls/container-scope-leak.md +2 -214
  96. package/docs/pitfalls/i18n-bundle-size.md +6 -176
  97. package/docs/pitfalls/proxy-binary-payloads.md +8 -12
  98. package/docs/pitfalls/ssr-hydration-mismatch.md +4 -160
  99. package/docs/pitfalls/ssr-vs-csr-globals.md +9 -170
  100. package/docs/zh/01-getting-started.md +67 -199
  101. package/docs/zh/02-routing-and-controllers.md +166 -244
  102. package/docs/zh/03-middleware.md +10 -212
  103. package/docs/zh/04-rendering-and-hydration.md +6 -333
  104. package/docs/zh/05-i18n.md +7 -237
  105. package/docs/zh/06-http-client.md +20 -263
  106. package/docs/zh/07-di-container.md +23 -257
  107. package/docs/zh/08-observability.md +6 -283
  108. package/docs/zh/09-server-and-deployment.md +59 -219
  109. package/docs/zh/10-features-platform-pwa.md +7 -231
  110. package/docs/zh/11-navigation.md +130 -290
  111. package/docs/zh/12-session-restoration.md +6 -214
  112. package/docs/zh/README.md +4 -4
  113. package/docs/zh/advanced/custom-action-handler.md +29 -229
  114. package/docs/zh/advanced/custom-adapter.md +7 -259
  115. package/docs/zh/advanced/custom-event-recorder.md +7 -312
  116. package/docs/zh/advanced/inline-proxy-codegen.md +6 -185
  117. package/docs/zh/advanced/multi-tenant-scopes.md +10 -323
  118. package/docs/zh/engineering/ci-release-flow.md +35 -222
  119. package/docs/zh/engineering/project-structure.md +34 -277
  120. package/docs/zh/engineering/testing.md +8 -310
  121. package/docs/zh/pitfalls/container-scope-leak.md +2 -214
  122. package/docs/zh/pitfalls/i18n-bundle-size.md +6 -176
  123. package/docs/zh/pitfalls/proxy-binary-payloads.md +8 -12
  124. package/docs/zh/pitfalls/ssr-hydration-mismatch.md +4 -160
  125. package/docs/zh/pitfalls/ssr-vs-csr-globals.md +9 -170
  126. package/package.json +118 -20
  127. package/dist/browser-BHhVWXik.mjs +0 -2
  128. package/dist/browser-BV2BBXm7.d.mts +0 -2811
@@ -1,244 +1,57 @@
1
- # Engineering: CI & release flow
1
+ # Engineering: validation and release
2
2
 
3
- How the framework itself is released, and how to set up the same workflow for an app that depends on it.
3
+ `@finesoft/front` and `@finesoft/create-app` are the published packages. Core, Web, Browser, SSR and Server remain private. The front build bundles their implementation into explicit ESM entries and declarations. Each native renderer keeps its selected UI peer external.
4
4
 
5
- ## What ships
5
+ ## Local checks
6
6
 
7
- Only `@finesoft/front` is published to npm. The internal `core` / `browser` / `ssr` / `server` packages are bundled into `front` via `tsdown`'s `noExternal: [@finesoft/*]`.
8
-
9
- This means:
10
-
11
- - One npm package for users to install
12
- - Internal refactors don't bump multiple versions
13
- - Single CHANGELOG to read
14
-
15
- `create-finesoft-app` is its own published package (a CLI), separate from the framework runtime.
16
-
17
- ## The release workflow
18
-
19
- The repo ships a single `.github/workflows/release.yml` that handles everything inline. Trigger: push to `main`.
20
-
21
- ```
22
- push to main
23
-
24
-
25
- Checkout main (with PAT, not GITHUB_TOKEN)
26
-
27
-
28
- Reconcile npm registry with main
29
- ├── npm == main? → continue
30
- ├── main > npm? → catch-up publish current main version
31
- └── npm > main? → error, manual investigation
32
-
33
-
34
- Generate auto-changeset (one patch per push)
35
-
36
-
37
- Apply version bump
38
- ├── changes? → continue
39
- └── no changes? → done, nothing to publish
40
-
41
-
42
- Commit "chore(release): version packages"
43
-
44
-
45
- Build all packages, publish @finesoft/front to npm
46
-
47
-
48
- Push commit + tag back to main (with rebase retry)
49
- ```
50
-
51
- ### Why one inline workflow instead of changesets/action's PR mode
52
-
53
- The standard changesets workflow opens a PR ("Version Packages") that, when merged, triggers a second workflow run that publishes. **But `GITHUB_TOKEN`-merged commits don't trigger subsequent workflows** (GitHub's anti-recursion safety) — the publish never fires. The inline workflow does everything in one run, no PR hop.
54
-
55
- ### Why a PAT instead of `GITHUB_TOKEN`
56
-
57
- The repo has a ruleset enforcing signed commits, linear history, and required PRs on `main`. Bypass actors include `RepositoryRole=5 (admin)` but **not** `github-actions[bot]`. GitHub's UI does not allow adding the bot to the bypass list. Push from a PAT owned by an admin user matches the existing bypass entry.
58
-
59
- The PAT is `Contents: Read & Write` only — the minimum needed for `git push`.
60
-
61
- ## Concurrency
62
-
63
- ```yaml
64
- concurrency: release-${{ github.ref }}
7
+ ```sh
8
+ vp install
9
+ vp run --filter '@finesoft/front...' build
10
+ vp check
11
+ vp test --coverage
12
+ vp run -r build
13
+ vp exec node scripts/verify-publish-transforms.mjs
14
+ vp exec node scripts/verify-runtime-boundaries.mjs
65
15
  ```
66
16
 
67
- Multiple pushes to `main` queue rather than cancel. This matters because:
68
-
69
- - Cancellation mid-publish leaves npm in an inconsistent state
70
- - Each push must wait for the previous to finish to avoid version-number races
71
- - The next run's reconcile step picks up whatever the previous one published
72
-
73
- ## Idempotency
74
-
75
- `changeset publish` skips versions already on npm. So if push to `main` fails after publish:
17
+ `vp pack` is the library build command. `vp pm pack` creates a local package tarball. Front's paired prepack/postpack scripts rewrite its publish manifest and restore the exact working manifest, including when preparation fails. Scaffolder preparation copies the current six templates and makes their package and TypeScript configuration standalone.
76
18
 
77
- - npm: has version 0.1.75
78
- - main: stuck at 0.1.74
19
+ ## Repository automation
79
20
 
80
- The next release run's reconcile detects `main < npm`, refuses to "catch up backwards," and errors out. Manual remediation: open a PR that bumps `packages/front/package.json` to match npm and merges. Subsequent pushes resume normally.
21
+ Both Quality jobs first build front and its workspace dependencies. The root Vite configuration loads the controller type generator, which imports the built core package; this preparation is required on a fresh checkout.
81
22
 
82
- ## Setting up the same for an app
23
+ Pull requests run Quality (`vp check` and `vp test --coverage`). A push to `main` starts Release, which calls the same Quality workflow and waits for both checks to pass. CodeQL runs independently over `packages/{core,web,browser,ssr,server,front}/src/**`.
83
24
 
84
- Most apps don't need a publish step they have deploys instead. But the changeset + auto-bump shape still works:
25
+ Release checks out the tested commit, generates a patch changeset for both public packages, applies Changesets (including any explicit minor/major changesets), updates the lockfile and builds. It then pushes the version commit to `main` with the built-in `GITHUB_TOKEN` before publishing to npm. If another push advances `main`, publication stops; the newer push gets its own validation and release. The workflow never rebases an already-built artifact or overwrites newer commits. Git tags are pushed after npm publication succeeds.
85
26
 
86
- ```yaml
87
- name: Release
27
+ The publishing job uses a GitHub-hosted runner with `id-token: write`, no dependency cache and no npm token. Changesets invokes the repository's pnpm 11 through Vite+; pnpm supports npm OIDC and automatic provenance natively. Both public packages declare their source repository and public registry. `RELEASE_PUSH_TOKEN` and `NPM_TOKEN` are no longer used.
88
28
 
89
- on:
90
- push:
91
- branches:
92
- - main
29
+ ### npm configuration
93
30
 
94
- concurrency: release-${{ github.ref }}
31
+ In Settings for **each** of `@finesoft/front` and `@finesoft/create-app`, add a GitHub Actions trusted publisher:
95
32
 
96
- jobs:
97
- release:
98
- runs-on: ubuntu-latest
99
- if: "!startsWith(github.event.head_commit.message, 'chore(release):')"
100
- permissions:
101
- contents: write
102
- steps:
103
- - uses: actions/checkout@v5
104
- with:
105
- ref: main
106
- fetch-depth: 0
107
- token: ${{ secrets.RELEASE_PUSH_TOKEN }}
33
+ | Field | Value |
34
+ | -------------------- | ----------------------------------------------------- |
35
+ | Organization or user | `GaKireiTsuki` |
36
+ | Repository | `finesoft-front` |
37
+ | Workflow filename | `release.yml` |
38
+ | Environment name | Leave empty; the workflow does not use an environment |
39
+ | Allow npm publish | Enabled for this automatic release workflow |
108
40
 
109
- - uses: voidzero-dev/setup-vp@v1
110
- with:
111
- node-version: 24
112
- cache: true
41
+ Keep “Require two-factor authentication and disallow bypass 2fa tokens” selected; it is compatible with trusted publishing. This pipeline publishes directly, so a stage-only connection cannot authorize it. See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/). Saving the connection alone does not prove publication works: verify the first Release run and the actual versions on npm.
113
42
 
114
- - run: vp install --frozen-lockfile
43
+ ### Retry a failed release
115
44
 
116
- - name: Configure git
117
- run: |
118
- git config user.name "github-actions[bot]"
119
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
45
+ After fixing the cause, select **Release → Run workflow → main**, or run:
120
46
 
121
- - name: Generate auto changeset
122
- run: vp run release:auto:changeset
123
-
124
- - name: Apply version bump
125
- id: bump
126
- run: |
127
- vp run version
128
- if git diff --quiet; then
129
- echo "should_publish=false" >> "$GITHUB_OUTPUT"
130
- else
131
- NEW=$(node -p "require('./package.json').version")
132
- echo "version=$NEW" >> "$GITHUB_OUTPUT"
133
- echo "should_publish=true" >> "$GITHUB_OUTPUT"
134
- fi
135
-
136
- - name: Commit version
137
- if: steps.bump.outputs.should_publish == 'true'
138
- run: |
139
- git add -A
140
- git commit -m "chore(release): version packages"
141
-
142
- - name: Build
143
- if: steps.bump.outputs.should_publish == 'true'
144
- run: vp run build
145
-
146
- - name: Deploy
147
- if: steps.bump.outputs.should_publish == 'true'
148
- run: vp run deploy # your deploy command
149
-
150
- - name: Push tag and commit
151
- if: steps.bump.outputs.should_publish == 'true'
152
- run: git push --follow-tags origin HEAD:main
47
+ ```sh
48
+ gh workflow run release.yml --ref main
153
49
  ```
154
50
 
155
- Replace `vp run deploy` with your platform's deploy command (Vercel, Cloudflare, your own infra).
156
-
157
- ## Conventional commits + auto-changeset
158
-
159
- The `release:auto:changeset` script (in this repo, generates one patch changeset per push) is intentionally simple — every merged PR becomes one patch bump. For semver-driven versioning, replace it with a script that:
160
-
161
- - Reads `git log` since the last tag
162
- - Maps commit prefixes (`feat:`, `fix:`, `BREAKING:`) to changeset types
163
- - Writes the right `.changeset/*.md`
164
-
165
- The framework's repo uses patch-only because:
166
-
167
- - Every push is a small change; large changes go through review and become small commits anyway
168
- - Real breaking changes are rare and warrant manual changesets
169
- - It avoids a class of "the prefix lies" bugs
170
-
171
- Pick the policy that matches how your team commits.
172
-
173
- ## Per-PR validation (`quality.yml`)
174
-
175
- The repo also has a `quality.yml` workflow on PRs:
176
-
177
- ```yaml
178
- on:
179
- pull_request:
180
- push:
181
- branches:
182
- - main
183
-
184
- jobs:
185
- coverage:
186
- runs-on: ubuntu-latest
187
- steps:
188
- - uses: actions/checkout@v5
189
- - uses: voidzero-dev/setup-vp@v1
190
- with: { node-version: 24, cache: true }
191
- - run: vp install --frozen-lockfile
192
- - run: vp test --coverage
193
- - uses: actions/upload-artifact@v7
194
- with:
195
- name: coverage-report
196
- path: reports/coverage
197
- if-no-files-found: error
198
- ```
199
-
200
- The `check` job (format + lint + types) is gated off by `if: false` in this repo because Vite+ runs them all locally via pre-commit. Re-enable it if your team doesn't run pre-commit hooks consistently.
201
-
202
- ## CodeQL
203
-
204
- The repo enables CodeQL on a schedule and PRs. Scan scope is restricted to `packages/{core,browser,ssr,server,front}/src/**` — tests, templates, scripts, and the scaffolder are excluded.
205
-
206
- For application repos, enable the default CodeQL config — its noise is low and it catches real issues (open redirects, SQL injection, secret exposure).
207
-
208
- ## Required status checks
209
-
210
- The repo ruleset requires:
211
-
212
- - `Coverage` (from `quality.yml`)
213
- - `CodeQL`
214
-
215
- PRs cannot merge until both pass. The release workflow bypasses these via the admin PAT — release runs after merge, on `main`, so the checks already passed on the PR.
216
-
217
- ## Migration: from changesets PR mode to inline
218
-
219
- If you're moving an existing repo from `changesets/action` (PR mode):
220
-
221
- 1. Delete the old release workflow
222
- 2. Create the inline workflow above
223
- 3. Generate a fine-grained PAT, store as `RELEASE_PUSH_TOKEN`
224
- 4. The first push to main after this change will:
225
- - Detect main == npm (no catch-up needed)
226
- - Generate one patch changeset
227
- - Bump + publish + push back to main
228
-
229
- If there's a pending "Version Packages" PR from the old workflow, close it without merging. The auto-changeset will pick up everything from there.
230
-
231
- ## What can go wrong
51
+ Manual dispatch validates and builds the versions already in `main`; it does **not** create another changeset or bump versions. Changesets skips versions already present on npm, so partial publication can be retried. The tag step restores missing release tags. If failure occurred before the version commit reached `main`, retry the original push run while its commit is still the branch head, or push the correction to start a new release.
232
52
 
233
- | Symptom | Cause | Fix |
234
- | ----------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------- |
235
- | `[remote rejected] HEAD -> main` | PAT actor not in ruleset bypass; PAT lacks `Contents: Write` | Verify PAT scope; confirm push actor is an admin user |
236
- | `npm ... is ahead of main` | Previous run published but failed to push | Open a PR syncing `packages/front/package.json` to npm version |
237
- | Workflow doesn't trigger after a release commit | `if: "!startsWith(github.event.head_commit.message, 'chore(release):'"` filtering | Working as intended — recursion prevention |
238
- | Pre-commit hook (`vp check`) fails CI | Local formatter not run | `vp check --fix` locally; commit; rerun |
53
+ Local versioning uses `vp run changeset` and `vp run version`. `vp run release` builds and publishes; `vp run release:publish` only publishes an existing build. A local build or tarball test does not publish, deploy, push a commit or validate an external environment.
239
54
 
240
- ## See also
55
+ ## Application projects
241
56
 
242
- - The actual release workflow: `.github/workflows/release.yml`
243
- - The actual quality workflow: `.github/workflows/quality.yml`
244
- - [Changesets docs](https://github.com/changesets/changesets) — for understanding `vp run version` and `vp run release`
57
+ Use the generated application's own `vp run build` and deployment configuration. Keep environment secrets in the chosen host's bindings. Install only the renderer and host peers that the application imports. The complete local runtime and installed-consumer evidence is described in the repository's `docs/application-boundaries-acceptance.md`.
@@ -1,296 +1,53 @@
1
- # Engineering: project structure
1
+ # Application structure
2
2
 
3
- Recommended layout for apps past the scaffolded starter. The shape that works for ~20 routes and ~10 engineers without major reorganization.
3
+ The React, Vue and Svelte templates have two consistent tiers. Each generated project is standalone and uses only the public package entries.
4
4
 
5
- ## Layout
5
+ ## Choose a tier
6
6
 
7
- ```
8
- my-app/
9
- ├── src/
10
- │ ├── bootstrap.ts # routes + DI setup (shared SSR + CSR)
11
- │ ├── main.ts # browser entry
12
- │ ├── ssr.ts # SSR entry
13
- │ ├── App.vue # root component
14
- │ │
15
- │ ├── routes/ # route definitions, grouped by domain
16
- │ │ ├── home.ts
17
- │ │ ├── product.ts
18
- │ │ ├── checkout.ts
19
- │ │ └── admin.ts
20
- │ │
21
- │ ├── controllers/ # controllers, one file per intent
22
- │ │ ├── home.ts
23
- │ │ ├── product.ts
24
- │ │ └── checkout.ts
25
- │ │
26
- │ ├── views/ # view components, mirror controllers
27
- │ │ ├── Home.vue
28
- │ │ ├── Product.vue
29
- │ │ └── Checkout.vue
30
- │ │
31
- │ ├── lib/
32
- │ │ ├── api/ # HttpClient subclasses
33
- │ │ │ ├── user.ts
34
- │ │ │ └── product.ts
35
- │ │ ├── guards/ # reusable middleware
36
- │ │ │ ├── auth.ts
37
- │ │ │ ├── locale.ts
38
- │ │ │ └── analytics.ts
39
- │ │ ├── di/
40
- │ │ │ ├── keys.ts # APP_KEYS const map
41
- │ │ │ └── register.ts # central registration
42
- │ │ ├── i18n/
43
- │ │ │ └── translator.ts # Translator factory
44
- │ │ └── pages/
45
- │ │ └── types.ts # Page union type
46
- │ │
47
- │ ├── locales/
48
- │ │ ├── en-US.json
49
- │ │ ├── zh-Hans.json
50
- │ │ └── ja-JP.json
51
- │ │
52
- │ └── env.ts # env var parsing + validation
53
-
54
- ├── public/ # static assets, served at /
55
- ├── index.html
56
- ├── vite.config.ts
57
- ├── package.json
58
- └── tsconfig.json
59
- ```
60
-
61
- ## Why this shape
62
-
63
- ### `routes/` separate from `controllers/`
64
-
65
- A route is **where** an intent is exposed (URL pattern, guards, render mode). A controller is **what** an intent does. Splitting them lets you:
66
-
67
- - Reuse a controller across multiple URLs without route definitions cluttering its file
68
- - Find "what URLs does my app serve" by reading one folder
69
- - Find "what does intent X compute" by reading one folder
70
-
71
- ### `controllers/` mirrors `views/`
72
-
73
- One file per intent on both sides. The intent id, controller file, and view file share the same name. Finding the rendering code for `/products/:id` becomes mechanical.
74
-
75
- ### `lib/` for everything cross-cutting
76
-
77
- Anything that isn't a route, controller, or view. The framework's `bootstrap()` reaches into `lib/di/register.ts` for the heavy registration; controllers reach into `lib/api/*` for clients; guards live in `lib/guards/`.
78
-
79
- ### `env.ts` at the top of `src/`
80
-
81
- Parse and validate environment variables once, in one file. Use [zod](https://zod.dev/) or hand-rolled checks. Re-export typed constants everywhere else.
82
-
83
- ```ts
84
- // src/env.ts
85
- function requireEnv(name: string): string {
86
- const v = process.env[name];
87
- if (!v) throw new Error(`Missing env: ${name}`);
88
- return v;
89
- }
90
-
91
- export const env = {
92
- UPSTREAM_URL: requireEnv("UPSTREAM_URL"),
93
- SESSION_SECRET: requireEnv("SESSION_SECRET"),
94
- NODE_ENV: process.env.NODE_ENV ?? "development",
95
- } as const;
96
- ```
97
-
98
- This makes "what env vars does this app need" trivially discoverable, and the build fails fast on missing values instead of crashing at request time.
99
-
100
- ## `bootstrap.ts` shape
101
-
102
- Keep `bootstrap.ts` thin — it should orchestrate, not contain logic:
103
-
104
- ```ts
105
- // src/bootstrap.ts
106
- import { type Framework } from "@finesoft/front";
107
- import { registerDependencies } from "./lib/di/register";
108
- import { homeRoutes } from "./routes/home";
109
- import { productRoutes } from "./routes/product";
110
- import { checkoutRoutes } from "./routes/checkout";
111
- import { adminRoutes } from "./routes/admin";
112
-
113
- export function bootstrap(framework: Framework): void {
114
- registerDependencies(framework.container);
115
-
116
- homeRoutes(framework);
117
- productRoutes(framework);
118
- checkoutRoutes(framework);
119
- adminRoutes(framework);
120
- }
121
- ```
122
-
123
- Each `*Routes` function calls `defineRoutes(framework, [...])` with its own routes. Adding a new route group is one import + one call.
124
-
125
- ## Per-domain route file
126
-
127
- ```ts
128
- // src/routes/product.ts
129
- import { defineRoutes, type Framework } from "@finesoft/front";
130
- import { ProductController } from "../controllers/product";
131
- import { ProductListController } from "../controllers/product-list";
132
- import { authGuard } from "../lib/guards/auth";
133
-
134
- export function productRoutes(framework: Framework): void {
135
- defineRoutes(framework, [
136
- { path: "/products", intentId: "product-list", controller: new ProductListController() },
137
- { path: "/products/:id", intentId: "product", controller: new ProductController() },
138
- {
139
- path: "/products/:id/edit",
140
- intentId: "product-edit",
141
- controller: new ProductEditController(),
142
- beforeLoad: [authGuard],
143
- renderMode: "csr",
144
- },
145
- ]);
146
- }
147
- ```
148
-
149
- ## Central DI registration
150
-
151
- ```ts
152
- // src/lib/di/register.ts
153
- import { type Container, DEP_KEYS } from "@finesoft/front";
154
- import { APP_KEYS } from "./keys";
155
- import { UserApi } from "../api/user";
156
- import { ProductApi } from "../api/product";
157
- import { ConsoleLogger } from "@finesoft/front";
158
- import { env } from "../../env";
159
-
160
- export function registerDependencies(container: Container): void {
161
- container.register(DEP_KEYS.LOGGER, () => new ConsoleLogger("app"));
162
-
163
- container.register(
164
- APP_KEYS.USER_API,
165
- () =>
166
- new UserApi({
167
- baseUrl: env.UPSTREAM_URL,
168
- }),
169
- );
170
- container.register(
171
- APP_KEYS.PRODUCT_API,
172
- () =>
173
- new ProductApi({
174
- baseUrl: env.UPSTREAM_URL,
175
- }),
176
- );
177
- }
178
- ```
179
-
180
- Centralizing keeps the wiring inspectable. Adding a service is a single edit, not a search across the project.
181
-
182
- ## Typed DI keys
183
-
184
- ```ts
185
- // src/lib/di/keys.ts
186
- export const APP_KEYS = {
187
- USER_API: "userApi",
188
- PRODUCT_API: "productApi",
189
- SESSION: "session",
190
- FEATURE_BUCKETING: "featureBucketing",
191
- } as const;
192
-
193
- export type AppKey = (typeof APP_KEYS)[keyof typeof APP_KEYS];
194
- ```
195
-
196
- Then in any controller:
197
-
198
- ```ts
199
- import { APP_KEYS } from "../lib/di/keys";
7
+ | Tier | Pages and routes | Demonstrated behavior |
8
+ | ------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
9
+ | Full | Home `/`, product `/products/:id`, search `/search?q=...`, About `/about` | SSR, CSR About, URL actions, typed parameters, DI, mapper/API client examples, authentication and SEO guards |
10
+ | Minimal | Feed `/`, detail `/item/:id`, Notes `/notes` | Tabs and stacks, entry drafts, global profile, session restoration, locale JSON and SSR hydration |
200
11
 
201
- async execute(params, container) {
202
- const api = container.resolve<UserApi>(APP_KEYS.USER_API);
203
- // ...
204
- }
205
- ```
206
-
207
- A typo in `APP_KEYS.USER_PAI` is a compile error. A typo in `"userPai"` is a runtime error.
208
-
209
- ## Page type union
210
-
211
- ```ts
212
- // src/lib/pages/types.ts
213
- import type { HomePage } from "../../controllers/home";
214
- import type { ProductPage } from "../../controllers/product";
215
- import type { CheckoutPage } from "../../controllers/checkout";
216
- import type { ErrorPage } from "./error";
217
-
218
- export type Page = HomePage | ProductPage | CheckoutPage | ErrorPage;
219
- ```
220
-
221
- Use a discriminated union with a `kind` field. The view layer's root component switches on `page.kind`:
12
+ Within a tier the three frameworks have the same routes, controller results, content and interactions. Both tiers use one native App with its layout around Outlet. The names describe the example applications, not a ranking of framework capabilities.
222
13
 
223
- ```vue
224
- <script setup lang="ts">
225
- import type { Page } from "@/lib/pages/types";
226
- const props = defineProps<{ page: Page }>();
227
- </script>
14
+ ## Files
228
15
 
229
- <template>
230
- <Home v-if="page.kind === 'home'" :page="page" />
231
- <Product v-else-if="page.kind === 'product'" :page="page" />
232
- <Checkout v-else-if="page.kind === 'checkout'" :page="page" />
233
- <Error v-else-if="page.kind === 'error'" :page="page" />
234
- </template>
16
+ ```text
17
+ src/config.ts # app identity / persistence namespace
18
+ src/app-definition.ts # page definitions, routes, guards and optional navigation
19
+ src/views.ts # native view registry shared by browser and SSR
20
+ src/main.ts # standard browser startup and HMR disposal
21
+ src/ssr.ts # selected standard SSR renderer
22
+ src/App.<native> # full layout or minimal navigation/profile chrome
23
+ src/pages/ # native page components (.tsx / .vue / .svelte)
24
+ src/components/ # reusable full-template presentation components
25
+ src/lib/controllers/ # BaseController page loaders and explicit public data
26
+ src/lib/models/ # typed page/data contracts
27
+ src/styles.css # identical styling within each tier
28
+ src/locales/ # minimal: en-US and zh-Hans JSON messages
235
29
  ```
236
30
 
237
- The discriminant narrows the type inside each branch view components get a fully typed `page` prop without casts.
31
+ Full additionally keeps its product data, mapper, HTTP client and guards under `src/lib/`,. Minimal reads `app.getSnapshot()` through the selected native `useSnapshot` binding. Profile state and component roots belong to the native app; the Web session owns guarded navigation and persistence.
238
32
 
239
- ## Splitting the SSR and browser entries
33
+ ## Controllers and page declarations
240
34
 
241
- Keep `ssr.ts` and `main.ts` thin. They differ only in:
35
+ All six templates use `BaseController`: classes in `lib/controllers/` implement business loading, while `app-definition.ts` registers factories with `definePage({ id, create: () => new HomeController() })` and reuses the returned `route()`, `leaf()` and `bindView()` references. `views.ts` binds native components, which receive the result through `page`.
242
36
 
243
- - `ssr.ts` calls `createSSRRender` and exports `render` + `serializeServerData`
244
- - `main.ts` calls `startBrowserApp` and mounts the view layer
37
+ Import controllers, Web page types and `markPublic` from `@finesoft/front`. Controllers are created for actual execution; entry drafts and global profile state belong to page instances and the application store respectively. The `getErrorPage` factory creates error pages. See [routes, controllers and typed pages](../02-routing-and-controllers.md) for parameters, DI, `fallback` and function handlers.
245
38
 
246
- Everything else routes, controllers, DI, i18n — is shared via `bootstrap.ts`.
39
+ ## State and locale
247
40
 
248
- ```ts
249
- // src/ssr.ts
250
- import { createSSRRender, serializeServerData } from "@finesoft/front";
251
- import { renderToString } from "vue/server-renderer";
252
- import { createSSRApp } from "vue";
253
- import App from "./App.vue";
254
- import { bootstrap } from "./bootstrap";
255
-
256
- export const render = createSSRRender({
257
- bootstrap,
258
- getErrorPage: () => ({ kind: "error", title: "Server error" }),
259
- async renderApp(page) {
260
- const html = await renderToString(createSSRApp(App, { page }));
261
- return { html, head: `<title>${page.title}</title>`, css: "" };
262
- },
263
- });
264
-
265
- export { serializeServerData };
266
- ```
267
-
268
- ```ts
269
- // src/main.ts
270
- import { startBrowserApp } from "@finesoft/front/browser";
271
- import { createSSRApp } from "vue";
272
- import App from "./App.vue";
273
- import { bootstrap } from "./bootstrap";
274
-
275
- startBrowserApp({
276
- bootstrap,
277
- mount(target, { framework }) {
278
- createSSRApp(App, { framework }).mount(target);
279
- },
280
- });
281
- ```
41
+ In minimal, the detail input and Notes textarea live inside `data-restore-root`. Switching tabs retains both drafts; reloading restores them. Popping a detail entry destroys its draft. The profile is global to one mounted application and survives reload. `mountApplication(target, persistenceKey, "memory", "/notes")` allows an embedded application with a distinct persistence key.
282
42
 
283
- ## When to break this layout
43
+ All three minimal templates default to `zh-Hans`. Set `configuration.locale` in `src/app-definition.ts` to `en-US` or `zh-Hans` and restart/rebuild. The locale demonstration covers JSON loading and translation after hydration; the navigation labels and sample item data remain English.
284
44
 
285
- The shape above works through ~50 routes. Past that, consider:
45
+ Full's unauthenticated `/admin` route redirects to `/login?from=...`; the template deliberately leaves the application's real login page and authentication service to its author. A demo `auth_token` cookie lets the guard continue.
286
46
 
287
- - **Per-feature folders** (`src/features/checkout/{routes,controllers,views,api}.ts`) for very large apps. Each feature is independently understandable.
288
- - **Lazy-loaded route bundles** with `import()` inside the route definition. The Vite plugin splits them automatically.
289
- - **Workspace packages** if multiple apps share the same controllers / API clients. Move shared code to `packages/shared` and import from there.
47
+ ## Maintaining parity
290
48
 
291
- Don't pre-emptively reorganize. The flat `routes/` + `controllers/` shape is fine well into the hundreds of files.
49
+ Framework-neutral files are identical checked-in source in each same-tier project. Native imports, UI syntax, framework dependencies and app identities are the allowed differences. The projects need no sibling-template imports or private runtime aliases. The scaffolder packages these same source files and includes a local README.
292
50
 
293
- ## See also
51
+ Run `vp test packages/create-app/test` to check file/adapter parity, then build the six templates and run `vp exec node scripts/verify-template-renderers.mjs` for shared browser behavior. `vp exec node scripts/verify-created-consumers.mjs /absolute/path/to/front.tgz` prepares, installs and builds every generated project outside the workspace against the selected local package.
294
52
 
295
- - [Engineering: testing](./testing.md) how to test against this structure
296
- - [DI container](../07-di-container.md) — registration patterns
53
+ Optional independent data operations and Node/Worker hosts can be added in separate `src/data-app.ts`, `src/node.ts` and `src/worker.ts` modules. They share application contracts through the public entries.