@akanjs/cli 3.0.0-alpha.11 → 3.0.0-alpha.111

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 (140) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-mjh5ppsq.js} +3 -5
  5. package/{agent.command-a3n15c6t.js → agent.command-d2gfxfck.js} +11 -13
  6. package/{application.command-rdm3et0g.js → application.command-pj2m7584.js} +65 -38
  7. package/{applicationBuildRunner-6m8n71w4.js → applicationBuildRunner-kxwbssgn.js} +27 -23
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-4yasxwv9.js} +8 -9
  9. package/buildBatch.proc.js +12 -14
  10. package/capacitorApp-36rbr858.js +12 -0
  11. package/{cloud.command-56aeekqn.js → cloud.command-gxqp8xdv.js} +15 -18
  12. package/commandManifest.json +1 -1
  13. package/{context.command-ysxawe7h.js → context.command-ez8a0zyz.js} +19 -23
  14. package/dependencyScanner-shs82ynp.js +8 -0
  15. package/devTui-40ggmd99.js +553 -0
  16. package/devTuiView-c08ccbat.js +9 -0
  17. package/getDirname-dr3jz1xg.js +7 -0
  18. package/{guideline.command-yqf9a372.js → guideline.command-xmqwx8zm.js} +24 -22
  19. package/guidelines/agentRule/agentRule.generate.json +31 -0
  20. package/guidelines/agentRule/agentRule.instruction.md +531 -0
  21. package/guidelines/conventions/conventions.instruction.md +493 -369
  22. package/guidelines/framework/framework.generate.json +3 -2
  23. package/guidelines/framework/framework.instruction.md +0 -9
  24. package/guidelines/mcpRule/mcpRule.generate.json +35 -0
  25. package/guidelines/mcpRule/mcpRule.instruction.md +460 -0
  26. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  27. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  28. package/guidelines/queryRule/queryRule.generate.json +31 -0
  29. package/guidelines/queryRule/queryRule.instruction.md +178 -0
  30. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  31. package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -0
  32. package/guidelines/ssrRule/ssrRule.instruction.md +51 -5
  33. package/guidelines/transportRule/transportRule.generate.json +31 -0
  34. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  35. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +54 -764
  36. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  37. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +467 -0
  38. package/incrementalBuilder.proc.js +26 -25
  39. package/{index-8pd02prk.js → index-259dsnz3.js} +58 -25
  40. package/{index-yzsxc30b.js → index-2tv3fdx4.js} +975 -234
  41. package/index-3d9y066c.js +76 -0
  42. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  43. package/{index-m90fn8xe.js → index-4edzf1gr.js} +32 -32
  44. package/{index-3789fdpg.js → index-600zhnve.js} +94 -61
  45. package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
  46. package/{index-cv9cxs5k.js → index-7737gxcp.js} +18 -18
  47. package/{index-a5cs7d8r.js → index-8few9nyh.js} +13 -13
  48. package/{index-mewacxye.js → index-8pm30m73.js} +57 -57
  49. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  50. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  51. package/{index-cav1azma.js → index-a64esmtb.js} +1106 -1369
  52. package/{index-nwqqssnb.js → index-aj7tyh5w.js} +26 -23
  53. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  54. package/{index-yzmqvg23.js → index-fz5q3gd7.js} +8 -8
  55. package/{index-kcsa1zff.js → index-gyd220p2.js} +4 -4
  56. package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
  57. package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
  58. package/index-j9yhg404.js +159 -0
  59. package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
  60. package/{index-0cj2zxbm.js → index-kn1h7p1x.js} +12 -11
  61. package/{index-e4b2ae6b.js → index-ktb5njgy.js} +41 -34
  62. package/{index-etwh7sd3.js → index-mak3cd28.js} +100 -32
  63. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  64. package/{index-z1s5f4e0.js → index-ne2qy0nk.js} +36 -37
  65. package/{index-65aeh789.js → index-pnqbkf5p.js} +1 -1
  66. package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
  67. package/{index-4mgkgba3.js → index-rwg2aa4f.js} +14 -14
  68. package/{index-bzznxgcx.js → index-v0qh106m.js} +1002 -128
  69. package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
  70. package/{index-h5whaj26.js → index-ws5e0j51.js} +16 -12
  71. package/{index-9va8zayb.js → index-wt7qrtd4.js} +27 -27
  72. package/index.js +22 -25
  73. package/{library.command-x6t29a30.js → library.command-nbsky672.js} +11 -10
  74. package/{localRegistry.command-649g1n7b.js → localRegistry.command-ts6t5e5a.js} +20 -23
  75. package/{module.command-8w8ys5t3.js → module.command-29vqzrh8.js} +13 -16
  76. package/{package.command-zgy7f3fz.js → package.command-axv0abmn.js} +8 -10
  77. package/package.json +5 -7
  78. package/{page.command-yjmnkyk2.js → page.command-vs9s3zrh.js} +7 -9
  79. package/{primitive.command-gfqaagsg.js → primitive.command-cbxmqbtc.js} +13 -16
  80. package/{quality.command-ec8r1dn3.js → quality.command-gmbs7nws.js} +184 -334
  81. package/recipeScanner-fpagtgc1.js +11 -0
  82. package/{repair.command-p9ey6mkm.js → repair.command-bqsjptsj.js} +12 -14
  83. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-apyqw7wk.js} +90 -19
  84. package/{scalar.command-9dhmqhtm.js → scalar.command-6k1vhe9s.js} +11 -14
  85. package/subspace.command-35a3t6vv.js +755 -0
  86. package/templates/app/env/env.client.debug.ts.template +1 -5
  87. package/templates/app/env/env.client.develop.ts.template +1 -5
  88. package/templates/app/env/env.client.local.ts.template +1 -5
  89. package/templates/app/env/env.client.main.ts.template +1 -5
  90. package/templates/app/env/env.client.testing.ts.template +1 -5
  91. package/templates/app/env/env.client.type.ts +2 -6
  92. package/templates/app/env/env.server.debug.ts.template +1 -5
  93. package/templates/app/env/env.server.develop.ts.template +1 -5
  94. package/templates/app/env/env.server.local.ts.template +1 -5
  95. package/templates/app/env/env.server.main.ts.template +1 -5
  96. package/templates/app/main.ts +3 -1
  97. package/templates/app/page/_index.tsx +3 -3
  98. package/templates/app/page/_layout.tsx +31 -32
  99. package/templates/appSample/lib/task/task.signal.ts +11 -6
  100. package/templates/appSample/page/task/[taskId]/_index.tsx +22 -27
  101. package/templates/appSample/page/task/[taskId]/edit.tsx +25 -30
  102. package/templates/appSample/page/task/_index.tsx +3 -7
  103. package/templates/appSample/page/task/_layout.tsx +4 -15
  104. package/templates/appSample/page/task/new.tsx +4 -10
  105. package/templates/appSample/srvkit/AuthGuard.ts +2 -2
  106. package/templates/crudPages/[__model__Id]/edit/page.tsx +27 -29
  107. package/templates/crudPages/[__model__Id]/page.tsx +24 -32
  108. package/templates/crudPages/new/page.tsx +26 -25
  109. package/templates/crudPages/page.tsx +27 -26
  110. package/templates/crudSinglePage/page.tsx +26 -21
  111. package/templates/env/_env.server.type.ts +7 -5
  112. package/templates/lib/db.ts +1 -1
  113. package/templates/lib/dict.ts +1 -1
  114. package/templates/lib/sig.ts +5 -1
  115. package/templates/lib/srv.ts +6 -2
  116. package/templates/module/__model__.dictionary.ts +4 -1
  117. package/templates/module/__model__.signal.ts +2 -3
  118. package/templates/module/__model__.store.ts +1 -2
  119. package/templates/workspaceRoot/.gitignore.template +0 -1
  120. package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
  121. package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
  122. package/templates/workspaceRoot/AGENTS.md.template +4 -0
  123. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  124. package/templates/workspaceRoot/biome.json.template +1 -1
  125. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  126. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  127. package/templates/workspaceRoot/package.json.template +2 -4
  128. package/typeChecker-3xgrzc2v.js +7 -0
  129. package/typecheck.proc.js +4 -5
  130. package/{workflow.command-ycwc6zr6.js → workflow.command-nwpcdc8p.js} +22 -25
  131. package/{workspace.command-xhyhajxa.js → workspace.command-aczjxzbf.js} +64 -66
  132. package/capacitorApp-mr81qdxq.js +0 -61
  133. package/dependencyScanner-f6grrep3.js +0 -9
  134. package/getDirname-m9s0cmhq.js +0 -8
  135. package/index-mxvakhsm.js +0 -66
  136. package/index-r24hmh0q.js +0 -4
  137. package/recipeScanner-tghkzrrt.js +0 -12
  138. package/typeChecker-kravn7ns.js +0 -8
  139. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  140. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { AppClientEnv } from "./env.client.type";
4
2
 
5
- export const env: AppClientEnv = {
6
- ...getEnv(),
7
- } as const;
3
+ export const env: AppClientEnv = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { AppClientEnv } from "./env.client.type";
4
2
 
5
- export const env: AppClientEnv = {
6
- ...getEnv(),
7
- } as const;
3
+ export const env: AppClientEnv = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { AppClientEnv } from "./env.client.type";
4
2
 
5
- export const env: AppClientEnv = {
6
- ...getEnv(),
7
- } as const;
3
+ export const env: AppClientEnv = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { AppClientEnv } from "./env.client.type";
4
2
 
5
- export const env: AppClientEnv = {
6
- ...getEnv(),
7
- } as const;
3
+ export const env: AppClientEnv = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { AppClientEnv } from "./env.client.type";
4
2
 
5
- export const env: AppClientEnv = {
6
- ...getEnv(),
7
- } as const;
3
+ export const env: AppClientEnv = {};
@@ -5,12 +5,8 @@ interface Dict {
5
5
  }
6
6
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict = {}) {
7
7
  return `
8
- import type { ClientEnv } from "akanjs/base";
9
-
10
- export type AppClientEnv = ClientEnv & {
11
- cloudflare?: {
12
- siteKey: string;
13
- };
8
+ export type AppClientEnv = {
9
+ // apply public envs here
14
10
  };
15
11
  `;
16
12
  }
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { ModulesOptions } from "../lib/option";
4
2
 
5
- export const env: ModulesOptions = {
6
- ...getEnv(),
7
- };
3
+ export const env: ModulesOptions = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { ModulesOptions } from "../lib/option";
4
2
 
5
- export const env: ModulesOptions = {
6
- ...getEnv(),
7
- };
3
+ export const env: ModulesOptions = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { ModulesOptions } from "../lib/option";
4
2
 
5
- export const env: ModulesOptions = {
6
- ...getEnv(),
7
- };
3
+ export const env: ModulesOptions = {};
@@ -1,7 +1,3 @@
1
- import { getEnv } from "akanjs/base";
2
-
3
1
  import type { ModulesOptions } from "../lib/option";
4
2
 
5
- export const env: ModulesOptions = {
6
- ...getEnv(),
7
- };
3
+ export const env: ModulesOptions = {};
@@ -5,7 +5,9 @@ interface Dict {
5
5
  }
6
6
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
7
7
  return `
8
- import { AkanApp } from "akanjs/server";
8
+ // Not the \`akanjs/server\` barrel: it re-exports AkanServer, whose graph the gateway never runs. Through the
9
+ // barrel this process evaluates 35MB of SSR renderer and DB driver to spawn children and relay bytes.
10
+ import { AkanApp } from "akanjs/server/akanApp";
9
11
 
10
12
  const run = async () => {
11
13
  await new AkanApp().start();
@@ -8,7 +8,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
8
8
  filename: "_index.tsx",
9
9
  content: `
10
10
  import { getEnv } from "akanjs/base";
11
- import { usePage } from "akanjs/client";
11
+ import { page, usePage } from "akanjs/client";
12
12
  import { Link, System } from "akanjs/ui";
13
13
  import {
14
14
  FaBookOpen,
@@ -22,7 +22,7 @@ import {
22
22
  FaTerminal,
23
23
  } from "react-icons/fa";
24
24
 
25
- export default function Page() {
25
+ export default page().render(() => {
26
26
  const appName = getEnv().appName;
27
27
  const { l } = usePage();
28
28
  return (
@@ -294,6 +294,6 @@ export default function Page() {
294
294
  </section>
295
295
  </main>
296
296
  );
297
- }`,
297
+ });`,
298
298
  };
299
299
  }
@@ -9,41 +9,40 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
9
9
  filename: "_layout.tsx",
10
10
  content: `
11
11
  import "./styles.css";
12
- import type { Font, LayoutProps } from "akanjs/client";
12
+ import { rootLayout } from "akanjs/client";
13
13
  ${isUsingShared ? "import { Auth } from '@shared/ui';" : ""}
14
14
 
15
- export const fonts: Font[] = [
16
- {
17
- name: "pretendard",
18
- default: true,
19
- paths: [
20
- { src: "/fonts/Pretendard-Thin.woff2", weight: 100 },
21
- { src: "/fonts/Pretendard-ExtraLight.woff2", weight: 200 },
22
- { src: "/fonts/Pretendard-Light.woff2", weight: 300 },
23
- { src: "/fonts/Pretendard-Regular.woff2", weight: 400 },
24
- { src: "/fonts/Pretendard-Medium.woff2", weight: 500 },
25
- { src: "/fonts/Pretendard-SemiBold.woff2", weight: 600 },
26
- { src: "/fonts/Pretendard-Bold.woff2", weight: 700 },
27
- { src: "/fonts/Pretendard-ExtraBold.woff2", weight: 800 },
28
- { src: "/fonts/Pretendard-Black.woff2", weight: 900 },
29
- ],
30
- },
31
- ];
32
-
33
- export const head = (
34
- <>
35
- <title>${dict.appName}</title>
36
- <link rel="icon" href="/favicon.ico" />
37
- </>
38
- );
39
-
40
- export default function Layout({ children }: LayoutProps) {
41
- return (
15
+ export default rootLayout()
16
+ .fonts([
17
+ {
18
+ name: "pretendard",
19
+ default: true,
20
+ paths: [
21
+ { src: "/fonts/Pretendard-Thin.woff2", weight: 100 },
22
+ { src: "/fonts/Pretendard-ExtraLight.woff2", weight: 200 },
23
+ { src: "/fonts/Pretendard-Light.woff2", weight: 300 },
24
+ { src: "/fonts/Pretendard-Regular.woff2", weight: 400 },
25
+ { src: "/fonts/Pretendard-Medium.woff2", weight: 500 },
26
+ { src: "/fonts/Pretendard-SemiBold.woff2", weight: 600 },
27
+ { src: "/fonts/Pretendard-Bold.woff2", weight: 700 },
28
+ { src: "/fonts/Pretendard-ExtraBold.woff2", weight: 800 },
29
+ { src: "/fonts/Pretendard-Black.woff2", weight: 900 },
30
+ ],
31
+ },
32
+ ])
33
+ .head(
42
34
  <>
43
- {children}${isUsingShared ? "\n <Auth.User />\n <Auth.Admin />" : ""}
44
- </>
45
- );
46
- }
35
+ <title>${dict.appName}</title>
36
+ <link rel="icon" href="/favicon.ico" />
37
+ </>,
38
+ )
39
+ .render(({ children }) => {
40
+ return (
41
+ <>
42
+ {children}${isUsingShared ? "\n <Auth.User />\n <Auth.Admin />" : ""}
43
+ </>
44
+ );
45
+ });
47
46
  `,
48
47
  };
49
48
  }
@@ -1,7 +1,8 @@
1
1
  import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
- return `import { endpoint, internal, Public, slice } from "akanjs/signal";
4
+ return `import { SignedIn } from "@apps/${dict.appName}/srvkit";
5
+ import { endpoint, internal, None, Public, slice } from "akanjs/signal";
5
6
 
6
7
  import * as cnst from "../cnst";
7
8
  import * as srv from "../srv";
@@ -21,20 +22,24 @@ import * as srv from "../srv";
21
22
 
22
23
  export class TaskInternal extends internal(srv.task, ({ interval }) => ({})) {}
23
24
 
24
- export class TaskSlice extends slice(srv.task, { guards: { root: Public, get: Public, cru: Public } }, (init) => ({
25
+ // Guards, per key: root = the generated admin API (runs any filter with caller-supplied args), get = reads,
26
+ // cru = create/update/remove. root and cru are never Public; None here means "nobody until you name a guard".
27
+ export class TaskSlice extends slice(srv.task, { guards: { root: None, get: Public, cru: SignedIn } }, (init) => ({
25
28
  inPublic: init().exec(function () {
26
29
  return this.taskService.queryAny();
27
30
  }),
28
31
  })) {}
29
32
 
33
+ // Every custom endpoint names its own guards array — the slice's map above only covers generated CRUD.
34
+ // It is also the MCP decision: an endpoint declaring no guard is not published to agents at all.
30
35
  export class TaskEndpoint extends endpoint(srv.task, ({ mutation }) => ({
31
- startTask: mutation(cnst.Task)
36
+ startTask: mutation(cnst.Task, { guards: [SignedIn] })
32
37
  .param("taskId", String)
33
38
  .exec(async function (taskId) {
34
39
  return await this.taskService.startTask(taskId);
35
40
  }),
36
41
 
37
- completeTask: mutation(cnst.Task)
42
+ completeTask: mutation(cnst.Task, { guards: [SignedIn] })
38
43
  .param("taskId", String)
39
44
  .exec(async function (taskId) {
40
45
  return await this.taskService.completeTask(taskId);
@@ -48,7 +53,7 @@ export class TaskEndpoint extends endpoint(srv.task, ({ mutation }) => ({
48
53
  // taskEvent: pubsub(cnst.Task).exec(...),
49
54
  // // search: search parameters (optional, URL query string)
50
55
  // searchTask: query(cnst.LightTask).search("keyword", String).exec(...),
51
- // // guards: authentication/authorization guards
52
- // // export class TaskEndpoint extends endpoint(srv.task, { guards: { root: SignedIn } }, ({ query, mutation }) => ({})) {}
56
+ // // mcp: false keeps a guarded endpoint off the agent shelf without changing its authorization
57
+ // startTask: mutation(cnst.Task, { guards: [SignedIn], mcp: false })...
53
58
  `;
54
59
  }
@@ -3,37 +3,32 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "_index.tsx",
6
- content: `
7
- import { fetch, usePage } from "@apps/${dict.appName}/client";
6
+ content: `import { fetch, usePage } from "@apps/${dict.appName}/client";
8
7
  import { Task } from "@apps/${dict.appName}/lib/task";
8
+ import { ID } from "akanjs/base";
9
+ import { page } from "akanjs/client";
9
10
  import { Link, buttonRecipe } from "akanjs/ui";
10
11
 
11
- // ===== page/task/[taskId]/_index.tsx =====
12
- // Convention: Akan.js file-based routing with dynamic segments.
13
- // [taskId] folder = URL path parameter extracted via useParams() from akanjs/client.
14
- // Uses usePage().l() for i18n — the framework convention for dictionary-based translations.
12
+ export default page()
13
+ .param("taskId", ID)
14
+ .render(async ({ taskId }) => {
15
+ const { l } = usePage();
16
+ const { taskView } = await fetch.viewTask(taskId);
17
+ return (
18
+ <main className="mx-auto max-w-4xl px-6 py-8">
19
+ <div className="mb-6 flex items-center justify-between">
20
+ <a href="/task" className={buttonRecipe({ variant: "ghost", size: "sm" })}>
21
+ {l("task.taskBackToTasks")}
22
+ </a>
23
+ <Link href={\`/task/\${taskId}/edit\`} className={buttonRecipe({ variant: "primary", size: "sm" })}>
24
+ {l("task.taskEdit")}
25
+ </Link>
26
+ </div>
15
27
 
16
- interface PageProps {
17
- params: { taskId: string };
18
- }
19
- export default async function Page({ params: { taskId } }: PageProps) {
20
- const { l } = usePage();
21
- const { taskView } = await fetch.viewTask(taskId);
22
- return (
23
- <main className="mx-auto max-w-4xl px-6 py-8">
24
- <div className="mb-6 flex items-center justify-between">
25
- <a href="/task" className={buttonRecipe({ variant: "ghost", size: "sm" })}>
26
- {l("task.taskBackToTasks")}
27
- </a>
28
- <Link href={\`/task/\${taskId}/edit\`} className={buttonRecipe({ variant: "primary", size: "sm" })}>
29
- {l("task.taskEdit")}
30
- </Link>
31
- </div>
32
-
33
- <Task.Zone.View className="max-w-2xl" view={taskView} />
34
- </main>
35
- );
36
- }
28
+ <Task.Zone.View className="max-w-2xl" view={taskView} />
29
+ </main>
30
+ );
31
+ });
37
32
  `,
38
33
  };
39
34
  }
@@ -4,39 +4,34 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
4
4
  return {
5
5
  filename: "edit.tsx",
6
6
  content: `import { fetch, Task } from "@apps/${dict.appName}/client";
7
+ import { ID } from "akanjs/base";
8
+ import { page } from "akanjs/client";
7
9
  import { Load, Link, buttonRecipe } from "akanjs/ui";
8
10
 
9
- // ===== page/task/[taskId]/edit.tsx =====
10
- // Convention: Server-side edit form page using Load.Edit from akanjs/ui.
11
- // async Page() — fetches model data on the server via fetch.editTask(params.taskId).
12
- // The same Template (Task.Template.General) is reused here — no separate edit template needed.
13
- // Load.Edit with type="form" provides the submit/cancel wrapper with the pre-loaded model.
11
+ export default page()
12
+ .param("taskId", ID)
13
+ .render(async ({ taskId }) => {
14
+ const { taskEdit } = await fetch.editTask(taskId);
14
15
 
15
- interface PageProps {
16
- params: { taskId: string };
17
- }
18
- export default async function Page({ params: { taskId } }: PageProps) {
19
- const { taskEdit } = await fetch.editTask(taskId);
20
-
21
- return (
22
- <main className="mx-auto max-w-2xl px-6 py-8">
23
- <div className="mb-6">
24
- <Link href={\`/task/\${taskId}\`} className={buttonRecipe({ variant: "ghost", size: "sm" })}>
25
- ← Back to Task
26
- </Link>
27
- </div>
28
- <div className="mb-6">
29
- <h1 className="font-extrabold text-3xl text-foreground">Edit Task</h1>
30
- <p className="mt-1 text-foreground/60 text-sm">Update the task details</p>
31
- </div>
32
- <div className="rounded-xl border border-foreground/10 bg-background p-6 shadow-sm">
33
- <Load.Edit slice={fetch.slice.taskInPublic} edit={taskEdit} type="form" onSubmit={\`/task/\${taskId}\`}>
34
- <Task.Template.General />
35
- </Load.Edit>
36
- </div>
37
- </main>
38
- );
39
- }
16
+ return (
17
+ <main className="mx-auto max-w-2xl px-6 py-8">
18
+ <div className="mb-6">
19
+ <Link href={\`/task/\${taskId}\`} className={buttonRecipe({ variant: "ghost", size: "sm" })}>
20
+ ← Back to Task
21
+ </Link>
22
+ </div>
23
+ <div className="mb-6">
24
+ <h1 className="font-extrabold text-3xl text-foreground">Edit Task</h1>
25
+ <p className="mt-1 text-foreground/60 text-sm">Update the task details</p>
26
+ </div>
27
+ <div className="rounded-xl border border-foreground/10 bg-background p-6 shadow-sm">
28
+ <Load.Edit slice={fetch.slice.taskInPublic} edit={taskEdit} type="form" onSubmit={\`/task/\${taskId}\`}>
29
+ <Task.Template.General />
30
+ </Load.Edit>
31
+ </div>
32
+ </main>
33
+ );
34
+ });
40
35
  `,
41
36
  };
42
37
  }
@@ -4,14 +4,10 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
4
4
  return {
5
5
  filename: "_index.tsx",
6
6
  content: `import { fetch, Task, usePage } from "@apps/${dict.appName}/client";
7
+ import { page } from "akanjs/client";
7
8
  import { Link, buttonRecipe } from "akanjs/ui";
8
9
 
9
- // ===== page/task/_index.tsx =====
10
- // Convention: Akan.js file-based routing — _index.tsx is the index page for /task.
11
- // Server-side data loading via loader() at the page level; passes init/view props to Zone components.
12
- // Uses usePage().l() for i18n — the framework convention for dictionary-based translations.
13
-
14
- export default async function Page() {
10
+ export default page().render(async () => {
15
11
  const { l } = usePage();
16
12
  const { taskInitInPublic } = await fetch.initTaskInPublic();
17
13
  return (
@@ -29,6 +25,6 @@ export default async function Page() {
29
25
  <Task.Zone.Card className="flex flex-col gap-3" init={taskInitInPublic} />
30
26
  </main>
31
27
  );
32
- }`,
28
+ });`,
33
29
  };
34
30
  }
@@ -3,22 +3,11 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "_layout.tsx",
6
- content: `
7
- // ===== page/task/_layout.tsx =====
8
- // Convention: Akan.js file-based routing — _layout.tsx wraps every sub-page under /task/*.
9
- // The underscores mark it as a routing file; children = sub-page component from nested routes.
10
- // Rendered by Akan's router: URL → _layout.tsx → children (e.g., _index.tsx or [taskId]/_index.tsx).
6
+ content: `import { layout } from "akanjs/client";
11
7
 
12
- interface LayoutProps {
13
- children: React.ReactNode;
14
- }
15
- export default function Layout({ children }: LayoutProps) {
16
- return (
17
- <div className="min-h-screen bg-muted">
18
- {children}
19
- </div>
20
- );
21
- }
8
+ export default layout().render(({ children }) => {
9
+ return <div className="min-h-screen bg-muted">{children}</div>;
10
+ });
22
11
  `,
23
12
  };
24
13
  }
@@ -3,17 +3,11 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "new.tsx",
6
- content: `import { type cnst, fetch, Task, usePage } from "@apps/${dict.appName}/client";
6
+ content: `import { type cnst, fetch, Task } from "@apps/${dict.appName}/client";
7
+ import { page } from "akanjs/client";
7
8
  import { Load, Link, buttonRecipe } from "akanjs/ui";
8
9
 
9
- // ===== page/task/new.tsx =====
10
- // Convention: Server-side form page using Load.Edit from akanjs/ui.
11
- // async Page() — server-side component that pre-initializes form data before rendering.
12
- // Load.Edit with type="form" renders the Template inside a form wrapper with submit/cancel actions.
13
- // onCancel="back" navigates to the previous page; onSubmit specifies the redirect after success.
14
- // Template is reused — same Task.Template.General used for create, edit, and client-side modals.
15
-
16
- export default async function Page() {
10
+ export default page().render(() => {
17
11
  const taskForm: Partial<cnst.Task> = { status: "todo" };
18
12
 
19
13
  return (
@@ -34,6 +28,6 @@ export default async function Page() {
34
28
  </div>
35
29
  </main>
36
30
  );
37
- }`,
31
+ });`,
38
32
  };
39
33
  }
@@ -15,8 +15,8 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
15
15
  export class SignedIn implements Guard {
16
16
  static name = "SignedIn";
17
17
  // "account" — the verdict reads the caller and nothing about the call, so an MCP catalogue can evaluate it with
18
- // no arguments and hide what this caller certainly cannot use. Unmarked means "resource", which is never
19
- // evaluated for a listing: a guard like this one would then filter nothing.
18
+ // no arguments and hide what this caller certainly cannot use. Required: "resource" is never evaluated for a
19
+ // listing, so a guard like this one marked that way would filter nothing.
20
20
  static scope: GuardScope = "account";
21
21
 
22
22
  canPass(context: SignalContext): boolean {
@@ -9,39 +9,37 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
9
9
  return {
10
10
  filename: "_index.tsx",
11
11
  content: `
12
+ import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { ID } from "akanjs/base";
14
+ import { page } from "akanjs/client";
12
15
  import { Load } from "akanjs/ui";
13
- import { fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
14
- import type { PageConfig } from "akanjs/client";
15
16
 
16
- interface PageProps {
17
- params: { ${dict.model}Id: string };
18
- }
19
-
20
- export default async function Page({ params }: PageProps) {
21
- const { l } = usePage();
22
- const { ${dict.model}Id } = params;
23
- const { ${dict.model}, ${dict.model}Edit } = await fetch.edit${dict.Model}(${dict.model}Id);
24
- return (
25
- <div className="container">
26
- <div className="flex justify-between m-4 mt-8">
27
- <div className="text-xl text-primary flex gap-2 items-center">
28
- {l("base.updateModel", { model: l("${dict.model}.modelName") })}
17
+ export default page()
18
+ .param("${dict.model}Id", ID)
19
+ .config({ transition: "none" })
20
+ .render(async ({ ${dict.model}Id }) => {
21
+ const { l } = usePage();
22
+ const [{ ${dict.model}Edit }] = await Promise.all([fetch.edit${dict.Model}(${dict.model}Id)]);
23
+ return (
24
+ <div className="container">
25
+ <div className="m-4 mt-8 flex justify-between">
26
+ <div className="flex items-center gap-2 text-primary text-xl">
27
+ {l("base.updateModel", { model: l("${dict.model}.modelName") })}
28
+ </div>
29
29
  </div>
30
+ <Load.Edit
31
+ className="flex flex-col items-center"
32
+ slice={fetch.slice.${dict.model}InPublic}
33
+ edit={${dict.model}Edit}
34
+ type="form"
35
+ onCancel="back"
36
+ onSubmit="/${dict.model}"
37
+ >
38
+ <${dict.Model}.Template.General />
39
+ </Load.Edit>
30
40
  </div>
31
- <Load.Edit
32
- className="flex flex-col items-center"
33
- slice={fetch.slice.${dict.model}InPublic}
34
- edit={${dict.model}Edit}
35
- type="form"
36
- onCancel="back"
37
- onSubmit="/${dict.model}"
38
- >
39
- <${dict.Model}.Template.General />
40
- </Load.Edit>
41
- </div>
42
- );
43
- }
44
- export const pageConfig = { transition: "none" } satisfies PageConfig;
41
+ );
42
+ });
45
43
  `,
46
44
  };
47
45
  }
@@ -10,43 +10,35 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  filename: "_index.tsx",
11
11
  content: `
12
12
  import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
- import { Link } from "akanjs/ui";
14
- import type { PageConfig } from "akanjs/client";
13
+ import { ID } from "akanjs/base";
14
+ import { page } from "akanjs/client";
15
+ import { buttonRecipe, Link } from "akanjs/ui";
15
16
 
16
- interface PageProps {
17
- params: { ${dict.model}Id: string };
18
- }
19
-
20
- export async function generateHead({ params }: PageProps) {
21
- const { ${dict.model}Id } = params;
22
- const { ${dict.model} } = await fetch.view${dict.Model}(${dict.model}Id);
23
- return (
17
+ export default page()
18
+ .param("${dict.model}Id", ID)
19
+ .config({ transition: "none" })
20
+ .head(({ ${dict.model}Id }) => (
24
21
  <>
25
- <title>{${dict.model}.id}</title>
26
- <meta name="description" content={${dict.model}.id} />
27
- <meta property="og:title" content={${dict.model}.id} />
28
- <meta property="og:description" content={${dict.model}.id} />
22
+ <title>{${dict.model}Id}</title>
23
+ <meta name="description" content={${dict.model}Id} />
24
+ <meta property="og:title" content={${dict.model}Id} />
25
+ <meta property="og:description" content={${dict.model}Id} />
29
26
  </>
30
- );
31
- }
32
- export default async function Page({ params }: PageProps) {
33
- const { l } = usePage();
34
- const { ${dict.model}Id } = params;
35
- const { ${dict.model}, ${dict.model}View } = await fetch.view${dict.Model}(${dict.model}Id);
36
- return (
37
- <div className="container flex flex-col gap-4">
38
- <div className="flex gap-4 font-bold text-lg items-center">
39
- <${dict.Model}.Zone.View view={${dict.model}View} />
40
- <Link href={\`/${dict.model}/\${${dict.model}.id}/edit\`}>
41
- <button className="inline-flex items-center justify-center gap-2 rounded-field bg-primary px-4 py-2 font-medium text-primary-foreground text-sm transition-colors hover:bg-primary/90">
27
+ ))
28
+ .render(async ({ ${dict.model}Id }) => {
29
+ const { l } = usePage();
30
+ const [{ ${dict.model}, ${dict.model}View }] = await Promise.all([fetch.view${dict.Model}(${dict.model}Id)]);
31
+ return (
32
+ <div className="container flex flex-col gap-4">
33
+ <div className="flex items-center gap-4 font-bold text-lg">
34
+ <${dict.Model}.Zone.View view={${dict.model}View} />
35
+ <Link className={buttonRecipe()} href={\`/${dict.model}/\${${dict.model}.id}/edit\`}>
42
36
  {l("base.updateModel", { model: l("${dict.model}.modelName") })}
43
- </button>
44
- </Link>
37
+ </Link>
38
+ </div>
45
39
  </div>
46
- </div>
47
- );
48
- }
49
- export const pageConfig = { transition: "none" } satisfies PageConfig;
40
+ );
41
+ });
50
42
  `,
51
43
  };
52
44
  }