@akanjs/cli 3.0.0-beta.27 → 3.0.0-beta.29

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 (63) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-h19atbr3.js → CodeAgent-86q8d13j.js} +2 -2
  3. package/{CodeTui-46zygq8p.js → CodeTui-r8tgxsyb.js} +1 -1
  4. package/{agent.command-rp505zxa.js → agent.command-q9bmqrag.js} +5 -5
  5. package/akanCode.js +2 -2
  6. package/{application.command-1p0cpt6y.js → application.command-712jd2z8.js} +70 -8
  7. package/{applicationBuildRunner-r6b9rkb8.js → applicationBuildRunner-ez37p161.js} +67 -22
  8. package/buildBatch.proc.js +3 -3
  9. package/{cloud.command-ps9dabbg.js → cloud.command-8eegwcsc.js} +9 -9
  10. package/cloud.script-55s1cmz9.js +20 -0
  11. package/{code.command-78qw3q3f.js → code.command-n0c2tyzx.js} +6 -6
  12. package/commandManifest.json +1 -1
  13. package/{context.command-x5f7t3gk.js → context.command-vxf6d7q2.js} +13 -13
  14. package/context.runner-70r2fmty.js +21 -0
  15. package/{guideline.command-d5qmxx29.js → guideline.command-ene55fkr.js} +2 -2
  16. package/guidelines/conventions/conventions.instruction.md +27 -7
  17. package/guidelines/runtimeRule/runtimeRule.instruction.md +116 -1
  18. package/incrementalBuilder.proc.js +2 -2
  19. package/{index-hzy4pe24.js → index-0pqzrbpr.js} +2 -2
  20. package/{index-w5pet0nq.js → index-16k7m3cs.js} +11 -9
  21. package/index-1hs6jrys.js +994 -0
  22. package/{index-6dt6zft6.js → index-20r2g1t3.js} +4 -4
  23. package/{index-xfgdmm5d.js → index-2ctqegdm.js} +2 -2
  24. package/{index-d65fzxvd.js → index-2s2e5qnd.js} +1 -1
  25. package/{index-j9z8vg2n.js → index-2yvy9ztj.js} +4 -4
  26. package/{index-t02g6dpn.js → index-2z30v4k5.js} +3 -3
  27. package/{index-vxqe24mp.js → index-8ej6r02m.js} +1 -1
  28. package/{index-58cyna2s.js → index-b68zfsvz.js} +1 -1
  29. package/{index-bp9b1g2r.js → index-fy42hyb9.js} +7 -3
  30. package/{index-tg09x14f.js → index-g44da4r2.js} +5 -5
  31. package/{index-zazvn45w.js → index-genegfnt.js} +4 -4
  32. package/{index-da72pfdv.js → index-h9ed99ax.js} +7 -7
  33. package/{index-per2kqy9.js → index-k7809wmg.js} +2 -2
  34. package/{index-2zcm7fjd.js → index-krwz9ek9.js} +3 -3
  35. package/{index-118vq9dh.js → index-n9gxx7bp.js} +247 -47
  36. package/{index-860zjnr1.js → index-nbg9bfnr.js} +1 -1
  37. package/{index-5mm3g24t.js → index-qtz88kh0.js} +2 -2
  38. package/{index-0n9q1f23.js → index-r8gp8zk3.js} +231 -98
  39. package/{index-ggfhz9ab.js → index-rbeahw6j.js} +3 -3
  40. package/{index-56xwsgyn.js → index-rrt9pnb4.js} +2 -2
  41. package/{index-v4hnkqz9.js → index-ss828r32.js} +1 -1
  42. package/{index-5kjsr2pg.js → index-ttn00pr3.js} +38 -3
  43. package/index.js +21 -21
  44. package/{library.command-sv6g1h2g.js → library.command-tz7vnwea.js} +3 -3
  45. package/{localRegistry.command-zppa6w7q.js → localRegistry.command-7dd85czz.js} +8 -8
  46. package/{module.command-ccdeder9.js → module.command-qhc9ns0s.js} +5 -5
  47. package/{package.command-xmw605b1.js → package.command-z3epnvas.js} +3 -3
  48. package/package.json +3 -3
  49. package/{page.command-ky7vafsn.js → page.command-exd2vakz.js} +3 -3
  50. package/{primitive.command-bh6j8nfs.js → primitive.command-0w6cha4j.js} +6 -6
  51. package/{quality.command-rqqrpe98.js → quality.command-je4kc9yv.js} +2 -2
  52. package/{repair.command-n9nsm9qe.js → repair.command-knvwfgtg.js} +5 -5
  53. package/{scalar.command-t09gmck8.js → scalar.command-60nxfchc.js} +4 -4
  54. package/{subspace.command-gx2cjpnv.js → subspace.command-683qxcw9.js} +4 -4
  55. package/templates/server.ts +1 -1
  56. package/{tunnel.command-4g95vhsn.js → tunnel.command-7m2kxtat.js} +2 -2
  57. package/{workflow.command-b91ph8sx.js → workflow.command-j5tk13ds.js} +10 -10
  58. package/{workspace.command-1r29qzzk.js → workspace.command-jxdm0wrb.js} +20 -20
  59. package/README.ko.md +0 -72
  60. package/README.md +0 -88
  61. package/cloud.script-wrw1kz6k.js +0 -20
  62. package/context.runner-nfs8ksh3.js +0 -21
  63. package/index-y736f42m.js +0 -556
@@ -28,7 +28,7 @@ you fetch on demand — `get_guideline` with the name, or `akan guideline show <
28
28
  | name | covers |
29
29
  |---|---|
30
30
  | `ssrRule` | server-share targets, the `akan.ssr.*` warnings, the client-boundary playbook |
31
- | `runtimeRule` | `web` / `csr` surfaces, gateway vs solo processes, logging, the generated image, shipped assets, database modes |
31
+ | `runtimeRule` | `web` / `csr` surfaces, gateway vs solo processes, logging, the generated image, a desktop app's server, shipped assets, database modes |
32
32
  | `queryRule` | slices and hydration, the generated filter methods, full-text search, cascade removal |
33
33
  | `transportRule` | guards across HTTP and websocket, socket identity and cleanup, binary pubsub, mutation verbs |
34
34
  | `mcpRule` | MCP configuration, wire behaviour, resource URIs, OAuth metadata, protocol revisions |
@@ -57,8 +57,9 @@ back.
57
57
  `no-inline-color`, `no-interpolated-arbitrary-class`)
58
58
  - **Never `throw new Error`.** Throw `new Err("<module>.error.<key>")` and register the key as `[en, ko]` in that
59
59
  module's dictionary `.error({})`. Import `Err` from `"../dict"` on the server and from `"@libs/<lib>/client"` or
60
- `"@apps/<app>/client"` in UI. `no-throw-raw-error.grit` exempts tests, `*.constant.ts`, `common/**`, and `env/**`
61
- — the last two have no legal `Err` import path, so keep throwing code out of them.
60
+ `"@apps/<app>/client"` in UI. `no-throw-raw-error.grit` exempts tests, `*.constant.ts`, `common/**`, `env/**`, and
61
+ a root `native/` folder — `common/` and `env/` have no legal `Err` import path, so keep throwing code out of them,
62
+ and a native plugin throws `AkanNativeError`, the error its bridge carries back to the page.
62
63
  - **Never import a third-party package** from `page/**`, from any barrel, or from any
63
64
  `*.{constant,dictionary,document,service,signal,store}.ts` / `*.{Template,Unit,Util,View,Zone}.tsx`
64
65
  (`no-import-external-library.grit`). Re-export the symbol through a lib first. One-line re-export shims in a lib's
@@ -87,7 +88,8 @@ back.
87
88
  `Notification.requestPermission` / `.permission` (no app page has them), `navigator.geolocation` (use
88
89
  `useGeoLocation` from `akanjs/webkit`) and `navigator.vibrate` (iOS has none; use `haptics`). They are legal on the
89
90
  web, so the rule does not ban them: it keeps them in a `webkit/` hook that branches on `isNativeApp()` from
90
- `akanjs/client/native`, which is where the app side goes too.
91
+ `akanjs/client/native`, which is where the app side goes too. A root `native/` folder is out of its scope: a
92
+ plugin's web part is where those APIs belong.
91
93
  - **Never return a value from a store action** (`no-return-in-store-action.grit`). Every method of a `store(...)`
92
94
  class dispatches through `st.do.<action>()`, typed `void` / `Promise<void>`, so the value is unreachable — write
93
95
  it into state with `this.set({ ... })`. A bare `return;` guard, a `return` inside a nested callback, a getter,
@@ -348,6 +350,15 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
348
350
  assembles one from; the string form takes no contributions. The generated image installs `ca-certificates` and
349
351
  `tzdata` and nothing else, so an app needing `ffmpeg` or Chromium declares it in `preRuns` / `postRuns`. A lib
350
352
  declares the steps its own runtime needs and every mounting app inherits them.
353
+ - **A desktop app's server (`native.desktop.server: true` on its target) gets nothing from `docker`.** An executable the app spawns
354
+ goes in `bin` — per platform, a download checked against its `sha256` or a file beside the config, put first on
355
+ the app's PATH so the server's `spawn("ffmpeg")` finds it, and in `ctx.binDir` for a native plugin — and a package
356
+ that builds itself at install goes in `trustedDependencies`, which the image honours too. Carry a static LGPL ffmpeg: a `--enable-nonfree` build may
357
+ not be redistributed. A file the user picks with `filePicker.pickFiles({ forServer: true })` reaches it as a
358
+ grant, never a copy or a path: `NativeFile.resolve(grant, "read")` in `akanjs/server` asks the shell for it.
359
+ What it carries is readable on the user's computer — `private/` (each lib's too, under `private/libs/<lib>`), the
360
+ one `env.server.<env>.ts` of its `--env` and its libs' server env defaults (`env.server.testing.ts`) — and it has
361
+ no `public/`: read runtime files from `AKAN_APP_DIR`, never `process.cwd()`.
351
362
  - **`assets: { pruneFonts, keepFonts }`** trims from the `dist` copy of `public/` the fonts nothing reads; source
352
363
  trees are never touched. A font with `optimize` on is a build input, not a runtime asset. `keepFonts` belongs to
353
364
  the `akan.config.ts` that owns the font, written against that scope's own `public/`.
@@ -872,6 +883,12 @@ shape, so `cascade` never means "related" — it means one of exactly these:
872
883
  `.layoutStyle()` on the root layout; `.prompt()` on a page. A named export beside the chain, or `page()` in a
873
884
  `_layout.tsx`, fails the build. `.head()` takes JSX — `<title>`, `<meta>`, `<link>` — or a function of the
874
885
  route's args returning it; there is no metadata object, and analytics is the app's own script, not a stage.
886
+ - **A CSR bundle (a native app, `?csr=true`) never shows a root layout's own markup.** Pages render into the
887
+ frame, and what the root layout draws around `{children}` stays in a hidden container, so its wrappers cannot
888
+ stack a second screen beside the frame. Its components still mount and their effects run (`<Auth.User />` works).
889
+ Anything a person must see from a root layout — an overlay, a gate, a banner — renders through
890
+ `createPortal(…, document.body)`, as `<Agent.Chat />` and the dialogs already do. A non-root `_layout.tsx` has no
891
+ such limit.
875
892
  - **A page names every `[x]` segment of its path with `.param("x", Type)`** and reads a query key only through
876
893
  `.search("k", Type)`. Values arrive typed — `ID`/`String` → string, `Int`/`Float` → number, `Boolean`, `Date` →
877
894
  Dayjs, an `enumOf` class → its union, `[T]` → array — a path value the type refuses answers not-found, and a
@@ -915,9 +932,9 @@ export default page()
915
932
  ## Akan Sync Conventions (`apps/**`, `libs/**`)
916
933
 
917
934
  - `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`.
918
- - `apps/<appName>` root may only contain these folders: `.akan`, `common`, `env`, `lib`, `page`, `plugin`, `private`, `public`, `script`, `secrets`, `srvkit`, `ui`, `webkit`.
935
+ - `apps/<appName>` root may only contain these folders: `.akan`, `common`, `env`, `lib`, `native`, `page`, `plugin`, `private`, `public`, `script`, `secrets`, `srvkit`, `ui`, `webkit`.
919
936
  - `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`.
920
- - `libs/<libName>` root may only contain these folders: `common`, `env`, `lib`, `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/mobile/<target>`.
937
+ - `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/mobile/<target>`.
921
938
  - Both allowlists have one source — `pkgs/@akanjs/devkit/workspaceLayout.ts`. `akan sync` (error), `akan doctor`
922
939
  (diagnostic), and `akan quality scan` (warning) all read it, so add a new root entry there and mirror it into this
923
940
  list, never into one of the three call sites.
@@ -941,12 +958,15 @@ export default page()
941
958
  | Folder | Admission test | Naming |
942
959
  |---|---|---|
943
960
  | `common/` | pure, isomorphic, zero-dependency; may import only sibling `common/*` and `akanjs/base`. Cannot import `Err`, so keep throwing code out of it. | camelCase file, filename equals the single export |
944
- | `webkit/` | touches `window` / `navigator` / the native bridge (`akanjs/client/native`), or is a React hook | `use<Thing>.tsx` — `.tsx` even with no JSX |
961
+ | `webkit/` | touches `window` / `navigator` / the native bridge (`akanjs/client/native`, or `akanjs/client/native/<plugin id>` for any builtin plugin), or is a React hook | `use<Thing>.tsx` — `.tsx` even with no JSX |
945
962
  | `srvkit/` | touches `node:*`, `Bun`, `process.env`, a secret, or a server SDK | camelCase file, PascalCase class |
946
963
  | `ui/` | renders JSX, or defines a look (a recipe in `Recipe/`, a lib's `tokens.css`), and is not bound to one model | PascalCase component, camelCase sidecar (`swipeCard.util.ts`), `Recipe/<name>.ts` |
947
964
  | `plugin/` | build- or CLI-time `AkanPlugin` | `<name>.plugin.ts`, registered in `akan.config.ts` |
965
+ | `native/` | a native plugin the app or lib owns: `native-plugin.json` beside its page API (`src/index.ts`, `definePlugin` from `akanjs/client/native`), its desktop part (`src/desktop.ts`, `defineDesktopPlugin` from `akanjs/native/desktop`), Kotlin and Swift. Every mobile and desktop target ships it; a lib's reach only the apps that depend on it, and the app's own wins an id | `native/<id>/`, the folder named after the plugin id |
948
966
 
949
967
  - Hooks return a named object of async closures, never a tuple.
968
+ - A `native/` plugin's page API is imported by a `webkit/` hook (`../native/<id>/src`) and nothing else; pages and
969
+ components call the hook, the same way they reach `akanjs/client/native`.
950
970
  - `ui/` is the presentation layer — markup and the looks it is built from — so a recipe lives there although it is
951
971
  neither a component nor a hook. Not `webkit/`: its hooks are `"use client"`, the opposite signal for a function
952
972
  server components call. Not `common/`: it cannot import `akanjs/ui`, where `recipe` / `tv` come from.
@@ -1,4 +1,4 @@
1
- # Runtime Rule — Serving, Processes, Logging, Image, Assets
1
+ # Runtime Rule — Serving, Processes, Logging, Image, Desktop Server, Assets
2
2
 
3
3
  How an Akan app is built, what it serves, how many processes it runs, where its logs go, and what ends up in
4
4
  its image. Everything here is declared in `akan.config.ts` or narrowed by an env at boot; none of it is reached
@@ -184,6 +184,121 @@ const config: AppConfig = { docker: "FROM oven/bun:1-slim\n…" }; // verbatim,
184
184
  - `AkanAppConfig.docker` is the resolved declaration; `AkanAppConfig.dockerfile` is the text `akan build` writes
185
185
  to `dist/apps/<app>/Dockerfile`.
186
186
 
187
+ ## A Desktop App's Server — `bin` And `trustedDependencies`
188
+
189
+ A mobile target that declares `native: { desktop: { server: true } }` carries the app's server: `akan build-desktop`,
190
+ `start-desktop --release` and `publish-update` put the backend `akan build` made into the app — everything the
191
+ backend build wrote (the `.js`, `akan.build.json`, `private/`, a bundled package's `.node`, `.wasm` or file asset)
192
+ but the Dockerfile, the RSC worker, the console, `csr/` and `public/` — and install its packages first with
193
+ `bun install --production --prefer-offline`, before `akan build` runs. `start-desktop` without `--release` starts
194
+ `akan start` beside the app when no dev server of this checkout answers. The setting is the app's backend,
195
+ so it does not change under an installed app: an update whose release carries a server when the app has none, or
196
+ none when it has one, is refused, and switching means a reinstall.
197
+ A desktop app builds only for the computer it is built on, so every native addon's prebuild matches the one it runs
198
+ on. It runs as an API-only edge server on SQLite, on a loopback port the launcher picks — the last session's when
199
+ it is free, which is not a guarantee, so a provider that needs an exact redirect URI signs in through a cloud
200
+ server's adapter. It refuses a `Host` other than its own, but any program on the computer can still call that port:
201
+ guard its endpoints as a network server's. It runs in one process, so a `main.ts` or an env asking for replicas (`replica`,
202
+ `solo: false`, `AKAN_SOLO=false`, `AKAN_REPLICA`, `AKAN_COMMAND_TYPE=start`) does not boot in the app. Its data lives in the app's local data folder (`%LOCALAPPDATA%\<id>\server`
203
+ on Windows, never Roaming; `server-debug` for a `--debug` build). It trusts the OS certificate store, and of the
204
+ user's environment it takes a fixed set and nothing else: the system's own (`PATH`, `HOME`, the temp folder, `LANG`,
205
+ `TZ`, Windows' folders and `ComSpec`), the desktop session a `bin` tool needs for the screen or the audio server
206
+ (`DISPLAY`, `XAUTHORITY`, `WAYLAND_DISPLAY`, the session bus, `PULSE_*`, `XDG_*`), and the proxy and CA variables
207
+ (`HTTP(S)_PROXY`, `NO_PROXY`, `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`).
208
+
209
+ **Everything it carries is on the user's computer, readable.** That is `private/` as it is — each lib's too, under
210
+ `private/libs/<lib>` — and the server env of the one environment the build is for (`--env`: `debug` for
211
+ `build-desktop`, `local` for `start-desktop --release`, `main` for `publish-update`, unless named): the build keeps
212
+ `env/env.server.<env>.ts` and swaps every other environment's file for exports that refuse to be read. The server env
213
+ each lib exports as its defaults (the lib's `env.server.testing.ts`, spread into every environment by
214
+ `env.server.type.ts`) ships as well. Keep a key in those files only if every user of the app may hold it; a secret the
215
+ server needs belongs to a cloud server its adapter calls. There is no `public/`: a file the server reads at runtime
216
+ goes in `private/` and is read from `AKAN_APP_DIR` (the folder `server.js` sits in), never from `process.cwd()`, which
217
+ is the app's data folder.
218
+
219
+ **Nothing from `docker` reaches it.** `preRuns`, `postRuns` and a whole Dockerfile install into a Linux image the
220
+ desktop app never runs in, and the build warns when an app has them and carries no `bin`. What the server needs
221
+ from them comes one of two ways:
222
+
223
+ ```ts
224
+ const config: AppConfig = {
225
+ trustedDependencies: ["rclnodejs"],
226
+ bin: {
227
+ ffmpeg: {
228
+ "darwin-arm64": {
229
+ url: "https://files.example.com/ffmpeg-7.1-lgpl-darwin-arm64.zip",
230
+ sha256: "…",
231
+ file: "ffmpeg-7.1/bin/ffmpeg",
232
+ },
233
+ "win32-x64": { url: "https://files.example.com/ffmpeg-7.1-lgpl-win64.zip", sha256: "…", file: "bin/ffmpeg.exe" },
234
+ "linux-x64": { path: "tools/linux-x64/ffmpeg" },
235
+ },
236
+ },
237
+ };
238
+ ```
239
+
240
+ - **An executable the desktop app spawns goes in `bin`** — its server's code or its native plugins, whether or not
241
+ it carries a server — keyed by the name the code spawns and then by
242
+ `${process.platform}-${process.arch}` (`darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, `win32-arm64`,
243
+ `win32-x64`). A source is `{ url, sha256, file? }` — downloaded when the app is built, refused unless it hashes
244
+ to `sha256` (so plain http is as safe as https), kept in `apps/<app>/.akan/cache/bin/<sha256>` — or
245
+ `{ path, file? }`, relative to the `akan.config.ts` that declares it. `file` is the executable inside an archive
246
+ (`.zip`, `.tar.gz`, `.tgz`, `.tar.xz`, `.tar.bz2`, `.tar`), unpacked with the OS's own `tar` (`unzip` for a zip
247
+ on Linux).
248
+ - Only the building computer's platform is fetched, and an entry without it fails the build instead of the user's
249
+ call. `build-desktop` and `start-desktop` copy the file into the app's `bin/` (keeping `.exe` on Windows), sign it
250
+ with the app on macOS, and put that folder first on the app's PATH. The server's `spawn("ffmpeg")` needs no change
251
+ and finds the carried file even in an app launched from the Finder, whose PATH is only
252
+ `/usr/bin:/bin:/usr/sbin:/sbin`. A native plugin finds the folder in `ctx.binDir`: Bun's own `spawn` and `which`
253
+ without `env` read the environment the app started with, so a plugin passes `env: process.env` to run one by name.
254
+ The server passes it too when it spawns a carried `bun build --compile` executable: the shell starts the server as
255
+ Bun with `BUN_BE_BUN=1`, which akanjs takes out of `process.env` at boot, and a child spawned without `env` still
256
+ inherits it and runs as Bun's own CLI instead of the tool.
257
+ - **One file per entry, so carry a static build.** A build that loads its own shared libraries — a `-shared`
258
+ archive, or Homebrew's ffmpeg with its 55 dylibs — runs on the computer that built it and nowhere else.
259
+ - **A lib's `bin` reaches only the apps that depend on it**, unlike its `docker` steps; the app's own entry of the
260
+ same name wins, and two libs that declare one name differently fail the build.
261
+ - **The image does not read `bin`.** Keep installing through `docker.preRuns` there.
262
+ - **A package that builds itself at install goes in `trustedDependencies`.** `bun install --production` runs no
263
+ dependency's install or postinstall script unless the package is trusted, so an addon with no prebuild arrives
264
+ unbuilt and fails at its first call. The list lands in the built `package.json`, so it applies to the image and to
265
+ the desktop server alike, and a lib's list reaches every app, like its `externalLibs`.
266
+
267
+ **Carry an LGPL ffmpeg.** A build configured with `--enable-nonfree` may not be redistributed at all — the macOS
268
+ binary npm's `ffmpeg-static` downloads is one — and a `--enable-gpl` build obliges you to offer its source. An LGPL
269
+ build has no `libx264`, so encode H.264 through the OS's encoder: `h264_videotoolbox` on macOS, `h264_mf` on
270
+ Windows, VAAPI or NVENC on Linux. Codec patents are a separate question to settle before shipping.
271
+
272
+ **A server bound to its machine stays a service.** A server that needs a whole environment — ROS, system services,
273
+ root to change the network or the clock — runs as a service on that machine (the image), and the desktop app's
274
+ target leaves `desktop.server` off, pinned to it with `AKAN_PUBLIC_SERVER_URL` at build time. A carried server runs
275
+ as the signed-in user and stops with the app, and so does what it started. On macOS and Linux the server leads its
276
+ own process group: quitting the app ends the group, and when the app was killed or crashed — even while the server
277
+ was still starting — the server sees its parent gone and ends the group itself, SIGTERM first and SIGKILL a second
278
+ later for whatever ignored it. On Windows the job object ends them all with the app. A process started `detached`
279
+ leaves the group, or the job, and keeps running.
280
+
281
+ **A file the user picks reaches the server as a grant, never as a copy or a path.** With `native.plugins:
282
+ ["file-picker"]` on the target, `filePicker.pickFiles({ forServer: true })` (also `pickDirectory` and `saveFile`,
283
+ from `akanjs/client/native`) copies nothing, whatever the size: its FileRefs serve the originals for a preview, and
284
+ each result carries a `grant`. The page hands the grant to an endpoint, and the server exchanges it:
285
+
286
+ ```ts
287
+ const input = await NativeFile.resolve(grant, "read"); // "write" for a saveFile grant, "folder" for pickDirectory
288
+ const clip = await NativeFile.resolveIn(folderGrant, "day1/a.mp4"); // refused if it climbs out of the folder
289
+ ```
290
+
291
+ The carried server asks the shell that showed the dialog over its IPC channel, so it reaches what the user picked
292
+ and nothing else, and no page ever holds a path. Behind a dev build `akan start` checks the grant's signature
293
+ against a key in `~/.akan/native` instead, in `operationMode` local only; every other server refuses a grant.
294
+
295
+ **Devices belong to the shell, not the server.** Displays and their changes (`screen`), windows placed on them
296
+ (`window`), the system volume and mute (`volume`), global shortcuts, keep-awake and launch at login are native
297
+ runtime plugins, each added to the target's `native.plugins`. Every builtin plugin's page API is
298
+ `akanjs/client/native/<id>` (`akanjs/client/native/window`, `…/screen`, `…/global-shortcut`), imported in a `webkit/`
299
+ hook; `volume` and `filePicker` also come from `akanjs/client/native` itself. A capability the shell
300
+ lacks is added there: an app's own plugin runs as Bun code in the plugin host and cannot add a native shell op.
301
+
187
302
  ## Database Modes — `database` In `akan.config.ts`
188
303
 
189
304
  | mode | database | cache · queue · pubsub | runs as |
@@ -8,7 +8,7 @@ import {
8
8
  CodegenLock,
9
9
  WorkspaceExecutor,
10
10
  AppExecutor
11
- } from "./index-0n9q1f23.js";
11
+ } from "./index-r8gp8zk3.js";
12
12
  import"./index-52w0fvh8.js";
13
13
  import {
14
14
  GraphClientEntryDiscovery,
@@ -20,7 +20,7 @@ import {
20
20
  DevChangePlanner,
21
21
  DevGeneratedIndexSync,
22
22
  SsrDevBundler
23
- } from "./index-5kjsr2pg.js";
23
+ } from "./index-ttn00pr3.js";
24
24
  import {
25
25
  prepareDevWatchBatch
26
26
  } from "./index-a5vmm6x4.js";
@@ -8,7 +8,7 @@ import {
8
8
  isScannedRootEntry,
9
9
  AppExecutor,
10
10
  LibExecutor
11
- } from "./index-0n9q1f23.js";
11
+ } from "./index-r8gp8zk3.js";
12
12
  import {
13
13
  FileSys2
14
14
  } from "./index-52w0fvh8.js";
@@ -21,7 +21,7 @@ import {
21
21
  workflowPathsForPlan,
22
22
  workflowRunArtifactPath,
23
23
  workflowSyncDir
24
- } from "./index-d65fzxvd.js";
24
+ } from "./index-2s2e5qnd.js";
25
25
 
26
26
  // pkgs/@akanjs/devkit/akanContext.ts
27
27
  import { readdir } from "fs/promises";
@@ -1,13 +1,13 @@
1
1
  // @bun
2
2
  import {
3
3
  AppExecutor
4
- } from "./index-0n9q1f23.js";
4
+ } from "./index-r8gp8zk3.js";
5
5
  import {
6
6
  jsonText,
7
7
  createWorkflowBaselineSummary,
8
8
  buildAkanModuleContextIndex,
9
9
  toolingRolloutGate
10
- } from "./index-d65fzxvd.js";
10
+ } from "./index-2s2e5qnd.js";
11
11
  import {
12
12
  buildResourceList,
13
13
  codexMcpConfigPath,
@@ -17,24 +17,25 @@ import {
17
17
  upsertCodexMcpServerBlock,
18
18
  renderDoctorText,
19
19
  AkanContextAnalyzer
20
- } from "./index-hzy4pe24.js";
20
+ } from "./index-0pqzrbpr.js";
21
21
  import {
22
22
  runner
23
- } from "./index-58cyna2s.js";
23
+ } from "./index-b68zfsvz.js";
24
24
  import {
25
25
  getMobileTargets,
26
+ appIdsOf,
26
27
  NativeApp,
27
28
  isPlaceholderAppId
28
- } from "./index-y736f42m.js";
29
+ } from "./index-1hs6jrys.js";
29
30
  import {
30
31
  Prompter2
31
32
  } from "./index-zzvvc845.js";
32
33
  import {
33
34
  RepairRunner
34
- } from "./index-t02g6dpn.js";
35
+ } from "./index-2z30v4k5.js";
35
36
  import {
36
37
  WorkflowRunner
37
- } from "./index-da72pfdv.js";
38
+ } from "./index-h9ed99ax.js";
38
39
 
39
40
  // pkgs/@akanjs/devkit/akanMcpContract.ts
40
41
  import path from "path";
@@ -606,13 +607,14 @@ class ContextRunner2 extends runner("context") {
606
607
  });
607
608
  }
608
609
  for (const { name, config: target } of await getMobileTargets(app)) {
609
- if (!isPlaceholderAppId(target.appId))
610
+ const placeholder = appIdsOf(target.appId).find(isPlaceholderAppId);
611
+ if (!placeholder)
610
612
  continue;
611
613
  diagnostics.push({
612
614
  severity: "warning",
613
615
  code: "mobile-appid-placeholder",
614
616
  path: `apps/${appName}/akan.config.ts`,
615
- message: `Mobile target '${name}' uses placeholder bundle id '${target.appId}'. Apple's developer portal almost always already claims it, so signing to a physical device fails with "cannot be registered to your development team". Set a unique mobile.appId (reverse-DNS of your org).`
617
+ message: `Mobile target '${name}' uses placeholder bundle id '${placeholder}'. Apple's developer portal almost always already claims it, so signing to a physical device fails with "cannot be registered to your development team". Set a unique mobile.appId (reverse-DNS of your org).`
616
618
  });
617
619
  }
618
620
  }