@finesoft/create-app 0.1.34 → 0.1.36

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 (198) hide show
  1. package/dist/index.mjs +22 -26
  2. package/package.json +14 -5
  3. package/templates/react/README.md +35 -0
  4. package/templates/react/index.html +2 -3
  5. package/templates/react/package.json +12 -8
  6. package/templates/react/src/App.tsx +7 -15
  7. package/templates/react/src/app-definition.ts +35 -0
  8. package/templates/react/src/components/Layout.tsx +5 -52
  9. package/templates/react/src/components/Loading.tsx +1 -1
  10. package/templates/react/src/components/Navigation.tsx +10 -37
  11. package/templates/react/src/components/ProductCard.tsx +5 -23
  12. package/templates/react/src/config.ts +2 -0
  13. package/templates/react/src/lib/controllers/about.ts +18 -13
  14. package/templates/react/src/lib/controllers/error.ts +11 -7
  15. package/templates/react/src/lib/controllers/home.ts +36 -41
  16. package/templates/react/src/lib/controllers/product-detail.ts +71 -40
  17. package/templates/react/src/lib/controllers/search.ts +33 -54
  18. package/templates/react/src/lib/data/products.ts +36 -0
  19. package/templates/react/src/lib/mappers/product.ts +3 -3
  20. package/templates/react/src/lib/models/product.ts +11 -1
  21. package/templates/react/src/lib/services/api-client.ts +7 -4
  22. package/templates/react/src/main.ts +34 -0
  23. package/templates/react/src/pages/About.tsx +2 -4
  24. package/templates/react/src/pages/Home.tsx +6 -15
  25. package/templates/react/src/pages/NotFound.tsx +4 -14
  26. package/templates/react/src/pages/ProductDetail.tsx +5 -23
  27. package/templates/react/src/pages/Search.tsx +5 -7
  28. package/templates/react/src/ssr.ts +11 -0
  29. package/templates/react/src/styles.css +71 -0
  30. package/templates/react/src/views.ts +12 -0
  31. package/templates/react/tsconfig.json +14 -9
  32. package/templates/react/vite.config.ts +9 -13
  33. package/templates/react-minimal/README.md +39 -0
  34. package/templates/react-minimal/index.html +2 -3
  35. package/templates/react-minimal/package.json +12 -8
  36. package/templates/react-minimal/src/App.tsx +40 -85
  37. package/templates/react-minimal/src/app-definition.ts +48 -0
  38. package/templates/react-minimal/src/config.ts +2 -0
  39. package/templates/react-minimal/src/lib/controllers/detail.ts +19 -18
  40. package/templates/react-minimal/src/lib/controllers/error.ts +15 -0
  41. package/templates/react-minimal/src/lib/controllers/home.ts +19 -24
  42. package/templates/react-minimal/src/lib/controllers/notes.ts +7 -5
  43. package/templates/react-minimal/src/lib/locale.ts +16 -0
  44. package/templates/react-minimal/src/lib/models/page.ts +27 -0
  45. package/templates/react-minimal/src/lib/navigation.ts +11 -0
  46. package/templates/react-minimal/src/locales/en-US.json +5 -0
  47. package/templates/react-minimal/src/locales/zh-Hans.json +5 -0
  48. package/templates/react-minimal/src/main.ts +43 -0
  49. package/templates/react-minimal/src/pages/Detail.tsx +20 -0
  50. package/templates/react-minimal/src/pages/Home.tsx +38 -0
  51. package/templates/react-minimal/src/pages/NotFound.tsx +11 -0
  52. package/templates/react-minimal/src/pages/Notes.tsx +21 -0
  53. package/templates/react-minimal/src/ssr.ts +11 -0
  54. package/templates/react-minimal/src/styles.css +74 -0
  55. package/templates/react-minimal/src/views.ts +6 -0
  56. package/templates/react-minimal/src/vite-env.d.ts +3 -0
  57. package/templates/react-minimal/tsconfig.json +14 -9
  58. package/templates/react-minimal/vite.config.ts +9 -7
  59. package/templates/svelte/README.md +35 -0
  60. package/templates/svelte/index.html +1 -2
  61. package/templates/svelte/package.json +9 -5
  62. package/templates/svelte/src/App.svelte +8 -80
  63. package/templates/svelte/src/app-definition.ts +35 -0
  64. package/templates/svelte/src/components/Layout.svelte +2 -2
  65. package/templates/svelte/src/components/Loading.svelte +1 -1
  66. package/templates/svelte/src/components/Navigation.svelte +5 -66
  67. package/templates/svelte/src/components/ProductCard.svelte +3 -24
  68. package/templates/svelte/src/config.ts +2 -0
  69. package/templates/svelte/src/lib/controllers/about.ts +18 -13
  70. package/templates/svelte/src/lib/controllers/error.ts +11 -7
  71. package/templates/svelte/src/lib/controllers/home.ts +36 -41
  72. package/templates/svelte/src/lib/controllers/product-detail.ts +71 -40
  73. package/templates/svelte/src/lib/controllers/search.ts +33 -54
  74. package/templates/svelte/src/lib/data/products.ts +36 -0
  75. package/templates/svelte/src/lib/mappers/product.ts +3 -3
  76. package/templates/svelte/src/lib/models/product.ts +12 -1
  77. package/templates/svelte/src/lib/services/api-client.ts +7 -4
  78. package/templates/svelte/src/main.ts +28 -24
  79. package/templates/svelte/src/pages/About.svelte +2 -2
  80. package/templates/svelte/src/pages/Home.svelte +3 -15
  81. package/templates/svelte/src/pages/NotFound.svelte +3 -12
  82. package/templates/svelte/src/pages/ProductDetail.svelte +4 -21
  83. package/templates/svelte/src/pages/Search.svelte +2 -10
  84. package/templates/svelte/src/ssr.ts +10 -9
  85. package/templates/svelte/src/styles.css +71 -0
  86. package/templates/svelte/src/views.ts +12 -0
  87. package/templates/svelte/svelte.config.js +2 -0
  88. package/templates/svelte/tsconfig.json +13 -8
  89. package/templates/svelte/vite.config.ts +9 -13
  90. package/templates/svelte-minimal/README.md +39 -0
  91. package/templates/svelte-minimal/index.html +1 -2
  92. package/templates/svelte-minimal/package.json +9 -5
  93. package/templates/svelte-minimal/src/App.svelte +49 -61
  94. package/templates/svelte-minimal/src/app-definition.ts +48 -0
  95. package/templates/svelte-minimal/src/config.ts +2 -0
  96. package/templates/svelte-minimal/src/lib/controllers/detail.ts +23 -0
  97. package/templates/svelte-minimal/src/lib/controllers/error.ts +15 -0
  98. package/templates/svelte-minimal/src/lib/controllers/home.ts +24 -23
  99. package/templates/svelte-minimal/src/lib/controllers/notes.ts +18 -0
  100. package/templates/svelte-minimal/src/lib/locale.ts +16 -0
  101. package/templates/svelte-minimal/src/lib/models/page.ts +27 -0
  102. package/templates/svelte-minimal/src/lib/navigation.ts +11 -0
  103. package/templates/svelte-minimal/src/locales/en-US.json +1 -3
  104. package/templates/svelte-minimal/src/locales/zh-Hans.json +1 -3
  105. package/templates/svelte-minimal/src/main.ts +37 -19
  106. package/templates/svelte-minimal/src/pages/Detail.svelte +11 -0
  107. package/templates/svelte-minimal/src/pages/Home.svelte +17 -21
  108. package/templates/svelte-minimal/src/pages/NotFound.svelte +10 -0
  109. package/templates/svelte-minimal/src/pages/Notes.svelte +11 -0
  110. package/templates/svelte-minimal/src/ssr.ts +9 -17
  111. package/templates/svelte-minimal/src/styles.css +74 -0
  112. package/templates/svelte-minimal/src/views.ts +6 -0
  113. package/templates/svelte-minimal/src/vite-env.d.ts +3 -0
  114. package/templates/svelte-minimal/svelte.config.js +2 -0
  115. package/templates/svelte-minimal/tsconfig.json +13 -8
  116. package/templates/svelte-minimal/vite.config.ts +9 -10
  117. package/templates/vue/README.md +35 -0
  118. package/templates/vue/index.html +1 -2
  119. package/templates/vue/package.json +9 -5
  120. package/templates/vue/src/App.vue +7 -22
  121. package/templates/vue/src/app-definition.ts +35 -0
  122. package/templates/vue/src/components/Layout.vue +4 -6
  123. package/templates/vue/src/components/Loading.vue +1 -1
  124. package/templates/vue/src/components/Navigation.vue +10 -43
  125. package/templates/vue/src/components/ProductCard.vue +4 -28
  126. package/templates/vue/src/config.ts +2 -0
  127. package/templates/vue/src/lib/controllers/about.ts +18 -13
  128. package/templates/vue/src/lib/controllers/error.ts +11 -7
  129. package/templates/vue/src/lib/controllers/home.ts +36 -41
  130. package/templates/vue/src/lib/controllers/product-detail.ts +71 -40
  131. package/templates/vue/src/lib/controllers/search.ts +33 -54
  132. package/templates/vue/src/lib/data/products.ts +36 -0
  133. package/templates/vue/src/lib/mappers/product.ts +3 -3
  134. package/templates/vue/src/lib/models/product.ts +12 -1
  135. package/templates/vue/src/lib/services/api-client.ts +7 -7
  136. package/templates/vue/src/main.ts +28 -43
  137. package/templates/vue/src/pages/About.vue +3 -5
  138. package/templates/vue/src/pages/Home.vue +5 -23
  139. package/templates/vue/src/pages/NotFound.vue +4 -13
  140. package/templates/vue/src/pages/ProductDetail.vue +5 -23
  141. package/templates/vue/src/pages/Search.vue +4 -18
  142. package/templates/vue/src/ssr.ts +5 -18
  143. package/templates/vue/src/styles.css +71 -0
  144. package/templates/vue/src/views.ts +12 -0
  145. package/templates/vue/tsconfig.json +14 -9
  146. package/templates/vue/vite.config.ts +9 -13
  147. package/templates/vue-minimal/README.md +39 -0
  148. package/templates/vue-minimal/index.html +1 -2
  149. package/templates/vue-minimal/package.json +9 -5
  150. package/templates/vue-minimal/src/App.vue +46 -43
  151. package/templates/vue-minimal/src/app-definition.ts +48 -0
  152. package/templates/vue-minimal/src/config.ts +2 -0
  153. package/templates/vue-minimal/src/lib/controllers/detail.ts +19 -18
  154. package/templates/vue-minimal/src/lib/controllers/error.ts +15 -0
  155. package/templates/vue-minimal/src/lib/controllers/home.ts +19 -24
  156. package/templates/vue-minimal/src/lib/controllers/notes.ts +7 -5
  157. package/templates/vue-minimal/src/lib/locale.ts +16 -0
  158. package/templates/vue-minimal/src/lib/models/page.ts +27 -0
  159. package/templates/vue-minimal/src/lib/navigation.ts +11 -0
  160. package/templates/vue-minimal/src/locales/en-US.json +5 -0
  161. package/templates/vue-minimal/src/locales/zh-Hans.json +5 -0
  162. package/templates/vue-minimal/src/main.ts +35 -70
  163. package/templates/vue-minimal/src/pages/Detail.vue +20 -0
  164. package/templates/vue-minimal/src/pages/Home.vue +34 -0
  165. package/templates/vue-minimal/src/pages/NotFound.vue +13 -0
  166. package/templates/vue-minimal/src/pages/Notes.vue +22 -0
  167. package/templates/vue-minimal/src/ssr.ts +7 -46
  168. package/templates/vue-minimal/src/styles.css +74 -0
  169. package/templates/vue-minimal/src/views.ts +6 -0
  170. package/templates/vue-minimal/src/vite-env.d.ts +3 -0
  171. package/templates/vue-minimal/tsconfig.json +14 -9
  172. package/templates/vue-minimal/vite.config.ts +9 -7
  173. package/templates/react/src/actions.ts +0 -14
  174. package/templates/react/src/bootstrap.ts +0 -36
  175. package/templates/react/src/components/PageRenderer.tsx +0 -41
  176. package/templates/react/src/main.tsx +0 -49
  177. package/templates/react/src/ssr.tsx +0 -23
  178. package/templates/react-minimal/src/bootstrap.ts +0 -39
  179. package/templates/react-minimal/src/main.tsx +0 -99
  180. package/templates/react-minimal/src/ssr.tsx +0 -51
  181. package/templates/react-minimal/src/views/DetailView.tsx +0 -28
  182. package/templates/react-minimal/src/views/HomeView.tsx +0 -27
  183. package/templates/react-minimal/src/views/NotesView.tsx +0 -11
  184. package/templates/svelte/src/actions.ts +0 -14
  185. package/templates/svelte/src/bootstrap.ts +0 -36
  186. package/templates/svelte/src/components/PageRenderer.svelte +0 -22
  187. package/templates/svelte/src/lib/framework-svelte.ts +0 -34
  188. package/templates/svelte/src/lib/render.ts +0 -15
  189. package/templates/svelte-minimal/src/bootstrap.ts +0 -6
  190. package/templates/svelte-minimal/src/lib/framework-svelte.ts +0 -34
  191. package/templates/svelte-minimal/src/lib/render.ts +0 -12
  192. package/templates/vue/src/actions.ts +0 -14
  193. package/templates/vue/src/bootstrap.ts +0 -42
  194. package/templates/vue/src/components/PageRenderer.vue +0 -26
  195. package/templates/vue-minimal/src/bootstrap.ts +0 -39
  196. package/templates/vue-minimal/src/views/DetailView.vue +0 -29
  197. package/templates/vue-minimal/src/views/HomeView.vue +0 -26
  198. package/templates/vue-minimal/src/views/NotesView.vue +0 -12
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import type { DetailPage } from "../lib/models/page";
3
+ let { page }: { page: DetailPage } = $props();
4
+ </script>
5
+
6
+ <section class="page">
7
+ <h1>{page.title}</h1>
8
+ <p>{page.description}</p>
9
+ <!-- The entry owns this draft; data-restore-root also restores it after reload. -->
10
+ <div class="draft" data-restore-root><label>Detail note<input name="note" placeholder="Kept while this screen is alive; lost when popped" /></label></div>
11
+ </section>
@@ -1,25 +1,21 @@
1
1
  <script lang="ts">
2
- import type { BasePage } from "@finesoft/front";
3
- import { getFramework } from "../lib/framework-svelte";
2
+ import type { ViewProps } from "@finesoft/front";
3
+ import type { HomePage } from "../lib/models/page";
4
+ import { getHomeLocale } from "../lib/locale";
4
5
 
5
- let { page }: { page: BasePage } = $props();
6
-
7
- const framework = getFramework();
8
- const locale = framework?.getLocale()?.lang ?? "unknown";
9
- const translator = framework?.getTranslator();
10
- const localeLabel = translator?.t("home.localeLabel") ?? "Current locale";
11
- const runtimeBadge =
12
- translator?.t("home.runtimeBadge") ??
13
- "Hydration kept the translator alive on the client.";
14
- const switchHint =
15
- translator?.t("home.switchHint") ??
16
- 'Change frameworkConfig.locale in src/bootstrap.ts to load another JSON file.';
6
+ let { page, app }: { page: HomePage; app: ViewProps["app"] } = $props();
7
+ const locale = $derived(getHomeLocale(app));
17
8
  </script>
18
9
 
19
- <div>
20
- <h1>{page.title}</h1>
21
- <p>{page.description}</p>
22
- <p><strong>{localeLabel}:</strong> {locale}</p>
23
- <p>{runtimeBadge}</p>
24
- <p>{switchHint}</p>
25
- </div>
10
+ <section class="page">
11
+ <h1>{page.title}</h1>
12
+ <p>{page.description}</p>
13
+ <ul class="feed">
14
+ {#each page.items as item (item.id)}<li><button onclick={() => void app.perform({ kind: "flow", url: `/item/${item.id}` })}>{item.title}</button></li>{/each}
15
+ </ul>
16
+ <section class="locale-info" aria-label="Locale">
17
+ <p><strong>{locale.label}:</strong> {locale.lang}</p>
18
+ <p>{locale.badge}</p>
19
+ <p>{locale.hint}</p>
20
+ </section>
21
+ </section>
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import type { ErrorPage } from "../lib/models/page";
3
+ let { page }: { page: ErrorPage } = $props();
4
+ </script>
5
+
6
+ <section class="page">
7
+ <h1>{page.title}</h1>
8
+ <p>{page.description}</p>
9
+ <a href="/">← Go Home</a>
10
+ </section>
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import type { NotesPage } from "../lib/models/page";
3
+ let { page }: { page: NotesPage } = $props();
4
+ </script>
5
+
6
+ <section class="page">
7
+ <h1>{page.title}</h1>
8
+ <p>{page.description}</p>
9
+ <!-- The entry owns this draft; data-restore-root also restores it after reload. -->
10
+ <div class="draft" data-restore-root><label>Notes draft<textarea name="notes" rows={6} placeholder="Write a note; switch tabs or reload to restore it"></textarea></label></div>
11
+ </section>
@@ -1,21 +1,13 @@
1
- import { createSSRRender, serializeServerData } from "@finesoft/front";
2
- import { bootstrap } from "./bootstrap";
3
- import { renderApp } from "./lib/render";
1
+ import { render as renderSvelte } from "svelte/server";
2
+ import { createSSRRender } from "@finesoft/front";
3
+ import { app } from "./app-definition";
4
+ import App from "./App.svelte";
4
5
 
5
6
  export const render = createSSRRender({
6
- bootstrap,
7
- frameworkConfig: {
8
- locale: "zh-Hans",
7
+ definition: app,
8
+ render: (app) => {
9
+ const result = renderSvelte(App, { props: { app } });
10
+ return { html: result.body, head: result.head, css: "" };
9
11
  },
10
- getErrorPage(status, message) {
11
- return {
12
- id: "error",
13
- pageType: "error",
14
- title: `Error ${status}`,
15
- description: message,
16
- };
17
- },
18
- renderApp,
19
12
  });
20
-
21
- export { serializeServerData };
13
+ export { serializeServerData } from "@finesoft/front";
@@ -0,0 +1,74 @@
1
+ :root {
2
+ font-family: system-ui, sans-serif;
3
+ color: #202124;
4
+ background: #fff;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ }
10
+ button,
11
+ input,
12
+ textarea {
13
+ font: inherit;
14
+ }
15
+ button {
16
+ cursor: pointer;
17
+ }
18
+
19
+ .app-chrome,
20
+ .page {
21
+ box-sizing: border-box;
22
+ max-width: 34rem;
23
+ margin: 0 auto;
24
+ padding: 1rem;
25
+ }
26
+
27
+ .profile,
28
+ .tabs {
29
+ display: flex;
30
+ gap: 0.5rem;
31
+ align-items: center;
32
+ margin-bottom: 1rem;
33
+ }
34
+
35
+ .profile label {
36
+ flex: 1;
37
+ }
38
+ .profile input {
39
+ display: block;
40
+ width: 100%;
41
+ box-sizing: border-box;
42
+ }
43
+ .tabs button[aria-current="true"] {
44
+ font-weight: 700;
45
+ }
46
+ .feed {
47
+ list-style: none;
48
+ padding: 0;
49
+ }
50
+ .feed li + li {
51
+ margin-top: 0.5rem;
52
+ }
53
+ .feed button {
54
+ width: 100%;
55
+ padding: 0.75rem;
56
+ text-align: left;
57
+ }
58
+ .draft label,
59
+ .draft input,
60
+ .draft textarea {
61
+ display: block;
62
+ width: 100%;
63
+ box-sizing: border-box;
64
+ }
65
+ .draft input,
66
+ .draft textarea {
67
+ margin-top: 0.5rem;
68
+ padding: 0.5rem;
69
+ }
70
+ .locale-info {
71
+ margin-top: 2rem;
72
+ color: #555;
73
+ font-size: 0.875rem;
74
+ }
@@ -0,0 +1,6 @@
1
+ import Home from "./pages/Home.svelte";
2
+ import Detail from "./pages/Detail.svelte";
3
+ import Notes from "./pages/Notes.svelte";
4
+ import NotFound from "./pages/NotFound.svelte";
5
+
6
+ export const views = { home: Home, detail: Detail, notes: Notes, error: NotFound };
@@ -0,0 +1,3 @@
1
+ declare module "virtual:finesoft-front/i18n-loader" {
2
+ export const loadMessages: import("@finesoft/front").MessagesLoader;
3
+ }
@@ -0,0 +1,2 @@
1
+ /** Standard Svelte defaults; compiler configuration belongs to the Vite plugin. */
2
+ export default {};
@@ -4,19 +4,24 @@
4
4
  "target": "ESNext",
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "bundler",
7
+ "allowImportingTsExtensions": true,
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
7
11
  "esModuleInterop": true,
8
12
  "forceConsistentCasingInFileNames": true,
9
13
  "skipLibCheck": true,
10
14
  "noEmit": true,
15
+ "rootDir": ".",
11
16
  "paths": {
12
- "@/*": ["./src/*"],
13
- "@finesoft/front": ["../../packages/front/src/index.ts"],
14
- "@finesoft/front/browser": ["../../packages/front/src/browser.ts"],
15
- "@finesoft/core": ["../../packages/core/src/index.ts"],
16
- "@finesoft/browser": ["../../packages/browser/src/index.ts"],
17
- "@finesoft/ssr": ["../../packages/ssr/src/index.ts"],
18
- "@finesoft/server": ["../../packages/server/src/index.ts"]
17
+ "@finesoft/front": [
18
+ "./.finesoft/front.d.ts"
19
+ ]
19
20
  }
20
21
  },
21
- "include": ["src/**/*", "env.d.ts", "vite.config.ts"]
22
+ "include": [
23
+ "src/**/*",
24
+ "env.d.ts",
25
+ "vite.config.ts"
26
+ ]
22
27
  }
@@ -1,15 +1,14 @@
1
1
  import { finesoftFrontViteConfig } from "@finesoft/front";
2
2
  import { svelte } from "@sveltejs/vite-plugin-svelte";
3
- import { defineConfig } from "vite-plus";
3
+ import { defineConfig, lazyPlugins } from "vite-plus";
4
+
5
+ const front = finesoftFrontViteConfig({
6
+ controllerTypes: { root: import.meta.dirname },
7
+ ssr: { entry: "src/ssr.ts" },
8
+ i18n: { messagesDir: "src/locales" },
9
+ });
4
10
 
5
11
  export default defineConfig({
6
- plugins: [
7
- svelte(),
8
- finesoftFrontViteConfig({
9
- ssr: { entry: "src/ssr.ts" },
10
- i18n: {
11
- messagesDir: "src/locales",
12
- },
13
- }),
14
- ],
12
+ lint: { options: { typeAware: true, typeCheck: true } },
13
+ plugins: lazyPlugins(() => [front, svelte()]),
15
14
  });
@@ -0,0 +1,35 @@
1
+ # Finesoft Front full application
2
+
3
+ Home, product detail, search and About demonstrate SSR, CSR, URL actions, typed route parameters, DI, data mapping and guards. All three native frameworks have the same example.
4
+
5
+ ## Run
6
+
7
+ Node must satisfy `^22.18.0 || >=24.11.0`. Use Vite+ for tooling.
8
+
9
+ ```sh
10
+ vp install
11
+ vp run dev
12
+ vp run build
13
+ vp run preview
14
+ ```
15
+
16
+ Visit `/`, open a product, try `/search?q=Vite`, and navigate to `/about` (CSR). `/admin` demonstrates an authentication guard: without an `auth_token` cookie it redirects to `/login?from=...`. Add your real login page and authentication service when adopting this example.
17
+
18
+ ## Server controller example
19
+
20
+ `src/lib/controllers/product-detail.ts` extends `BaseServerController` from `@finesoft/front`. Open `/products/1`, then navigate to another product. The controller reads the incoming `last_product` cookie and explicitly writes a new HttpOnly cookie when the product changes. It also sets `Cache-Control: private, no-store`. This is an application-owned browsing preference; the framework provides request/response tools and does not manage login state or automatically forward credentials.
21
+
22
+ SSR executes the controller directly. Browser navigation invokes it on the server, and hydration reuses the SSR result. Its implementation stays out of the browser bundle; `markPublic` selects the page data sent to the browser. Home, search and About keep ordinary `BaseController` examples. Route types still come from `src/app-definition.ts`; the Vite plugin maintains the generated `Input` / `Failure` types and provides the server context automatically.
23
+
24
+ Keep the configured SSR entry and deploy to a request host such as Node or Worker. Serving only `dist/client` cannot execute this controller. The template runs locally without external API credentials.
25
+
26
+ ## Edit
27
+
28
+ - `src/config.ts`: application identity and persistence namespace.
29
+ - `src/app-definition.ts`: pages, routes, guards and navigation.
30
+ - `src/lib/controllers/` and `src/lib/models/`: page loading and typed public data.
31
+ - `src/pages/`, `src/App.*`, `src/styles.css`: native UI and presentation.
32
+ - `src/views.ts`: the view registry shared by browser and server.
33
+ - `src/main.ts` and `src/ssr.ts`: standard browser and SSR startup.
34
+
35
+ All framework APIs import from `@finesoft/front`. Select the native component with `Outlet("react")`, `Outlet("vue")`, or `Outlet("svelte")`, and pass the same renderer to `useSnapshot`. The plugin maintains `.finesoft/front.d.ts` and the exact TypeScript import mapping; install only the chosen renderer. For standalone TypeScript checks, first run `vp exec finesoft-types`. The project is standalone and requires no sibling templates or private runtime packages.
@@ -6,8 +6,7 @@
6
6
  <!--ssr-head-->
7
7
  </head>
8
8
  <body>
9
- <div id="app"><!--ssr-body--></div>
10
- <!--ssr-data-->
9
+ <div id="app"><!--ssr-body--><!--ssr-data--></div>
11
10
  <script type="module" src="/src/main.ts"></script>
12
11
  </body>
13
12
  </html>
@@ -8,12 +8,16 @@
8
8
  "preview": "vp preview"
9
9
  },
10
10
  "dependencies": {
11
- "@finesoft/front": "^0.5.1",
12
- "vue": "^3.5.0"
11
+ "@finesoft/front": "^0.5.3",
12
+ "vue": "^3.5.41"
13
13
  },
14
14
  "devDependencies": {
15
- "@vitejs/plugin-vue": "^5.0.0",
16
- "vite": "npm:@voidzero-dev/vite-plus-core@0.1.16",
17
- "vite-plus": "0.1.24"
15
+ "@hono/node-server": "^2.1.0",
16
+ "@types/node": "^22.20.1",
17
+ "@vitejs/plugin-vue": "^6.0.8",
18
+ "hono": "^4.13.1",
19
+ "typescript": "^6.0.3",
20
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
21
+ "vite-plus": "0.2.8"
18
22
  }
19
23
  }
@@ -1,29 +1,14 @@
1
1
  <script setup lang="ts">
2
- import type { Action } from "@finesoft/front";
3
- import { computed } from "vue";
2
+ import { Outlet as selectOutlet, useSnapshot, type WebAppView } from "@finesoft/front";
4
3
  import Layout from "./components/Layout.vue";
5
- import Loading from "./components/Loading.vue";
6
- import PageRenderer from "./components/PageRenderer.vue";
7
- import type { AppPage } from "./lib/models/product";
8
-
9
- // state: reactive object for CSR; page: initial prop for SSR
10
- const {
11
- state,
12
- page: ssrPage,
13
- onAction,
14
- } = defineProps<{
15
- state?: { page: AppPage | null; loading: boolean };
16
- page?: AppPage;
17
- onAction?: (action: Action) => void;
18
- }>();
19
-
20
- const currentPage = computed(() => state?.page ?? ssrPage ?? null);
21
- const loading = computed(() => state?.loading ?? false);
4
+ import { views } from "./views";
5
+ const Outlet = selectOutlet("vue");
6
+ const { app } = defineProps<{ app: WebAppView }>();
7
+ const snapshot = useSnapshot("vue", app);
22
8
  </script>
23
9
 
24
10
  <template>
25
- <Layout :current-path="currentPage?.url ?? '/'" :on-action="onAction">
26
- <Loading v-if="loading" />
27
- <PageRenderer v-else-if="currentPage" :page="currentPage" :on-action="onAction" />
11
+ <Layout :current-path="snapshot.entries.find((entry) => entry.visible)?.page.url ?? '/'">
12
+ <Outlet :app="app" :views="views" />
28
13
  </Layout>
29
14
  </template>
@@ -0,0 +1,35 @@
1
+ import { appId } from "./config";
2
+ import { definePage, defineWebApp, int, str, withDefault } from "@finesoft/front";
3
+ import { AboutController } from "./lib/controllers/about";
4
+ import { HomeController } from "./lib/controllers/home";
5
+ import { ProductDetailController } from "./lib/controllers/product-detail";
6
+ import { SearchController } from "./lib/controllers/search";
7
+ import { authGuard } from "./lib/guards/auth";
8
+ import { seoGuard } from "./lib/guards/seo";
9
+ import { getErrorPage } from "./lib/controllers/error";
10
+ export const homePage = definePage({
11
+ id: "home",
12
+ create: () => new HomeController(),
13
+ routes: ["/", { path: "/admin", beforeLoad: [authGuard] }],
14
+ });
15
+ export const productDetailPage = definePage({
16
+ id: "product-detail",
17
+ routes: [{ path: "/products/:id", params: { id: int() } }],
18
+ create: () => new ProductDetailController(),
19
+ });
20
+ export const searchPage = definePage({
21
+ id: "search",
22
+ create: () => new SearchController(),
23
+ routes: [{ path: "/search", query: { q: withDefault(str(), "") } }],
24
+ });
25
+ export const aboutPage = definePage({
26
+ id: "about",
27
+ create: () => new AboutController(),
28
+ routes: [{ path: "/about", renderMode: "csr" }],
29
+ });
30
+ export const app = defineWebApp({
31
+ id: appId,
32
+ pages: [homePage, productDetailPage, searchPage, aboutPage],
33
+ getErrorPage,
34
+ afterLoad: [seoGuard],
35
+ });
@@ -1,17 +1,15 @@
1
1
  <script setup lang="ts">
2
- import type { Action } from "@finesoft/front";
3
2
  import Navigation from "./Navigation.vue";
4
3
 
5
- const { currentPath = "/", onAction } = defineProps<{
4
+ const { currentPath = "/" } = defineProps<{
6
5
  currentPath?: string;
7
- onAction?: (action: Action) => void;
8
6
  }>();
9
7
  </script>
10
8
 
11
9
  <template>
12
- <div>
13
- <Navigation :current-path="currentPath" :on-action="onAction" />
14
- <main style="padding: 1rem">
10
+ <div class="app-shell">
11
+ <Navigation :current-path="currentPath" />
12
+ <main class="app-main">
15
13
  <slot />
16
14
  </main>
17
15
  </div>
@@ -1,3 +1,3 @@
1
1
  <template>
2
- <div style="text-align: center; padding: 2rem; color: #999">Loading…</div>
2
+ <div class="loading">Loading…</div>
3
3
  </template>
@@ -1,58 +1,25 @@
1
1
  <script setup lang="ts">
2
- import { makeExternalUrlAction, makeFlowAction, type Action } from "@finesoft/front";
3
-
4
- const { currentPath = "/", onAction } = defineProps<{
2
+ const { currentPath = "/" } = defineProps<{
5
3
  currentPath?: string;
6
- onAction?: (action: Action) => void;
7
4
  }>();
8
-
9
5
  const links = [
10
- { label: "Home", action: makeFlowAction("/"), path: "/" },
11
- { label: "Search", action: makeFlowAction("/search"), path: "/search" },
12
- { label: "About", action: makeFlowAction("/about"), path: "/about" },
13
- { label: "GitHub", action: makeExternalUrlAction("https://github.com"), path: null as null },
14
- ];
15
-
16
- function handleClick(action: Action) {
17
- return (e: MouseEvent) => {
18
- if (onAction) {
19
- e.preventDefault();
20
- onAction(action);
21
- }
22
- };
23
- }
6
+ { label: "Home", href: "/" },
7
+ { label: "Search", href: "/search" },
8
+ { label: "About", href: "/about" },
9
+ { label: "GitHub", href: "https://github.com/nicepkg/finesoft" },
10
+ ] as const;
24
11
  </script>
25
12
 
26
13
  <template>
27
- <nav>
14
+ <nav class="navigation">
28
15
  <a
29
16
  v-for="link in links"
30
17
  :key="link.label"
31
- :href="link.action.url"
32
- :class="{ active: link.path !== null && currentPath === link.path }"
33
- @click="handleClick(link.action)"
18
+ :href="link.href"
19
+ class="navigation-link"
20
+ :class="{ active: currentPath === link.href }"
34
21
  >
35
22
  {{ link.label }}
36
23
  </a>
37
24
  </nav>
38
25
  </template>
39
-
40
- <style scoped>
41
- nav {
42
- display: flex;
43
- gap: 1rem;
44
- padding: 1rem;
45
- border-bottom: 1px solid #eee;
46
- }
47
- a {
48
- text-decoration: none;
49
- color: #333;
50
- }
51
- a:hover {
52
- color: #007bff;
53
- }
54
- a.active {
55
- color: #007bff;
56
- font-weight: bold;
57
- }
58
- </style>
@@ -1,39 +1,15 @@
1
1
  <script setup lang="ts">
2
- import type { Action } from "@finesoft/front";
3
2
  import type { ProductItem } from "../lib/models/product";
4
3
 
5
- const { item, onAction } = defineProps<{
4
+ const { item } = defineProps<{
6
5
  item: ProductItem;
7
- onAction?: (action: Action) => void;
8
6
  }>();
9
-
10
- function handleClick(e: MouseEvent) {
11
- if (onAction && item.clickAction) {
12
- e.preventDefault();
13
- onAction(item.clickAction);
14
- }
15
- }
16
7
  </script>
17
8
 
18
9
  <template>
19
- <div class="product-card">
10
+ <article class="product-card">
20
11
  <h3>{{ item.name }}</h3>
21
12
  <p class="price">${{ item.price.toFixed(2) }}</p>
22
- <a v-if="item.clickAction" :href="item.clickAction.url" @click="handleClick">
23
- View Details →
24
- </a>
25
- </div>
13
+ <a v-if="item.clickAction" :href="item.clickAction.url"> View Details → </a>
14
+ </article>
26
15
  </template>
27
-
28
- <style scoped>
29
- .product-card {
30
- border: 1px solid #eee;
31
- border-radius: 8px;
32
- padding: 1rem;
33
- min-width: 200px;
34
- }
35
- .price {
36
- color: #007bff;
37
- font-weight: bold;
38
- }
39
- </style>
@@ -0,0 +1,2 @@
1
+ /** App identity also namespaces persisted browser sessions. */
2
+ export const appId = "vue";
@@ -1,18 +1,23 @@
1
- import { BaseController } from "@finesoft/front";
1
+ import { BaseController, type ControllerInput } from "@finesoft/front";
2
+ import { markPublic } from "@finesoft/front";
2
3
  import type { AboutPage } from "../models/product";
3
4
 
4
- export class AboutController extends BaseController<Record<string, string>, AboutPage> {
5
- readonly intentId = "about";
6
-
5
+ export class AboutController extends BaseController<
6
+ ControllerInput<Record<string, string>>,
7
+ AboutPage
8
+ > {
7
9
  execute(): AboutPage {
8
- return {
9
- id: "about",
10
- pageType: "about",
11
- url: "/about",
12
- title: "About",
13
- description: "About Finesoft Front",
14
- content:
15
- "Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
16
- };
10
+ return markPublic(
11
+ {
12
+ id: "about",
13
+ pageType: "about",
14
+ title: "About",
15
+ description: "About Finesoft Front",
16
+ url: "/about",
17
+ content:
18
+ "Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
19
+ },
20
+ ["content"],
21
+ );
17
22
  }
18
23
  }
@@ -1,11 +1,15 @@
1
+ import { markPublic } from "@finesoft/front";
1
2
  import type { ErrorPage } from "../models/product";
2
3
 
3
4
  export function getErrorPage(status: number, message: string): ErrorPage {
4
- return {
5
- id: "error",
6
- pageType: "error",
7
- title: `Error ${status}`,
8
- description: message,
9
- status,
10
- };
5
+ return markPublic(
6
+ {
7
+ id: "error",
8
+ pageType: "error",
9
+ title: `Error ${status}`,
10
+ description: message,
11
+ status,
12
+ },
13
+ ["status"],
14
+ );
11
15
  }