@akanjs/cli 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-zfab7pkn.js → CodeAgent-szbjv4gv.js} +2 -2
  3. package/{CodeTui-0zhnr0pw.js → CodeTui-5nw86ab6.js} +1 -1
  4. package/{agent.command-aja6z9mn.js → agent.command-np1dffkb.js} +5 -5
  5. package/akanCode.js +2 -2
  6. package/{application.command-049ehgx3.js → application.command-gk69v1xs.js} +8 -8
  7. package/{applicationBuildRunner-75n5rpwp.js → applicationBuildRunner-wx09n9g6.js} +3 -3
  8. package/buildBatch.proc.js +4 -4
  9. package/{cloud.command-gw4kqxt8.js → cloud.command-tw06613r.js} +10 -10
  10. package/cloud.script-64gzba8r.js +20 -0
  11. package/{code.command-x9t6z5nw.js → code.command-hmtx14b2.js} +6 -6
  12. package/{context.command-yfbe1rtd.js → context.command-6aqvdx43.js} +13 -13
  13. package/context.runner-hnkvv3z3.js +21 -0
  14. package/{guideline.command-kq9gp690.js → guideline.command-axvf68gc.js} +2 -2
  15. package/guidelines/conventions/conventions.instruction.md +89 -17
  16. package/guidelines/queryRule/queryRule.instruction.md +4 -2
  17. package/guidelines/runtimeRule/runtimeRule.instruction.md +10 -5
  18. package/guidelines/transportRule/transportRule.instruction.md +40 -4
  19. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +1 -1
  20. package/incrementalBuilder.proc.js +3 -3
  21. package/{index-66yspb67.js → index-1bjkfvm9.js} +29 -27
  22. package/{index-b95yhqjr.js → index-1gs9g12n.js} +3 -3
  23. package/{index-y7hx1ygz.js → index-3xmzr1p1.js} +2 -2
  24. package/{index-mzjb6zp4.js → index-50jjwg8y.js} +1 -1
  25. package/{index-150s07n8.js → index-50wb2a26.js} +313 -93
  26. package/{index-8b639zes.js → index-6keyf4sg.js} +4 -4
  27. package/{index-vs8mefhy.js → index-bsv901fd.js} +7 -7
  28. package/{index-5pvxf7z0.js → index-cb6yc5mb.js} +2 -2
  29. package/{index-rfcz04w9.js → index-f4e08ptp.js} +4 -4
  30. package/{index-25fvrxmy.js → index-g38nhv87.js} +1 -1
  31. package/{index-m3xsb1ms.js → index-gtjgnv36.js} +29 -11
  32. package/{index-mb59b8x0.js → index-h9ht6s60.js} +1 -1
  33. package/{index-5g4rkrvt.js → index-hp3stk1m.js} +3 -3
  34. package/{index-h54yj9wr.js → index-hxnnfy57.js} +6 -5
  35. package/{index-kn5d4jmg.js → index-j38qkvkk.js} +7 -7
  36. package/{index-y5ga5w9t.js → index-jb0hmt3k.js} +3 -3
  37. package/{index-7p4c0yer.js → index-jk9xnck6.js} +33 -11
  38. package/{index-senscydg.js → index-k9rrten6.js} +6 -1
  39. package/{index-qe71g4ze.js → index-nwd6qzvv.js} +42 -23
  40. package/{index-a37by9hn.js → index-q6ft9fkc.js} +4 -4
  41. package/{index-br8pkrk1.js → index-t91a7339.js} +5 -5
  42. package/{index-c14nsvnv.js → index-xwprn9nr.js} +1 -1
  43. package/{index-fkvb79qe.js → index-z750gk1j.js} +15 -11
  44. package/{index-5q14pwzd.js → index-zwd84m0w.js} +4 -4
  45. package/{index-8kvy8qb4.js → index-zyw622y1.js} +12 -2
  46. package/index.js +21 -21
  47. package/{library.command-keqjevpp.js → library.command-9m2rdwwz.js} +3 -3
  48. package/{localRegistry.command-pydeq5zf.js → localRegistry.command-kda81jyk.js} +9 -9
  49. package/{module.command-dkrnqx3v.js → module.command-q63dffya.js} +5 -5
  50. package/{package.command-wmc0ys5y.js → package.command-y2jh0m99.js} +3 -3
  51. package/package.json +2 -2
  52. package/{page.command-tf3erhrv.js → page.command-f3e4cwhh.js} +3 -3
  53. package/{primitive.command-7hqpegj4.js → primitive.command-r4kq75qj.js} +6 -6
  54. package/{quality.command-8122axq3.js → quality.command-f7gzh26z.js} +9 -2
  55. package/{repair.command-mcy12h0n.js → repair.command-0qmyr6qa.js} +5 -5
  56. package/{scalar.command-vvvbzttc.js → scalar.command-kddn7av4.js} +4 -4
  57. package/{subspace.command-pzvqfy5h.js → subspace.command-ft65qvc4.js} +5 -7
  58. package/templates/app/tsconfig.json.template +1 -6
  59. package/templates/app/ui/Recipe/jellyButton.ts +1 -0
  60. package/templates/facetIndex/index.ts +21 -6
  61. package/templates/lib/dict.ts +9 -1
  62. package/templates/lib/srv.ts +2 -3
  63. package/templates/libRoot/common/index.ts +2 -7
  64. package/templates/libRoot/srvkit/index.ts +2 -7
  65. package/templates/libRoot/ui/index.ts +2 -7
  66. package/templates/libRoot/webkit/index.ts +2 -7
  67. package/templates/moduleRoot/index.tsx +3 -3
  68. package/{tunnel.command-82zz5xnq.js → tunnel.command-b3ymdk4f.js} +2 -2
  69. package/{workflow.command-y8x3kgjv.js → workflow.command-z1srcdfw.js} +10 -10
  70. package/{workspace.command-jvsb7brc.js → workspace.command-vp6vmx3z.js} +21 -21
  71. package/cloud.script-c723cpcb.js +0 -20
  72. package/context.runner-erfe68j5.js +0 -21
  73. package/index-bjwxs904.js +0 -77
  74. package/templates/libRoot/common/commonLogic.ts +0 -14
  75. package/templates/libRoot/srvkit/backendLogic.ts +0 -13
  76. package/templates/libRoot/webkit/frontendLogic.ts +0 -13
package/.build-stamp CHANGED
@@ -1 +1 @@
1
- 2cb1ad11b821f78e2a35e5f27d0db3062cb550ce30e9b96b6ae253a08cd22cf7
1
+ ed6c6a9442699bc1b0b2b6ddbe72d0cdd440f78e0d21be5e06bc85a7790491eb
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  CodeAgent2
4
- } from "./index-y5ga5w9t.js";
5
- import"./index-c14nsvnv.js";
4
+ } from "./index-jb0hmt3k.js";
5
+ import"./index-xwprn9nr.js";
6
6
  import"./index-7md2g9vw.js";
7
7
  export {
8
8
  CodeAgent2 as CodeAgent
@@ -4,7 +4,7 @@ import {
4
4
  McpServerConfig,
5
5
  McpTokenStore,
6
6
  McpSignIn
7
- } from "./index-c14nsvnv.js";
7
+ } from "./index-xwprn9nr.js";
8
8
  import {
9
9
  CodeTuiLines2,
10
10
  CodeTuiAgents2,
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  Workspace,
4
4
  command
5
- } from "./index-a37by9hn.js";
5
+ } from "./index-q6ft9fkc.js";
6
6
  import"./index-52w0fvh8.js";
7
- import"./index-150s07n8.js";
7
+ import"./index-50wb2a26.js";
8
8
  import"./index-hj6w4ka1.js";
9
- import"./index-mb59b8x0.js";
10
- import"./index-8kvy8qb4.js";
9
+ import"./index-h9ht6s60.js";
10
+ import"./index-zyw622y1.js";
11
11
  import"./index-zzvvc845.js";
12
12
  import {
13
13
  AgentScript
14
- } from "./index-b95yhqjr.js";
14
+ } from "./index-1gs9g12n.js";
15
15
 
16
16
  // pkgs/@akanjs/cli/agent/agent.command.ts
17
17
  class AgentCommand extends command("agent", [AgentScript], ({ public: target }) => ({
package/akanCode.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  CodeMailbox2,
29
29
  CodeSessionFork2,
30
30
  CodeAgent2
31
- } from "./index-y5ga5w9t.js";
31
+ } from "./index-jb0hmt3k.js";
32
32
  import {
33
33
  akanCodeDefaultModel2,
34
34
  akanCodeModel2,
@@ -39,7 +39,7 @@ import {
39
39
  import {
40
40
  SubagentPool2,
41
41
  akanCodePaths2
42
- } from "./index-c14nsvnv.js";
42
+ } from "./index-xwprn9nr.js";
43
43
  import"./index-7md2g9vw.js";
44
44
  import {
45
45
  CodeAgentRpcHost2
@@ -6,19 +6,19 @@ import {
6
6
  Exec,
7
7
  Workspace,
8
8
  command
9
- } from "./index-a37by9hn.js";
9
+ } from "./index-q6ft9fkc.js";
10
10
  import"./index-52w0fvh8.js";
11
- import"./index-150s07n8.js";
11
+ import"./index-50wb2a26.js";
12
12
  import {
13
13
  getMobileTargetChoices
14
- } from "./index-7p4c0yer.js";
15
- import"./index-senscydg.js";
16
- import"./index-66yspb67.js";
14
+ } from "./index-jk9xnck6.js";
15
+ import"./index-k9rrten6.js";
16
+ import"./index-1bjkfvm9.js";
17
17
  import {
18
18
  ApplicationScript
19
- } from "./index-qe71g4ze.js";
19
+ } from "./index-nwd6qzvv.js";
20
20
  import"./index-7md2g9vw.js";
21
- import"./index-mzjb6zp4.js";
21
+ import"./index-50jjwg8y.js";
22
22
 
23
23
  // pkgs/@akanjs/cli/application/application.command.ts
24
24
  var mobileTargetOption = {
@@ -185,7 +185,7 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
185
185
  packUpdate: target({
186
186
  desc: "Pack an unsigned over-the-air update of the mobile web bundle, for the signer to publish"
187
187
  }).with(App).option("platform", String, { enum: ["ios", "android"], desc: "the app it updates" }).option("target", String, mobileTargetOption).option("env", String, releaseEnvOption).option("out", String, {
188
- desc: "output folder (default: .akan/native/<target>/updates/<platform>)",
188
+ desc: "output folder (default: dist/native/<app>/<target>/updates/<platform>)",
189
189
  nullable: true
190
190
  }).option("against", String, {
191
191
  desc: "bundle.json of the store build it must run in; fails when it needs a new one",
@@ -1,15 +1,15 @@
1
1
  // @bun
2
2
  import {
3
3
  Spinner
4
- } from "./index-150s07n8.js";
5
- import"./index-m3xsb1ms.js";
4
+ } from "./index-50wb2a26.js";
5
+ import"./index-gtjgnv36.js";
6
6
  import {
7
7
  AllRoutesBuilder,
8
8
  CsrArtifactBuilder,
9
9
  FontPruner,
10
10
  precompressArtifacts,
11
11
  SsrBaseArtifactBuilder
12
- } from "./index-fkvb79qe.js";
12
+ } from "./index-z750gk1j.js";
13
13
  import"./index-52w0fvh8.js";
14
14
  import"./index-n68jx79w.js";
15
15
 
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  import {
3
3
  ApplicationBuildReporter
4
- } from "./index-senscydg.js";
4
+ } from "./index-k9rrten6.js";
5
5
  import {
6
6
  resolveSsrPageEntriesForApp,
7
7
  WorkspaceExecutor,
8
8
  AppExecutor
9
- } from "./index-150s07n8.js";
9
+ } from "./index-50wb2a26.js";
10
10
  import {
11
11
  computeRouteSeedIndex,
12
12
  saveRouteSeedIndex,
@@ -14,14 +14,14 @@ import {
14
14
  PagesBundleBuilder,
15
15
  CsrDevBundler,
16
16
  SsrDevBundler
17
- } from "./index-m3xsb1ms.js";
17
+ } from "./index-gtjgnv36.js";
18
18
  import"./index-52w0fvh8.js";
19
19
  import {
20
20
  CsrArtifactBuilder,
21
21
  CssCompiler,
22
22
  FontOptimizer,
23
23
  SsrBaseArtifactBuilder
24
- } from "./index-fkvb79qe.js";
24
+ } from "./index-z750gk1j.js";
25
25
  import"./index-n68jx79w.js";
26
26
 
27
27
  // pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts
@@ -6,22 +6,22 @@ import {
6
6
  import {
7
7
  Workspace,
8
8
  command
9
- } from "./index-a37by9hn.js";
10
- import"./index-150s07n8.js";
9
+ } from "./index-q6ft9fkc.js";
10
+ import"./index-50wb2a26.js";
11
11
  import {
12
12
  PlatformTestRun
13
- } from "./index-h54yj9wr.js";
14
- import"./index-senscydg.js";
15
- import"./index-7p4c0yer.js";
16
- import"./index-66yspb67.js";
17
- import"./index-qe71g4ze.js";
13
+ } from "./index-hxnnfy57.js";
14
+ import"./index-k9rrten6.js";
15
+ import"./index-jk9xnck6.js";
16
+ import"./index-1bjkfvm9.js";
17
+ import"./index-nwd6qzvv.js";
18
18
  import"./index-7md2g9vw.js";
19
19
  import"./index-2ahj896a.js";
20
- import"./index-y7hx1ygz.js";
20
+ import"./index-3xmzr1p1.js";
21
21
  import {
22
22
  CloudScript2
23
- } from "./index-br8pkrk1.js";
24
- import"./index-mzjb6zp4.js";
23
+ } from "./index-t91a7339.js";
24
+ import"./index-50jjwg8y.js";
25
25
 
26
26
  // pkgs/@akanjs/cli/cloud/cloud.command.ts
27
27
  var localRegistryUrl = () => process.env.AKAN_NPM_REGISTRY ?? "http://127.0.0.1:4873";
@@ -0,0 +1,20 @@
1
+ // @bun
2
+ import {
3
+ CloudScript2
4
+ } from "./index-t91a7339.js";
5
+ import"./index-hxnnfy57.js";
6
+ import"./index-3xmzr1p1.js";
7
+ import"./index-nwd6qzvv.js";
8
+ import"./index-50jjwg8y.js";
9
+ import"./index-1bjkfvm9.js";
10
+ import"./index-jk9xnck6.js";
11
+ import"./index-94s080qm.js";
12
+ import"./index-q6ft9fkc.js";
13
+ import"./index-7md2g9vw.js";
14
+ import"./index-k9rrten6.js";
15
+ import"./index-50wb2a26.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-a37by9hn.js";
7
+ } from "./index-q6ft9fkc.js";
8
8
  import"./index-52w0fvh8.js";
9
- import"./index-150s07n8.js";
9
+ import"./index-50wb2a26.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-zfab7pkn.js"),
22
+ import("./CodeAgent-szbjv4gv.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-zfab7pkn.js"),
38
- import("./CodeTui-0zhnr0pw.js")
37
+ import("./CodeAgent-szbjv4gv.js"),
38
+ import("./CodeTui-5nw86ab6.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-zfab7pkn.js");
63
+ const { CodeAgent } = await import("./CodeAgent-szbjv4gv.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));
@@ -2,24 +2,24 @@
2
2
  import {
3
3
  Workspace,
4
4
  command
5
- } from "./index-a37by9hn.js";
5
+ } from "./index-q6ft9fkc.js";
6
6
  import"./index-52w0fvh8.js";
7
- import"./index-150s07n8.js";
7
+ import"./index-50wb2a26.js";
8
8
  import"./index-hj6w4ka1.js";
9
- import"./index-mb59b8x0.js";
10
- import"./index-8kvy8qb4.js";
11
- import"./index-kn5d4jmg.js";
12
- import"./index-7p4c0yer.js";
9
+ import"./index-h9ht6s60.js";
10
+ import"./index-zyw622y1.js";
11
+ import"./index-j38qkvkk.js";
12
+ import"./index-jk9xnck6.js";
13
13
  import"./index-zzvvc845.js";
14
- import"./index-5g4rkrvt.js";
15
- import"./index-25fvrxmy.js";
16
- import"./index-rfcz04w9.js";
17
- import"./index-8b639zes.js";
18
- import"./index-5pvxf7z0.js";
19
- import"./index-vs8mefhy.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";
20
20
  import {
21
21
  ContextScript
22
- } from "./index-5q14pwzd.js";
22
+ } from "./index-zwd84m0w.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-j38qkvkk.js";
5
+ import"./index-bsv901fd.js";
6
+ import"./index-cb6yc5mb.js";
7
+ import"./index-6keyf4sg.js";
8
+ import"./index-hp3stk1m.js";
9
+ import"./index-f4e08ptp.js";
10
+ import"./index-g38nhv87.js";
11
+ import"./index-jk9xnck6.js";
12
+ import"./index-zyw622y1.js";
13
+ import"./index-h9ht6s60.js";
14
+ import"./index-zzvvc845.js";
15
+ import"./index-q6ft9fkc.js";
16
+ import"./index-50wb2a26.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-a37by9hn.js";
5
+ } from "./index-q6ft9fkc.js";
6
6
  import"./index-52w0fvh8.js";
7
- import"./index-150s07n8.js";
7
+ import"./index-50wb2a26.js";
8
8
  import {
9
9
  Prompter2
10
10
  } from "./index-zzvvc845.js";
@@ -54,9 +54,13 @@ back.
54
54
  `foreground`, `<color>-foreground`, `destructive`), and color literals in `style={{...}}`. Use semantic tokens
55
55
  (`bg-primary`, `text-foreground/70`). A legitimate fixed color (OS-chrome mockups, data-viz) takes a
56
56
  `// biome-ignore lint/plugin: <reason>`. (`no-raw-palette-class`, `no-arbitrary-color`, `no-daisyui-legacy-class`,
57
- `no-inline-color`, `no-interpolated-arbitrary-class`)
58
- - **Never `throw new Error`.** Throw `new Err("<module>.error.<key>")` and register the key as `[en, ko]` in that
59
- module's dictionary `.error({})`. Import `Err` from `"../dict"` on the server and from `"@libs/<lib>/client"` or
57
+ `no-inline-color`, `no-interpolated-arbitrary-class`) Color literals in SVG color attributes (`fill`, `stroke`,
58
+ `stopColor`, `floodColor`, `lightingColor`, `color`) and in `el.style.<prop> = …` / `el.style.setProperty(…)`
59
+ writes are flagged as warnings for now; `currentColor`, `none`, `transparent`, `var(--…)` and `url(#id)` stay legal.
60
+ - **Never `throw new Error`, and never construct a raw `Error` at all.** Throw `new Err("<module>.error.<key>")` and
61
+ register the key as `[en, ko]` in that module's dictionary `.error({})`. A raw `Error` passed to `reject(...)`,
62
+ returned or stored loses the key and the status just the same; that shape is flagged as a warning for now, a
63
+ `throw` as an error. Import `Err` from `"../dict"` on the server and from `"@libs/<lib>/client"` or
60
64
  `"@apps/<app>/client"` in UI. `no-throw-raw-error.grit` exempts tests, `*.constant.ts`, `common/**`, `env/**`, and
61
65
  a root `native/` folder — `common/` and `env/` have no legal `Err` import path, so keep throwing code out of them,
62
66
  and a native plugin throws `AkanNativeError`, the error its bridge carries back to the page.
@@ -77,12 +81,20 @@ back.
77
81
  `*.constant.ts`, `*.store.ts`, and the five module component suffixes (`no-bang-comment-in-client.grit`). Bun
78
82
  classifies `//!` and `/*!` as legal comments and keeps them through minification, so the note ships to every
79
83
  visitor. Use `// FIXME:` there; `//!` stays legal in server, `srvkit/`, and CLI files.
84
+ - **Write `{name}`, never `{{name}}`, in a dictionary `.error()` / `.translate()` entry**
85
+ (`no-double-brace-placeholder.grit`): interpolation replaces single braces only, so a doubled one renders as typed.
86
+ - **Every endpoint and named slice names its guards** (`no-unguarded-endpoint.grit`, a warning). A
87
+ `query` / `mutation` / `pubsub` / `message`, a named slice's `init()`, or a `slice()` guards map without `root`
88
+ that declares no guards (or `guards: []`) answers every HTTP and websocket caller. Write `guards: [Public]` when
89
+ anonymous access is the intent.
80
90
  - **Never touch `document.cookie`, `localStorage` or `sessionStorage` in app or lib code** (`no-document-cookie.grit`,
81
91
  `no-web-storage.grit`). A native shell serves the page from `app://localhost` on iOS, macOS and Linux, which keeps
82
92
  no cookies: `document.cookie` reads `""` and a write is dropped, so the value exists in the browser and vanishes in
83
93
  the app. Web Storage does work in the shells, but it bypasses the store akanjs picks per platform (the shell's
84
94
  Preferences in an app) and throws during SSR. Use `getCookie` / `setCookie` / `removeCookie`, `storage`, and
85
- `secretStorage` for tokens and credentials, all from `akanjs/client`.
95
+ `secretStorage` for tokens and credentials, all from `akanjs/client`. `setCookie` writes `path=/; SameSite=Lax;
96
+ Secure`, and an options object overrides only the keys it names — pass `sameSite: "none"` only for a cookie a
97
+ cross-site POST must carry back.
86
98
  - **Browser APIs an app WebView lacks live in `webkit/`** (`no-web-only-api-outside-webkit.grit`, a warning):
87
99
  `navigator.share` / `canShare` (Android's WebView has none), `navigator.serviceWorker` and
88
100
  `Notification.requestPermission` / `.permission` (no app page has them), `navigator.geolocation` (use
@@ -177,12 +189,15 @@ back.
177
189
  - Never use a non-null assertion. Narrow with `?.`, an early return, or a type predicate such as `.filter((id): id is string => !!id)`.
178
190
  - Escape with `as unknown as T`. Never `as any`.
179
191
  - Never annotate a component's return type. Annotate a helper only when the return is a union, a tuple, or a type predicate.
180
- - Use `as const` on every `enumOf(...)` array, every `via(Model, [...] as const, …)` Light tuple, and every module-scope lookup map. Never use the TypeScript `enum` keyword.
192
+ - Use `as const` on every `enumOf(...)` array, every `via(Model, [...] as const, …)` Light tuple, and every module-scope lookup map. Never use the TypeScript `enum` keyword. (`enumOf` infers its literal values even without `as const`, so a typo there fails to compile; write it anyway for consistency.)
181
193
  - Async functions carry no `Async` suffix.
182
194
 
183
195
  ### Test Code
184
196
 
185
197
  - Write TypeScript tests with Bun's test runner and import `describe`, `expect`, and `test` from `bun:test`.
198
+ - In a signal test every `fetch.clone()` reports its own client address (`198.18.x.y`), so an `ip` rate limit counts
199
+ each agent apart. Clone once per simulated user, signup steps included, rather than driving many users through
200
+ the root `fetch`; `clone({ clientIp })` names one explicitly.
186
201
  - Keep tests colocated with the source they cover using `*.test.ts` or `*.spec.ts`, following the existing nearby pattern.
187
202
  - Prefer focused behavior tests for public contracts and edge cases over implementation-detail assertions.
188
203
  - 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.
@@ -362,7 +377,7 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
362
377
  the same shape and overrides the section field by field — objects merge key by key, a list or any other value
363
378
  replaces the section's. `akan start-ios` / `start-android` / `start-desktop` run a dev build that loads its pages
364
379
  from `akan start`; `build-ios` / `build-android` / `build-desktop` write
365
- `apps/<app>/.akan/native/<target>/build/<platform>`, a desktop app only for the OS that builds it — `--arch` picks
380
+ `dist/native/<app>/<target>/build/<platform>`, a desktop app only for the OS that builds it — `--arch` picks
366
381
  a Windows or Linux app's CPU, and a macOS app is Apple silicon only. `--installer true` adds a Windows setup, a
367
382
  macOS dmg or a Linux AppImage; `AKAN_NATIVE_MACOS_*` signs with a Developer ID and notarizes,
368
383
  `AKAN_NATIVE_WINDOWS_*` signs with Authenticode. With `updates: { url, publicKey }` an installed app takes the signed
@@ -455,6 +470,12 @@ so chains compose (`org.removeUser(id).removeInvite(id).save()`). Put a one-line
455
470
  transition. Atomic counters live on the Model class with the updater-callback form, returning `!!modifiedCount`.
456
471
  Indexes and derived totals go in `static override _onSchema`, not in the service. **Removal is always soft** — the
457
472
  framework has no hard delete for a model table, and `delete` is deliberately left unused so it can mean one later.
473
+ Every read and query-level write sees live rows only (`removedAt IS NULL`). `{ withRemoved: true }` reaches removed
474
+ rows: the third argument of `find` / `findOne` / `findById` (projection second, `null` when none), the last of
475
+ `count` / `exists` / `updateMany`, or inside `updateOne` / `updateById`'s options. Without it, a `removedAt`
476
+ condition only a removed row can meet (`q.exists("removedAt")`, a comparison) throws `can never match`. Removes,
477
+ slices, filter methods, loaders and `pickById` stay live-only, a removed document is read-only (`save()` throws),
478
+ and `updateById(id, { removedAt: null }, { withRemoved: true })` revives one without hooks.
458
479
  The facade spells `Many`/`One` out on its writes (`updateOne` / `updateMany` / `removeOne` / `removeMany`); only the
459
480
  count was shortened to `count(query)`, with `countDocuments` kept as `@deprecated`. `updateById(id, update)` and
460
481
  `removeById(id)` are those same query-level writes narrowed to one id, **not** the document path: they fire no hooks,
@@ -508,7 +529,12 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
508
529
 
509
530
  - Guards run on both HTTP and websocket calls. Read the caller with `context.get<T>("account")` instead of
510
531
  branching on `getHttpContext()` / `getWebSocketContext()`, and keep them side-effect free and safe to re-run —
511
- a pubsub room's guards are re-run whenever the socket's credential changes.
532
+ a pubsub room's guards are re-run whenever the socket's credential changes, each time on a fresh context, so a
533
+ guard may memoize per context without replaying the verdict it gave at subscribe.
534
+ - **Arguments are parsed after the `"account"` guards, and `"resource"` guards run after the parse**, whatever
535
+ order `guards: [...]` lists them in. A caller an account guard refuses never reaches an argument parser, so an
536
+ expensive scalar parser is not a pre-auth attack surface. A guard or middleware that reads an argument earlier
537
+ still gets it (parsed on first read). An unauthenticated call with a malformed argument answers 401/403, not 400.
512
538
  - Slice-level `guards` only reach the generated query/mutation endpoints. A `pubsub`/`message` endpoint is
513
539
  unguarded unless it declares its own `guards` in its signal option.
514
540
  - **Never read the caller's IP off the socket or the request peer — take `.with(Ip)`** (`context.getClientIp()`
@@ -538,14 +564,32 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
538
564
  per caller and one shared entry would be one caller's answer handed to the next — that endpoint and every
539
565
  `mutation` are named in the log and left uncached. The lookup runs after the guards, so a hit reaches only a
540
566
  caller they admitted; what is cached is the handler's result, so `resolveReturn` still masks fields and resolves
541
- relations per call; a cache backend that is down is warned about and the call runs uncached. The default
542
- middleware chain is `Logging → Timeout → <lib middlewares>`, then guards → internal arguments → cache →
543
- handler, and there is no `Retry` middleware.
567
+ relations per call; a cache backend that is down is warned about and the call runs uncached. A call runs: the
568
+ cross-site check and an `ip` rate limit, the body read, then the middleware chain `Logging → Timeout → <lib
569
+ middlewares>`, then an `account` rate limit → account guards → argument parse → resource guards → internal
570
+ arguments → cache → handler. There is no `Retry` middleware.
571
+ - **A rate limit is the endpoint's `{ rateLimit: { calls, windowMs?, by? } }`** — `windowMs` 60 000 unless named,
572
+ `by: "ip"` (counted before the body is read) or `"account"` (counted after the middlewares, before the guards;
573
+ a caller with no account counts by address). It answers 429 `base.error.tooManyRequests` with `{ seconds }` and
574
+ a `Retry-After` header. Counters share the cache across instances. An app tunes it in `option.ts` with
575
+ `option.setRateLimit({ budget, endpoints, accountKey })` — `budget` covers every endpoint that declares none,
576
+ `endpoints` overrides one by key (a lib's included, `false` exempts), `accountKey` names who `by: "account"`
577
+ counts — and `rateLimit: false` exempts an endpoint from the app's budget. `AKAN_RATE_LIMIT=off` turns every
578
+ budget off whatever the code says; budgets are off in a `local` environment unless `AKAN_RATE_LIMIT=on`. A
579
+ business key (a phone number, an email) is counted in a service with `plug(RateLimit)` and
580
+ `consume(key, { calls, windowMs })`, which no switch turns off. Callers whose address is unknown share one
581
+ budget, and a page's server-side fetches carry the visitor's address, not the server's.
544
582
 
545
583
  ### Authorization Defaults
546
584
 
547
585
  - **Every `slice()` takes an explicit `{ guards: {…} }` second argument, and `root:` is always `Admin`.**
548
- - **Every custom `mutation` / `query` / `message` names its own `guards: [...]` array.** Never rely on the slice default. `Public` belongs on a slice `get:`, never on a mutation.
586
+ - **A generated verb is taken off HTTP with `guards: { cru: false }`** (or `get`/`create`/`update`/`remove:
587
+ false`), and a verb whose guards resolve to none is not mounted at all. `mcp: { cru: false }` does **not** do
588
+ this: it keeps the verbs off the agent shelf while HTTP serves them to whoever passes their guards.
589
+ - **Every endpoint that declares no guards is named in a boot `warn`**, since it answers anyone over HTTP and
590
+ WebSocket — a named slice's `init()`, a root slice, a `pubsub`/`message`/`query`/`mutation`. Write
591
+ `guards: [Public]` to keep one open on purpose.
592
+ - **Every custom `mutation` / `query` / `message` names its own `guards: [...]` array.** Never rely on the slice default. `Public` belongs on a slice `get:`; on a mutation it is reserved for an act that must work signed out — signing up or in, a token refresh, an OAuth protocol route — and such a mutation also declares a `rateLimit`. MCP refuses a mutation whose only guard is `Public`, so it never reaches an agent.
549
593
  - **The guards are also the MCP exposure decision** — see MCP Exposure. An endpoint that names none is not published to agents at all, and a mutation whose only guard is `Public` is refused, so a missing `guards` array now costs visibility as well as authorization.
550
594
  - Resource guards are `Can<Verb><Model>` classes in `srvkit/guards.ts` that `implements Guard` with an `async canPass(context)`. They **fail closed**: no resource named ⇒ `false`; a load that throws ⇒ `logger.warn` then `false`. Admin bypass goes first.
551
595
  - Keep `static name = "User";` on guard classes. `fetch` serializes guard names and the API explorer filters on them; it looks like dead code, and deleting it breaks the UI. Comment it so the next reader knows.
@@ -560,7 +604,8 @@ Full contract — credential handshake, room revalidation, socket cleanup scopin
560
604
  - `.body(...)` / `.param(...)` args accept `ConstantFieldTypeInput` only: scalars, model refs, or `enumOf(...)`.
561
605
  - Numbers must use `Int` or `Float` — `Number` is rejected (`pkgs/akanjs/signal/endpointInfo.ts`).
562
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.
563
- - Bytes are `Binary`, never `Any` — see Scalar & Field Type Reference.
607
+ - Bytes are `Binary`, never `Any` — see Scalar & Field Type Reference. A `Binary` return travels as base64 in an
608
+ HTTP body, and a return declared `Any` that turns out to be bytes fails instead of reaching the caller as `{}`.
564
609
 
565
610
  ### Binary Pubsub And Mutation Verbs
566
611
 
@@ -598,8 +643,12 @@ Full contract — filter-arg `ref` pickers, `getQueryMeta` summary counters, `la
598
643
  - **A projection is bare on the facade and nested under `select` on a filter accessor.** `pickById(id, { secret:
599
644
  true })` and `find`/`findOne`/`findById`/`pickOne` take the projection as their second argument; a generated
600
645
  `findBy<Filter>(...args, { select: { secret: true } })` takes it inside the option object. The shapes do not swap
601
- — `{ select: … }` handed to the facade projects a field named `select`, which no model has, and the read comes
602
- back empty with no error. This is the only way to read a `field.secret(...)` value.
646
+ — `{ select: … }` handed to the facade projects a field named `select` and throws `Unknown document field path`.
647
+ The facade's third argument is the scope (`{ withRemoved: true }`), never a field. This is the only way to read a
648
+ `field.secret(...)` value.
649
+ - **`limit: 0` means every row on the server, never on the client.** A server `list<Filter>(…, { limit: 0 })`
650
+ reads without a ceiling, like an omitted limit, and `null` pages at 20. A client's `0` (`initX(…, { limit: 0 })`)
651
+ is served `MAX_PAGE_SIZE` (500) with a one-time warn. Paginate (`loadMoreOf<Model>`) or name a page size.
603
652
  - **A slice list is either one window or an accumulated one.** `setPageOf<Model>` swaps the window;
604
653
  `loadMoreOf<Model>()` appends the rows after the ones loaded and takes **no page number** — it skips by
605
654
  `<model>List.length`, so the offset cannot drift from what is on screen when a live insertion moves the server's
@@ -685,6 +734,10 @@ Conventions that hold for both shapes:
685
734
  - Best-effort code returns a sentinel (`null`, `undefined`, `[0, 0]`, `{}`). There are no Result/Either wrappers.
686
735
  - `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.
687
736
  - 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
+ - **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.
739
+ - `String(err)` and `${err}` read `Err: <key> {…data}`, the data as one line of JSON with secret-named keys masked,
740
+ so a ``logger.warn(`…: ${String(error)}`)`` keeps the exit code or status the `Err` carried.
688
741
  - **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.
689
742
 
690
743
  ### MCP Exposure
@@ -702,7 +755,8 @@ it.
702
755
  signal option (`mutation(Boolean, { guards: [Every], mcp: false })`, `init({ guards: [SignedIn], mcp: false })`),
703
756
  or as an `mcp` map on `slice()` that **mirrors the `guards` map key for key** (`mcp: { cru: false }`) — root
704
757
  slice and generated CRUD only, never a named slice or a custom endpoint. It is an opt-**out**, and it is
705
- curation, not authorization: HTTP serves the endpoint exactly as before.
758
+ curation, not authorization: HTTP serves the endpoint exactly as before. To take generated CRUD off HTTP too,
759
+ write `guards: { cru: false }`.
706
760
  - **Narrow by cost, and read the boot log first.** MCP forbids a `$ref` across entries, so every entry inlines the
707
761
  schema of every model it mentions and the listing is re-sent whole to every agent that connects. By default a
708
762
  response schema names a nested model instead of inlining it (`outputSchema: "shallow"`; `"full"` restores the
@@ -813,6 +867,8 @@ what every developer must know even when not building one.
813
867
  - Follow the established model layering pattern in this order: `Input`, `Object`, `Light<Model>`, full `<Model>`, and `<Model>Insight`. Write all five, and write `<Model>Insight` even when it is empty.
814
868
  - Put display and predicate logic on the `Light<Model>` class rather than in a util module — see Module File Playbook.
815
869
  - Defaults are a literal for scalars and a thunk for anything constructed. Arrays are `field([T])`; optional is the postfix `.optional()`.
870
+ - A document-path change to an `{ immutable: true }` field is refused with `base.error.immutableField` (400,
871
+ `data.field`), which the client toasts in the user's language; query-level writes are not checked.
816
872
  - **`field.visual(T)` is a field the page renders and an agent never sees** — a blur placeholder, a rendered HTML body, a serialized geometry. It stays an ordinary stored `property` (persistence, search, forms and the page response untouched) and is stripped wherever a value is masked for an AI caller: every in-page-agent read and every MCP result, along with the MCP readable schema. Unlike `hidden`/`secret` it is cost, not secrecy — nothing is refused over one. Reach for it whenever a field is bulky and useless to a model; that is cheaper than every tool learning to avoid it.
817
873
  - **A `field.hidden` / `field.secret` value reads `null` on the client, and the type still declares it.** `SignalContext.resolveReturn` skips both field types while it builds an endpoint's response, and hydration writes `null` over the key rather than leaving it absent — the first branch of the loop in `constant/getDefault.ts`, ahead of the field's own default. So the value is a deliberate `null` behind a type promising a `string`, every use of it typechecks, and the failure lands wherever it is finally dereferenced instead of where it was read. **Guard with `??` or `== null`**: `=== undefined`, a destructuring default and an optional parameter default all catch only a missing key and sail straight past this one. A projection (`pickById(id, { secret: true })`) widens the *server's* read, never the response. If a screen needs the value, the field is neither hidden nor secret; if it only needs to be cheap for a model rather than unseen, that is `field.visual`.
818
874
 
@@ -843,6 +899,7 @@ Full contract — the trigger-maintained mirror, tokenizer changes, `AKAN_SEARCH
843
899
  `filter` 0): `title` is the one line a human scans for, `desc` is prose, `tag` is a keyword list, `filter` is a
844
900
  scoping value (status, owner, role) that must be matchable but must never outrank a real title hit. `thumb` is
845
901
  mirrored for rendering a hit and is **not** indexed — never expect it to match.
902
+ - `schema.index({ x: "text" })` (a Mongo text spec) builds nothing and warns at boot: full-text search is this role.
846
903
  - **`field.secret`, `field.hidden` and `resolve()` take no `text` role — it is a compile error**, because the
847
904
  mirror is plaintext and an indexed secret would leak through search. The same refusal throws at runtime as a
848
905
  backstop, including for a `text` field *underneath* one of those. Do not work around either.
@@ -954,7 +1011,7 @@ export default page()
954
1011
  - `apps/<appName>` root may only contain these files: `AGENTS.md`, `CLAUDE.md`, `akan.app.json`, `akan.config.ts`, `client.ts`, `main.ts`, `package.json`, `server.ts`, `tsconfig.json`, `tsconfig.tsbuildinfo`.
955
1012
  - `apps/<appName>` root may only contain these folders: `.akan`, `common`, `env`, `lib`, `native`, `page`, `plugin`, `private`, `public`, `script`, `secrets`, `srvkit`, `ui`, `webkit`.
956
1013
  - `libs/<libName>` root may only contain these files: `AGENTS.md`, `CLAUDE.md`, `README.md`, `akan.config.ts`, `akan.lib.json`, `client.ts`, `index.ts`, `package.json`, `server.ts`, `tsconfig.json`, `tsconfig.spec.json`, `tsconfig.tsbuildinfo`.
957
- - `libs/<libName>` root may only contain these folders: `common`, `env`, `lib`, `native`, `page`, `plugin`, `private`, `public`, `srvkit`, `ui`, `webkit`. A library is never booted or packaged as an app, so the run and mobile entries an app carries (`main.ts`, `.akan`, `script`, `secrets`) are rejected there. A Capacitor-era `ios` / `android` / `mobile` folder or `capacitor.config.*` is refused in an app root too, named as a leftover: the native runtime generates its projects under `.akan/native/<target>`.
1014
+ - `libs/<libName>` root may only contain these folders: `common`, `env`, `lib`, `native`, `page`, `plugin`, `private`, `public`, `srvkit`, `ui`, `webkit`. A library is never booted or packaged as an app, so the run and mobile entries an app carries (`main.ts`, `.akan`, `script`, `secrets`) are rejected there. A Capacitor-era `ios` / `android` / `mobile` folder or `capacitor.config.*` is refused in an app root too, named as a leftover: the native runtime generates its projects under `dist/native/<app>/<target>`.
958
1015
  - Both allowlists have one source — `pkgs/@akanjs/devkit/workspaceLayout.ts`. `akan sync` (error), `akan doctor`
959
1016
  (diagnostic), and `akan quality scan` (warning) all read it, so add a new root entry there and mirror it into this
960
1017
  list, never into one of the three call sites.
@@ -970,7 +1027,22 @@ export default page()
970
1027
  - Scalar module UI files are limited to `<Scalar>.Template.tsx` and `<Scalar>.Unit.tsx`.
971
1028
  - Module `*.test.ts`, `*.test.tsx`, `*.spec.ts`, and `*.spec.tsx` files are allowed.
972
1029
  - `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.
973
- - Generated facet indexes export only 1-depth files/folders with `export * from "./name";`.
1030
+ - Generated facet indexes export only 1-depth files/folders with `export * from "./name";`. A facet folder with
1031
+ nothing to export syncs to `export {};` (a missing folder gets no barrel), so a stale barrel heals on the next
1032
+ sync; `akan create-library` creates `common/`, `srvkit/`, `webkit/` and `ui/` holding only that empty barrel.
1033
+ - **Never hand-write `index.tsx` (or `index.jsx` / `index.mts`) at the root of `ui/`, `webkit/`, `srvkit/`,
1034
+ `common/` or `plugin/`.** Bun resolves it ahead of the generated `index.ts` while TypeScript resolves `index.ts`
1035
+ first, so an export the generated barrel adds typechecks and is `undefined` at runtime. `akan sync` refuses it and
1036
+ `akan doctor` reports `facet-index-shadowed`; a hand-written namespace belongs in `ui/<Folder>/index.tsx`.
1037
+ - Sync writes generated barrels, module indexes and `akan.*.json` exactly as Biome prints them, so
1038
+ `akan lint --no-fix` passes right after a sync. A lint diff in one of them is a generator bug: fix the generator.
1039
+ - **`akan sync` owns the `dependencies` of an app's or lib's package.json.** It adds what the code imports at the
1040
+ root's version, realigns every root-declared entry to the root's version, and removes a root-declared entry
1041
+ nothing imports (logged). An entry the root does not declare is kept, with a warning. A package no TS file imports
1042
+ (a CSS `@plugin`, a runtime-only package) is kept by listing it in `package.json` under
1043
+ `akan.keepDependencies`; `externalLibs` and `trustedDependencies` are always kept. devDependencies are realigned,
1044
+ never pruned. `akan install-library` lets the root win: it adds only what the root lacks and warns when the lib
1045
+ wants a newer version.
974
1046
  - `libs/<libName>` may hold a `page` folder of route modules; scanSync links it into every app that opts in with `syncPageLibs`, so `apps/*/page/**/(libs)` is generated and gitignored like `public/libs`.
975
1047
 
976
1048
  ## Layer Placement (`common/`, `webkit/`, `srvkit/`, `ui/`)
@@ -192,6 +192,8 @@ shape, so `cascade` never means "related" — it means one of exactly these:
192
192
  - **Query-level removes fire no hooks and therefore no cascade** — `removeManyByQuery` / `updateManyByQuery`, the
193
193
  generated `remove<Filter>` / `update<Filter>`, and the facade's `removeById` / `updateById`. Remove one document
194
194
  at a time when it cascades.
195
- - Cascades are **idempotent**: `removedAt IS NULL` is ANDed into every query-level write. Cycles are cut by a
196
- visited set carried down the whole chain, with a depth cap of 16.
195
+ - Cascades are **idempotent**: `removedAt IS NULL` is ANDed into every query-level write, and a remove never takes
196
+ `{ withRemoved: true }`, so an already-removed row keeps its original `removedAt`. Cycles are cut by a visited set
197
+ carried down the whole chain, with a depth cap of 16. `q.search()` cannot see removed rows (the mirror drops
198
+ them), so search with the flag throws, as does `upsert` with it.
197
199
 
@@ -18,7 +18,7 @@ const config: AppConfig = { web: false }; // api only
18
18
  - **`web: { csr: false }`** drops the CSR build phase and the `/__csr` + `?csr=true` routes. The CSR bundle is
19
19
  what the native apps ship, so a web-only deployment never needs it — and an app that declares a `native` section
20
20
  is refused, because every native build copies `dist/apps/<app>/csr/<target>.html` into the target's web root,
21
- `.akan/native/<target>/web`.
21
+ `dist/native/<app>/<target>/web`.
22
22
  - **`web: false`** is an API-only build: no base artifact, no pages or client bundles, no RSC worker
23
23
  entrypoint, and no `public/` in the image (the web router's catch-all is its only reader). Nothing under
24
24
  `page/` is served, including routes a lib contributed through `syncPageLibs`.
@@ -236,17 +236,22 @@ const config: AppConfig = {
236
236
  none is a template, and `--target <basePath>` builds that client from it.
237
237
  - Every native command takes `--target <name>`. `build-*`, `release-*`, `update-keygen` and `publish-update` also
238
238
  take `all`; `start-*` and `pack-update` handle one target at a time.
239
- - **Everything a build makes is under `apps/<app>/.akan/native/<target>/`**: `build/<platform>` (`build-ios`,
239
+ - **Everything a build makes is under `dist/native/<app>/<target>/`**: `build/<platform>` (`build-ios`,
240
240
  `build-android`, `build-desktop`), `dev/<platform>` (the dev builds `start-*` run), `updates` (what
241
- `publish-update` signs and `pack-update` packs), `web` (the bundle the app loads) and `bin`. `akan start` leaves
242
- the folder alone.
241
+ `publish-update` signs and `pack-update` packs), `web` (the bundle the app loads) and `bin`. It sits outside
242
+ `dist/apps/<app>`, which every `akan build` empties, so a signed release waiting for upload survives the next
243
+ build; `akan start` leaves it alone too.
243
244
  - **`start-ios` / `start-android` / `start-desktop` run a dev build that loads its pages from `akan start`.** It has
244
245
  to be running — except for a desktop app that carries its server, which starts it when none of this checkout
245
246
  answers. `--release true` runs a release build of the app's own bundle instead.
246
247
  - **A desktop app builds only for the computer that builds it** — a `.app` on macOS, and an unsigned folder on
247
248
  Windows and Linux. `build-desktop --installer true` adds what a person downloads: a per-user NSIS setup on Windows
248
249
  (`/S` installs silently), a dmg on macOS, an AppImage on Linux (it needs `mksquashfs`; an AppImage cannot update
249
- itself, so a release with `updates` ships a new one). `--arch arm64|x64` builds a Windows or Linux app for the
250
+ itself, so a release with `updates` ships a new one). The dmg opens on a drag-to-Applications window with the app's
251
+ icon on the mounted disk, laid out without Finder so CI builds it too; `native.desktop.dmg` (per target as well)
252
+ replaces the background (`background`, `background2x` for Retina) and moves the window and the icons (`window`,
253
+ `iconSize`, `textSize`, `app`, `applications`, in points) — the default background lines up with the default icon
254
+ positions only. `--arch arm64|x64` builds a Windows or Linux app for the
250
255
  other CPU of that OS: the Rust library, Bun's executable, the server's `bun install --cpu` and each `bin` file
251
256
  follow it, and a server addon with no binary for that CPU stops the build. A macOS app is Apple silicon only —
252
257
  Intel Macs are not a target, so `--arch x64` on macOS is refused.