@akanjs/cli 3.0.2-rc.0 → 3.1.0-rc.0

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 (86) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-szbjv4gv.js → CodeAgent-q7a4srt6.js} +2 -2
  3. package/{CodeTui-5nw86ab6.js → CodeTui-ak23dxc9.js} +1 -1
  4. package/{TunnelShare-cwh9ezsm.js → TunnelShare-21mm6dr5.js} +2 -2
  5. package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-fyjy9ryc.js → application.command-3v2d53gd.js} +38 -16
  8. package/{applicationBuildRunner-etrzfwjj.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
  9. package/buildBatch.proc.js +17 -6
  10. package/cloud.command-kk2rwkvw.js +187 -0
  11. package/cloud.script-r7eyfey4.js +20 -0
  12. package/{code.command-hmtx14b2.js → code.command-30sca09d.js} +6 -6
  13. package/commandManifest.json +1 -1
  14. package/{context.command-6aqvdx43.js → context.command-k7eqacq6.js} +13 -13
  15. package/context.runner-hqd1ppqe.js +21 -0
  16. package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +10 -2
  18. package/guidelines/conventions/conventions.instruction.md +68 -25
  19. package/guidelines/cssRule/cssRule.instruction.md +19 -0
  20. package/guidelines/mcpRule/mcpRule.instruction.md +67 -12
  21. package/guidelines/queryRule/queryRule.instruction.md +11 -1
  22. package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
  23. package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
  24. package/guidelines/transportRule/transportRule.instruction.md +19 -0
  25. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
  26. package/incrementalBuilder.proc.js +25 -4
  27. package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
  28. package/{index-j38qkvkk.js → index-0bm04n5g.js} +12 -10
  29. package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
  30. package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
  31. package/{index-jb0hmt3k.js → index-2t4tqd1c.js} +3 -3
  32. package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
  33. package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
  34. package/index-6w0rbr9d.js +68 -0
  35. package/{index-2wc41k5w.js → index-7bymavqk.js} +2 -2
  36. package/{index-krjcg4y6.js → index-aprt7pkz.js} +7 -10
  37. package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
  38. package/{index-d95tfg3n.js → index-c2yrsxdh.js} +14 -8
  39. package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
  40. package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
  41. package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
  42. package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
  43. package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
  44. package/{index-zwd84m0w.js → index-jyv387qw.js} +4 -4
  45. package/{index-senscydg.js → index-k9rrten6.js} +6 -1
  46. package/{index-jk9xnck6.js → index-kjvrq3nc.js} +231 -133
  47. package/{index-xwprn9nr.js → index-nz15k0b6.js} +1 -1
  48. package/{index-61mmsdjg.js → index-p9ystjrp.js} +174 -72
  49. package/index-q958tqqg.js +77 -0
  50. package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
  51. package/{index-94s080qm.js → index-r2tpeb3n.js} +127 -1
  52. package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
  53. package/{index-gvpvxdm1.js → index-tn4rpqp1.js} +96 -12
  54. package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
  55. package/index-ww9bhp67.js +62 -0
  56. package/{index-hxnnfy57.js → index-y8q31011.js} +180 -8
  57. package/index.js +22 -21
  58. package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
  59. package/{localRegistry.command-vacc2njh.js → localRegistry.command-9e84e32q.js} +10 -10
  60. package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
  61. package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
  62. package/package.json +2 -2
  63. package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
  64. package/pipeline.command-d0v38572.js +1334 -0
  65. package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
  66. package/projectTypecheck-cvtkrzp2.js +7 -0
  67. package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
  68. package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
  69. package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
  70. package/{subspace.command-j7ayp382.js → subspace.command-f02j5tge.js} +158 -91
  71. package/templates/app/tsconfig.json.template +1 -6
  72. package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
  73. package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
  74. package/templates/appSample/page/task/_index.tsx +2 -2
  75. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
  76. package/templates/crudPages/[__model__Id]/page.tsx +3 -3
  77. package/templates/crudPages/page.tsx +2 -2
  78. package/templates/crudSinglePage/page.tsx +3 -3
  79. package/templates/lib/cnst.ts +2 -2
  80. package/templates/lib/sig.ts +3 -3
  81. package/{tunnel.command-b3ymdk4f.js → tunnel.command-9x8nfn75.js} +16 -13
  82. package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
  83. package/{workspace.command-17c46dbp.js → workspace.command-cmj6mqyk.js} +58 -28
  84. package/cloud.command-c0rnwy65.js +0 -95
  85. package/cloud.script-6ba8e9v8.js +0 -20
  86. package/context.runner-hnkvv3z3.js +0 -21
@@ -0,0 +1,187 @@
1
+ // @bun
2
+ import {
3
+ App,
4
+ Workspace,
5
+ command
6
+ } from "./index-6jjtnhkw.js";
7
+ import"./index-52w0fvh8.js";
8
+ import"./index-55mynnqs.js";
9
+ import"./index-kjvrq3nc.js";
10
+ import"./index-r2tpeb3n.js";
11
+ import {
12
+ PlatformTestRun
13
+ } from "./index-y8q31011.js";
14
+ import"./index-k9rrten6.js";
15
+ import"./index-c1x0be5j.js";
16
+ import"./index-p9ystjrp.js";
17
+ import"./index-7md2g9vw.js";
18
+ import"./index-2ahj896a.js";
19
+ import"./index-gnyf1fqy.js";
20
+ import {
21
+ CloudScript2
22
+ } from "./index-tn4rpqp1.js";
23
+ import"./index-qt22xstj.js";
24
+ import {
25
+ getMobileTargetChoices
26
+ } from "./index-q958tqqg.js";
27
+
28
+ // pkgs/@akanjs/cli/cloud/cloud.command.ts
29
+ var localRegistryUrl = () => process.env.AKAN_NPM_REGISTRY ?? "http://127.0.0.1:4873";
30
+ var hostOption = {
31
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
32
+ nullable: true
33
+ };
34
+ var targetOption = {
35
+ desc: "native target name (a key of native.targets)",
36
+ ask: "Select native target",
37
+ enum: async ({ app }) => await getMobileTargetChoices(app)
38
+ };
39
+ var envOption = {
40
+ enum: ["debug", "develop", "main", "local"],
41
+ desc: "backend environment",
42
+ default: "main"
43
+ };
44
+ var resolveRegistryUrl = (registry) => registry === "local" ? localRegistryUrl() : undefined;
45
+
46
+ class CloudCommand extends command("cloud", [CloudScript2], ({ public: target }) => ({
47
+ login: target({ desc: "Login to Akan Cloud services" }).option("host", String, {
48
+ desc: "host of the cloud (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
49
+ nullable: true
50
+ }).with(Workspace).exec(async function(host, workspace) {
51
+ await this.cloudScript.login(workspace, host);
52
+ }),
53
+ logout: target({ desc: "Logout from Akan Cloud services" }).option("host", String, {
54
+ desc: "host of the cloud (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
55
+ nullable: true
56
+ }).with(Workspace).exec(async function(host, workspace) {
57
+ await this.cloudScript.logout(workspace, host);
58
+ }),
59
+ deployAkan: target({ devOnly: true, desc: "Deploy Akan.js framework to cloud (internal use)" }).option("test", Boolean, { desc: "test the deployment", default: true }).option("registry", String, {
60
+ desc: "registry target for publishing Akan packages",
61
+ ask: "Select a registry target",
62
+ enum: [
63
+ { label: "local", value: "local" },
64
+ { label: "npm", value: "npm" }
65
+ ]
66
+ }).option("platforms", String, {
67
+ desc: "platforms to test on besides this machine, comma-separated: linux, windows (none to skip)",
68
+ default: "linux,windows"
69
+ }).with(Workspace).exec(async function(test, registry, platforms, workspace) {
70
+ await this.cloudScript.deployAkan(workspace, {
71
+ test,
72
+ registryUrl: resolveRegistryUrl(registry),
73
+ platforms: PlatformTestRun.parsePlatforms(platforms)
74
+ });
75
+ }),
76
+ testPlatforms: target({
77
+ devOnly: true,
78
+ desc: "Run the Akan package suites on Linux (Docker) and Windows (SSH) against a snapshot of this tree"
79
+ }).option("platforms", String, {
80
+ desc: "platforms to test on, comma-separated: linux, windows",
81
+ default: "linux,windows"
82
+ }).option("pkgs", String, {
83
+ flag: "k",
84
+ desc: "packages to test, comma-separated (default: every Akan package)",
85
+ nullable: true
86
+ }).with(Workspace).exec(async function(platforms, pkgs, workspace) {
87
+ await this.cloudScript.testPlatforms(workspace, {
88
+ platforms: PlatformTestRun.parsePlatforms(platforms),
89
+ pkgs: pkgs?.split(",").map((pkg) => pkg.trim()).filter(Boolean)
90
+ });
91
+ }),
92
+ update: target({ desc: "Update Akan.js framework to the latest version" }).with(Workspace).option("tag", String, {
93
+ desc: "tag of the update",
94
+ default: "latest",
95
+ enum: ["latest", "dev", "canary", "beta", "rc", "alpha"]
96
+ }).option("registry", String, {
97
+ desc: "registry target for resolving Akan packages",
98
+ ask: "Select a registry target",
99
+ enum: [
100
+ { label: "npm", value: "npm" },
101
+ { label: "local", value: "local" }
102
+ ],
103
+ default: process.env.USE_AKANJS_PKGS === "true" ? undefined : "npm"
104
+ }).exec(async function(workspace, tag, registry) {
105
+ await this.cloudScript.update(workspace, tag, { registryUrl: resolveRegistryUrl(registry) });
106
+ }),
107
+ pushUpdate: target({
108
+ desc: "Pack an over-the-air update of a phone's web bundle and upload it to the akasys project, which signs and publishes it"
109
+ }).with(App).option("platform", String, { enum: ["ios", "android"], desc: "the app it updates" }).option("target", String, targetOption).option("env", String, envOption).option("channel", String, { desc: "the channel (default: the target's updates.channel)", nullable: true }).option("notes", String, { desc: "release notes shown in the project", nullable: true }).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, hostOption).option("write", Boolean, { desc: "write code generation", default: true }).option("allowLocalRelease", Boolean, { flag: "l", desc: "allow packing with --env local", default: false }).with(Workspace).exec(async function(app, platform, target, env, channel, notes, subspace, host, write, allowLocalRelease, workspace) {
110
+ await this.cloudScript.pushUpdate(workspace, app, {
111
+ platform,
112
+ target,
113
+ env,
114
+ channel,
115
+ notes,
116
+ subspace,
117
+ host,
118
+ write,
119
+ allowLocalRelease
120
+ });
121
+ }),
122
+ registerBinary: target({
123
+ desc: "Register the store build of a phone app with the akasys project, with a pack of the same commit as its embedded bundle"
124
+ }).with(App).option("platform", String, { enum: ["ios", "android"], desc: "the store build to register" }).option("distribution", String, {
125
+ enum: ["appStore", "testFlight", "playStore", "playTesting", "direct"],
126
+ desc: "where the binary ships"
127
+ }).option("target", String, targetOption).option("env", String, envOption).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, hostOption).option("write", Boolean, { desc: "write code generation", default: true }).option("allowLocalRelease", Boolean, { flag: "l", desc: "allow packing with --env local", default: false }).with(Workspace).exec(async function(app, platform, distribution, target, env, subspace, host, write, allowLocalRelease, workspace) {
128
+ await this.cloudScript.registerBinary(workspace, app, {
129
+ platform,
130
+ distribution,
131
+ target,
132
+ env,
133
+ subspace,
134
+ host,
135
+ write,
136
+ allowLocalRelease
137
+ });
138
+ }),
139
+ uploadSecret: target({
140
+ desc: "Upload a named pipeline secret to the akasys project; write-only, it can never be read back"
141
+ }).arg("name", String, { desc: "secret name, e.g. DISCORD_WEBHOOK" }).option("branch", String, {
142
+ desc: "only builds of this branch use it: debug, develop or main (default: every branch)",
143
+ nullable: true
144
+ }).option("file", String, {
145
+ desc: "read the value from this file instead of stdin or a hidden prompt",
146
+ nullable: true
147
+ }).option("subspace", String, { desc: "the subspace whose project gets it", nullable: true }).option("host", String, {
148
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
149
+ nullable: true
150
+ }).with(Workspace).exec(async function(name, branch, file, subspace, host, workspace) {
151
+ await this.cloudScript.uploadSecret(workspace, { name, branch, file, subspace, host });
152
+ }),
153
+ listSecrets: target({ desc: "List the pipeline secrets of the akasys project: names and scopes, never values" }).option("branch", String, {
154
+ desc: "only the secrets builds of this branch use: debug, develop or main",
155
+ nullable: true
156
+ }).option("subspace", String, { desc: "the subspace whose project to list", nullable: true }).option("host", String, {
157
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
158
+ nullable: true
159
+ }).with(Workspace).exec(async function(branch, subspace, host, workspace) {
160
+ await this.cloudScript.listSecrets(workspace, { branch, subspace, host });
161
+ }),
162
+ removeSecret: target({ desc: "Remove a named pipeline secret from the akasys project" }).arg("name", String, { desc: "secret name" }).option("branch", String, {
163
+ desc: "the branch override to remove: debug, develop or main (default: the every-branch value)",
164
+ nullable: true
165
+ }).option("subspace", String, { desc: "the subspace whose project holds it", nullable: true }).option("host", String, {
166
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
167
+ nullable: true
168
+ }).with(Workspace).exec(async function(name, branch, subspace, host, workspace) {
169
+ await this.cloudScript.removeSecret(workspace, { name, branch, subspace, host });
170
+ }),
171
+ downloadEnv: target({ desc: "Download environment variables from cloud or SCP server" }).option("host", String, {
172
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
173
+ nullable: true
174
+ }).with(Workspace).exec(async function(host, workspace) {
175
+ await this.cloudScript.downloadEnv(workspace, undefined, { host });
176
+ }),
177
+ uploadEnv: target({ desc: "Upload environment variables to cloud or SCP server" }).option("host", String, {
178
+ desc: "host of the cloud to target (default: cloud.host of the root akan.config.ts, else Akan Cloud)",
179
+ nullable: true
180
+ }).with(Workspace).exec(async function(host, workspace) {
181
+ await this.cloudScript.uploadEnv(workspace, { host });
182
+ })
183
+ })) {
184
+ }
185
+ export {
186
+ CloudCommand
187
+ };
@@ -0,0 +1,20 @@
1
+ // @bun
2
+ import {
3
+ CloudScript2
4
+ } from "./index-tn4rpqp1.js";
5
+ import"./index-y8q31011.js";
6
+ import"./index-p9ystjrp.js";
7
+ import"./index-qt22xstj.js";
8
+ import"./index-c1x0be5j.js";
9
+ import"./index-gnyf1fqy.js";
10
+ import"./index-kjvrq3nc.js";
11
+ import"./index-r2tpeb3n.js";
12
+ import"./index-6jjtnhkw.js";
13
+ import"./index-7md2g9vw.js";
14
+ import"./index-k9rrten6.js";
15
+ import"./index-55mynnqs.js";
16
+ import"./index-2ahj896a.js";
17
+ import"./index-52w0fvh8.js";
18
+ export {
19
+ CloudScript2 as CloudScript
20
+ };
@@ -4,9 +4,9 @@ import {
4
4
  runner,
5
5
  script,
6
6
  command
7
- } from "./index-q6ft9fkc.js";
7
+ } from "./index-6jjtnhkw.js";
8
8
  import"./index-52w0fvh8.js";
9
- import"./index-50wb2a26.js";
9
+ import"./index-55mynnqs.js";
10
10
  import {
11
11
  akanCodeDefaultModel2,
12
12
  CodeAgentStreamPrinter2
@@ -19,7 +19,7 @@ class CodeRunner extends runner("code") {
19
19
  async serve(options, listen) {
20
20
  await import("./consoleToStderr-h5bryj94.js");
21
21
  const [{ CodeAgent }, { CodeAgentRpcHost }, { CodeAgentRpcListener }] = await Promise.all([
22
- import("./CodeAgent-szbjv4gv.js"),
22
+ import("./CodeAgent-q7a4srt6.js"),
23
23
  import("./CodeAgentRpcHost-p7h6n2hp.js"),
24
24
  import("./CodeAgentRpcListener-7p7z5dd7.js")
25
25
  ]);
@@ -34,8 +34,8 @@ class CodeRunner extends runner("code") {
34
34
  }
35
35
  async tui(options, seed) {
36
36
  const [{ CodeAgent }, { CodeTui }] = await Promise.all([
37
- import("./CodeAgent-szbjv4gv.js"),
38
- import("./CodeTui-5nw86ab6.js")
37
+ import("./CodeAgent-q7a4srt6.js"),
38
+ import("./CodeTui-ak23dxc9.js")
39
39
  ]);
40
40
  const agentOptions = await CodeRunner.#agentOptions(options, false);
41
41
  let resume = options.resume;
@@ -60,7 +60,7 @@ class CodeRunner extends runner("code") {
60
60
  }
61
61
  }
62
62
  async run(prompt, options) {
63
- const { CodeAgent } = await import("./CodeAgent-szbjv4gv.js");
63
+ const { CodeAgent } = await import("./CodeAgent-q7a4srt6.js");
64
64
  const agent = await CodeAgent.create(await CodeRunner.#agentOptions(options, false));
65
65
  const printer = new CodeAgentStreamPrinter2({ json: options.json, thinking: options.thinking });
66
66
  agent.on((event) => printer.print(event));
@@ -1 +1 @@
1
- {"byCommand":{"create-workspace":"workspace","generate-agent-rules":"workspace","lint":"workspace","lint-all":"workspace","sync-all":"workspace","init":"workspace","agent":"agent","code":"code","create-application":"application","remove-application":"application","plan-slice":"application","sync":"application","script":"application","console":"application","logs":"application","build":"application","b":"application","typecheck":"application","t":"application","test":"application","build-ios":"application","bi":"application","build-android":"application","ba":"application","build-desktop":"application","bd":"application","start":"application","s":"application","start-ios":"application","si":"application","start-android":"application","sa":"application","start-desktop":"application","sd":"application","release-ios":"application","release-android":"application","pack-update":"application","update-keygen":"application","publish-update":"application","dbup":"application","db-export":"application","db-import":"application","dbdown":"application","create-library":"library","remove-library":"library","sync-library":"library","library-status":"library","install-library":"library","start-registry":"localRegistry","reset-registry":"localRegistry","smoke-registry":"localRegistry","version":"package","create-package":"package","remove-package":"package","sync-package":"package","build-package":"package","verify-dist-package":"package","verify-akan-publish-packages":"package","create-module":"module","remove-module":"module","create-service":"module","create-view":"module","create-unit":"module","create-template":"module","create-crud-page":"page","context":"context","doctor":"context","mcp-install":"context","mcp":"context","mcp-call":"context","login":"cloud","logout":"cloud","deploy-akan":"cloud","test-platforms":"cloud","update":"cloud","download-env":"cloud","upload-env":"cloud","subspace":"subspace","guideline":"guideline","create-scalar":"scalar","remove-scalar":"scalar","create-ui":"primitive","add-field":"primitive","add-enum-field":"primitive","quality":"quality","repair":"repair","workflow":"workflow","tunnel":"tunnel"}}
1
+ {"byCommand":{"create-workspace":"workspace","generate-agent-rules":"workspace","lint":"workspace","lint-all":"workspace","machine-keygen":"workspace","sync-all":"workspace","init":"workspace","agent":"agent","code":"code","create-application":"application","remove-application":"application","plan-slice":"application","sync":"application","script":"application","console":"application","logs":"application","build":"application","b":"application","typecheck":"application","t":"application","test":"application","build-ios":"application","bi":"application","build-android":"application","ba":"application","build-desktop":"application","bd":"application","start":"application","s":"application","start-ios":"application","si":"application","start-android":"application","sa":"application","start-desktop":"application","sd":"application","release-ios":"application","release-android":"application","pack-update":"application","update-keygen":"application","publish-update":"application","dbup":"application","db-export":"application","db-import":"application","dbdown":"application","create-library":"library","remove-library":"library","sync-library":"library","library-status":"library","install-library":"library","start-registry":"localRegistry","reset-registry":"localRegistry","smoke-registry":"localRegistry","version":"package","create-package":"package","remove-package":"package","sync-package":"package","build-package":"package","verify-dist-package":"package","verify-akan-publish-packages":"package","create-module":"module","remove-module":"module","create-service":"module","create-view":"module","create-unit":"module","create-template":"module","create-crud-page":"page","context":"context","doctor":"context","mcp-install":"context","mcp":"context","mcp-call":"context","login":"cloud","logout":"cloud","deploy-akan":"cloud","test-platforms":"cloud","update":"cloud","push-update":"cloud","register-binary":"cloud","upload-secret":"cloud","list-secrets":"cloud","remove-secret":"cloud","download-env":"cloud","upload-env":"cloud","subspace":"subspace","guideline":"guideline","create-scalar":"scalar","remove-scalar":"scalar","create-ui":"primitive","add-field":"primitive","add-enum-field":"primitive","quality":"quality","repair":"repair","workflow":"workflow","pipeline":"pipeline","tunnel":"tunnel"}}
@@ -2,24 +2,24 @@
2
2
  import {
3
3
  Workspace,
4
4
  command
5
- } from "./index-q6ft9fkc.js";
5
+ } from "./index-6jjtnhkw.js";
6
6
  import"./index-52w0fvh8.js";
7
- import"./index-50wb2a26.js";
7
+ import"./index-55mynnqs.js";
8
8
  import"./index-hj6w4ka1.js";
9
- import"./index-h9ht6s60.js";
10
- import"./index-zyw622y1.js";
11
- import"./index-j38qkvkk.js";
12
- import"./index-jk9xnck6.js";
9
+ import"./index-05g3jayg.js";
10
+ import"./index-2mx692qq.js";
11
+ import"./index-0bm04n5g.js";
12
+ import"./index-kjvrq3nc.js";
13
13
  import"./index-zzvvc845.js";
14
- import"./index-hp3stk1m.js";
15
- import"./index-g38nhv87.js";
16
- import"./index-f4e08ptp.js";
17
- import"./index-6keyf4sg.js";
18
- import"./index-cb6yc5mb.js";
19
- import"./index-bsv901fd.js";
14
+ import"./index-fm46xrf1.js";
15
+ import"./index-2etcv0sc.js";
16
+ import"./index-e0cvze1s.js";
17
+ import"./index-ctanya41.js";
18
+ import"./index-c76k723e.js";
19
+ import"./index-rwq3ekqc.js";
20
20
  import {
21
21
  ContextScript
22
- } from "./index-zwd84m0w.js";
22
+ } from "./index-jyv387qw.js";
23
23
 
24
24
  // pkgs/@akanjs/cli/context/context.command.ts
25
25
  class ContextCommand extends command("context", [ContextScript], ({ public: target }) => ({
@@ -0,0 +1,21 @@
1
+ // @bun
2
+ import {
3
+ ContextRunner2
4
+ } from "./index-0bm04n5g.js";
5
+ import"./index-rwq3ekqc.js";
6
+ import"./index-c76k723e.js";
7
+ import"./index-ctanya41.js";
8
+ import"./index-fm46xrf1.js";
9
+ import"./index-e0cvze1s.js";
10
+ import"./index-2etcv0sc.js";
11
+ import"./index-kjvrq3nc.js";
12
+ import"./index-2mx692qq.js";
13
+ import"./index-05g3jayg.js";
14
+ import"./index-zzvvc845.js";
15
+ import"./index-6jjtnhkw.js";
16
+ import"./index-55mynnqs.js";
17
+ import"./index-hj6w4ka1.js";
18
+ import"./index-52w0fvh8.js";
19
+ export {
20
+ ContextRunner2 as ContextRunner
21
+ };
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  script,
4
4
  command
5
- } from "./index-q6ft9fkc.js";
5
+ } from "./index-6jjtnhkw.js";
6
6
  import"./index-52w0fvh8.js";
7
- import"./index-50wb2a26.js";
7
+ import"./index-55mynnqs.js";
8
8
  import {
9
9
  Prompter2
10
10
  } from "./index-zzvvc845.js";
@@ -47,7 +47,9 @@ pages declaring one name never clash, and a registry kept outside the surface re
47
47
  `AgentProvider` or an `Agent.Zone` belongs to whoever provided it.
48
48
  - **The panel is customized in three widening steps, and most designs stop at the first.** Props first:
49
49
  `open` / `onOpenChange` drive it from the app's own control (`launcher={false}` then draws no launcher of its
50
- own), `launcherClassName` / `panelClassName` reach one surface each where `className` reaches both, `intro`
50
+ own), `launcherPlacement` / `hideLauncherBelow` place the launcher — which sits above the page's bottom chrome
51
+ and under every menu and modal — `launcherClassName` / `panelClassName` reach one surface each where
52
+ `className` reaches both, `intro`
51
53
  replaces the empty-state line — where starter questions go — and `header` adds controls beside the built-in
52
54
  clear and close. `chrome={false}` drops the header bar whole, for an `inline` chat inside a panel the app
53
55
  already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
@@ -306,7 +308,9 @@ pages declaring one name never clash, and a registry kept outside the surface re
306
308
  an inline arrow can be clicked by a person and by nobody else. `.desc()` is required and comes first, `.arg()`
307
309
  is an argument the caller must pass and `.opt()` one it may leave out (the `exec` parameter is then `| null`),
308
310
  and `.exec()` is the only hook, so the chain completes in one unconditional statement and the callable carries
309
- `data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate. Reach a store action
311
+ `data-akan-action` like a store setter does. A `remove*` name defaults to a confirm gate. `st.tool("x", { label:
312
+ l("ticket.archive") })` gives the action a name a person reads — `Agent.useScreenActions()` hands it to a command
313
+ palette, and the model never sees it. Reach a store action
310
314
  from the body — `.exec((id) => st.do.removeX(id))` — which is how an agent gets CRUD; `st.do` on its own
311
315
  reaches nobody.
312
316
  - **`.card()` is the other way the chain ends, for an answer that is the user's to give.** Where `.exec()` runs a
@@ -357,6 +361,10 @@ pages declaring one name never clash, and a registry kept outside the surface re
357
361
  (a different `modal`, a different redirect) cannot honestly share a description, and that pair wants a
358
362
  `namespace` or nothing at all. The ids come from the `<slice>.items` resource `Load.Units` and
359
363
  `Data.ListContainer` already expose.
364
+ - **A command palette lists the same declarations.** `Agent.useScreenActions()` returns the screen's published
365
+ tools that take no required argument, each with a `run` that calls the control's own handler (guard included,
366
+ not logged as an agent call) and `needsConfirm` for the ones a person should confirm — so ⌘K offers exactly
367
+ what the agent can do here, with nothing declared twice.
360
368
 
361
369
  ## What `akanjs/ui` Publishes For You
362
370
  - **`akanjs/ui` publishes its own controls, so an app writes nothing for them.** `Data.ListContainer` (and every
@@ -67,7 +67,9 @@ back.
67
67
  - **Never import a third-party package** from `page/**`, from any barrel, or from any
68
68
  `*.{constant,dictionary,document,service,signal,store}.ts` / `*.{Template,Unit,Util,View,Zone}.tsx`
69
69
  (`no-import-external-library.grit`). Re-export the symbol through a lib first. One-line re-export shims in a lib's
70
- `common/`, `webkit/`, or `ui/` exist for exactly this reason — they are load-bearing, not cruft.
70
+ `common/`, `webkit/`, or `ui/` exist for exactly this reason — they are load-bearing, not cruft. A module's
71
+ `*.signal.test.ts` / `*.service.test.ts` is held to the same rule but may import the runtime's `node:*` and
72
+ `bun:*` modules (a temp dir, a socket, sqlite), which module code leaves to `srvkit/`.
71
73
  - **`#private` is banned in exactly four file suffixes:** `*.constant.ts`, `*.document.ts`, `*.service.ts`, and
72
74
  `*.store.ts` (`no-js-private-class-method.grit`). The rule is scoped by file path, not by class shape, so
73
75
  `#private` remains the house style everywhere under `srvkit/`, including `adapt()` adapter classes.
@@ -202,6 +204,13 @@ back.
202
204
  - Prefer focused behavior tests for public contracts and edge cases over implementation-detail assertions.
203
205
  - Run package suites with `bun run akan test <pkg>` from the repo root, or `cd <pkg> && bun test --isolate`. Plain `bun test` without `--isolate` shares one global object across test files and fails dozens of tests from cross-file state pollution (`bunfig.toml` `[test] isolate` is not honored as of Bun 1.3), and running `bun test` from the repo root breaks subprocess stdio pipes.
204
206
  - Split signal tests in two. `<model>.signal.spec.ts` holds reusable fixtures built on `sampleOf(cnst.XInput)` with explicit `Promise<cnst.X>` return types and **no assertions**. `<model>.signal.test.ts` holds the assertions: `describe("<Model> Signal")`, `let` fixtures at describe scope, one `beforeAll`, story-ordered `it`s, and negatives via `await expect(p).rejects.toThrow()`.
207
+ - **One suite per module file, named after it.** A module test is `<model>.<role>.test.ts` beside the file it covers
208
+ (`chatMessage.signal.test.ts`, `chatMessage.service.test.ts`, `ChatMessage.Zone.test.tsx`). Never split one role
209
+ across topic files such as `chatMessage.trim.signal.test.ts` / `chatMessage.security.signal.test.ts` — add a
210
+ `describe` block to the existing suite. A suite that drives several modules (guards across models, an app's MCP
211
+ exposure list) belongs to none of them and goes in `srvkit/`, like `libs/shared/srvkit/guards.signal.test.ts`.
212
+ Never put a test at the `lib/` root. `akan sync` refuses all three shapes. Nothing imports a test, so a build never
213
+ typechecks one; `akan typecheck <app-or-lib>` does, `bun:test` types included.
205
214
  - `lib/user/user.signal.spec.ts` is the one place agent types are re-exported and re-typed; import `UserAgent` / `AdminAgent` from there rather than from the owning lib directly.
206
215
  - A placeholder `it` with a descriptive title is an acceptable floor. Write a real suite when the behaviour is security-relevant.
207
216
 
@@ -310,11 +319,11 @@ are, because those are the ones the server could have done.
310
319
  consume them in a server component, never as a `Zone` prop.
311
320
  6. **Use named `ReactNode` slots, not just `children`** — a client shell that takes one slot per region lets a
312
321
  server component supply each of them, instead of absorbing the whole subtree into the bundle. `Layout.Navbar`
313
- declares `title` / `left` / `right` alongside `back` and `children`, but renders only the last two today, so
314
- copy the shape rather than the component.
322
+ is the shape: `title` / `left` / `right` beside `back` and `children`, each filled by the server.
315
323
  7. **Let the server do the derived work.** Display and predicate logic belongs on `Light<Model>`; enum→class
316
324
  lookups belong in a module-scope `as const` map.
317
- 8. **Gate auth on the server** — `getSelf({ unauthorize: "/signin" })` in `_layout.tsx`, before any HTML is sent.
325
+ 8. **Gate auth on the server** — `getSelf({ unauthorize: "/signin" })` in `_layout.tsx`, before any of the page's
326
+ markup renders. After its fetch the redirect is an in-page one (HTTP 200); `.config({ ssr: "block" })` makes it a 3xx.
318
327
  9. **Prefer CSS over client state for pure visibility.** A `data-*` attribute plus `group-data-[…]` variants, or
319
328
  `<details>`/`<summary>`, keeps both branches server-rendered.
320
329
  10. **Keep the heavy island out of the first load** — behind the `ui/<Folder>/index_.tsx` + `lazy()` pair.
@@ -379,8 +388,8 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
379
388
  from `akan start`; `build-ios` / `build-android` / `build-desktop` write
380
389
  `dist/native/<app>/<target>/build/<platform>`, a desktop app only for the OS that builds it — `--arch` picks
381
390
  a Windows or Linux app's CPU, and a macOS app is Apple silicon only. `--installer true` adds a Windows setup, a
382
- macOS dmg or a Linux AppImage; `AKAN_NATIVE_MACOS_*` signs with a Developer ID and notarizes,
383
- `AKAN_NATIVE_WINDOWS_*` signs with Authenticode. With `updates: { url, publicKey }` an installed app takes the signed
391
+ macOS dmg or a Linux AppImage; `--sign` signs with the identity `AKAN_NATIVE_MACOS_*` / `AKAN_NATIVE_WINDOWS_*`
392
+ names and `--notarize` uploads the signed macOS app to Apple — the env alone does neither. With `updates: { url, publicKey }` an installed app takes the signed
384
393
  releases `akan publish-update` makes; `akan update-keygen` makes the key and prints its `publicKey`.
385
394
  - **A desktop app that carries its server (`desktop: { server: true }` in `native`, or in one target) gets nothing
386
395
  from `docker`** — and `server: { omit: [...] }` leaves out packages only the image needs. That server is API-only, runs in database mode `single`, and listens on a loopback port any
@@ -393,7 +402,9 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
393
402
  `NativeFile.resolve(grant, "read")` in `akanjs/server` asks the shell for it. What it carries is readable on the
394
403
  user's computer — `private/` (each lib's too, under `private/libs/<lib>`), the one `env.server.<env>.ts` of its
395
404
  `--env` and its libs' server env defaults (`env.server.testing.ts`) — and it has no `public/`: read runtime files
396
- from `AKAN_APP_DIR`, never `process.cwd()`.
405
+ from `AKAN_APP_DIR`, never `process.cwd()`. **`server: { profile: "edge" }` bundles `env.server.<env>.edge.ts` in
406
+ that file's place**, and the build refuses one holding a secret-named value; `server: { modules: ["@edge"] }`
407
+ mounts only the modules whose `serve()` lists `operationMode: ["edge"]`, and what they reach.
397
408
  - **`assets: { pruneFonts, keepFonts }`** trims from the `dist` copy of `public/` the fonts nothing reads; source
398
409
  trees are never touched. A font with `optimize` on is a build input, not a runtime asset. `keepFonts` belongs to
399
410
  the `akan.config.ts` that owns the font, written against that scope's own `public/`.
@@ -481,7 +492,9 @@ count was shortened to `count(query)`, with `countDocuments` kept as `@deprecate
481
492
  `removeById(id)` are those same query-level writes narrowed to one id, **not** the document path: they fire no hooks,
482
493
  so a model whose removal cascades or carries a `_postRemove` still goes through `remove<Model>(id)`. The
483
494
  write-through pair is `pickAndWrite(id, data)` / `pickOneAndWrite(query, data)` — pick the document, write into it
484
- and save it, so the save hooks do run; reach for those when the write has to be seen by them.
495
+ and save it, so the save hooks do run; reach for those when the write has to be seen by them. `upsertById(data, {
496
+ preserveTimestamps })` writes a record whole under its own id, new or in place, with no hook: the write a sync or a
497
+ backfill repeats safely, keeping the record's `createdAt`/`updatedAt` when asked.
485
498
 
486
499
  **`<model>.service.ts`** — keep methods to a few lines: load → chain → `return await ….save()`. Write `return await`
487
500
  explicitly in tail position; do not "optimize" it away. Side effects belong in `override async _preUpdate` /
@@ -517,7 +530,9 @@ workflow changes.
517
530
 
518
531
  - Keep domain business operations in `.service.ts` classes built with `serve(...)`.
519
532
  - Keep execution contracts and triggers in `.signal.ts` classes built with `internal(...)`, `slice(...)`, and `endpoint(...)`.
520
- - Use `Internal` for internal triggers such as init, interval, cron, or queue jobs.
533
+ - Use `Internal` for internal triggers such as init, interval, cron, or queue jobs. A `process` job's
534
+ `backoff: { type: "exponential", delay, maxDelay }` caps the wait between attempts, and the internal's
535
+ `onFailed(job, error)` runs once a job has failed its last attempt — the dead letter, on either queue.
521
536
  - Use `Slice` for typed data views that feed client stores and zones; keep each slice focused on one purpose.
522
537
  - Use `Endpoint` for query and mutation contracts exposed to callers.
523
538
  - Connect external APIs or infrastructure through adapters, usually under `srvkit/`, and inject them into services instead of importing vendor clients directly into domain logic.
@@ -541,6 +556,11 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
541
556
  inside a guard or middleware). Behind a federation gateway, `ws.remoteAddress` and the child's own peer are the
542
557
  *gateway* (`127.0.0.1`) for every caller and for the whole life of every socket. `Ip` reads what a proxy
543
558
  recorded, is unwrapped from its `::ffff:` form, and is `null` rather than a placeholder when genuinely unknown.
559
+ - **A device or another server proves itself with a machine token, never a staff session.**
560
+ `MachineToken.issue({ kind, id, version })` (`akanjs/server`) signs one with `AKAN_MACHINE_PRIVATE_KEY` (made by
561
+ `akan machine-keygen <file>`), with no expiry; a server holding that key or `AKAN_MACHINE_PUBLIC_KEY` sets
562
+ `account.machine` from a verified one on HTTP and websocket alike, and only from one. Read it with `.with(Machine)`
563
+ or in a guard, and revoke by bumping the version the guard compares to the record's own.
544
564
  - **Every socket carries a `socketId`, and only the framework mints one** — read it off the `Ws` internal arg
545
565
  (`.with(Ws)` hands `{ ws, socketId, subscribe, on, off }`). Never mint your own from `ws.data`: the room
546
566
  bookkeeping keys on the framework's id and a second one fails to match it silently. It identifies a
@@ -603,7 +623,7 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
603
623
 
604
624
  - `.body(...)` / `.param(...)` args accept `ConstantFieldTypeInput` only: scalars, model refs, or `enumOf(...)`.
605
625
  - Numbers must use `Int` or `Float` — `Number` is rejected (`pkgs/akanjs/signal/endpointInfo.ts`).
606
- - `Upload` is valid only inside a mutation flagged for file upload: `mutation([cnst.File], { fileUpload: true }).body("files", [Upload])`, as the `file` module does. It is not a model field type.
626
+ - `Upload` is valid only inside a mutation flagged for file upload: `mutation([cnst.File], { fileUpload: true }).body("files", [Upload])`, as the `file` module does. It is not a model field type. The other body args of such a mutation arrive typed as in a JSON body. A file's `type` on the server is what its name implies, not its bytes — check the bytes when the type matters.
607
627
  - Bytes are `Binary`, never `Any` — see Scalar & Field Type Reference. A `Binary` return travels as base64 in an
608
628
  HTTP body, and a return declared `Any` that turns out to be bytes fails instead of reaching the caller as `{}`.
609
629
 
@@ -669,7 +689,8 @@ Full contract — filter-arg `ref` pickers, `getQueryMeta` summary counters, `la
669
689
  "archived" })`. The patch cannot trail the filter args, so it lands on a terminal `.set()`; building the chain
670
690
  touches nothing.
671
691
  - `removeOne` / `updateOne` hit the **newest** match, always, and report only counts — they are for "there is at
672
- most one of these", not for claiming the next item off a queue.
692
+ most one of these", not for claiming the next item off a queue. A condition in the query is a compare-and-set in
693
+ every mode: of two concurrent calls that both saw the row match, one applies, and `modifiedCount` says which.
673
694
  - **A filter may not be keyed after its own model.** A filter `chat` on model `chat` would silently swap the
674
695
  single-document `removeChat`/`updateChat` for a hookless query-level one; it throws at boot instead.
675
696
  - **"Has no value" is `q.empty`, never `q.missing`.** `q.missing` is the key being *absent from the stored JSON*,
@@ -735,7 +756,12 @@ Conventions that hold for both shapes:
735
756
  - `try/catch` is rare and always converts an exception into a decision, never swallows one. Guards catch → `logger.warn` → `return false`; adapters catch → `logger.error` → `return null`; UI uses `try/finally` to reset a spinner. A bodyless `catch {}` is acceptable only with a one-line reason.
736
757
  - Store actions do not `try/catch` — let the framework toast the `Err`. Client-side validation failure is `msg.error("<key>")` plus an early return, never a throw.
737
758
  - **A server-side `fetch.clone({ origin })` opens no websocket unless `connect: true`**: it makes one-off calls to
738
- another server, and a socket it opened would reconnect forever with nobody to close it.
759
+ another server, and a socket it opened would reconnect forever with nobody to close it. It never carries the
760
+ visitor's session either: a call to another origin sends only the `jwt` its caller gave the clone.
761
+ - **Retry a call with `retry`, never with a loop of your own.** `fetch.clone({ origin, retry: {} })` (or a call's
762
+ own `{ retry }`) tries again on an unreachable server, a 502/503/504 and a 429 after its Retry-After, backing off
763
+ with jitter; a mutation retries under one `Idempotency-Key` it mints, or the `idempotencyKey` you name, and the
764
+ server runs a mutation once per key and caller, answering a repeat with the first result. A 4xx is never retried.
739
765
  - `String(err)` and `${err}` read `Err: <key> {…data}`, the data as one line of JSON with secret-named keys masked,
740
766
  so a ``logger.warn(`…: ${String(error)}`)`` keeps the exit code or status the `Err` carried.
741
767
  - **A failure another process reported travels as itself.** A server-to-server `fetch.<endpoint>(..., { origin })` restores the remote `Err` — the same class, key and `data` — so a hop that rethrows it answers its own caller with that error and the dictionary translates it. Never wrap the catch in a `new Error`, and never re-key it as a `new Err` of your own: both discard what the endpoint chose to say, and a bare `Error` is generalized to `Internal Server Error` on the way out.
@@ -768,9 +794,10 @@ it.
768
794
  `Person` for an act reserved for a person, `mcp: { cru: false }` for the tools whose input models cost the model
769
795
  tokens, `outputSchema` for nothing that reaches the model.
770
796
  - Settings live in the app's `lib/option.ts` — `option.setMcp({ enabled, readOnly, path, version, instructions,
771
- allowedOrigins, pageSize, language, outputSchema, rateLimit, auth })`, or `setMcp((env) => …)` for one derived from the server env, **not
797
+ allowedOrigins, pageSize, language, outputSchema, rateLimit, apps, modern, auth })`, or `setMcp((env) => …)` for one derived from the server env, **not
772
798
  `main.ts`**. Each field has an `AKAN_MCP_*` env spelling. `AKAN_MCP_READONLY=true` is the read-only-deployment
773
- valve, not the exposure switch; `AKAN_MCP=false` takes the whole surface off. Every call that executes an
799
+ valve, not the exposure switch; `AKAN_MCP=false` takes the whole surface off, and an edge server
800
+ (`AKAN_PUBLIC_OPERATION_MODE=edge`) starts with it off unless `AKAN_MCP=true` or `setMcp(true)`. Every call that executes an
774
801
  endpoint is rate-limited per caller (120 a minute, 8 in flight, per process; 429 + `Retry-After`) — `rateLimit`
775
802
  / `AKAN_MCP_RATE_LIMIT` tunes or disables it.
776
803
  - **Authentication is the same server.** An app mounting `libs/shared` serves the OAuth 2.1 authorization server
@@ -811,7 +838,13 @@ it.
811
838
  from, minus what is already attached; lists — only lists — are cut to `promptBudget`. A guard refusing a query
812
839
  inside the body reads as the screen refusing the account, the same way a redirect does. A
813
840
  required argument left out answers a pointer to the `<model>List…` tool, never a guess. The description is the
814
- only instruction, in English — `<Agent.Guide>` never reaches MCP, and the in-page chat lists no page prompts.
841
+ only instruction, in English — `<Agent.Guide>` never reaches a prompt, and the in-page chat lists no page prompts.
842
+ - **The same page is a tool too, and an MCP Apps host renders it.** A `.prompt()` page answers `tools/call` with
843
+ that briefing plus its web URL, and a host that declares the `io.modelcontextprotocol/ui` extension (claude.ai,
844
+ ChatGPT, VS Code) shows the screen itself in the chat — the CSR bundle in a sandboxed iframe whose every call the
845
+ host relays under the agent's token. An endpoint kept off the shelf only by `mcp: false` stays callable by that
846
+ view and is listed to no model; a `Person` act is still refused there. `option.setMcp({ apps: false })` keeps
847
+ pages prompts only, and a page named like an endpoint stays one (boot warns).
815
848
 
816
849
  Full contract — configuration, wire behaviour, resource URIs, OAuth metadata, protocol revisions, and
817
850
  `McpProgress.report`: `get_guideline` with `mcpRule`, or `akan guideline show mcpRule`.
@@ -905,7 +938,7 @@ Full contract — the trigger-maintained mirror, tokenizer changes, `AKAN_SEARCH
905
938
  backstop, including for a `text` field *underneath* one of those. Do not work around either.
906
939
  - The role works on a relation (`image: field(File, { text: "thumb" })`) and on an array; an array of objects
907
940
  indexes by leaf key. A field inside a `Map` indexes nothing — there is no fixed path to extract it from.
908
- - Search runs in every database mode — fts5 on SQLite/libSQL, a weighted `tsvector` (`pg_trgm` for `trigram`) on
941
+ - Search runs in every database mode — fts5 on SQLite, a weighted `tsvector` (`pg_trgm` for `trigram`) on
909
942
  Postgres. The same text matches the same documents; only the order may differ on Postgres.
910
943
 
911
944
  ### Image & File Fields
@@ -957,9 +990,12 @@ shape, so `cascade` never means "related" — it means one of exactly these:
957
990
  `_layout.tsx`, `rootLayout()…render(…)` for the app's (or a basePath's) root `_layout.tsx`. Every route setting
958
991
  is a stage of the chain — `.param()`, `.search()`, `.config()`, `.head()`, `.loading()`; `.notFound()` /
959
992
  `.error()` on a layout; `.fonts()` / `.theme()` / `.manifest()` / `.reconnect()` / `.wsConnect()` /
960
- `.layoutStyle()` on the root layout; `.prompt()` on a page. A named export beside the chain, or `page()` in a
961
- `_layout.tsx`, fails the build. `.head()` takes JSX — `<title>`, `<meta>`, `<link>` — or a function of the
962
- route's args returning it; there is no metadata object, and analytics is the app's own script, not a stage.
993
+ `.layoutStyle()` / `.viewport()` on the root layout; `.prompt()` on a page. A named export beside the chain, or
994
+ `page()` in a `_layout.tsx`, fails the build. `.head()` takes JSX — `<title>`, `<meta>`, `<link>` — or a function
995
+ of the route's args returning it, and one that throws renders no head (warned about) so a layout's sign-in redirect
996
+ still answers; there is no metadata object, and analytics is the app's own script, not a stage.
997
+ Heads merge root first: a deeper `.head()` replaces the `<title>`, a `<meta>` with the same `name` / `property`,
998
+ and a canonical / manifest / icon link, and keeps the rest — so a page states only what it changes.
963
999
  - **A CSR bundle (a native app, `?csr=true`) never shows a root layout's own markup.** Pages render into the
964
1000
  frame, and what the root layout draws around `{children}` stays in a hidden container, so its wrappers cannot
965
1001
  stack a second screen beside the frame. Its components still mount and their effects run (`<Auth.User />` works).
@@ -971,7 +1007,9 @@ shape, so `cascade` never means "related" — it means one of exactly these:
971
1007
  Dayjs, an `enumOf` class → its union, `[T]` → array — a path value the type refuses answers not-found, and a
972
1008
  search value that fails is dropped. A layout may declare a subset. Names are string literals: `akan sync` reads
973
1009
  them off the source and refuses a `[projectId]` folder whose page declares no `.param("projectId")`. `lang` is
974
- never declared: every route sits under `/:lang`, and the locale segment reaches every stage as `lang`.
1010
+ never declared: every route sits under `/:lang`, and the locale segment reaches every stage as `lang`. On a web
1011
+ page's first render `st.use.searchParams()` is still the server's empty snapshot, synced right after hydration, so
1012
+ an effect that needs the query on its first run takes the page's `.search()` value as a prop.
975
1013
  - `_overrides.tsx` is a logic-free UI-override manifest: imports plus a single `export default override({ Slot: AppComponent })` (from `akanjs/ui`), no `"use client"`. It re-skins framework `akanjs/ui` components for its route subtree; nested manifests merge over ancestors slot-by-slot (closest wins). See the UI Customization reference for the slot list.
976
1014
  - The legacy shape — `export default async function Page` beside `pageConfig` / `head` / `Loading` exports — still
977
1015
  loads and logs one deprecation warning per file at boot. Migrate with the `workspaceRecipes` recipe "Migrating
@@ -988,10 +1026,10 @@ shape, so `cascade` never means "related" — it means one of exactly these:
988
1026
  export default page()
989
1027
  .param("orgId", ID)
990
1028
  .config({ transition: "stack" })
991
- .render(async ({ orgId }) => {
1029
+ .render(({ orgId }) => {
992
1030
  const { l } = usePage();
993
1031
  getSelf({ unauthorize: "/signin" });
994
- const [{ org }, { taskInitInOrg }] = await Promise.all([fetch.viewOrg(orgId), fetch.initTaskInOrg(orgId)]);
1032
+ const { taskInitInOrg } = fetch.initTaskInOrg(orgId);
995
1033
  return <Task.Zone.Card init={taskInitInOrg} prefix={`/org/${orgId}`} />;
996
1034
  });
997
1035
  ```
@@ -999,7 +1037,8 @@ export default page()
999
1037
  - There is no `loader=` / `render=` page prop and no `PageProps` interface: the chain declares the arguments and
1000
1038
  `.render()` receives them flat, typed, with `lang` on every route and `children` added for a layout.
1001
1039
  - Body order inside `.render()`: `usePage()`, auth, fetch, return.
1002
- - Run independent fetches through `Promise.all`, even when there is only one.
1040
+ - Hand each `fetch.init*` / `view*` / `edit*` field to its section unawaited. Await only what the shell itself
1041
+ reads — a title, a redirect, an id for a link — and run those awaits through one `Promise.all`.
1003
1042
  - Gate auth at `_layout.tsx`; repeating `getSelf({ unauthorize: "/signin" })` in the page is fine and common.
1004
1043
  - `async` on the render callback only when the body awaits; the callback is not a React component, so it takes no
1005
1044
  name and no return type.
@@ -1020,12 +1059,12 @@ export default page()
1020
1059
  content outside the markers is yours. `akan lint` fails when the generated section is stale.
1021
1060
  - The `plugin/` facet holds Akan plugin declarations; files use the `<name>.plugin.ts` convention (e.g. `pushNotification.plugin.ts`) and are re-exported from the generated `plugin/index.ts` barrel.
1022
1061
  - Do not add `apps/*/base` or `libs/*/base`; place shared utilities under that app or lib's own `common/`.
1023
- - `apps/*/lib` and `libs/*/lib` root files are limited to generated/support files: `cnst.ts`, `db.ts`, `dict.ts`, `option.ts`, `sig.ts`, `srv.ts`, `st.ts`, `useClient.ts`, `useServer.ts` — plus a `<model>.signal.test.ts` / `.spec.ts`, the one hand-written file that belongs there because the suite boots the whole barrel.
1062
+ - `apps/*/lib` and `libs/*/lib` root files are limited to generated/support files: `cnst.ts`, `db.ts`, `dict.ts`, `option.ts`, `sig.ts`, `srv.ts`, `st.ts`, `useClient.ts`, `useServer.ts`. Nothing hand-written sits beside them, tests included: a test goes in the module folder it covers (`lib/<model>/<model>.signal.test.ts`), or in `srvkit/` when it drives several modules.
1024
1063
  - Domain module folders are `lib/<model>` for database modules, `lib/_<service>` for service modules, and `lib/__scalar/<scalar>` for scalar modules.
1025
1064
  - Database module UI files are limited to `<Model>.Template.tsx`, `<Model>.Unit.tsx`, `<Model>.Util.tsx`, `<Model>.View.tsx`, and `<Model>.Zone.tsx`.
1026
1065
  - Service module UI files are limited to `<Service>.Util.tsx` and `<Service>.Zone.tsx`.
1027
1066
  - Scalar module UI files are limited to `<Scalar>.Template.tsx` and `<Scalar>.Unit.tsx`.
1028
- - Module `*.test.ts`, `*.test.tsx`, `*.spec.ts`, and `*.spec.tsx` files are allowed.
1067
+ - A module test is named after the one module file it covers — `<model>.<role>.test.ts` / `.spec.ts`, `<Model>.<Role>.test.tsx` — so `<model>.<topic>.signal.test.ts` is refused, just like `<model>.<topic>.signal.ts`.
1029
1068
  - `ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, `plugin/index.ts`, and module `lib/**/index.ts` files are generated by scanSync; do not hand-edit or track them.
1030
1069
  - Generated facet indexes export only 1-depth files/folders with `export * from "./name";`. A facet folder with
1031
1070
  nothing to export syncs to `export {};` (a missing folder gets no barrel), so a stale barrel heals on the next
@@ -1131,6 +1170,10 @@ actions and state (see `<model>.store.ts` above).
1131
1170
  exist yet and the whole `libs/` container is watched, which is the safe direction.
1132
1171
  - Test production build generation with `bun run akan build <appName>`.
1133
1172
  - To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
1173
+ - A server a test boots in-process takes `start({ listen: false, schedules: false })` to run no internal (init,
1174
+ interval, cron, queue worker); `listen: false` alone still runs them, as a batch replica does. A server process a
1175
+ test spawns from source resolves the workspace from its cwd and gets the root `.env` from Bun, so spawn it inside
1176
+ the repo with `bun --no-env-file`, or spawn the built `dist/apps/<app>` output, which needs neither.
1134
1177
 
1135
1178
  ```bash
1136
1179
  cd dist/apps/<appName> && USE_AKANJS_PKGS=true AKAN_PUBLIC_REPO_NAME=<repo> AKAN_PUBLIC_SERVE_DOMAIN="<domain>" \