@akanjs/devkit 3.0.0 → 3.0.2-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.
- package/CHANGELOG.md +1832 -0
- package/akanConfig/akanConfig.test.ts +3 -0
- package/akanConfig/akanConfig.ts +3 -1
- package/akanContext.ts +18 -1
- package/artifact/implicitRootLayout.test.ts +2 -1
- package/artifact/implicitRootLayout.ts +2 -1
- package/artifact/routeSeedIndex.test.ts +0 -14
- package/biome.base.json +12 -1
- package/commandDecorators/command.ts +3 -2
- package/commandDecorators/commandDecorators.test.ts +7 -0
- package/csrE2e/csrE2eHarness.fixture.ts +3 -2
- package/csrE2e/csrPageStack.e2e.test.ts +25 -3
- package/executors.test.ts +329 -4
- package/executors.ts +49 -47
- package/frontendBuild/csrArtifactBuilder.test.ts +34 -0
- package/frontendBuild/csrArtifactBuilder.ts +14 -8
- package/frontendBuild/csrDevModuleCompiler.ts +0 -1
- package/frontendBuild/csrDevResolver.ts +0 -2
- package/frontendBuild/devGeneratedIndexSync.test.ts +13 -1
- package/frontendBuild/devGeneratedIndexSync.ts +21 -4
- package/frontendBuild/frontendBuild.test.ts +1 -1
- package/frontendBuild/hmrWatcher.ts +6 -0
- package/integration/devStability.integration.test.ts +8 -5
- package/integration/devStabilityHarness.ts +2 -0
- package/jsonFormatter.test.ts +64 -0
- package/jsonFormatter.ts +78 -0
- package/lint/__fixtures__/README.md +12 -3
- package/lint/__fixtures__/no-double-brace-placeholder/bad.ts +22 -0
- package/lint/__fixtures__/no-double-brace-placeholder/fixture.json +1 -0
- package/lint/__fixtures__/no-double-brace-placeholder/good.ts +15 -0
- package/lint/__fixtures__/no-inline-color/bad.tsx +17 -0
- package/lint/__fixtures__/no-inline-color/good.tsx +13 -0
- package/lint/__fixtures__/no-static-in-object-light-model/bad.ts +43 -0
- package/lint/__fixtures__/no-static-in-object-light-model/fixture.json +1 -0
- package/lint/__fixtures__/no-static-in-object-light-model/good.ts +42 -0
- package/lint/__fixtures__/no-throw-raw-error/bad.tsx +8 -0
- package/lint/__fixtures__/no-throw-raw-error/good.tsx +8 -1
- package/lint/__fixtures__/no-unguarded-endpoint/bad.ts +20 -0
- package/lint/__fixtures__/no-unguarded-endpoint/fixture.json +1 -0
- package/lint/__fixtures__/no-unguarded-endpoint/good.ts +28 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -7
- package/lint/gritRules.test.ts +23 -15
- package/lint/no-double-brace-placeholder.grit +15 -0
- package/lint/no-inline-color.grit +21 -5
- package/lint/no-static-in-object-light-model.grit +27 -0
- package/lint/no-throw-raw-error.grit +18 -33
- package/lint/no-unguarded-endpoint.grit +73 -0
- package/manifestDependencies.ts +65 -0
- package/mobile/desktopBin.test.ts +1 -1
- package/mobile/desktopServerStage.test.ts +7 -4
- package/mobile/nativeApp.test.ts +57 -8
- package/mobile/nativeApp.ts +27 -9
- package/mobile/nativeConfig.test.ts +26 -4
- package/mobile/nativeConfig.ts +19 -1
- package/package.json +2 -2
- package/platformTest/LinuxDockerTarget.ts +1 -1
- package/platformTest/PlatformTestTarget.ts +3 -1
- package/platformTest/WindowsSshTarget.ts +1 -1
- package/qualityScanner.test.ts +51 -0
- package/qualityScanner.ts +22 -1
- package/scanInfo.ts +14 -2
- package/subspace.test.ts +10 -1
- package/testHelpers.ts +17 -0
- package/transforms/transforms.test.ts +13 -0
- package/tsconfig.json +4 -15
- package/workspaceLayout.test.ts +24 -1
- package/workspaceLayout.ts +19 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,1837 @@
|
|
|
1
1
|
# @akanjs/devkit
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4871ae5: New lint rule `no-static-in-object-light-model` for `*.constant.ts`: a `static` on an `XObject` or `LightX` class is an error. The full model `via(XObject, LightX, …)` mixes in their prototypes only, so such a static never reaches `X`; declare it on `XInput` (or a scalar), `X`, or `XInsight` instead.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [4871ae5]
|
|
12
|
+
- Updated dependencies [4871ae5]
|
|
13
|
+
- Updated dependencies [4871ae5]
|
|
14
|
+
- akanjs@3.1.0
|
|
15
|
+
|
|
16
|
+
## 3.0.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- ccf2ae2: A CSR dev page — `?csr=true`, `/__csr`, and every native dev build — updates in place on save instead of reloading.
|
|
21
|
+
|
|
22
|
+
- `akan start` serves the CSR page as a module registry: each module is its own factory, and a save sends only the
|
|
23
|
+
modules it changed. A component edit keeps hook and DOM state (React Fast Refresh), a store edit keeps the store,
|
|
24
|
+
and a build error shows the overlay and recovers without a reload.
|
|
25
|
+
- A page or layout edit swaps that route in place through `replacePages`: the page stack, history and stores stay,
|
|
26
|
+
and so does the state of the components under the edited page or layout. A `*.constant.ts`, an added or removed
|
|
27
|
+
route, a new npm dependency or a signal/dictionary change still reloads.
|
|
28
|
+
- `AKAN_DEV_CSR=artifact` brings back the single-file dev bundle, which reloads on every save. `akan build` and a
|
|
29
|
+
release mobile build are unchanged.
|
|
30
|
+
- HMR sockets now say what they are: a CSR page connects as `/_akan/hmr?client=csr` and gets `csr-update` instead of
|
|
31
|
+
the SSR `reload` and `rsc-refresh`, and a page that missed an update while disconnected reloads
|
|
32
|
+
when it reconnects.
|
|
33
|
+
|
|
34
|
+
- ccf2ae2: A Windows desktop app can share its screen without the picker
|
|
35
|
+
|
|
36
|
+
`native.desktop.screenCapture: "auto"` in `akan.config.ts`, or `desktop.screenCapture` on one target, makes the
|
|
37
|
+
Windows app answer the page's `getDisplayMedia()` with the first screen at once, without the picker and without a user
|
|
38
|
+
gesture, for remote support on a screen nobody attends. It is Chromium's switch for automated media tests and covers
|
|
39
|
+
every media request, so leave it off in an app whose pages ask for a camera or a microphone. The default `"picker"`
|
|
40
|
+
keeps the picker; macOS and Linux ignore it.
|
|
41
|
+
|
|
42
|
+
- ccf2ae2: `akan build-desktop --installer` makes a Windows setup program
|
|
43
|
+
|
|
44
|
+
On Windows the build adds `<file>-<version>-<arch>-setup.exe`, made with NSIS (`winget install NSIS.NSIS`). It installs
|
|
45
|
+
for the current user under `%LOCALAPPDATA%\Programs\<app name>`, where the updates plugin swaps the app without an
|
|
46
|
+
administrator, with a Start menu shortcut and an uninstall entry. `/S` installs silently and `/RUN` starts the app
|
|
47
|
+
afterwards, and a PC without the WebView2 Runtime gets it. The program is not code-signed yet.
|
|
48
|
+
|
|
49
|
+
- What can fail without touching the installed app comes first: WebView2, then the new files, unpacked beside the
|
|
50
|
+
folder. Only then is a copy of the app running from the folder stopped, found by its path, and the folder swapped by
|
|
51
|
+
two renames. A swap that fails puts the old folder back, and with `/RUN` the app left in place starts again.
|
|
52
|
+
|
|
53
|
+
- The uninstaller lives beside the app folder (`<folder>.uninstall.exe`), not in the folder an update replaces, so
|
|
54
|
+
Settings › Apps can still remove an updated app. It finds the folder from the uninstall entry and removes it with
|
|
55
|
+
whatever updates left beside it (`.previous`, unpacked or failed releases) and itself, the launch-at-login entry and
|
|
56
|
+
the updates state; the server's data stays. An uninstaller that cannot be
|
|
57
|
+
written there (a `/D=` right under a drive root) fails the install.
|
|
58
|
+
- The Start menu shortcut and the app `/RUN` or the last page starts work in `%LOCALAPPDATA%`: Windows cannot rename a
|
|
59
|
+
folder some process works in, which is what applying an update does.
|
|
60
|
+
- `/RUN` starts the app after a silent install only; an interactive one offers it on its last page.
|
|
61
|
+
- Run again without `/D=`, it installs into the folder the uninstall entry names, so a reinstall replaces the copy
|
|
62
|
+
already installed wherever it was put, and a setup started while another one runs refuses to start.
|
|
63
|
+
- A silent install that cannot finish exits with 2: files it could not write (the script is long-path aware, and the
|
|
64
|
+
build warns when the deepest file nears 260 characters under `%LOCALAPPDATA%\Programs`), a WebView2 Runtime still
|
|
65
|
+
missing after its setup ran (an interactive install asks), or a `/D=` folder holding someone else's files.
|
|
66
|
+
- An update the app confirms sets the version Settings › Apps shows.
|
|
67
|
+
|
|
68
|
+
- ccf2ae2: `akan start-desktop <app>` (alias `sd`) runs a native target as a desktop app on this computer — macOS, Windows or
|
|
69
|
+
Linux, whichever it is — the way `start-ios` and `start-android` run it on a phone: a debug build whose pages come
|
|
70
|
+
from `akan start` through the dev gateway, so every save shows up, or with `--release` a release build of its own
|
|
71
|
+
bundle. It takes `--target`, `--env`, `--release` and `--write`. `NativeApp` accepts the desktop platforms, and
|
|
72
|
+
`NativeApp.desktopPlatform()` names this computer's.
|
|
73
|
+
- ccf2ae2: An app and a lib ship native plugins of their own from a `native/` folder
|
|
74
|
+
|
|
75
|
+
A plugin the runtime has no builtin for sits in `apps/<app>/native/<id>/` (or `libs/<lib>/native/<id>/`), the folder
|
|
76
|
+
named after the id in its `native-plugin.json`. It is listed nowhere: every mobile and desktop target of the app
|
|
77
|
+
ships it, and its manifest says what runs on each platform. A lib's plugins reach only the apps that depend on it; an
|
|
78
|
+
app's own plugin wins an id a lib also uses, and two libs claiming one id stop the build. `akan sync`, `akan doctor`
|
|
79
|
+
and `akan quality scan` accept the folder.
|
|
80
|
+
|
|
81
|
+
The page API is written with `definePlugin` from `akanjs/client/native` (which also exports `defineWebPlugin`,
|
|
82
|
+
`createLiveValue`, `useLiveValue` and `usePluginEvent`), and the desktop part with `defineDesktopPlugin` from the new
|
|
83
|
+
`akanjs/native/desktop`. A `native/` folder is out of the scope of `no-throw-raw-error` (a plugin throws
|
|
84
|
+
`AkanNativeError`) and of `no-web-only-api-outside-webkit`. A plugin a target names by folder in `native.plugins` is now
|
|
85
|
+
granted by its id instead of its path, and ships once when it is also the app's own `native/<id>`. A desktop plugin's
|
|
86
|
+
`ctx.launch.exit(code)` after its setup ran past the launch phase's 3 s quits the app, since its window exists by then.
|
|
87
|
+
|
|
88
|
+
- ccf2ae2: An installed app updates itself: a phone its web bundle over the air, a desktop app the whole app.
|
|
89
|
+
|
|
90
|
+
- `native.updates: { url, publicKey, channel?, readyTimeout? }` in `akan.config.ts`, per target too, the target's
|
|
91
|
+
fields winning. It brings the runtime's `updates` plugin. A channel left unnamed is the backend env the binary is
|
|
92
|
+
built for (`main`, `develop`, `debug`, …), so a `build-desktop` app (`debug` unless `--env` names another) takes
|
|
93
|
+
only the releases published for that env.
|
|
94
|
+
- The CSR frame confirms a release on trial once the first page is on screen, in every native shell (an unconfirmed
|
|
95
|
+
one is rolled back at the next launch). On a phone it also keeps the bundle current by itself: at start and on each
|
|
96
|
+
return to the front it looks for a newer one and downloads it, and it runs from the next cold start. A desktop
|
|
97
|
+
release is the whole app and a relaunch, so the app checks, downloads and applies it on its own schedule. A dev
|
|
98
|
+
build's pages are left alone. `akanjs/client/native` exports `updates`, `markReady` and `useUpdateState`.
|
|
99
|
+
- `akan update-keygen <app> [--platform]` makes the signing key of the app's id once and prints its public half.
|
|
100
|
+
`akan publish-update <app>` builds a release on this machine and signs it: the whole app for this computer's desktop
|
|
101
|
+
OS and CPU (with the app's server when `desktop.server` says so for the target, as for `build-desktop`), or the
|
|
102
|
+
web bundle for `--platform android|ios`. It writes the manifest and its files under `.akan/native/<target>/updates`,
|
|
103
|
+
to upload to `updates.url`, on the channel of its `--env` (`main` unless named). `--channel` names only the manifest
|
|
104
|
+
written, not the channel the release follows, so a pilot group gets a target whose `updates.channel` is the pilot's.
|
|
105
|
+
- The updates folder holds only what is uploaded, and whoever can reach `updates.url` reads it: the updater sends no
|
|
106
|
+
credentials, and a desktop release is the whole app, with the carried server's `private/` (the app's and its libs')
|
|
107
|
+
and its env file.
|
|
108
|
+
- `akan pack-update <app> --platform ios|android [--target] [--env] [--out]` writes an unsigned phone update instead,
|
|
109
|
+
for a signer that keeps the key elsewhere: `files/<sha256>`, `bundle.json` and `manifest.template.json`, the
|
|
110
|
+
manifest with `channel`, `sequence` and `bundle` left for the signer. `--against <store bundle.json>` also checks
|
|
111
|
+
the bundle runs in that store build, writes `compat.json`, and fails when it needs a new binary. The signing
|
|
112
|
+
contract is in the native runtime's architecture notes: fill the three fields, sign exactly the bytes you upload,
|
|
113
|
+
upload `files/` first.
|
|
114
|
+
- `publish-update` refuses a `--channel` outside the names `updates.channel` accepts (lowercase letters, digits, `.`,
|
|
115
|
+
`_`, `-`) before it builds, and numbers a release past the one its channel already has in the updates folder,
|
|
116
|
+
warning when this computer's clock is behind it. Apps compare against the time their own build was made, so keep the
|
|
117
|
+
building and the publishing computers' clocks in step.
|
|
118
|
+
- While a desktop release is on trial, `updates.check()` answers `available: false` for it and `updates.apply()`
|
|
119
|
+
rejects with `NOT_ALLOWED`: applying would replace the app a failed trial goes back to.
|
|
120
|
+
- A release of a desktop app that carries its server is confirmed only once that server answered ready and stayed up
|
|
121
|
+
for 5 s, and only while it is up; its trial clock (`readyTimeout`) starts then, so a server slow on its first run
|
|
122
|
+
(its new files being scanned) costs no rollback. A server that gives up, or is not up within 120 s of the start,
|
|
123
|
+
rolls the release back at once; the release stays downloaded for the next apply, and its third such failure
|
|
124
|
+
excludes it for good.
|
|
125
|
+
- A desktop release's manifest says whether it carries a server, and an installed app refuses one that differs from
|
|
126
|
+
itself before downloading anything (`check()` answers `available: false`, `download()` rejects with
|
|
127
|
+
`NOT_ALLOWED`), since either way its pages' backend would change place. `publish-update` refuses, before it builds,
|
|
128
|
+
a desktop release whose server presence differs from the channel's previous one: publish it on another channel
|
|
129
|
+
(`updates.channel`), or remove that `<channel>.json` from the output folder to start the channel over. Turning
|
|
130
|
+
`native.desktop.server` on or off for an app already installed takes a reinstall.
|
|
131
|
+
- On Windows an app started from its install folder (the installer, the Start menu, Explorer) applies and rolls back
|
|
132
|
+
updates: Windows renames no folder a process works in, so the app moves to its local data folder before the webview
|
|
133
|
+
starts, and the update helper and the app it starts work elsewhere.
|
|
134
|
+
- The update state and the unpacked releases live in the app's local data (`%LOCALAPPDATA%\<app id>\akan-native-updates`
|
|
135
|
+
on Windows), and plugins get that folder as `ctx.appLocalDataDir`.
|
|
136
|
+
- An app installed under another folder name (`/D=`, a renamed `.app`) takes updates; a download older than a
|
|
137
|
+
reinstalled build is dropped; a Windows swap that did not happen (a file stayed locked, the helper never ran) keeps
|
|
138
|
+
the release downloaded for the next apply until its third such failure excludes it. Reinstalling the app clears the
|
|
139
|
+
list of releases the earlier install refused.
|
|
140
|
+
- `publish-update` checks every target before anything builds — its `updates` settings, its native config, the
|
|
141
|
+
signing key on this computer, and whether its channel's previous release carries a server as it would — so a CI
|
|
142
|
+
without the key stops at once and a publish never releases only the first targets. It writes `<channel>.json`
|
|
143
|
+
together with its signature.
|
|
144
|
+
The channels `bundle`, `manifest.template` and `compat` are refused. `akan start` keeps `.akan/native`, where
|
|
145
|
+
releases wait to be uploaded, and the `bin` downloads in `.akan/cache/bin`.
|
|
146
|
+
|
|
147
|
+
- ccf2ae2: An app image carries the server env of the environment it runs and no other.
|
|
148
|
+
|
|
149
|
+
- `env/env.server.ts` imports every environment's file, so `akan build` used to bundle all of them into `server.js`:
|
|
150
|
+
anyone holding the image of one environment could read the keys of every other. The backend build now keeps only
|
|
151
|
+
`env.server.<AKAN_PUBLIC_ENV>.ts`, the environment the generated Dockerfile fixes, and swaps each other
|
|
152
|
+
environment's file (`local`, `testing`, every branch) for exports that refuse to be read. Booting such an image under
|
|
153
|
+
another `AKAN_PUBLIC_ENV` stops with `env/env.server.<name>.ts is not in this build` instead of running on a
|
|
154
|
+
config it does not have. Nothing in `env/` needs to change.
|
|
155
|
+
- An app's generated `server.ts` no longer re-exports `env` from `env.server.testing.ts`, which put the testing env in
|
|
156
|
+
every image. Signal tests read it from the file. A script or test that imported `env` from an app's `server.ts`
|
|
157
|
+
imports `./env/env.server.testing` instead. A lib's `server.ts` still exports it, since each app's
|
|
158
|
+
`env.server.type.ts` spreads it as the lib's defaults.
|
|
159
|
+
- Keys that already shipped in an image stay readable in it. Rotate them.
|
|
160
|
+
- A build for a named `--env` (a mobile or desktop build) writes that env into the Dockerfile too, so the image boots
|
|
161
|
+
the environment its `server.js` carries.
|
|
162
|
+
|
|
163
|
+
**Breaking for an image started under another `AKAN_PUBLIC_ENV`, and for code importing `env` from an app's
|
|
164
|
+
`server.ts`:** the image stops at boot until it runs the environment it was built for, and the import moves to
|
|
165
|
+
`./env/env.server.testing`.
|
|
166
|
+
|
|
167
|
+
- ccf2ae2: A desktop app nobody attends recovers its page, relaunches itself and opens as a kiosk
|
|
168
|
+
|
|
169
|
+
`native.desktop.recovery: "reload"`, for the app or one target, loads a page whose web process ended (a crash, a hang,
|
|
170
|
+
out of memory) again, once per end, instead of showing an error page after the second end, waiting longer after each
|
|
171
|
+
end in a row (1 s, doubling to a minute). It relaunches the app when the webview's browser process ends, where the app
|
|
172
|
+
used to quit: at once the first time, then after 1 s doubling to a minute while each relaunched app dies within a
|
|
173
|
+
minute, and after ten in a row it exits with code 1 instead of restarting for good.
|
|
174
|
+
`native.desktop.window: { fullscreen, skipTaskbar }` opens the main window borderless fullscreen and without a taskbar
|
|
175
|
+
button (Windows, Linux) from its first frame; a plugin's launch phase decides the same per launch with
|
|
176
|
+
`ctx.launch.setWindow`. `app.relaunch()` ends the app and starts it again in a new process on the desktop and Android,
|
|
177
|
+
and reloads the page on the web; one process starts one new app, however many windows or calls asked for it.
|
|
178
|
+
`native.android.autoplay: true` lets media play with sound without a tap first, as it already does on iOS and the
|
|
179
|
+
desktop.
|
|
180
|
+
|
|
181
|
+
An app relaunched by an update on Windows no longer quits the moment it starts when `akan start-desktop` had started
|
|
182
|
+
the one before it.
|
|
183
|
+
|
|
184
|
+
- ccf2ae2: A desktop app can carry the app's own server: with `native: { desktop: { server: true } }` in `akan.config.ts`, or
|
|
185
|
+
`desktop.server` on one target, `akan build-desktop`, `akan start-desktop --release` and `akan publish-update` put the
|
|
186
|
+
backend `akan build` made into the app, so it works on one computer with no backend elsewhere.
|
|
187
|
+
|
|
188
|
+
- The build stages what the backend build wrote into `apps/<app>/.akan/desktop/server` — `main.js`, `server.js`, the
|
|
189
|
+
chunks, `akan.build.json`, `private/`, and any `.node`, `.wasm` or file asset a bundled package brought — without the
|
|
190
|
+
Dockerfile, the RSC worker, the console, `csr/` and `public/`. Its packages install there first
|
|
191
|
+
(`bun install --production --prefer-offline`, without the RSC renderer and the drivers of every database mode but
|
|
192
|
+
`single`), before `akan build` runs, so a computer that cannot reach the registry or find them in Bun's cache stops at
|
|
193
|
+
once. The app needs `single` in `database.modes`; the command says so before it builds.
|
|
194
|
+
- The backend is built for the command's own `--env`, not the workspace's `AKAN_PUBLIC_ENV` (the root `.env` usually
|
|
195
|
+
names `local`), so `server.js` holds that environment's `env.server.<env>.ts` and no other.
|
|
196
|
+
- What it carries is readable in plain text by anyone with the app: `private/` (each lib's too, under
|
|
197
|
+
`private/libs/<lib>`), that `env.server.<env>.ts` and the server env defaults of the libs it uses (each lib's
|
|
198
|
+
`env.server.testing.ts`). Keep deployment secrets out of them. It has no `public/`, and its working
|
|
199
|
+
folder is its data folder: read a runtime file from the app folder (`AKAN_APP_DIR`, else the folder of `Bun.main`),
|
|
200
|
+
never from `process.cwd()`.
|
|
201
|
+
- At launch the shell starts it as a child on its own Bun (`BUN_BE_BUN`, with `.env`, `bunfig.toml` and
|
|
202
|
+
auto-install off) on a loopback port, and hands the page its URL as `PUBLIC_AKAN_SERVER_URL`. The port of the last
|
|
203
|
+
session comes first and a fresh one is picked only when it is taken, so an address registered somewhere usually
|
|
204
|
+
stays valid from one launch to the next. The window waits for it up to 8 s.
|
|
205
|
+
- It runs API only, `operationMode` edge, database mode `single`, SSR, CSR and MCP off, bound to 127.0.0.1 with every
|
|
206
|
+
other Host refused. Any program on the computer can still call that port, so guard its endpoints as a network
|
|
207
|
+
server's. It keeps its SQLite data, files, logs and a per-install JWT secret in the app data folder's
|
|
208
|
+
`server/`, and a debug build, which has the release app's id, in its own `server-debug/`. On Windows that folder is
|
|
209
|
+
`%LOCALAPPDATA%\<app id>\server`, not the Roaming folder a profile copies on every sign-in and an organisation may
|
|
210
|
+
put on a file share, where SQLite's WAL does not work. Bun's transpiler cache goes to
|
|
211
|
+
`<server data>/runtime/transpiler-cache`.
|
|
212
|
+
- It trusts the operating system's CAs (`--use-system-ca`), as the page does, and gets the proxy and CA variables
|
|
213
|
+
(`HTTP(S)_PROXY`, `NO_PROXY`, `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `NODE_USE_SYSTEM_CA`) and the desktop session's
|
|
214
|
+
(`DISPLAY`, `WAYLAND_DISPLAY`, the session bus, the audio server, `XDG_*`, and Windows' `ProgramFiles`, `ComSpec`,
|
|
215
|
+
`PATHEXT` and the like), so a `bin` tool reaches what the page reaches and can open the screen, the audio server or a
|
|
216
|
+
shell.
|
|
217
|
+
- `AkanApp` takes `BUN_BE_BUN` off `process.env` at boot and gives it back only to a spawn of its own executable (the
|
|
218
|
+
ops snapshot, which also gets the server's runtime flags), so a `bin` tool built with `bun build --compile` starts as
|
|
219
|
+
itself rather than as the Bun CLI. A child started with `Bun.spawn` and no `env` still gets the environment the
|
|
220
|
+
process started with, so pass `env: process.env`.
|
|
221
|
+
- A crash restarts it on the same port (1 s doubling to 30 s; the fifth in a row gives up with an alert). A server that
|
|
222
|
+
exits before its first ready starts again 250 ms later, on the same port while it is still free, so one that cannot
|
|
223
|
+
boot gives up while the window still waits for it and the window opens at once with the alert. A server
|
|
224
|
+
that cannot start at all (an unreadable `jwt.secret`, a data folder that cannot be made) still hands the page a
|
|
225
|
+
loopback URL and shows the same alert once the window is up, so the page never falls back to the backend its bundle
|
|
226
|
+
was built for. A restart that throws counts as one failure. Its standard error, and its standard output until it is
|
|
227
|
+
ready, also go to `server/runtime/logs/server-output.log`, where a server that fails before it listens leaves its
|
|
228
|
+
reason.
|
|
229
|
+
- Quitting the app stops it within its 1 s shutdown budget, inside the launcher's 1.5 s grace, and a server still
|
|
230
|
+
there after the grace is killed. On macOS and Linux it leads its own process group, and whatever it started ends
|
|
231
|
+
with it: the launcher ends the group when the app quits, and when the app was killed or crashed — even while the
|
|
232
|
+
server was still starting — the server sees its parent gone and ends the group itself, SIGTERM first and SIGKILL a
|
|
233
|
+
second later. A process started `detached` leaves the group. On Windows the job object ends them all with the app.
|
|
234
|
+
- It runs in one process: a `main.ts` or an env asking for a gateway and replicas (`replica`, `solo: false`,
|
|
235
|
+
`AKAN_SOLO=false`, `AKAN_REPLICA`, `AKAN_COMMAND_TYPE=start`) does not boot in the app, and says which.
|
|
236
|
+
- A native plugin follows the server with `ctx.server.state` and `ctx.server.onState` (`starting`, `up`,
|
|
237
|
+
`restarting`, `gaveUp`, `stopped`), and a page with the `app` plugin's `serverState` event, so an app nobody attends
|
|
238
|
+
can act on a server that gave up (`app.relaunch()`, say).
|
|
239
|
+
- `XAUTHORITY`, `PULSE_COOKIE`, `PULSE_RUNTIME_PATH` and `XDG_DATA_DIRS` reach it too. A second launch that hands
|
|
240
|
+
over to the first starts none. The single-instance plugin comes with it.
|
|
241
|
+
- `akan start-desktop` without `--release`, for such a target, follows this checkout's dev server already answering on
|
|
242
|
+
the app's dev port, or starts `akan start` in the same command and opens the app once it serves; it asks for one
|
|
243
|
+
target before it starts one, needs `single` as a release does, and stops at once when that dev server fails to boot
|
|
244
|
+
(a replica's crash loop included) instead of waiting it out. Ctrl+C or closing the app stops the app, then the dev
|
|
245
|
+
server it started, then the local database, one after another, and exits 130.
|
|
246
|
+
- `akan start-desktop --release` of such a target shows the carried server's lines at the level its logger wrote them.
|
|
247
|
+
- `AkanAppConfig.getProductionEnv()` is the env the image runs with; the Dockerfile's `ENV` lines come from it (same
|
|
248
|
+
keys and order, without the two blank lines the template used to leave).
|
|
249
|
+
|
|
250
|
+
- ccf2ae2: `akan build-desktop --arch arm64|x64` picks the CPU a Windows or Linux desktop app runs on
|
|
251
|
+
|
|
252
|
+
A desktop app is still built on its own OS, now for either of its CPUs: the Rust library is built for that target,
|
|
253
|
+
the executable with `bun build --compile --target=bun-<os>-<arch>`, the carried server installs with
|
|
254
|
+
`bun install --cpu=<arch>` so its addons' prebuilt binaries are that CPU's, and each `bin` entry is that platform's.
|
|
255
|
+
The setup program and the AppImage name the CPU they hold. A macOS app is Apple silicon only: Intel Macs are not a
|
|
256
|
+
target, so `--arch x64` on macOS is refused.
|
|
257
|
+
|
|
258
|
+
- ccf2ae2: `bin` in `akan.config.ts` puts executables in a desktop app, and the build says what the image installs that the app does not
|
|
259
|
+
|
|
260
|
+
A desktop app ran whatever `ffmpeg` the user's computer had on its PATH, if any, and a Finder-launched app sees only
|
|
261
|
+
`/usr/bin:/bin:/usr/sbin:/sbin`. `bin` names an executable and, per platform (`darwin-arm64`, `win32-x64`, …), where it
|
|
262
|
+
comes from: a download with its `sha256` (an archive takes `file`, the executable inside it) or a path next to the
|
|
263
|
+
declaring `akan.config.ts`. `build-desktop` and `start-desktop` fetch the file for the computer they build on, check it,
|
|
264
|
+
and carry it in the app's `bin/`, which the app puts first on its PATH: the carried server's `spawn("ffmpeg")` runs
|
|
265
|
+
that file, and a native plugin finds it in `ctx.binDir`. A download is kept as `download` plus its URL's extension
|
|
266
|
+
(`.zip`, `.tar.xz`, `.exe`, …), never under a name the URL spells. Bun's own `spawn` without `env` reads the environment the app
|
|
267
|
+
started with, so a plugin passes `env: process.env` to run one by name. A lib's entries reach the apps that depend on
|
|
268
|
+
it, and an app's own entry of the same name wins. macOS builds sign every executable the app carries, in `bin/` and
|
|
269
|
+
in the carried server, found by its Mach-O header rather than its name, so one a package ships without an extension
|
|
270
|
+
passes Developer ID signing and notarization too. The image does not read `bin`: it still installs through `docker`.
|
|
271
|
+
|
|
272
|
+
`build-desktop` of a target that carries its server now warns when the image runs `docker` steps (or the app writes
|
|
273
|
+
its own Dockerfile) and the app carries no `bin`, since the carried server runs none of those steps.
|
|
274
|
+
|
|
275
|
+
- ccf2ae2: `desktop.server: { omit }` carries the desktop app's server without packages only the image needs
|
|
276
|
+
|
|
277
|
+
`native: { desktop: { server: { omit: ["rclnodejs"] } } }` carries the server as `server: true` does, minus the named
|
|
278
|
+
packages and whatever only they pull in: an addon tied to the image's system (a ROS install), or code only a process
|
|
279
|
+
the desktop app never starts loads. The image's `package.json` keeps them, `externalLibs` included; the desktop
|
|
280
|
+
server's `package.json`, its `bun install --production` and the addon check do not see them. A package another
|
|
281
|
+
dependency still installs stops the build, naming the dependents, and the targets that carry one build's server must
|
|
282
|
+
omit the same packages.
|
|
283
|
+
|
|
284
|
+
- ccf2ae2: A desktop app's server reads the files the user picks, with no copy and no path in the page
|
|
285
|
+
|
|
286
|
+
`filePicker.pickFiles({ forServer: true })` (also `pickDirectory` and `saveFile`; `filePicker` is now exported from
|
|
287
|
+
`akanjs/client/native`) copies nothing on the desktop, whatever the file's size: its FileRefs serve the originals,
|
|
288
|
+
and each result carries a grant. The server exchanges the grant with `NativeFile.resolve(grant, "read" | "write" |
|
|
289
|
+
"folder")` from `akanjs/server`, or `NativeFile.resolveIn(folderGrant, relative)`. The carried server asks the shell
|
|
290
|
+
that showed the dialog over its IPC channel, so it reaches what the user picked and nothing else; a debug build
|
|
291
|
+
that carries its server does the same. Behind a dev build without one, `akan start` checks the grant's signature
|
|
292
|
+
instead, in `operationMode` local only. A phone or the web refuses `forServer`.
|
|
293
|
+
|
|
294
|
+
`resolveIn` judges a relative path by where it lands once the links on it are resolved — through its nearest existing
|
|
295
|
+
folder when the file is about to be written — so a link inside the granted folder cannot take it elsewhere, and a
|
|
296
|
+
dangling link is refused. A name that starts with two dots (`..cache/x`) is inside the folder, not above it.
|
|
297
|
+
|
|
298
|
+
- ccf2ae2: `akan build-desktop --installer true` on Linux adds an AppImage
|
|
299
|
+
|
|
300
|
+
The app folder gains an `AppRun`, a `.desktop` entry naming the app's icon and its `deepLinks.schemes`, and a 256px icon,
|
|
301
|
+
and is packed with `mksquashfs` behind the AppImage type 2 runtime, pinned to a dated release and checked by its
|
|
302
|
+
digest. The AppImage needs only the WebKitGTK 4.1 and GTK 3 the folder already needs, and runs without FUSE through
|
|
303
|
+
`--appimage-extract-and-run`. It runs from a read-only image, so the updates plugin cannot replace it: an app with
|
|
304
|
+
`updates` is warned to publish a new AppImage for each release.
|
|
305
|
+
|
|
306
|
+
- ccf2ae2: A macOS desktop release can be downloaded: Developer ID signing, the hardened runtime, notarization and a dmg
|
|
307
|
+
|
|
308
|
+
`akan build-desktop` signs with a Developer ID when `AKAN_NATIVE_MACOS_IDENTITY` names one in a keychain, or
|
|
309
|
+
`AKAN_NATIVE_MACOS_CERTIFICATE` + `_CERTIFICATE_PASSWORD` a `.p12` (imported into a keychain made for the build and
|
|
310
|
+
deleted after it, for CI). Every Mach-O file is signed inside out with the hardened runtime and a secure timestamp, and
|
|
311
|
+
the executable gets Bun's JIT entitlements plus the camera's or microphone's when a usage text asks for them;
|
|
312
|
+
`native.desktop.entitlements` adds the app's own. With `AKAN_NATIVE_MACOS_NOTARY_KEY` + `_KEY_ID` + `_ISSUER` (an App
|
|
313
|
+
Store Connect API key) or `_NOTARY_PROFILE`, the app is notarized, stapled and checked with `spctl`.
|
|
314
|
+
`--installer true` on macOS adds a dmg with an Applications link, signed, notarized and stapled the same way.
|
|
315
|
+
`publish-update` signs a macOS release with the same identity, since the updater checks the installed app's signature.
|
|
316
|
+
A release that is not signed with a Developer ID, or not notarized, warns that Gatekeeper blocks a downloaded copy.
|
|
317
|
+
|
|
318
|
+
- ccf2ae2: A native platform section takes its own `indexPath`
|
|
319
|
+
|
|
320
|
+
`ios.indexPath`, `android.indexPath` and `desktop.indexPath` win over the section's (or the target's) `indexPath` on
|
|
321
|
+
that platform, so one target opens the phones on `/mobile` and the desktop app on `/` with no `--target`. It moves
|
|
322
|
+
both the dev build's first page and the `indexPath` a release bundle carries.
|
|
323
|
+
|
|
324
|
+
- ccf2ae2: Mobile apps build and run on `@akanjs/native` instead of Capacitor.
|
|
325
|
+
|
|
326
|
+
The runtime ships inside `akanjs` (vendored, not published on its own), so an app installs nothing extra for it.
|
|
327
|
+
It generates each target's native projects under `.akan/native/<target>/build/<platform>` and loads the web
|
|
328
|
+
root it assembles in `.akan/native/<target>/web` from the production CSR build — nothing native is committed.
|
|
329
|
+
|
|
330
|
+
- **Configuration (`akan.config.ts`).** A `native` section, and each of its `targets`, takes `appName`, `appId`,
|
|
331
|
+
`fileName`, `version`, `buildNum`, `basePath`, `indexPath`, `icon`, `splash`, `permissions`, `plugins` (builtin
|
|
332
|
+
ids such as `iap`), `deepLinks`, `ios.{infoPlist, entitlements, files}` and
|
|
333
|
+
`android.{manifest, application, activity, googleServices, files}`; a target overrides the section field by field.
|
|
334
|
+
`files` maps where a file lands (a path in the iOS app bundle, `res/<type>/<file>` or `assets/<path>` on Android)
|
|
335
|
+
to its source. A Capacitor-era key fails, naming the keys the section takes.
|
|
336
|
+
- **Plugins declare, they do not edit projects.** `AkanPlugin.native` names the permission it serves and what the
|
|
337
|
+
target then needs: native plugins, usage texts, plist and entitlement entries, Android permissions and features.
|
|
338
|
+
It replaces `capacitor.configureNative`, `editIosAppDelegate` and `runtimePackages`. A release build names each
|
|
339
|
+
plugin's default permissions as its capabilities.
|
|
340
|
+
- **Commands.** `build-`, `start-` and `release-ios|android` go through the runtime. `start-*` follows `akan start`
|
|
341
|
+
(it refuses when no dev server answers), runs one target at a time and takes `--device` (and `--team`/`-T` on
|
|
342
|
+
iOS); `--release` runs a release build of its own bundle. `release-ios` takes `--team` and `--ad-hoc`;
|
|
343
|
+
`release-android` builds an `.aab` by default (`--assemble-type apk` for an `.apk`), signed from
|
|
344
|
+
`MYAPP_RELEASE_STORE_FILE`, `MYAPP_RELEASE_STORE_PASSWORD` and `MYAPP_RELEASE_KEY_ALIAS` (plus
|
|
345
|
+
`MYAPP_RELEASE_KEY_PASSWORD` when the key has its own), which it checks before anything builds. `build-*` take
|
|
346
|
+
`--debug`, and
|
|
347
|
+
`akan doctor --ios` adds the native toolchain checks.
|
|
348
|
+
- **Pages.** `akanjs/client/native` wraps the native plugins a page uses, and `isNativeApp()` is true only inside
|
|
349
|
+
a shell. `Device` reads the runtime's device plugin and safe-area variables, `storage` the shell's preferences,
|
|
350
|
+
and the new `secretStorage` keeps the JWT and refresh tokens in the OS credential store (cleared on the first
|
|
351
|
+
launch after a reinstall). `useCamera` is one hook — the native sheet asks camera or library and answers an
|
|
352
|
+
upright JPEG data URL — and `useGeoLocation` answers the flat native position. A native release bundle opened at
|
|
353
|
+
`/` starts on its target's home, and a target opened in a browser is a web device.
|
|
354
|
+
|
|
355
|
+
**Breaking, with a migration path.**
|
|
356
|
+
|
|
357
|
+
- Delete the app's `ios/`, `android/`, `mobile/` folders and `capacitor.config.*`: `akan sync`, `akan doctor` and
|
|
358
|
+
`akan quality scan` now refuse them in an app root and say why.
|
|
359
|
+
- Move Capacitor settings to the keys above; `google-services.json` goes to `native.android.googleServices`, and
|
|
360
|
+
`GoogleService-Info.plist` is no longer needed.
|
|
361
|
+
- Removed: `-g/--regenerate`, `--open`, `--allow-provisioning-updates`, `configure-app`, `codepush`,
|
|
362
|
+
`release-source`, `capacitor.base.config`, `useContact`, `useCodepush`, the native path of `useSpeech`, and every
|
|
363
|
+
`@capacitor/*`, `@capacitor-community/*`, `@capgo/*`, `capacitor-plugin-safe-area`, `cordova-plugin-purchase`
|
|
364
|
+
and `@trapezedev/project` dependency — `akanjs` no longer lists them as peers.
|
|
365
|
+
- `baseSt` drops `deviceToken`; the notification store owns it.
|
|
366
|
+
|
|
367
|
+
- ccf2ae2: `native` in `akan.config.ts` replaces `mobile`, with each platform's settings in a section of its own
|
|
368
|
+
|
|
369
|
+
**Breaking:** `mobile` is gone with no fallback; a config that still has it, or a key that moved, stops and names the
|
|
370
|
+
new place. `native` and each of `native.targets` take the same fields, a target merging objects key by key and
|
|
371
|
+
replacing lists and every other value, and without `targets` the app has one target, `default`. The next `akan start`
|
|
372
|
+
removes the old `.akan/mobile`.
|
|
373
|
+
|
|
374
|
+
The agent guide `akan agent install` writes now covers native apps: the `native` section, the `start-*` / `build-*`
|
|
375
|
+
commands and updates, with the full contract in the `runtimeRule` guideline.
|
|
376
|
+
|
|
377
|
+
| Before | Now |
|
|
378
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
379
|
+
| `mobile` | `native` |
|
|
380
|
+
| `mobile.native.X`, `targets.<t>.native.X` (`plugins`, `ios`, `android`, `desktop`) | `native.X`, `native.targets.<t>.X` |
|
|
381
|
+
| `native.push.android` | `android.push` |
|
|
382
|
+
| `native.privacy` | `ios.privacy` |
|
|
383
|
+
| `assets.icon`, `assets.splash` | `icon`, `splash` |
|
|
384
|
+
| `files["ios/<path>"]` | `ios.files["<path>"]` |
|
|
385
|
+
| `files["android/res/…"]`, `files["android/assets/…"]` | `android.files["res/…"]`, `android.files["assets/…"]` |
|
|
386
|
+
| `deepLinks.ios.teamId` | `ios.teamId` |
|
|
387
|
+
| `deepLinks.android.sha256CertFingerprints` | `android.sha256CertFingerprints` |
|
|
388
|
+
| `indexPath` on a target only | on `native` too, inherited by every target |
|
|
389
|
+
| root and target `plugins` joined, Android XML lists appended | the target's list replaces |
|
|
390
|
+
| `.akan/mobile/<target>/native/<platform>` | `.akan/native/<target>/build/<platform>` |
|
|
391
|
+
| `.akan/mobile/<target>/{dev,updates,web,bin}` | `.akan/native/<target>/{dev,updates,web,bin}` |
|
|
392
|
+
| `AkanMobileConfig`, `AkanMobileTargetConfig` | `AkanNativeAppConfig` (resolved: `AkanNativeAppResult`), `AkanNativeTarget`; `AkanNativeSettings` is what `native` and a target share |
|
|
393
|
+
| `AkanMobileUpdatesConfig`, `AkanMobileAppId`, `AkanMobileTargetDeepLinks` | `AkanNativeUpdatesConfig`, `AkanNativeAppId`, `AkanNativeDeepLinks` |
|
|
394
|
+
| `AkanMobileNativeConfig` | `AkanNativeIosConfig`, `AkanNativeAndroidConfig`, `AkanNativeDesktopConfig` |
|
|
395
|
+
| `AkanMobileTargetAssets`, `AkanMobileTargetFiles` | removed: `icon`, `splash` and each platform's `files` |
|
|
396
|
+
| `MobilePermission`, `MobileEnv`, `AppConfigResult.mobile` | `NativePermission`, `NativeEnv`, `AppConfigResult.native` |
|
|
397
|
+
|
|
398
|
+
- 5f53462: `akan subspace upload-env <name>` sends one subspace's env to the cloud workspace it deploys from
|
|
399
|
+
|
|
400
|
+
A subspace's `env/` values were the one thing the mirror had no way to move. Push holds them back and pull
|
|
401
|
+
leaves them alone — deliberately, because the values belong to the repo that deploys — so getting a changed
|
|
402
|
+
value into a customer's deployment meant running `akan upload-env` from somewhere that had both the values and
|
|
403
|
+
that customer's `AKAN_WORKSPACE_ID`, which is nowhere: a workspace has every customer's values and exactly one
|
|
404
|
+
id, its own.
|
|
405
|
+
|
|
406
|
+
`SubspaceDeclaration` now carries an optional `workspaceId`, the cloud workspace that subspace deploys from,
|
|
407
|
+
and `akan subspace upload-env <name>` uploads against it. `akan.subspace.ts` is a workspace-only entry that no
|
|
408
|
+
push ever ships, so declaring every customer's id there puts none of them in a customer's repo.
|
|
409
|
+
|
|
410
|
+
What is uploaded is the slice a push ships — the declared apps, the libraries their closure pulls in, and those
|
|
411
|
+
apps' `secrets` globs — and nothing else. The whole workspace would be the simpler thing to send, and it is the
|
|
412
|
+
wrong one: an env archive is replaced whole on arrival, so one customer's cloud workspace would end up holding
|
|
413
|
+
every other customer's secrets. `CloudRunner.gatherEnvFiles` takes that scope and an archive path (the slice
|
|
414
|
+
lands in `local/env.<name>.tar`, never over the full `local/env.tar`), while the `akan:secrets` block it
|
|
415
|
+
maintains in the root `.gitignore` keeps being written from every app in the workspace — narrowing that file to
|
|
416
|
+
one slice would silently un-ignore every other app's secrets.
|
|
417
|
+
|
|
418
|
+
Because the upload replaces an archive in a repo you are not standing in, three things are refused rather than
|
|
419
|
+
guessed: a subspace with no `workspaceId`, a subspace declaring this workspace's own id (a slice uploaded there
|
|
420
|
+
would leave the workspace's next `download-env` short of every other app), and two subspaces declaring the same
|
|
421
|
+
id. The command asks for confirmation before it replaces anything, naming the subspace and the cloud workspace;
|
|
422
|
+
`-F, --force` skips the question, and with no terminal to ask in the command refuses instead of assuming yes.
|
|
423
|
+
(`-f` is already `--format` and `-y` already `--verify`.)
|
|
424
|
+
|
|
425
|
+
The direction is one-way on purpose. There is no `subspace download-env`: it would write a customer's values
|
|
426
|
+
over the workspace's own copies, and which of the two is right is not something a command can know.
|
|
427
|
+
|
|
428
|
+
- ccf2ae2: `trustedDependencies` in `akan.config.ts` names the packages whose install scripts run where the app is installed
|
|
429
|
+
|
|
430
|
+
`bun install --production` skips every dependency's install and postinstall scripts unless the package is trusted, so
|
|
431
|
+
a native addon that builds itself at install time (one that ships no prebuilds) reached the image unbuilt and failed
|
|
432
|
+
at its first call. An app or a lib now lists those packages in `trustedDependencies`; the built `package.json`
|
|
433
|
+
carries the list, and both the image and a desktop app's server run their scripts. A lib's list reaches every app.
|
|
434
|
+
|
|
435
|
+
- ccf2ae2: Three grit rules keep app and lib code off the browser APIs that behave differently inside a native app, and each
|
|
436
|
+
message names what to use instead.
|
|
437
|
+
|
|
438
|
+
- `no-document-cookie` (error): an `app://localhost` page on iOS, macOS and Linux keeps no cookies, so
|
|
439
|
+
`document.cookie` reads empty and drops writes in the app. Use `getCookie` / `setCookie` / `removeCookie` from
|
|
440
|
+
`akanjs/client`.
|
|
441
|
+
- `no-web-storage` (error): `localStorage` / `sessionStorage` work in the shells but bypass the store akanjs picks
|
|
442
|
+
per platform and throw during SSR. Use `storage`, or `secretStorage` for credentials, from `akanjs/client`.
|
|
443
|
+
- `no-web-only-api-outside-webkit` (warning): `navigator.share` / `canShare`, `navigator.serviceWorker`,
|
|
444
|
+
`Notification.requestPermission` / `.permission`, `navigator.geolocation` and `navigator.vibrate` belong in a
|
|
445
|
+
`webkit/` hook that branches on `isNativeApp()`.
|
|
446
|
+
|
|
447
|
+
The first two apply to `apps/**` and `libs/**` outside tests, the third to the same files outside `webkit/`.
|
|
448
|
+
|
|
449
|
+
**Breaking for a workspace that reads Web Storage or cookies directly:** `akan lint` now fails there. Move the
|
|
450
|
+
call to the `akanjs/client` helpers, or keep it with `// biome-ignore lint/plugin: <reason>`.
|
|
451
|
+
|
|
452
|
+
- ccf2ae2: A Windows desktop release is signed with Authenticode
|
|
453
|
+
|
|
454
|
+
With `AKAN_NATIVE_WINDOWS_CERTIFICATE` + `_CERTIFICATE_PASSWORD` (a `.pfx`), `AKAN_NATIVE_WINDOWS_THUMBPRINT` (a
|
|
455
|
+
certificate in the store) or `AKAN_NATIVE_WINDOWS_SIGN_COMMAND` (a JSON array run once per file with `{file}`, for Azure
|
|
456
|
+
Trusted Signing or a cloud HSM), `akan build-desktop` signs every PE file of the app — the executable, its DLL, the
|
|
457
|
+
server's addons and `bin` — SHA-256 with an RFC 3161 timestamp (`AKAN_NATIVE_WINDOWS_TIMESTAMP_URL`), and verifies them.
|
|
458
|
+
`--installer` signs the setup program and, through makensis's `!uninstfinalize`, the uninstaller it writes; the
|
|
459
|
+
settings reach the signer through the environment only. `publish-update` signs a Windows release the same way, and an
|
|
460
|
+
unsigned release build warns that SmartScreen flags a downloaded copy.
|
|
461
|
+
|
|
462
|
+
### Patch Changes
|
|
463
|
+
|
|
464
|
+
- 5f53462: fix(tunnel): reach the tunnel control plane at its real path, and report the status instead of parsing the error page
|
|
465
|
+
|
|
466
|
+
`akan tunnel` and `akan start --share` could not open a share at all: `CloudApi` called `/api/requestTunnel`,
|
|
467
|
+
`/api/tunnelListInSelf` and `/api/revokeTunnel`, none of which exist. An endpoint's path carries the model's
|
|
468
|
+
refName — `fetch.serializer.ts` takes it from `sliceCls.srv.cnst?.refName` — so every database module's endpoints
|
|
469
|
+
sit under a segment of their own and the service modules beside them do not. `tunnel` is a database module; its
|
|
470
|
+
neighbours in the same file, `uploadEnv` and `getRemoteSelf`, belong to the `_cloud` service module and have no
|
|
471
|
+
`cnst` to take a prefix from. The three paths now carry `/tunnel/`, with a note saying why they differ from the
|
|
472
|
+
methods above them.
|
|
473
|
+
|
|
474
|
+
**The 404 was invisible, which is the more expensive half.** `HttpClient.get` and `post` called `response.json()`
|
|
475
|
+
without looking at `response.ok`, so the control plane's HTML error page reached the user as
|
|
476
|
+
`JSON Parse error: Unrecognized token '<'` — a parse error where the status line had already said `404 Not Found`.
|
|
477
|
+
Both now read the status first and throw with it, matching `getFile`, which has always done so.
|
|
478
|
+
|
|
479
|
+
Three failures were being swallowed the same way and now surface:
|
|
480
|
+
|
|
481
|
+
- `uploadEnv` returned the parsed error body, which is a truthy object, so **a failed env upload reported success**
|
|
482
|
+
and the project went on to build against env that never arrived.
|
|
483
|
+
- `getRemoteSelf` and `getRemoteAuthToken` are written to catch and answer `null`; with nothing thrown they
|
|
484
|
+
answered an error object instead.
|
|
485
|
+
- `refreshAuthToken` fed a rejected response straight into `toAccessToken`, minting a token from it.
|
|
486
|
+
|
|
487
|
+
- ccf2ae2: fix: a dev server whose replica crash-loops reports it at once
|
|
488
|
+
|
|
489
|
+
When an app's `init` threw at every boot, the gateway gave up on the replica and waited for a code change, but the dev
|
|
490
|
+
host kept reporting the app as starting: `akan start` showed no reason and held its next wave of apps for 180 s, and
|
|
491
|
+
`akan start-desktop` waited 180 s before it gave up. The host now reports the app `failed` with the gateway's message
|
|
492
|
+
as soon as it gives up; `akan start` keeps running and shows the app ready again after the fix is saved.
|
|
493
|
+
|
|
494
|
+
- ccf2ae2: A CSR page or layout whose async render throws says so, a cached page whose layout redirected renders again, and a
|
|
495
|
+
dev client module that imports a macro builds.
|
|
496
|
+
|
|
497
|
+
- An async render that rejected used to leave its layer blank with nothing in the console, since the promise's
|
|
498
|
+
failure was dropped. It is now logged with the stack and the layer it belongs to (`render of page 2 of
|
|
499
|
+
/:lang/home failed: …`).
|
|
500
|
+
- `akan:debug:frame` takes `memory` besides `1`: the frame trace goes to `window.__AKAN_FRAME_TRACE__` (the last
|
|
501
|
+
2000 events) instead of the console. A native dev build mirrors each console call over the bridge, which slows the
|
|
502
|
+
frame enough to hide a timing bug; `memory` does not. Async page and layout renders add `layer.render`,
|
|
503
|
+
`layer.settle` and `layer.fail` events.
|
|
504
|
+
- A cached page whose layout redirected renders that layout again when it is back on screen. Signed out, a
|
|
505
|
+
layout that sends the person to the sign-in page kept its empty result, so the page stayed blank after the sign-in
|
|
506
|
+
brought them back (a native app opens its home first, so it always did). A render that issued a redirect while it
|
|
507
|
+
ran is now rerun the next time its page becomes current; `router.redirectCount()` is what it compares.
|
|
508
|
+
- `window.__AKAN_DUMP_FRAME__()` reports the frame's location, stack and phase and every recent async page and
|
|
509
|
+
layout render: settled or pending, still awaited, drawn. It records only the latest state, so it does not change
|
|
510
|
+
the timing a trace would.
|
|
511
|
+
- `akan start` no longer fails a dev client build with `ReferenceError: __akanMeta is not defined` when a file
|
|
512
|
+
imports a macro (`useClient.ts`'s `with { type: "macro" }`) whose modules read `import.meta` at the top level.
|
|
513
|
+
Bun applied the dev build's `import.meta` rewrite to the macro's modules too, and ran them where nothing declared
|
|
514
|
+
the rewritten name; such a file now has its macros run by a build of its own first.
|
|
515
|
+
|
|
516
|
+
- ccf2ae2: A native dev build's API calls and sockets go through the dev gateway, so a phone on the same network — or the
|
|
517
|
+
simulator, the emulator and a desktop app — reaches `akan start` without a reversed port.
|
|
518
|
+
|
|
519
|
+
- A page the gateway served calls its own origin: `getEnv().serverHttpUri` is `<page origin><api prefix>` and the
|
|
520
|
+
websocket follows it, unless `AKAN_PUBLIC_SERVER_URL` names a server. A release build is unchanged.
|
|
521
|
+
- The iOS, Android and desktop hosts carry the method, the body and the request headers, `authorization`
|
|
522
|
+
included, to the gateway; the gateway relays the app's `<prefix><websocketPrefix>` socket. Android hands the
|
|
523
|
+
host no request body, so a call that carries one goes to the gateway's `http://localhost` origin directly and
|
|
524
|
+
the server answers its CORS.
|
|
525
|
+
- A desktop dev build opens the target's start page instead of `/`, and builds when the web root has no
|
|
526
|
+
`index.html` yet.
|
|
527
|
+
- `NativeWebDir` lists the web root in `/`-separated paths on Windows too.
|
|
528
|
+
|
|
529
|
+
- ccf2ae2: A native app can sign in through the system browser and hear the callback on every desktop OS.
|
|
530
|
+
|
|
531
|
+
- `akanjs/client/native` exports `authSession` and its `AuthSessionApi` type, the runtime's system-browser sign-in,
|
|
532
|
+
and `isNativeShell()`, true in any native shell; `isNativeApp()` stays iOS and Android only.
|
|
533
|
+
- `getServerOrigin()` in `akanjs/base` is the server's origin as a browser outside the page opens it. For a page the
|
|
534
|
+
native dev gateway served, that is the dev server (`http://localhost:<dev port>`), not the app origin the page
|
|
535
|
+
calls; otherwise it is the origin of `getEnv().serverHttpUri`.
|
|
536
|
+
- A native dev build on Android reverses the dev server's port as well as the gateway's, so a browser on the device
|
|
537
|
+
reaches it.
|
|
538
|
+
- A target with `deepLinks.schemes` ships `single-instance`. Windows and Linux open a link by starting the app again,
|
|
539
|
+
and the hand-over is how the link reaches the running app. A second launch of such an app now hands over and exits
|
|
540
|
+
instead of opening a second window, and a desktop debug build keeps the release app id, so it does the same while
|
|
541
|
+
the release app runs.
|
|
542
|
+
- In an app with basePaths whose only target names none, `start-*` and `build-*` without `--target` ask for a basePath
|
|
543
|
+
instead of opening the root, which has no CSR page.
|
|
544
|
+
|
|
545
|
+
- 3ebd8e1: fix(agent): stop a workspace's agent guide and lint config from freezing at the release that created it
|
|
546
|
+
|
|
547
|
+
`createWorkspace` copies the whole `templates/workspaceRoot` tree — 21 `.cursor/rules/*.mdc`, `AGENTS.md`,
|
|
548
|
+
`biome.json`, `tsconfig.json` — but the only maintenance path, `generateAgentRules`, rewrites three of them, and
|
|
549
|
+
`akan agent install` refreshes only the `akan:agent` block inside `AGENTS.md`. Twenty-one files created, three
|
|
550
|
+
maintained: everything else was a one-time copy that no framework release could ever reach. Adding one lint rule
|
|
551
|
+
meant writing the same sentence into `AGENTS.md`, `.cursor/rules/lint-enforced-rules.mdc`, its `.mdc.template`,
|
|
552
|
+
`AGENTS.md.template`, `biome.json`, and `biome.json.template` — and even then, no existing workspace saw it.
|
|
553
|
+
|
|
554
|
+
Four changes, ordered by how much drift each removes.
|
|
555
|
+
|
|
556
|
+
**Lint config now extends a config shipped in the package.** `@akanjs/devkit/biome.base.json` carries the
|
|
557
|
+
formatter, the rule set, and every grit plugin registration; a workspace `biome.json` is `extends` plus its own
|
|
558
|
+
`files.includes`. Biome resolves the specifier through node*modules (it does not consult the package `exports`
|
|
559
|
+
map), and `plugins` paths inside an extended config resolve from the entry config's directory, so the
|
|
560
|
+
`./node_modules/@akanjs/devkit/lint/*.grit` form the template already used is correct. Rule changes now reach a
|
|
561
|
+
workspace on `bun update` with no command to run. Two merge behaviours decided the split: `overrides` concatenate,
|
|
562
|
+
so the generated-file opt-out moved there from `files.includes`, which \_replaces* and would silently drop the base
|
|
563
|
+
list the moment a workspace added one exclusion of its own. Because Biome moves rules between groups across minors
|
|
564
|
+
(`noUnnecessaryConditions` is `nursery` at 2.4 and `suspicious` at 2.5, and the old position is a hard error), the
|
|
565
|
+
base config and the Biome version travel together: `biomeBase.ts` pins the version `createWorkspace` installs
|
|
566
|
+
instead of resolving `latest`.
|
|
567
|
+
|
|
568
|
+
**The conventions body moved into the managed block.** It now ships as the `conventions` guideline and renders
|
|
569
|
+
between the `akan:agent` markers, so `akan agent install` refreshes it. `AGENTS.md.template` carried a _second_,
|
|
570
|
+
independently written 990-line convention guide — not a copy of the repo's 791-line one, a parallel fork — which
|
|
571
|
+
became the `workspaceOnboarding` guideline. Onboarding renders only outside the framework monorepo, where it would
|
|
572
|
+
otherwise double an always-loaded file for readers who are changing Akan rather than building on it. The two
|
|
573
|
+
guides still overlap and would repay a merge.
|
|
574
|
+
|
|
575
|
+
**The 20 duplicate Cursor rules are gone.** `akan.mdc` is `alwaysApply: true` plus `@AGENTS.md`, so the per-topic
|
|
576
|
+
`.mdc` files were a second copy of the same rules drifting on their own schedule. This repo had all 20 and no
|
|
577
|
+
`akan.mdc` at all; it now has the pointer and nothing else.
|
|
578
|
+
|
|
579
|
+
**`akan doctor` reports drift.** The block is stamped with the `@akanjs/devkit` version that rendered it, and
|
|
580
|
+
doctor warns `agent-guide-stale` (or `agent-guide-unstamped`) with `akan agent install agents-md` as the repair.
|
|
581
|
+
|
|
582
|
+
**`repoName` no longer comes from the folder name.** `WorkspaceExecutor.fromRoot` defaulted to
|
|
583
|
+
`path.basename(process.cwd())`, so every generated file that names the repo — the `AGENTS.md` title, its `- Repo:`
|
|
584
|
+
line — depended on what each person called the directory they cloned into, and the committed diff never settled.
|
|
585
|
+
It now resolves from the `origin` remote, falling back to the directory name when there is no git or no remote.
|
|
586
|
+
`AKAN_PUBLIC_REPO_NAME` is deliberately not consulted: that is a deployment namespace (queue prefixes, cache keys,
|
|
587
|
+
secret paths) which a monorepo hosting several products legitimately points elsewhere.
|
|
588
|
+
|
|
589
|
+
Two bugs surfaced on the way. `## Validation` listed `akan quality scan` and `akan quality ssr` in a _generated_
|
|
590
|
+
section — hand-added, and due to be wiped by the next install; they are in the generator's list now. And
|
|
591
|
+
`no-throw-raw-error` exempted `apps/akan/env/**` by name, so `apps/minimal/env/*` failed a rule the guide said did
|
|
592
|
+
not apply to `env/`; the exemption is `**/env/**`. Lint output over the whole repo is otherwise byte-identical
|
|
593
|
+
before and after, and formatter output is unchanged.
|
|
594
|
+
|
|
595
|
+
Biome itself moves to **2.5.8** in the same change, so the shared config targets the current release rather than
|
|
596
|
+
freezing the ecosystem at 2.4.4 — workspaces created before this already installed a 2.5.x, and pinning backwards
|
|
597
|
+
would have made adoption a downgrade. The jump adds 263 diagnostics across the monorepo, but only **32 of them are
|
|
598
|
+
in `apps/` and `libs/`** (30 `noUnnecessaryConditions`, 2 `useArrayFind`) and the rest are in the framework's own
|
|
599
|
+
`pkgs/`. No scope regressed from passing to failing: `akan lint minimal`, the one green scope, is still green, and
|
|
600
|
+
`apps/akan`, `libs/util`, and `libs/shared` were already failing on 2.4.4 with pre-existing errors. Every grit
|
|
601
|
+
plugin was re-verified against 2.5.8. `noUnnecessaryConditions` stays at `error` and now reports 220 repo-wide,
|
|
602
|
+
190 of them in `pkgs/` — a real backlog, and a deliberate one to leave visible rather than downgrade to a warning.
|
|
603
|
+
|
|
604
|
+
Existing workspaces keep working untouched — they simply stay frozen at the release that created them, which
|
|
605
|
+
`akan doctor` now reports. The migration guide walks through adopting the change in four steps, only one of which
|
|
606
|
+
needs a human decision.
|
|
607
|
+
|
|
608
|
+
- ccf2ae2: `akan.config.ts` reaches the native settings a store release needs, and an app keeps a different id on each store.
|
|
609
|
+
|
|
610
|
+
- `native.android.push` (channel, `smallIcon` relative to the app folder, `color`) and `native.ios.privacy` (the iOS
|
|
611
|
+
privacy manifest) pass through, per target too.
|
|
612
|
+
- `native.icon` takes `{ image, backgroundColor }` and `native.splash` the runtime's `{ image, backgroundColor,
|
|
613
|
+
autoHide, timeout }`, besides a path.
|
|
614
|
+
- `appId` is a string or one per platform — `{ ios, android, default }` — for an app whose listings already carry
|
|
615
|
+
different ids. The app-link files serve each platform's own id.
|
|
616
|
+
|
|
617
|
+
- 2d8de1c: fix(build): a barrel's own export wins over the same name a star re-export brings when imports are flattened
|
|
618
|
+
|
|
619
|
+
The `barrelImports` rewrite turns `import { X } from "pkg"` into an import of the file `X` lives in. It read a barrel
|
|
620
|
+
top to bottom and kept the first file it met for each name, so in `export * from "./a"; export { X } from "./c";`
|
|
621
|
+
it sent `X` to `./a` — while in JavaScript a module's own export, named or declared, shadows whatever its stars
|
|
622
|
+
bring, and `import("pkg").X` is `./c`'s. A flattened import could therefore bind a different value than the
|
|
623
|
+
unflattened one. Each module's own exports are now mapped before its stars are walked, which is the ES rule; a name
|
|
624
|
+
two stars both bring still goes to the first. No barrel in this repository had such a name, so its builds are
|
|
625
|
+
unchanged.
|
|
626
|
+
|
|
627
|
+
- c45ac6c: perf(build): bundle every route's client entries in one pass instead of one per route
|
|
628
|
+
|
|
629
|
+
`AllRoutesBuilder` ran a `RouteClientBuilder` per route, and each one calls `Bun.build`. Entries were already
|
|
630
|
+
deduplicated globally through `knownEntries` — 169 routes produced 128 client entries — but **chunk splitting is
|
|
631
|
+
scoped to a single `Bun.build` invocation**, so any dependency shared by entries that landed in different
|
|
632
|
+
invocations was emitted once per invocation.
|
|
633
|
+
|
|
634
|
+
In `apps/akan` that meant four complete copies of mermaid's parser layer. Six client entries reach
|
|
635
|
+
`import("mermaid")` — `pkgs/akanjs/ui/Constant/Mermaid.tsx`, `Constant/Doc.tsx`, `apps/akan/ui/ModalTests.tsx`,
|
|
636
|
+
`Docs/ConstantDocsDemo.tsx`, `Docs/Mermaid.tsx`, `SampleSchema.tsx` — and they were first discovered by four
|
|
637
|
+
different routes, so each pass emitted its own 32-chunk, 868KB copy. The macro payload in `lib/useClient.ts` was
|
|
638
|
+
duplicated the same way.
|
|
639
|
+
|
|
640
|
+
Discovery now runs for every route first and the collected entries go through one build. Discovery does no
|
|
641
|
+
bundling and is already cached per file, so the extra pass costs almost nothing.
|
|
642
|
+
|
|
643
|
+
| | before | after |
|
|
644
|
+
| --------------------------- | ------ | ------ |
|
|
645
|
+
| `.akan/artifact/client` | 26 MB | 8.1 MB |
|
|
646
|
+
| `.akan/artifact/client-ssr` | 17 MB | 4.7 MB |
|
|
647
|
+
| chunks | 605 | 258 |
|
|
648
|
+
| mermaid copies | 4 | 1 |
|
|
649
|
+
| build time | 12.9s | 10.1s |
|
|
650
|
+
|
|
651
|
+
Client entries stay at 128 and the emitted manifest is unchanged: same 169 `routeIds` in the same order, the same
|
|
652
|
+
151 `clientManifest` keys, and the same 114 `ssrManifest.moduleMap` entries. mermaid is still reached through
|
|
653
|
+
`await import(...)`, so its chunk stays lazy and out of the initial load.
|
|
654
|
+
|
|
655
|
+
**Only the production path changes.** `AllRoutesBuilder` is constructed once, by `applicationBuildRunner` with
|
|
656
|
+
`command: "build"`. Dev keeps one build per route through `incrementalBuilder`, which is what makes an incremental
|
|
657
|
+
rebuild cheap, and `discoveredEntries` / `clientDepsByEntry` — consumed only there — are untouched.
|
|
658
|
+
`RouteClientBuilder` takes an optional `entries` list that skips discovery; dev never passes it.
|
|
659
|
+
|
|
660
|
+
- 2d8de1c: fix(build): each reason a failed bundle gives names the file and line it points at
|
|
661
|
+
|
|
662
|
+
`akan build` and `akan start` listed the bundler's reasons under "Bundle failed" as bare messages, so
|
|
663
|
+
`Could not resolve: "./not-there"` never said which file imported it. Each reason now carries the location Bun
|
|
664
|
+
reports, relative to the workspace root:
|
|
665
|
+
|
|
666
|
+
```
|
|
667
|
+
Bundle failed
|
|
668
|
+
Could not resolve: "./not-there" (apps/demo/ui/Broken.tsx:2:22)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
A reason Bun gives no location for prints as before, and a file outside the workspace keeps its absolute path. An
|
|
672
|
+
error a plugin raised while loading a file names that file without a line, unless its message already names it.
|
|
673
|
+
|
|
674
|
+
- ccf2ae2: fix(dev): the incremental builder's default memory ceiling is 2GB, up from 1.2GB
|
|
675
|
+
|
|
676
|
+
- With no `AKAN_BUILDER_MAX_RSS_MB` set and no memory limit to take a share of, `akan start` now recycles its
|
|
677
|
+
builder past 2GB instead of 1.2GB. apps/akan's builder peaks near 950MB holding the SSR registry's patcher through
|
|
678
|
+
a route build, and arming CSR adds a second patcher, which left 1.2GB little room above ordinary work.
|
|
679
|
+
- A container limit or `AKAN_MEMORY_LIMIT` still sets the ceiling at 35% of it, and `AKAN_BUILDER_MAX_RSS_MB=0` still
|
|
680
|
+
leaves the builder unbounded.
|
|
681
|
+
|
|
682
|
+
- 5f53462: fix(cli): scaffolds name their guards, and the mobile and package commands do what they say
|
|
683
|
+
|
|
684
|
+
Guards and templates:
|
|
685
|
+
|
|
686
|
+
- The module scaffold's slice is `{ root: Admin, get: Public, cru: Admin }` when the workspace mounts libs/shared,
|
|
687
|
+
and `{ root: None, get: Public, cru: None }` — closed until you name a guard — when it does not; its sample slice
|
|
688
|
+
and the sample app's `inPublic` take `init({ guards: [Public] })`. `cru: Public` is gone.
|
|
689
|
+
- `akan add-mutation` / `add-slice` write `mutation(Boolean, { guards: [None] })` / `init({ guards: [None] })` and
|
|
690
|
+
import `None`, and their surface step returns a review note instead of stopping as unsupported.
|
|
691
|
+
- The sample app's `SignedIn` admits only a caller with an identity (`id`, or libs/shared's `self` / `me`); a guest
|
|
692
|
+
carries an account object too and used to pass. `CurrentUserId` reads the same three places, and the sample's
|
|
693
|
+
task fixtures sign a user in through libs/shared when it is installed. Its spec holds no assertions.
|
|
694
|
+
- Every scaffolded abstract has the current shape — a title, one sentence and `## Rules` — instead of six headings
|
|
695
|
+
of placeholder text: `create-module` writes `# <model> Abstract` with its guard and soft-removal rules,
|
|
696
|
+
`create-service` writes `# <service> Service Abstract` and `create-scalar` `# <scalar> Abstract`, each with a
|
|
697
|
+
placeholder sentence and two placeholder rules. The sample app's task, noti and workHistory abstracts state their
|
|
698
|
+
real invariants instead of describing the module layout. The dead `module/__model__.abstract.md` template is
|
|
699
|
+
removed.
|
|
700
|
+
- `create-crud-page` imports from the module's own client (`@libs/<lib>/client` for a lib module), and
|
|
701
|
+
`create-package` writes a tsconfig that extends the workspace root from any depth and references no missing file.
|
|
702
|
+
- `akan add-field --default now` (and every Date default) writes a thunk, `() => dayjs()`.
|
|
703
|
+
|
|
704
|
+
CLI and mobile:
|
|
705
|
+
|
|
706
|
+
- iOS usage-description keys map to Apple's real Info.plist keys (`NSPhotoLibraryAddUsageDescription`,
|
|
707
|
+
`NSPhotoLibraryUsageDescription`, both location-always keys) instead of `NS` + the option name.
|
|
708
|
+
- A non-main server's `assetlinks.json` also lists `<appId>.debug`, so a debug build's App Links can verify.
|
|
709
|
+
- `start-ios` takes `--no-allow-provisioning-updates` (short flag `-a`); every boolean option that defaults to
|
|
710
|
+
`true` now accepts `--no-<name>`.
|
|
711
|
+
- `akan update` installs `@akanjs/cli@<tag>`, the package that provides the `akan` binary.
|
|
712
|
+
- `sync-package` reads package dependencies again (the tsconfig paths start with `./pkgs/`).
|
|
713
|
+
- The quality scanner no longer recommends the nonexistent `akan compact`; `AbstractDoc.compactMinLines` is gone.
|
|
714
|
+
- `--role` lists `federation`, and `--since` names its units.
|
|
715
|
+
|
|
716
|
+
- 3ebd8e1: feat(lint): enforce the client/server import boundary in both directions
|
|
717
|
+
|
|
718
|
+
The layering was documented but nothing checked it. A `.tsx` could `import * as db from "../db"`, a `*.service.ts`
|
|
719
|
+
could `import { st } from "@libs/shared/client"`, and a `*.constant.ts` could reach either side — each one a value
|
|
720
|
+
edge that drags the whole opposite graph along. In the client direction that means the database driver,
|
|
721
|
+
`node:crypto`, and any secret resolved from `process.env` land in the browser bundle; in the server direction it
|
|
722
|
+
means React, the store, and the browser globals underneath them load into every CLI command, worker, and migration
|
|
723
|
+
that touches a service. Both usually fail the build rather than failing at runtime, and the failure names a
|
|
724
|
+
transitive module, never the import that caused it.
|
|
725
|
+
|
|
726
|
+
Two rules now hold the line, symmetric by design:
|
|
727
|
+
|
|
728
|
+
- `no-import-server-in-client.grit` runs on client files (`ui/`, `webkit/`, `page/`, `*.store.ts`, every `.tsx`) and
|
|
729
|
+
bans `*.document` / `*.dictionary` / `*.service` / `*.signal` modules, `srvkit/`, package `server` entrypoints
|
|
730
|
+
including `akanjs/server`, and the `db` / `srv` / `sig` / `dict` / `option` / `useServer` barrels.
|
|
731
|
+
- `no-import-client-in-server.grit` runs on server files (those four suffixes plus `srvkit/`) and bans `*.store`
|
|
732
|
+
modules, module components (`*.Template` / `*.Unit` / `*.Util` / `*.View` / `*.Zone`), `ui/`, `webkit/`, package
|
|
733
|
+
`client` entrypoints including `akanjs/client`, and the `st` / `store` / `useClient` barrels.
|
|
734
|
+
|
|
735
|
+
Shared files — `common/` and `*.constant.ts` — are wired into **both** overrides, so they reach neither side, which
|
|
736
|
+
is what makes them safe for either to import.
|
|
737
|
+
|
|
738
|
+
`import type` is exempt in both directions: it is erased before bundling, so it emits no edge, and a shared file
|
|
739
|
+
that only needs to name a server-side type (`libs/shared/lib/summary/summary.constant.ts` names `UserFilter` from
|
|
740
|
+
`user.document`) stays legal. A mixed value-and-type import is not exempt — it emits a real edge. Test files are
|
|
741
|
+
excluded, as are `pkgs/akanjs/**`, which implements the boundary and is where the two graphs legitimately meet.
|
|
742
|
+
|
|
743
|
+
Both rules land green: no `apps/**` or `libs/**` file crosses the boundary today, so this is a ratchet, not a
|
|
744
|
+
migration.
|
|
745
|
+
|
|
746
|
+
- 2d8de1c: fix(cloud): an `akan` command started while another is refreshing the cloud session waits for it
|
|
747
|
+
|
|
748
|
+
A command that read `~/.akan/config.json` while another process was mid-refresh saw the session with its refresh
|
|
749
|
+
token already taken and went ahead without signing in. It now waits for that refresh under the same lock and uses the
|
|
750
|
+
session it stored.
|
|
751
|
+
|
|
752
|
+
- 2d8de1c: fix(cloud): a cloud session in its last hour is refreshed, once across every running `akan` process
|
|
753
|
+
|
|
754
|
+
A stored cloud session within an hour of expiry, or past it, was treated as signed out: nothing ever called the
|
|
755
|
+
refresh endpoint, so `akan login` reopened the browser about once a week and `akan tunnel` answered a raw 401 in
|
|
756
|
+
that window. Every command that reaches the cloud — `login`, `upload-env`, `download-env`, `tunnel` and
|
|
757
|
+
`start --share` — now refreshes such a session before using it, and `akan login` only opens the browser when the
|
|
758
|
+
refresh fails.
|
|
759
|
+
|
|
760
|
+
The refresh is written for a cloud that rotates the refresh token on every use and treats a second use as theft,
|
|
761
|
+
revoking every session of the account, browser included:
|
|
762
|
+
|
|
763
|
+
- It runs under a lock beside `~/.akan/config.json`, held across CLI processes, and re-reads the file once it has
|
|
764
|
+
the lock, so of two commands started together only one refreshes and the other uses its result. A lock whose
|
|
765
|
+
holder has exited is taken over; one whose holder is still running is waited for, and a refresh is never made
|
|
766
|
+
without it.
|
|
767
|
+
- The refresh token leaves the file before it is sent, and the new pair is written before it is used. A command
|
|
768
|
+
killed mid-refresh, or a refresh that fails, therefore leaves no token that could be presented a second time;
|
|
769
|
+
the cost is one browser sign-in.
|
|
770
|
+
- Every other write to the file (hosts, remote env servers, test targets) re-reads under the same lock, so none can
|
|
771
|
+
put back a token that was rotated away, and the file is replaced atomically with mode 0600, so a concurrent
|
|
772
|
+
reader never sees half of it.
|
|
773
|
+
|
|
774
|
+
- 8c31d54: fix: `akan code` says when compaction failed, leaves the Claude Code-sized buffer, and resolves its default model
|
|
775
|
+
|
|
776
|
+
**A failed compaction read as a successful one.** The engine reports a compaction that failed or was cancelled on
|
|
777
|
+
the same `compaction_end` event as one that worked, with `errorMessage` and `aborted` beside the reason. The wire
|
|
778
|
+
kept only the reason, so every end printed "Compacted the conversation" — including an automatic compaction that
|
|
779
|
+
had failed, which nobody else reports, leaving the next sign of it an overflow error turns later. The `compaction`
|
|
780
|
+
frame now carries `error` and `aborted`, and the transcript prints the engine's own sentence as a warning, or
|
|
781
|
+
"Compaction cancelled.". A failed `/compact` still reports once, through the caller it threw to.
|
|
782
|
+
|
|
783
|
+
**The buffer is 32,768 tokens, up from the engine's 16,384.** On a million-token window that moves the threshold
|
|
784
|
+
from 98.4% to 96.7% — where Claude Code compacts. The reserve is all the room the response at the threshold gets
|
|
785
|
+
(the request clamps its output to the window less the prompt less 4,096), so at 16k a reasoning model ran out
|
|
786
|
+
mid-thought there and the recovery re-sent a near-full window to retry. It is also the summary's output cap at 80%.
|
|
787
|
+
The small-window warning now reads the settings the session actually runs with rather than the engine's defaults.
|
|
788
|
+
|
|
789
|
+
**The default model is `deepseek/deepseek-flash`.** `deepseek-v4-flash` is gone from both the engine's catalogue
|
|
790
|
+
and DeepSeek's own model list, so the default resolved to nothing and `akan code` silently ran on whichever model
|
|
791
|
+
sorted first among those with a key.
|
|
792
|
+
|
|
793
|
+
- 5f53462: fix(console): a production `akan console` brings services up and starts no schedule
|
|
794
|
+
|
|
795
|
+
The `console.js` an `akan build` writes never marked its process as a console, so `server.start()` took the replica
|
|
796
|
+
branch: it registered every `interval` / `cron` / queue worker, ran every internal `init()`, and opened the log
|
|
797
|
+
transport — a second scheduler inside a running deployment. The shim now marks the process before it loads the
|
|
798
|
+
server, the same way `akan console` does in development, so a console runs service and adaptor `onInit` and nothing
|
|
799
|
+
the internals schedule.
|
|
800
|
+
|
|
801
|
+
`assertAkanConsoleAllowed()` was handed `server.env`, which carries neither `environment` nor `operationMode`, so it
|
|
802
|
+
effectively checked `NODE_ENV` alone. Called with no argument it now reads the deployment from `AKAN_PUBLIC_ENV` /
|
|
803
|
+
`AKAN_PUBLIC_OPERATION_MODE` (deriving the mode the way `getEnv()` does): a `main` environment, a `cloud` or `edge`
|
|
804
|
+
operation mode, or `NODE_ENV=production` refuses the console unless `AKAN_CONSOLE=1`.
|
|
805
|
+
|
|
806
|
+
- ccf2ae2: fix(dev): a CSR dev save reaches the open tabs before `app.js` is rewritten, and a tab opened in between boots once
|
|
807
|
+
|
|
808
|
+
A dev CSR save used to rewrite the whole `app.js` before telling the open tabs about the patch, although only a tab
|
|
809
|
+
that boots later needs it. The patch is now announced first and `app.js` follows. The manifest records which
|
|
810
|
+
generation `app.js` holds (`appGeneration`), and the server holds a booting tab's `app.js?g=N` for up to two seconds
|
|
811
|
+
until the file holds `N`, instead of booting it one generation behind and reloading it again. A build that died in
|
|
812
|
+
that gap leaves `app.js` behind; the next update rewrites it even when nothing else changed.
|
|
813
|
+
|
|
814
|
+
- ccf2ae2: perf(dev): a CSR patch reaches the tabs before its module files and graph.json are written
|
|
815
|
+
|
|
816
|
+
The dev registry announced a patch only after writing each changed module's file and the whole `graph.json`, which
|
|
817
|
+
only the next build reads. The patch file and the manifest now go first, and the rest follows the announcement; a
|
|
818
|
+
build that dies in between recompiles those files on its next run, since their mtimes no longer match the graph. The
|
|
819
|
+
dev entry also stopped parsing every page with TypeScript on each save to see whether its default export is async:
|
|
820
|
+
the result is kept per file until the file changes. On apps/akan a component save now reaches the tabs 72ms after the
|
|
821
|
+
builder picks it up, down from about 200ms.
|
|
822
|
+
|
|
823
|
+
- ccf2ae2: perf(dev): a CSR dev save is patched by the resident builder, without spawning a build worker
|
|
824
|
+
|
|
825
|
+
- The dev builder keeps the CSR registry's graph, manifest and module factories in memory and compiles a save itself.
|
|
826
|
+
A save no longer pays for a build worker's spawn and imports (about 130ms on minimal) or a pass over every module
|
|
827
|
+
file, and `app.js` is rebuilt from memory after the patch is announced.
|
|
828
|
+
- A save that needs a whole-app build still goes to a build worker, which exits and returns its memory: a first build,
|
|
829
|
+
a config or signal/dictionary change, a new npm module, and an import no recorded resolution answers.
|
|
830
|
+
- The builder works in two lanes. A save's codegen and CSR patch run in one, and the file watcher waits only for it;
|
|
831
|
+
build workers, route builds and client-entry discovery run in the other, where a batch queued behind another folds
|
|
832
|
+
into it. Consecutive saves no longer wait behind each other's pages and css builds, and css now builds before pages.
|
|
833
|
+
- `AKAN_DEV_CSR_PATCHER=off` sends every CSR save to a build worker as before. A builder that dies mid-patch turns the
|
|
834
|
+
patcher off until the dev server next replaces its builder and backend together (a config, signal or dictionary
|
|
835
|
+
change), and says so.
|
|
836
|
+
|
|
837
|
+
- 69f7178: fix(css): fail the build on a stylesheet import that resolves to nothing, and let a lib own its design tokens
|
|
838
|
+
|
|
839
|
+
Two halves of the same problem, reported together. An `@import` the pipeline could not resolve was allowed to
|
|
840
|
+
produce nothing, and a lib had nowhere to declare the colours its own components pin — so the workaround was to
|
|
841
|
+
copy one vendor palette into every app's stylesheet, where a single missed import broke that app alone, silently.
|
|
842
|
+
Since vocabulary closure (`@theme { --color-*: initial }`) makes a component whose token declaration never
|
|
843
|
+
arrived render unstyled rather than wrong, the quiet half is the dangerous one.
|
|
844
|
+
|
|
845
|
+
**Every specifier is verified, path-shaped ones included.** `@import "./missing.css"` and
|
|
846
|
+
`@import "../../../libs/shared/ui/brand.css"` used to be turned into a path without asking whether a file was
|
|
847
|
+
there, surfacing later as a bare `ENOENT` that names no importer — or, in the dev server, as one log line while
|
|
848
|
+
the previous CSS kept serving. They now raise the same `[css] failed to resolve stylesheet import "…" from …`
|
|
849
|
+
error a bare specifier already did, naming the path that was tried.
|
|
850
|
+
|
|
851
|
+
**A package subpath resolves literally or not at all.** `@import "@libs/shared/ui/brand.css"` fell back to the
|
|
852
|
+
package's own `style` entry (`pkg.exports["."].style`, `pkg.style`, `index.css`) when the subpath was not in
|
|
853
|
+
`exports` — loading a _different_ stylesheet than the author asked for and reporting success. The fallback now
|
|
854
|
+
applies only to the bare package name.
|
|
855
|
+
|
|
856
|
+
**`libs/<lib>/ui/tokens.css` is the one CSS file a lib owns.** Plain `:root` custom properties for colours pinned
|
|
857
|
+
by someone else's brand guide — Kakao `#fee500`, Naver `#1ec800` — or by a fixed surface. Every app whose page
|
|
858
|
+
graph reaches that lib compiles the file automatically, ordered ahead of the app's own stylesheets so the app
|
|
859
|
+
stays the last word on any variable both declare. Nothing is imported by hand and adding an app cannot forget it.
|
|
860
|
+
Reference them as `bg-[var(--kakao)]`, which `no-arbitrary-color` allows by design; `@theme` extensions stay in
|
|
861
|
+
the app stylesheet, because the colour vocabulary is closed per stylesheet.
|
|
862
|
+
|
|
863
|
+
**A stylesheet under `page/` that no route imports now warns.** It compiles to nothing and reports success, which
|
|
864
|
+
reads exactly like an empty theme — the failure the two fixes above cannot see, because nothing ever asked for
|
|
865
|
+
the file.
|
|
866
|
+
|
|
867
|
+
**An `@import` that resolved, was read, and still contributed nothing now warns twice over.** Custom properties
|
|
868
|
+
declared outside `@theme` pass through Tailwind verbatim, so a stylesheet whose every declaration is missing from
|
|
869
|
+
the output did not make it in, whatever dropped it — the last shape of this bug that no check upstream of the
|
|
870
|
+
output can see. The compiled text is checked per compiled stylesheet, and the written asset is checked again
|
|
871
|
+
against the imports of its own base path, because those are two different places a declaration can go missing: a
|
|
872
|
+
build that ships a token to the CSR bundle and not to `styles/<base>-<hash>.css` renders unstyled on a
|
|
873
|
+
server-rendered page while looking correct in the browser bundle. Each resolution also logs at `verbose` with the
|
|
874
|
+
specifier, the importer, the resolved path, and the byte count.
|
|
875
|
+
|
|
876
|
+
A name that survives only because something else declares it — an app overriding a lib token, which is the
|
|
877
|
+
ordering this release introduces — keeps the check quiet: the warning fires only when _no_ declaration of the
|
|
878
|
+
imported file's own names is present anywhere in the asset.
|
|
879
|
+
|
|
880
|
+
- 22aa04f: fix(devkit): stop a labelled tuple type from wiping every variant out of the build
|
|
881
|
+
|
|
882
|
+
Since arbitrary variants were kept whole, a candidate could open with `[`, which also made every
|
|
883
|
+
labelled tuple type in the tree look like a Tailwind arbitrary property —
|
|
884
|
+
`[key: string, make: () => unknown]` in `constant/getDefault.ts`,
|
|
885
|
+
`[setQueryArgs: (...) => QueryArgs, options?: FetchPolicy]` in `store/action.ts`. Tailwind compiled each
|
|
886
|
+
into a rule whose declaration was TypeScript, and the lightningcss pass in `optimize()` could not parse
|
|
887
|
+
it. Its error recovery does not stop at the offending rule: on `apps/minimal` it took 33KB of the
|
|
888
|
+
stylesheet with it, and what it took was **every variant** — no `hover:`, no `focus-visible:`, no
|
|
889
|
+
`disabled:`, no `sm:`/`md:`, no `dark:`, in a build that reported no error.
|
|
890
|
+
|
|
891
|
+
What that looked like: buttons and badges with no hover or press feedback, inputs with the browser's
|
|
892
|
+
own blue focus ring because `focus-visible:outline-none` was gone, a Switch whose knob never moved, a
|
|
893
|
+
Tooltip that never appeared, a Popconfirm that lost its offset, and modals stretched to full width with
|
|
894
|
+
their `sm:`/`md:` sizing missing. All of it from two type annotations.
|
|
895
|
+
|
|
896
|
+
Brackets in a candidate may no longer contain whitespace. Tailwind spells a space in an arbitrary value
|
|
897
|
+
`_`, so nothing legitimate is lost — `[&_td]:px-3` and `[&>*]:gap-2` still scan whole — while a tuple
|
|
898
|
+
type, which always has a space after its `:`, no longer reaches the compiler. The cache version is
|
|
899
|
+
bumped so extractions cached under the old pattern are re-read.
|
|
900
|
+
|
|
901
|
+
- 5f53462: `akan start` sizes its boot wave against the machine instead of always booting one app at a time.
|
|
902
|
+
|
|
903
|
+
- With no `--concurrency`, the wave is `min(apps, half the memory budget / ~1.8GB per app, cores / 4)` and never
|
|
904
|
+
below one, so a laptop boots its apps together while a small container still staggers them — which is the case
|
|
905
|
+
the old default of 1 existed for. 1.8GB is a cold boot's peak: the SSR registry's boot build adds a build worker
|
|
906
|
+
to every boot, and apps/akan's whole dev host peaks at about 1.76GB.
|
|
907
|
+
- The next wave starts once each app of the previous one serves and its builder's boot builds have settled — the SSR
|
|
908
|
+
registry's, and CSR's when `AKAN_DEV_CSR_REBUILD=1` arms it — or 30 seconds after it serves. The app shows as ready,
|
|
909
|
+
and `--open` opens it, as soon as it serves.
|
|
910
|
+
- The memory budget is the smaller of the host's RAM and `AKAN_MEMORY_LIMIT` / the cgroup limit. `os.freemem()` is
|
|
911
|
+
not consulted: it counts free pages rather than reclaimable ones and reports ~0.3GB on an idle 48GB laptop, which
|
|
912
|
+
would pin every machine to one app at a time.
|
|
913
|
+
- The session prints the wave size and what set it, so a boot that still waits app by app says why.
|
|
914
|
+
- `--concurrency <n>` is unchanged as an override and is clamped to the number of apps selected.
|
|
915
|
+
|
|
916
|
+
- ccf2ae2: feat(dev): a dev save now says where its HMR latency went
|
|
917
|
+
|
|
918
|
+
- Each save carries epoch-ms marks from the file watcher to the page: the first fs event and the watcher's flush,
|
|
919
|
+
the builder picking the batch up, the build worker's spawn, start and imports, the update on disk, the send, the
|
|
920
|
+
backend's broadcast, and the page receiving and applying it. The page keeps the last 64 in `__AKAN_HMR_TRACES__`.
|
|
921
|
+
- `AKAN_DEV_WATCH_DEBOUNCE_MS` sets how long the watcher collects changes before it hands a batch over.
|
|
922
|
+
|
|
923
|
+
- ccf2ae2: The dev module registries load packages that used to take the whole vendor file down with them
|
|
924
|
+
|
|
925
|
+
- A module that reads `import.meta` (jotai's and zustand's ESM builds) no longer makes the vendor file a SyntaxError:
|
|
926
|
+
`import.meta` is the module's own `{ url }`, as in a browser module, with no `env`, so an `import.meta.env` check
|
|
927
|
+
branches as it does in the ESM bundle. `import.meta.url` is an http URL instead of this machine's `file://` path.
|
|
928
|
+
- A package esbuild already bundled (the `@mermaid-js/parser` chunks) keeps its own `__reExport` / `__toESM` helpers
|
|
929
|
+
instead of stopping the registry build, and a package file's `#!` line no longer breaks its vendor file.
|
|
930
|
+
- A file the browser build tree-shook away (vfile, behind a side-effect-free barrel) resolves its imports with the
|
|
931
|
+
browser condition, and a package `browser` field mapping a file or a module to `false` gives an empty module;
|
|
932
|
+
neither reaches a Node built-in stub that throws at startup.
|
|
933
|
+
- Every module factory is parsed as a classic script before it is written. A package file that cannot be one (a
|
|
934
|
+
top-level await, or anything else a script cannot hold) is left out and named in the dev log, and requiring it
|
|
935
|
+
throws why, where it used to fail the whole registry or drop every module of its vendor file in the browser; app
|
|
936
|
+
code that cannot be one still fails the build. A registry built before this rebuilds once.
|
|
937
|
+
|
|
938
|
+
- 2d8de1c: fix(start): a route whose client code fails to bundle says why
|
|
939
|
+
|
|
940
|
+
`akan start` bundles a route's client components the first time the route is requested. When the bundler refused
|
|
941
|
+
them — an import that does not resolve, a syntax error — the builder reported only the bundler's own message,
|
|
942
|
+
"Bundle failed", in the request's error, the build status and the dev log alike. It now lists the bundler's reasons
|
|
943
|
+
under it, the way `akan build` and the rest of the dev build already did:
|
|
944
|
+
|
|
945
|
+
```
|
|
946
|
+
Bundle failed
|
|
947
|
+
Could not resolve: "./not-there" (apps/demo/ui/Broken.tsx:2:22)
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
- 69f7178: fix(dev): `akan start` is the dev server even when the environment says `NODE_ENV=production`
|
|
951
|
+
|
|
952
|
+
An ambient `NODE_ENV=production` turned every route of the dev server into a 500:
|
|
953
|
+
|
|
954
|
+
```
|
|
955
|
+
[SSR] render failed scope=/en/: [SSR] route /:lang missing from production artifact — rebuild with `akan build` to include it
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
`WebRouter` picked its mode from `NODE_ENV` alone, so it installed the production route cache — the one whose
|
|
959
|
+
`buildRoute` throws because `akan build` was supposed to have written a routes manifest already. A dev artifact
|
|
960
|
+
never has one, so nothing could render, and the same ambient value also sent the gateway's runtime directory to
|
|
961
|
+
`<workspace>/runtime` and baked production React into the dev bundles.
|
|
962
|
+
|
|
963
|
+
The value does not have to be exported to arrive: Bun auto-loads the workspace `.env`, so a `NODE_ENV=production`
|
|
964
|
+
line in a downloaded env file reaches the CLI while `env | grep NODE_ENV` in the shell stays empty. A CI image
|
|
965
|
+
default or a container base image does the same.
|
|
966
|
+
|
|
967
|
+
**The command now outranks the environment.** `akan start` pins `NODE_ENV=development` for the processes it
|
|
968
|
+
spawns and for the in-process builder that bakes the value into dev bundles; `AkanApp` resolves a child's
|
|
969
|
+
`NODE_ENV` from `AKAN_COMMAND_TYPE` before falling back to the inherited value; and `WebRouter` refuses the
|
|
970
|
+
production branch outright while dev-hosted, warning once that it did rather than failing every request. Running
|
|
971
|
+
a built artifact (`bun main.js`, Docker) is untouched — it carries no command type and keeps honouring
|
|
972
|
+
`NODE_ENV`.
|
|
973
|
+
|
|
974
|
+
- bf27773: fix(cli): stop `akan start` from leaving the terminal in raw mode, which killed Ctrl+C
|
|
975
|
+
|
|
976
|
+
A dev server that had been running for a few minutes stopped responding to Ctrl+C. The cause was not
|
|
977
|
+
signal handling — the terminal had been left in **raw mode**, so with `isig` off the tty never turned
|
|
978
|
+
`^C` into SIGINT at all. `kill -INT <pid>` still ended the process instantly, which is the tell.
|
|
979
|
+
|
|
980
|
+
A Bun child snapshots the controlling terminal's termios when it is spawned and writes that snapshot
|
|
981
|
+
back when it exits. ora's `discardStdin` holds the terminal raw for as long as a spinner runs, and the
|
|
982
|
+
dev host spawns the builder and the backend under the "Preparing backend..." spinner — so those
|
|
983
|
+
children snapshot a raw terminal. The spinner then restores it and Ctrl+C works, which is why a fresh
|
|
984
|
+
`akan start` behaved. The first dev-host recycle (an `akan.config.ts` change, or regenerated runtime
|
|
985
|
+
metadata such as `dict.ts` / `sig.ts`) SIGTERMs the builder, and its exit writes the stale raw termios
|
|
986
|
+
back over a terminal nobody restores again. `Spinner` no longer takes stdin.
|
|
987
|
+
|
|
988
|
+
Two nearby hazards with the same symptom are fixed as well.
|
|
989
|
+
|
|
990
|
+
`Spinner` refuses to animate against a tty that reports **0 columns** — an unsized pty, which CI
|
|
991
|
+
runners, `expect`/`script` harnesses and some detached panes produce. ora sizes its clear loop as
|
|
992
|
+
`ceil(lineWidth / stream.columns)`, so zero made it `Infinity`: measured 750MB of cursor moves and
|
|
993
|
+
8.7GB RSS inside a minute, in a loop past the point where SIGINT or SIGTERM could be handled, leaving
|
|
994
|
+
SIGKILL as the only way out. Such a terminal now gets plain lines.
|
|
995
|
+
|
|
996
|
+
`akan start`'s local-database teardown no longer hangs the exit. Registering a SIGINT listener
|
|
997
|
+
replaces the kernel's "terminate now", so that handler became the only thing that could end the
|
|
998
|
+
process — and it exited only if `docker compose down` succeeded. The teardown is now bounded
|
|
999
|
+
(`ApplicationScript.dbShutdownTimeoutMs`, 20s), the exit runs even when it fails or times out, and a
|
|
1000
|
+
second Ctrl+C abandons it instead of queueing behind the first.
|
|
1001
|
+
|
|
1002
|
+
- ccf2ae2: perf(dev): a save starts building 50ms sooner
|
|
1003
|
+
|
|
1004
|
+
The dev file watcher collected changes for 80ms before handing a batch over, the largest fixed wait in a save once the
|
|
1005
|
+
build is fast. It now waits 30ms. Measured on minimal, a CSR component save went from 340ms to 278ms, and a second write
|
|
1006
|
+
30ms after the first (a format-on-save) still produced one update, because the build reads the file's latest content.
|
|
1007
|
+
`AKAN_DEV_WATCH_DEBOUNCE_MS=80` restores the old window.
|
|
1008
|
+
|
|
1009
|
+
- 2d8de1c: fix(devkit): executor, doctor, barrel and code-agent fixes from the devkit/cli bug sweep
|
|
1010
|
+
|
|
1011
|
+
- **Workspace apps, libs and packages are listed in name order**, not in the order the file system answered. It
|
|
1012
|
+
shows in pickers, `akan context` / `akan doctor` listings, the `## Workspace` lines `akan agent install` writes,
|
|
1013
|
+
and the order a lib's `docker.preRuns` / `postRuns` land in a generated Dockerfile, which is now the same on every
|
|
1014
|
+
build.
|
|
1015
|
+
- **Barrel import rewriting no longer drops re-exports when two barrels are analyzed at once.** Concurrent analyses
|
|
1016
|
+
shared one regex position, so a build could leave an import on the whole barrel, differently from build to build.
|
|
1017
|
+
- **`akan doctor`, `akan workflow validate` and MCP `doctor_workspace` ignore a malformed workflow artifact** instead
|
|
1018
|
+
of throwing a `TypeError`.
|
|
1019
|
+
- **`SysExecutor`'s module listings filter on each module's own file** — they returned every folder — and find a
|
|
1020
|
+
service module at `lib/_<name>/<name>.service.ts`. `getScalarDictionaryFiles` reads `lib/__scalar/`, and
|
|
1021
|
+
`getTsConfig({ refresh: true })` re-reads the config it extends.
|
|
1022
|
+
- `akan code`: a prompt typed instead of answering drops the questions and approvals it replaced, a session past
|
|
1023
|
+
512 KiB still shows in `/sessions`, and an MCP token refreshed without `expires_in` is kept until the server
|
|
1024
|
+
refuses it instead of being refreshed on every connect.
|
|
1025
|
+
|
|
1026
|
+
- 2d8de1c: fix(devkit): stop re-exporting `lowerlize` from `@akanjs/devkit/workflow`
|
|
1027
|
+
|
|
1028
|
+
The re-export was CLI plumbing that leaked through the facet's `export *`: it existed so one CLI script could
|
|
1029
|
+
import it, and that script has imported it from `akanjs/common` for a while. Nothing else used it. The function
|
|
1030
|
+
itself is unchanged — import `lowerlize` from `akanjs/common`, which is where it always lived.
|
|
1031
|
+
|
|
1032
|
+
- ccf2ae2: fix(dev): a page that quotes `export default function` in a code snippet builds for the CSR dev page
|
|
1033
|
+
|
|
1034
|
+
The Fast Refresh rewrite of `export default function Name(` also rewrote the same text inside a template literal or a
|
|
1035
|
+
block comment, and appended a second `export default` for it. The CSR bundle refused the result with `Multiple
|
|
1036
|
+
exports with the same name "default"`, so `?csr=true` failed on any page quoting such a component — eight docs pages
|
|
1037
|
+
in the akan app — while SSR rendered them. Only a declaration the parser reports as an export is rewritten now.
|
|
1038
|
+
|
|
1039
|
+
- 69f7178: fix: the first field run's findings — request context, module identity, and an honest agent surface
|
|
1040
|
+
|
|
1041
|
+
Six fixes from running the in-page agent on a real product app, plus the one its own docs app hit.
|
|
1042
|
+
|
|
1043
|
+
**Server components see the request again.** The RSC worker wrapped a render in `requestStorage.run(...)` only,
|
|
1044
|
+
and the flight stream pumps components after that scope has exited — every `getSelf()` read no cookies and
|
|
1045
|
+
bounced authed pages to `/signin`. The worker now keeps a request fallback pushed until the render settles, the
|
|
1046
|
+
same discipline `ssrFromRscRenderer` already used. The stack is global and last-push-wins across concurrent
|
|
1047
|
+
renders; pumping the flight render inside the ALS scope itself is the follow-up that removes the caveat.
|
|
1048
|
+
|
|
1049
|
+
**`akanjs/fetch` is a vendor shared module.** It was bundled separately into the app and the vendored
|
|
1050
|
+
`akanjs/store` chunk, so `FetchClient`'s static serialized-signal registry split in two and the agent catalogue
|
|
1051
|
+
silently read the empty copy — every endpoint-named action refused as undescribed. The registry also moved to a
|
|
1052
|
+
`globalThis` symbol (the anchoring the shared client proxy already had), so a future duplicated module instance
|
|
1053
|
+
degrades to nothing instead of an empty surface.
|
|
1054
|
+
|
|
1055
|
+
**The relay's return scalar registers on the client graph.** `agentTurn` lived in `akanjs/signal`, which no
|
|
1056
|
+
client bundle loads, so the chat's first probe of `fetch.runAgentTurn` threw `No scalar constant model for
|
|
1057
|
+
agentTurn`. The scalar now lives in `akanjs/fetch` and `FetchClient` itself force-registers it — Bun links a
|
|
1058
|
+
barrel's `export *` modules lazily per used binding and its transpiler drops unused imports, so only a real call
|
|
1059
|
+
survives to do that.
|
|
1060
|
+
|
|
1061
|
+
**`labelOf` reads the `text: "title"` role as the `Set` it is.** It asserted `readonly string[]` and called
|
|
1062
|
+
`.find`, so every screen-context label on a search-indexed model threw.
|
|
1063
|
+
|
|
1064
|
+
**`akan build` relaunches itself once with the app env in place.** Bun macros snapshot `process.env` at process
|
|
1065
|
+
start, so the env the build resolves and publishes later is invisible to them — and any hand-written
|
|
1066
|
+
`@libs/<lib>/server` import (the sanctioned cross-lib entrypoint) puts that lib's env files, which read
|
|
1067
|
+
`getEnv()` at module scope, into the `getSerializedSignal` macro graph. `AKAN_PUBLIC_APP_NAME` is the one
|
|
1068
|
+
required key a workspace root `.env` cannot carry, so the CLI build now spawns itself again with it set and the
|
|
1069
|
+
child does the real build — the `AKAN_PUBLIC_APP_NAME=<app> akan build <app>` workaround is built in. The
|
|
1070
|
+
relaunch decision compares the env the process was **born** with, never the live `process.env`: the command
|
|
1071
|
+
dispatcher writes the resolved app name into `process.env` before any command method runs, which satisfies
|
|
1072
|
+
runtime readers but not macros — a guard reading the live value never fires (that inert shape is what alpha.19
|
|
1073
|
+
shipped). A `Relaunching with AKAN_PUBLIC_APP_NAME=…` line in the build output is the observable proof. The
|
|
1074
|
+
generated `sig`/`dict`/`srv`/`db` barrels also stopped walking parent _server_ barrels (they import the lib's
|
|
1075
|
+
own `@libs/<lib>/lib/<facet>` modules after `akan sync`), and `FetchClient.from` resolves its origin tolerantly,
|
|
1076
|
+
so a lib whose graph stays clean serializes with no env at all.
|
|
1077
|
+
|
|
1078
|
+
**The agent surface stops publishing two lies.** Form setters for the base document fields (`setIdOnX`,
|
|
1079
|
+
`setCreatedAtOnX`, `setUpdatedAtOnX`, `setRemovedAtOnX`) are no longer tools — the server stamps those. And an
|
|
1080
|
+
action named after an endpoint is published with the endpoint's schema only when it declares at least as many
|
|
1081
|
+
parameters: one declaring fewer would silently drop the borrowed tail and read stale form state, which shipped a
|
|
1082
|
+
resident SMS with the wrong unit number in the field run. Such an action is refused by name and warned once in
|
|
1083
|
+
the console; trailing extras beyond the endpoint's arguments stay legal, which is the generated
|
|
1084
|
+
`create<Model>(data, options?)` shape.
|
|
1085
|
+
|
|
1086
|
+
- 5f53462: The guideline set drops what only the removed code generator read
|
|
1087
|
+
|
|
1088
|
+
Six guidelines existed for `AiSession`'s codegen contract and nothing else, and are gone:
|
|
1089
|
+
`docPageRule`, `docSyncRule`, `moduleCodegen`, `enumConstant`, `sharedUiUsage` and `utilUiUsage`. Nothing in the
|
|
1090
|
+
workspace or in any skill named them, and their bodies asked the model for a `// File: <path>` block contract that
|
|
1091
|
+
an agent with edit tools cannot use. `akan-module` no longer offers `moduleCodegen` as a deeper read.
|
|
1092
|
+
|
|
1093
|
+
Every `<name>.generate.json` is deleted, along with `Prompter.getGuideJson` and the `GuideGenerateJson` /
|
|
1094
|
+
`GuideScan` / `GuideUpdate` types `@akanjs/devkit` re-exported. The `codegenPriority`, `scans` and `update.rules`
|
|
1095
|
+
fields they carried were read by nothing after the generator left; the only consumer was `akan guideline show
|
|
1096
|
+
--format json` echoing them back.
|
|
1097
|
+
|
|
1098
|
+
`akan guideline` therefore loses its `--format` option — `list` prints names and `show` prints one instruction,
|
|
1099
|
+
which is what both did in their default mode anyway. Guideline names drop from 35 to 29. What remains is the
|
|
1100
|
+
deep-dive set the `AGENTS.md` table points at, plus the three documents `akan agent install` composes the
|
|
1101
|
+
workspace guide from (`conventions`, `workspaceOnboarding`, `framework`). MCP `get_guideline`, the
|
|
1102
|
+
`akan://guidelines/<name>` resources and the `akan guideline list` / `show` commands are unchanged otherwise.
|
|
1103
|
+
|
|
1104
|
+
- 69f7178: feat(config): a lib's `externalLibs` reaches every app in the workspace
|
|
1105
|
+
|
|
1106
|
+
`libs/<lib>/akan.config.ts` has always accepted `externalLibs` — it typechecks, `AkanLibConfig` resolves it, and the
|
|
1107
|
+
lib scan result carries it — but nothing ever read the value back. All three consumers (the backend and rsc-worker
|
|
1108
|
+
bundle externals, the SSR pages bundle, and the production `package.json` dependencies) read the _app_ config, so a
|
|
1109
|
+
lib wrapping a runtime-sensitive dependency had to ask every app to repeat the declaration. Forgetting one surfaced
|
|
1110
|
+
as a native package bundled into the app rather than as an error.
|
|
1111
|
+
|
|
1112
|
+
An app's `externalLibs` is now its own declaration plus every lib's, deduped with the app's entries first. A
|
|
1113
|
+
workspace where no lib declares one resolves exactly as before.
|
|
1114
|
+
|
|
1115
|
+
The libs are read workspace-wide rather than narrowed to the app's own dependencies. That set comes from the
|
|
1116
|
+
dependency scan, and the incremental page rebundle re-reads this config on every file change from a child process
|
|
1117
|
+
that never scans — so narrowing it would put a full dependency scan in the hot path. The trade is that a lib's
|
|
1118
|
+
declaration also lands in the production dependencies of an app that does not use that lib.
|
|
1119
|
+
|
|
1120
|
+
`LibExecutor.getConfig({ refresh: true })` now busts the module import cache the way the app config already did, so
|
|
1121
|
+
a `libs/<lib>/akan.config.ts` edited while the dev server runs is re-read instead of returning the first-loaded
|
|
1122
|
+
module.
|
|
1123
|
+
|
|
1124
|
+
- 69f7178: fix(sync): hold libraries to the root layout allowlist, and check a library's own `page/` tree
|
|
1125
|
+
|
|
1126
|
+
`apps/<app>/base` was refused by `akan sync`, `akan doctor` and `akan quality scan`; `libs/<lib>/base` passed all
|
|
1127
|
+
three. The rule lives in one file — `workspaceLayout.ts` exists precisely so the three tiers cannot disagree — but
|
|
1128
|
+
the call sites read it behind `exec.type === "app"` / `for (const app of context.apps)` / `segments[0] === "apps"`,
|
|
1129
|
+
so a library root was never compared to anything at all. A lib could carry a `base/`, a `helper.ts`, a stray
|
|
1130
|
+
`script/`, and every command stayed green.
|
|
1131
|
+
|
|
1132
|
+
Libraries now answer to their own allowlist, and it is the app's minus what only an app can use: no `main.ts`, no
|
|
1133
|
+
`capacitor.config.ts`, no `.akan` / `android` / `ios` / `mobile` / `script` / `secrets`, because a library is never
|
|
1134
|
+
booted or packaged. `index.ts`, `README.md` and `tsconfig.spec.json` are added, since a library ships as a package.
|
|
1135
|
+
`public/` and `private/` stay, because `syncAssets` symlinks them into each consuming app.
|
|
1136
|
+
|
|
1137
|
+
Three more places the same gate was one-sided:
|
|
1138
|
+
|
|
1139
|
+
- `akan doctor` walked `context.apps` only. It now walks libraries too and reports `lib-root-unknown-entry` beside
|
|
1140
|
+
the existing `app-root-unknown-entry`.
|
|
1141
|
+
- `akan quality scan` warned on an unexpected app root _file_ and nothing else — not a library root file, and not a
|
|
1142
|
+
root _folder_ on either side, which is the shape `base/` actually takes. It now reports all four
|
|
1143
|
+
(`akan.layout.{app,lib}-root-{file,folder}`). Its `lib/` facet rule kept its own copy of the allowlist and flagged
|
|
1144
|
+
a `<model>.signal.test.ts` that `akan sync` explicitly permits; both now read the shared helper, and the rule is
|
|
1145
|
+
renamed `akan.layout.lib-facet-file` so its name no longer collides with the library root it was never about.
|
|
1146
|
+
- A library's `page/` folder was validated only once an app opted into it with `syncPageLibs`. An app checks route
|
|
1147
|
+
filenames in `getPageKeys`, which a library has no equivalent of, so `libs/<lib>/page/Component.tsx` synced clean
|
|
1148
|
+
in the library it belongs to and failed in somebody else's app. `akan sync <lib>` now reports it as a scan
|
|
1149
|
+
violation, from the same rule `validatePageSourceFile` throws — split out as `getPageSourceFileViolation` so the
|
|
1150
|
+
reason is available where throwing is the wrong answer.
|
|
1151
|
+
|
|
1152
|
+
- 69f7178: fix(lint): scope the module plugins to apps/libs, downgrade `noUnnecessaryConditions`, and stop truncating at 20
|
|
1153
|
+
|
|
1154
|
+
Four reports from one workspace migration (5 apps, 11 libs, ~2,950 linted files), all in what the shared config
|
|
1155
|
+
and `akan lint` do rather than in what a rule finds.
|
|
1156
|
+
|
|
1157
|
+
**`noUnnecessaryConditions` ships as `warn`.** Biome 2.5.8's inference, with `domains.types: "all"` on, is wrong
|
|
1158
|
+
on three shapes this framework uses everywhere — reproduced here, not taken on report: `useRef(false).current`
|
|
1159
|
+
narrowed to its initial literal, `useRef<HTMLTextAreaElement>(null).current` treated as non-null, a `#private`
|
|
1160
|
+
boolean flag narrowed the same way, and `RegExp.prototype.exec()` treated as non-nullable. Of 86 findings in the
|
|
1161
|
+
reporting workspace 65 were of those shapes, where deleting the guard is a null dereference. The 21 real findings
|
|
1162
|
+
are worth keeping, so the rule stays on as a warning rather than being dropped.
|
|
1163
|
+
|
|
1164
|
+
**The akan-module plugins apply to `apps/**`and`libs/**` only.** `no-import-external-library` was scoped to a
|
|
1165
|
+
bare `**/index.ts`, so it fired on any package entry in the workspace — a Hardhat package whose public surface
|
|
1166
|
+
genuinely _is_ third-party types had no way out but a nested `biome.json` for the whole subtree, which replaces
|
|
1167
|
+
the parent's config rather than merging. Same scoping for `no-deep-internal-import`,
|
|
1168
|
+
`no-redeclare-predefined-endpoint`, `no-return-in-store-action`, `no-js-private-class-method`, and the
|
|
1169
|
+
server-component plugins. `no-bang-comment-in-client` stays broad on purpose: it is about browser-reachable code,
|
|
1170
|
+
not about akan module conventions, and a `//!` shipping to every visitor is no better in a plain package.
|
|
1171
|
+
|
|
1172
|
+
**`**/lib/\_\_lib/**` joins the generated-file override.** It is scaffolded, gitignored, regenerated by `akan sync`,
|
|
1173
|
+
and unused-import-clean by construction — so `biome check --write` stripped the imports and the next sync put
|
|
1174
|
+
them back, 116 diagnostics per lap.
|
|
1175
|
+
|
|
1176
|
+
**`akan lint` prints up to 200 diagnostics** and takes `--max-diagnostics <n>` (`0` for no limit). Biome's default
|
|
1177
|
+
is 20; a run that shows 20 of 44 reads as "20 problems left", so a change in the _mix_ of findings looks like
|
|
1178
|
+
progress.
|
|
1179
|
+
|
|
1180
|
+
**`akan lint` pins the config path.** `biome.json` is strict JSON, and a JSONC comment in it makes Biome 2.5.8
|
|
1181
|
+
fall back to config discovery instead of reporting the parse error — it then aborts on whatever nested root
|
|
1182
|
+
config the walk finds, typically inside a directory `files.includes` excludes, naming a file the author never
|
|
1183
|
+
touched. With `--config-path` the parse error lands on the offending line. `Linter` and the workspace runner also
|
|
1184
|
+
accept `biome.jsonc`, which is where a comment is legal, and the workspace template now excludes `.claude` so an
|
|
1185
|
+
agent worktree's stale config is not in the walk to begin with.
|
|
1186
|
+
|
|
1187
|
+
Grit plugin diagnostics _are_ suppressible, contrary to the report: the category is `lint/plugin`, so
|
|
1188
|
+
`// biome-ignore lint/plugin: <reason>` and `// biome-ignore-all lint/plugin: <reason>` both work. The bare
|
|
1189
|
+
`// biome-ignore plugin:` form Biome's own diagnostic label suggests does not. Written down in the agent guide.
|
|
1190
|
+
|
|
1191
|
+
- 5f53462: A server-side loopback fetch follows the port the process was actually run with, so a container started with
|
|
1192
|
+
`PORT=80` renders instead of failing every page with `base.error.serverUnreachable`.
|
|
1193
|
+
|
|
1194
|
+
- `getEnv().serverPort` fell back to a literal `8282` on the server side, and only `akan start` ever set
|
|
1195
|
+
`AKAN_PUBLIC_SERVER_PORT`. A production image overriding the Dockerfile's `ENV PORT=8282` therefore bound one
|
|
1196
|
+
port and pointed the RSC worker's `fetch.*` at another; the SSR render failed on every route, with no other
|
|
1197
|
+
symptom than the transport error. When the origin resolves to `localhost` — the self-call — the port now comes
|
|
1198
|
+
from `PORT`. A `SERVER_HOST` naming another host is not a self-call and keeps the explicit port, and
|
|
1199
|
+
`AKAN_PUBLIC_SERVER_PORT` still outranks both.
|
|
1200
|
+
- `AkanApp` publishes the port it resolved as `PORT` to the replica it runs in-process and to every child it
|
|
1201
|
+
spawns, so `new AkanApp({ port })` reaches the tree the same way the env var does. In solo mode that is also
|
|
1202
|
+
what makes the option bind: `AkanServer` reads `PORT`, so an explicitly configured port was previously ignored.
|
|
1203
|
+
- `akan build` no longer publishes `AKAN_PUBLIC_CLIENT_PORT` / `AKAN_PUBLIC_SERVER_PORT` into the build process's
|
|
1204
|
+
own env. Bundling `define`s every `AKAN_PUBLIC_*` into a literal, so with `PORT_OFFSET` set the builder's dev
|
|
1205
|
+
port was baked into the artifact, where no deployment env could override it.
|
|
1206
|
+
|
|
1207
|
+
- 2d8de1c: fix(code): an MCP server that refuses a stored token gets one refresh before `akan code` asks for a sign-in
|
|
1208
|
+
|
|
1209
|
+
`akan code` refreshed an MCP server's token only when the stored expiry said it had run out. A server that refused
|
|
1210
|
+
the token earlier — it was revoked, or it was a refreshed token the server issued with no lifetime, which is kept
|
|
1211
|
+
until refused — sent the session straight to `sign-in needed · /mcp login <name>`, even though the stored refresh
|
|
1212
|
+
token would still have worked.
|
|
1213
|
+
|
|
1214
|
+
On a 401 at connect, a server holding a refresh token now gets exactly one refresh (or the token another session
|
|
1215
|
+
already stored since), and the connect is retried with it. Only when that refresh is refused, or the new token is
|
|
1216
|
+
refused too, is the server listed as needing a sign-in. A 401 is never retried a second time, so a server that
|
|
1217
|
+
refuses every token costs one refresh, not a loop.
|
|
1218
|
+
|
|
1219
|
+
- 2d8de1c: fix(code): an MCP tool call refused mid-session refreshes the token once and retries
|
|
1220
|
+
|
|
1221
|
+
`akan code` refreshed an MCP server's token only while connecting. A token that expired or was revoked during a
|
|
1222
|
+
long session made every call to that server fail with `MCP server "<name>" requires authentication`, which named no
|
|
1223
|
+
way out, until the session was reloaded.
|
|
1224
|
+
|
|
1225
|
+
A call answered 401 now gets one refresh (or the token another session stored since), reopens the server with it —
|
|
1226
|
+
on a new streamable HTTP session, since a server may bind its session to the token that opened it — and is retried
|
|
1227
|
+
once. If the refresh is refused, or the new token is refused too, the call fails with `MCP server "<name>" needs
|
|
1228
|
+
signing in — /mcp login <name>`, and later calls to that server answer the same without refreshing again.
|
|
1229
|
+
|
|
1230
|
+
Calls refused together share one refresh, and so does every connection in the process that would present the same
|
|
1231
|
+
refresh token — a sub-agent's, or one opening with an expired token — because a server that rotates refresh tokens
|
|
1232
|
+
may revoke the whole grant when one is presented twice. The token file's format and location are unchanged.
|
|
1233
|
+
|
|
1234
|
+
- 2d8de1c: fix(code): two `akan code` sessions refreshing the same MCP token make one refresh between them
|
|
1235
|
+
|
|
1236
|
+
Every `akan code` process refreshes an MCP server's token itself, from the one token file they all share. Two
|
|
1237
|
+
sessions whose calls were refused at the same moment each presented the same refresh token, and a server that
|
|
1238
|
+
rotates refresh tokens refuses the second use — signing that session out, or revoking the whole grant.
|
|
1239
|
+
|
|
1240
|
+
A refresh now runs under a lock file beside the token file (`mcpAuth.json.lock`, the same kind of lock `akan login`
|
|
1241
|
+
takes on `~/.akan/config.json`). The token is read again once the lock is held: if another session already replaced
|
|
1242
|
+
it, that token is used and no refresh is made. A lock left by a process that died is taken over; one a live process
|
|
1243
|
+
holds for more than 30 seconds reads as a failed refresh. The token file's format and location are unchanged.
|
|
1244
|
+
|
|
1245
|
+
- 2d8de1c: fix(code): `/mcp remove` says when the home file still declares the server, and honours `--local`
|
|
1246
|
+
|
|
1247
|
+
`akan code` reads MCP servers from two files — `~/.akan/code/mcp.json`, which every repo reads, and the repo's
|
|
1248
|
+
`.akan/code/mcp.json`, which wins a name both declare. `/mcp remove <name>` takes the repo's entry first, so on a
|
|
1249
|
+
name both files declare the home file's declaration took over after the reload, possibly with a different command
|
|
1250
|
+
or url, while the notice read only "removed X from the workspace file". The notice now adds that the global file
|
|
1251
|
+
still declares it, so it still applies, and that running `/mcp remove X` again removes that one too.
|
|
1252
|
+
|
|
1253
|
+
`--local` was accepted by `/mcp remove` and ignored. It now means what it means for `/mcp add`: this repo's file
|
|
1254
|
+
only. A name only the home file declares is left alone, and the reply says where it is declared.
|
|
1255
|
+
|
|
1256
|
+
`McpServerConfig.remove(workspaceRoot, name, only?)` takes the scope as an optional third argument; without it the
|
|
1257
|
+
lookup order is unchanged. The `akan-code` skill's manual now names both files and both flags.
|
|
1258
|
+
|
|
1259
|
+
- 2d8de1c: fix(code): `/mcp` says `sign-in needed` for a server whose token stopped working mid-session
|
|
1260
|
+
|
|
1261
|
+
An MCP server whose token could not be renewed during a session answered every tool call with `needs signing in —
|
|
1262
|
+
/mcp login <name>`, but `/mcp` kept listing it as `signed in` — the state from when the session connected. Its row
|
|
1263
|
+
now turns to the same `sign-in needed · /mcp login <name>` a failed connect shows, and `/mcp <name>` says it needs
|
|
1264
|
+
signing in instead of listing tools that cannot answer; a server that failed to connect for want of a sign-in no
|
|
1265
|
+
longer claims it "connected and published no tools" there either. `/mcp login <name>` (or `/mcp reload` once a
|
|
1266
|
+
usable token is stored) reopens the session, and the server reads as signed in again.
|
|
1267
|
+
|
|
1268
|
+
- ccf2ae2: fix: a native target's dev build has its own output folder
|
|
1269
|
+
|
|
1270
|
+
`akan start-ios`, `start-android` and `start-desktop` without `--release` built into the same
|
|
1271
|
+
`.akan/native/<target>/build/<platform>` as `build-ios`, `build-android`, `build-desktop` and `--release`, so a dev
|
|
1272
|
+
session deleted the release build there, a desktop installer included, and put a dev build under its name. Dev builds
|
|
1273
|
+
now go to `.akan/native/<target>/dev/<platform>`; the first dev session after the update builds from scratch once.
|
|
1274
|
+
|
|
1275
|
+
- ccf2ae2: `akan start-desktop`, `start-ios` and `start-android` print a page's log once, at the level it was logged, and say a
|
|
1276
|
+
boot came up in one line
|
|
1277
|
+
|
|
1278
|
+
- A boot that comes up prints `cmdc ios ready · iPhone 16 · http://localhost:8283 · 12.4s` and nothing else; its steps
|
|
1279
|
+
(prepare, build, install, launch) are debug. A boot that fails prints every step it took ahead of the error. A native
|
|
1280
|
+
config warning still prints as it happens.
|
|
1281
|
+
- A page line reads `WARN [page:WsClient] WebSocket message process failed …`: stamped once by the CLI's logger, at
|
|
1282
|
+
the page's own level, with no `[default]` label, no empty line after it, and a multi-line message kept together. A
|
|
1283
|
+
page logger writes `[Name] message` at the console method of its level inside a native shell, and the desktop host
|
|
1284
|
+
and iOS tag each line `[page<+><#window> <level>]` for the CLI to read back.
|
|
1285
|
+
- Android forwards the page console through the bridge in a dev build too, so logcat carries each line at its level
|
|
1286
|
+
and a `console.error("%s", …)` substitution the way the devtools show it; iOS writes os_log at the page's level. An
|
|
1287
|
+
uncaught error forwards its message along with WebKit's frames, which carry none.
|
|
1288
|
+
- Frame tracing (`[akan:frame:…]`) is opt-in (`?akanFrameDebug=1` or `localStorage["akan:debug:frame"] = "1"`) and
|
|
1289
|
+
logs at debug, once per event; a mobile or desktop target no longer turns it on for every visibility change.
|
|
1290
|
+
- Lines no page or host wrote (the simulator, WebKit) and where the host's pages come from are debug; `--verbose` shows
|
|
1291
|
+
the former. `start-desktop` and friends ask a dev server whose builder idled out for its health, not its home page,
|
|
1292
|
+
so they no longer report it missing while it wakes.
|
|
1293
|
+
|
|
1294
|
+
- c45ac6c: fix(lint): stop `//!` markers from shipping to the browser
|
|
1295
|
+
|
|
1296
|
+
Bun's bundler classifies `//!` and `/*!` as legal comments — the `@license` / `@preserve` class it must preserve
|
|
1297
|
+
for licence compliance — and keeps them through `minify: true`. Everything else goes: `//`, `/* */`, `//*`, `//?`,
|
|
1298
|
+
and `// TODO:` are all stripped. But the workspace `AGENTS.md` prescribes `//!` as in-code marker 4, "disabled or
|
|
1299
|
+
must-fix code", so the convention itself guaranteed that internal notes about broken and unfinished code shipped
|
|
1300
|
+
verbatim to every visitor. A deployed app served eight of them in one chunk, including `//!env로 옮겨야함`,
|
|
1301
|
+
`//!need to change`, and `//! Need to fix after bunjs migration`.
|
|
1302
|
+
|
|
1303
|
+
There is no bundler-side fix: `legalComments` is not a `Bun.build` option, and `Bun.Transpiler.transformSync`
|
|
1304
|
+
preserves `//!` too. The source is the only place to stop it.
|
|
1305
|
+
|
|
1306
|
+
`no-bang-comment-in-client.grit` now bans the marker in browser-reachable code — `ui/`, `webkit/`, `common/`,
|
|
1307
|
+
`page/**/*.tsx`, `*.constant.ts`, `*.store.ts`, and the five module component suffixes — while leaving it legal in
|
|
1308
|
+
server, `srvkit/`, and CLI files, which never reach a bundle. The five existing occurrences in `akanjs` and the
|
|
1309
|
+
libs became `// FIXME:`.
|
|
1310
|
+
|
|
1311
|
+
The rule anchors the marker to line start or to whitespace after code, so a literal like `"https://host//!path"`
|
|
1312
|
+
does not trip it. Comments are trivia rather than nodes, so the diagnostic spans the module and a marker on a
|
|
1313
|
+
file's very first line is invisible to the pattern — the one case the rule cannot see.
|
|
1314
|
+
|
|
1315
|
+
Rebuilding `apps/akan` takes the bundle from two `//!` comments to zero, with the third-party `/*!` licence
|
|
1316
|
+
blocks left intact.
|
|
1317
|
+
|
|
1318
|
+
- f882bbc: feat(lint): `no-init-fetch-in-client` — keep slice hydration on the server
|
|
1319
|
+
|
|
1320
|
+
`fetch.init<Model><Suffix>` is not a request, it is a hydration snapshot: `#registerSlice` composes it out of the
|
|
1321
|
+
slice's list and insight queries and returns a `ServerInit` whose only consumer is the `init` prop of
|
|
1322
|
+
`Load.Units` / `Load.View`, which seeds the store from it before React renders. `fetch.get<Model>Init<Suffix>` is
|
|
1323
|
+
the same call returning the payload alone. From a route both resolve before the first byte and the markup ships
|
|
1324
|
+
populated; from a client component they are two extra round-trips for a shell the browser already painted empty,
|
|
1325
|
+
landing in a local variable nothing reads — `Load.*` seeds state, so a value held in a client closure reaches
|
|
1326
|
+
nowhere. Nothing in the framework flagged this, and the mistake reads as an ordinary load.
|
|
1327
|
+
|
|
1328
|
+
The fix the message points at is the route: `await fetch.initXInY(...)` in `page/**` and pass the result down as
|
|
1329
|
+
an `init` prop, or hand the unawaited promise to a `Zone`. The client is not missing the load either — every
|
|
1330
|
+
slice also generates `st.do.init<Model><Suffix>()`, which runs the same two queries and commits them to state.
|
|
1331
|
+
|
|
1332
|
+
The gate is the file, not the call site. `"use client"` is anchored to `JsDirective`, so the same text as an
|
|
1333
|
+
ordinary string literal or inside a template-literal code sample — both common in docs pages — is not mistaken
|
|
1334
|
+
for the directive. `*.store.ts` is added by name, being client-only by role while carrying no directive.
|
|
1335
|
+
|
|
1336
|
+
The name is matched by shape, a lint rule having no way to know which slices exist. The generated one is `init`
|
|
1337
|
+
|
|
1338
|
+
- `Capitalize<refName>` + `Capitalize<suffix>`, so it always carries two capital-led segments; requiring the
|
|
1339
|
+
second is what keeps a hand-written `initPayment` / `initSession` endpoint out, and `initializeSomething` was
|
|
1340
|
+
never at risk since `init` is followed by a lowercase letter there. A custom endpoint spelling the generated
|
|
1341
|
+
shape exactly — `initPaymentSession`, `get<X>Init<Y>` — is the residue, suppressible with
|
|
1342
|
+
`// biome-ignore lint/plugin: <reason>`.
|
|
1343
|
+
|
|
1344
|
+
`view` / `edit` hydrate the same way but are deliberately unmatched: `edit<X>` is a plausible custom endpoint
|
|
1345
|
+
name.
|
|
1346
|
+
|
|
1347
|
+
A runtime guard was considered instead and rejected: CSR and Capacitor builds render route modules in the
|
|
1348
|
+
browser (`RootRenderLayer` in `pkgs/akanjs/webkit/bootCsr.tsx` awaits the page's own async render), so
|
|
1349
|
+
`typeof window !== "undefined"` holds for a perfectly correct page there and nothing at runtime can tell that
|
|
1350
|
+
apart from a client component calling the same thing after hydration.
|
|
1351
|
+
|
|
1352
|
+
- 69f7178: feat(lint): ban `cnst` model types in `*.Util.tsx` / `*.Zone.tsx` props
|
|
1353
|
+
|
|
1354
|
+
`Util` and `Zone` are always client components, so a `cnst.Banner` / `cnst.LightBanner` prop is a hydrated class
|
|
1355
|
+
instance the server has to hand across the boundary — the functions are stripped on the way and what arrives is a
|
|
1356
|
+
plain object wearing the model's type. `no-model-type-in-util-zone.grit` reports it and points at the two shapes
|
|
1357
|
+
that work: take `bannerId: string` and read the model from the store, or take the payload the framework already
|
|
1358
|
+
serializes.
|
|
1359
|
+
|
|
1360
|
+
Three exemptions, because none of them is an instance. `cnst.<Enum>["value"]` is an indexed access that resolves to
|
|
1361
|
+
a string union, which is how every enum prop in the codebase is already written (`roles: cnst.AdminRole["value"][]`).
|
|
1362
|
+
A `ClientInit` / `ClientView` / `ClientEdit` type argument is mapped through `GetStateObject<…>` before it reaches
|
|
1363
|
+
a prop, which is the sanctioned server-to-client handoff. A `ModelsProps<cnst.Setting>` type argument spends the
|
|
1364
|
+
model on `onClickItem?: (model: M) => unknown` and nowhere else, so it is the function-typed-prop exemption reached
|
|
1365
|
+
through a generic — whoever passes the callback is a client component already holding the value.
|
|
1366
|
+
`ModelProps<"setting", cnst.LightSetting>` stays reported: it spreads the model onto the props themselves, and the
|
|
1367
|
+
`Unit` / `View` files that take it are server components outside this rule's scope. Any _other_ indexed access is
|
|
1368
|
+
still reported — `cnst.Banner["image"]` is a `File`.
|
|
1369
|
+
|
|
1370
|
+
The rule keys on the `cnst.` qualifier in a type position, so it never touches a value expression, and it is scoped
|
|
1371
|
+
to those two filename suffixes: `Unit` and `View` are server components and keep taking the model itself.
|
|
1372
|
+
|
|
1373
|
+
Only prop positions are read: a `*Props` interface or type alias, and the inline object type on the component's own
|
|
1374
|
+
parameter. A `cnst` type that never leaves the file is not a boundary crossing and is left alone — a local
|
|
1375
|
+
annotation, a callback parameter the framework itself types with the model (`renderItem`, `renderList`), a
|
|
1376
|
+
module-scope helper, a non-`Props` local shape, and the props of a component nested inside another one. A
|
|
1377
|
+
function-typed prop (`onPick?: (t: cnst.LightTicket) => void`) is exempt too: a closure cannot cross the RSC
|
|
1378
|
+
boundary at all, so whoever passes it is a client component that already holds the value.
|
|
1379
|
+
|
|
1380
|
+
- 5f53462: fix(lint): `non-scalar-props-restricted` covers `*.Unit.tsx` and `*.View.tsx`
|
|
1381
|
+
|
|
1382
|
+
The guide has always said a server component may not hand a function prop across the client boundary in a page, a
|
|
1383
|
+
Unit or a View, but the rule was scoped to `page/**` only. Units and Views are always server components, so a
|
|
1384
|
+
function prop there fails at the boundary at runtime; lint now says so first.
|
|
1385
|
+
|
|
1386
|
+
- ccf2ae2: `akan start` replaces its backend one restart at a time, so saves in quick succession can no longer leave a second
|
|
1387
|
+
gateway holding the port.
|
|
1388
|
+
|
|
1389
|
+
- A save whose restart came due while the previous one still waited for the old backend to exit used to signal the
|
|
1390
|
+
same process again and then spawn a second backend beside the first. The host kept the one that lost the port,
|
|
1391
|
+
restarted it on every save, and the untracked one went on answering. A restart that comes due now runs after the
|
|
1392
|
+
one in flight, and a config or metadata recycle and `akan start`'s shutdown wait for it too.
|
|
1393
|
+
- A gateway that cannot bind its port no longer reports itself ready while it waits for its replicas to exit.
|
|
1394
|
+
|
|
1395
|
+
- 2d8de1c: fix(release): the published packages no longer carry test-run files from `local/`
|
|
1396
|
+
|
|
1397
|
+
`akanjs` 3.0.0-beta.19 shipped 65 files (SQLite databases and logs, 820 KB) and `@akanjs/devkit` 2 from the
|
|
1398
|
+
gitignored `local/` directory the test suites write into, because both builds copied the package directory whole.
|
|
1399
|
+
`build-package` and `akanjs`'s own build now leave `local/` out, and `verify-akan-publish-packages` refuses a package
|
|
1400
|
+
whose pack list still contains it.
|
|
1401
|
+
|
|
1402
|
+
- c45ac6c: perf(build): precompress the CSS bundle, and serve brotli sidecars ahead of gzip
|
|
1403
|
+
|
|
1404
|
+
The compiled stylesheet went out uncompressed in production. `precompressArtifacts` only walked
|
|
1405
|
+
`.akan/artifact/client`, so every JS chunk got a `.gz` sidecar while the CSS — written to
|
|
1406
|
+
`.akan/artifact/styles` — got none, and `#fileResponse` fell through to streaming the raw file. Nothing was
|
|
1407
|
+
wrong on the serving side: `#isCompressible` already accepts `text/css` and the sidecar lookup already ran.
|
|
1408
|
+
The file simply was not there. On one deployed app that was 319KB on the wire per cold load, against 45KB
|
|
1409
|
+
gzipped. The CSS is and always was minified — Lightning CSS runs in `prepareCssAsset` — so this is purely the
|
|
1410
|
+
transfer encoding.
|
|
1411
|
+
|
|
1412
|
+
Sidecars are now written for `styles/` too, and a `.br` sidecar is written alongside every `.gz`.
|
|
1413
|
+
|
|
1414
|
+
Brotli is tried first and gzip remains the fallback, because browsers only advertise `br` on secure origins.
|
|
1415
|
+
Quality is 11 for CSS and 9 for everything else: there is one CSS asset per basePath and it is the largest
|
|
1416
|
+
single file an app ships, worth ~20% over gzip for ~0.2s, while spending 11 on several hundred JS chunks costs
|
|
1417
|
+
~12s of build time for a few hundred KB. Measured on `apps/akan`: 371 files, 14.5MB raw, 4.2MB gzip, 3.9MB
|
|
1418
|
+
brotli, and the CSS bundle 103KB raw → 16.2KB gzip → 13.0KB brotli. The compress phase went from ~0.3s to
|
|
1419
|
+
~0.64s. Gzip sidecars moved to level 9, which is free at build time and was previously left at the default.
|
|
1420
|
+
|
|
1421
|
+
Encoding negotiation moved out of `WebRouter` and `AkanApp` into `resolveEncodedSidecar`. Both classes carried
|
|
1422
|
+
their own copy of the gzip lookup plus `#acceptsGzip` / `#isCompressible`, and a gateway that disagrees with
|
|
1423
|
+
its child about which encoding a request accepts serves a body the caller cannot decode. The shared version
|
|
1424
|
+
also honours `q=0` as the refusal it is, and no longer treats a token merely starting with `br` as brotli.
|
|
1425
|
+
|
|
1426
|
+
**Anything caching in front of the app must pass `Vary: Accept-Encoding` through.** The header was already
|
|
1427
|
+
set, but it only mattered when one encoding was on offer; with two, a proxy that drops or ignores it will
|
|
1428
|
+
hand a brotli body to a client that asked for gzip. Artifacts now carry both sidecars, so a build's static
|
|
1429
|
+
output grows by roughly the brotli total (3.9MB for `apps/akan`).
|
|
1430
|
+
|
|
1431
|
+
- 5f53462: feat(route): a route's head is JSX only, and analytics is the app's own component
|
|
1432
|
+
|
|
1433
|
+
**`.metadata()` is gone, along with the `metadata` / `generateMetadata` exports and the `AkanMetadata` type.** A route
|
|
1434
|
+
had two ways to say the same thing — an object of `title` / `description` / `openGraph` / `twitter` / `alternates`
|
|
1435
|
+
that the server translated into tags, and `.head()` taking the tags themselves — and could use only one of them per
|
|
1436
|
+
module. Every app in the workspace already wrote `.head(<>…</>)`. Rewrite an object as the tags it produced:
|
|
1437
|
+
|
|
1438
|
+
```tsx
|
|
1439
|
+
.head(({ projectId }) => (
|
|
1440
|
+
<>
|
|
1441
|
+
<title>{`Project ${projectId}`}</title>
|
|
1442
|
+
<meta name="description" content="Project workspace" />
|
|
1443
|
+
<meta property="og:image" content="/og/project.png" />
|
|
1444
|
+
<link rel="canonical" href="https://example.com/project" />
|
|
1445
|
+
</>
|
|
1446
|
+
))
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
A legacy route that still exports `metadata` or `generateMetadata` now fails the load as an unsupported export,
|
|
1450
|
+
the same way any other stray export does. The hreflang alternates Akan adds per locale are unchanged; the only thing
|
|
1451
|
+
that could suppress them was an `alternates.languages` object, so leave them out of the JSX.
|
|
1452
|
+
|
|
1453
|
+
**`.gaTrackingId()` and the `gaTrackingId` export / `System.Provider` prop are gone.** Which tags load, in which
|
|
1454
|
+
environment and behind which consent banner are the app's decisions, and the framework's built-in `Gtag` made none
|
|
1455
|
+
of them. Render a `"use client"` component that loads gtag.js from the root layout's `.render()` instead — the
|
|
1456
|
+
routing guide has one. GA4's Enhanced measurement already counts the router's `history.pushState` navigations, so
|
|
1457
|
+
the component only loads the tag.
|
|
1458
|
+
|
|
1459
|
+
The head-snapshot path used by the experimental partial commit (`AKAN_PUBLIC_RSC_PARTIAL_COMMIT=1` with
|
|
1460
|
+
`rscPatchHeadSafe`) was fed only by the metadata object, so it now always falls back to a full navigation.
|
|
1461
|
+
|
|
1462
|
+
- 5f53462: fix(build): the production server bundle keeps its identifiers, so a class still names its own log lines
|
|
1463
|
+
|
|
1464
|
+
`akan build` compiled `main.ts`, `server.ts`, the RSC worker and the console runtime with `minify: true`, which
|
|
1465
|
+
includes `identifiers`. Renaming every class in a server bundle is renaming the only identity several parts of
|
|
1466
|
+
the framework read back at runtime:
|
|
1467
|
+
|
|
1468
|
+
- `serve()` gives each service `new Logger(this.constructor.name)`, so a production log line read
|
|
1469
|
+
`[P6e] ... Updating 0 Floors' Awairs Latest Air Data` — the class the message came from is exactly what a log
|
|
1470
|
+
filter (`akan logs --child`, `--grep`) needs and the one thing the record no longer carried.
|
|
1471
|
+
- `Exception` and `Err` set `this.name = this.constructor.name`, so a 500's logged error name was mangled too.
|
|
1472
|
+
- A guard's `static name = "User";` is what `fetch` serializes, what the API explorer filters on, and what
|
|
1473
|
+
`mcpExposure` compares against `"Public"`. The convention held the framework's own guards together; a guard
|
|
1474
|
+
that omitted it silently published a two-letter name.
|
|
1475
|
+
- Every frame of every stack trace lost its function and class name, on a single-line bundle that has no
|
|
1476
|
+
source map to fall back on.
|
|
1477
|
+
|
|
1478
|
+
Server bytes are read from the image, never sent to a client, and the measurements say identifier mangling was
|
|
1479
|
+
not buying much: on `minimal`, `server.js` grows 5.5MB → 7.4MB, boot moves 202ms → 206ms (median of five), and
|
|
1480
|
+
peak RSS stays inside the noise band. `minify.whitespace` and `minify.syntax` are unchanged, and the browser
|
|
1481
|
+
bundles — CSR, page and client entries — still mangle identifiers, where the bytes actually travel.
|
|
1482
|
+
|
|
1483
|
+
`minify.keepNames` would have been the narrow fix; Bun 1.4.2 accepts the flag in `BuildConfig` and renames
|
|
1484
|
+
classes anyway, which `applicationBuildRunner.test.ts` now pins so the flag can be adopted when it lands.
|
|
1485
|
+
|
|
1486
|
+
- 69f7178: fix(build): target the server when bundling `client-ssr`, so SSR keeps happening
|
|
1487
|
+
|
|
1488
|
+
`ClientEntriesBundler` hardcoded `target: "browser"`, and both bundles go through it: the browser one under
|
|
1489
|
+
`client` and the **server-executed** one under `client-ssr`, which `SsrFromRscRenderer` loads with
|
|
1490
|
+
`await import(chunkId)`. The browser target resolves the `browser` export condition, so a dependency whose
|
|
1491
|
+
`browser` entry touches a DOM global at module scope threw the moment the SSR renderer imported the chunk.
|
|
1492
|
+
|
|
1493
|
+
That throw lands while React is rendering the shell, so the failure is not scoped to the offending subtree — the
|
|
1494
|
+
whole document degrades to client rendering, with one browser console line (`Switched to client rendering because
|
|
1495
|
+
the server rendering errored: document is not defined`) and a ~1KB server body holding nothing but an empty
|
|
1496
|
+
Suspense placeholder. It was reported against `react-markdown` → `micromark` →
|
|
1497
|
+
`decode-named-character-reference`, whose browser entry is `const element = document.createElement('i')`, and it
|
|
1498
|
+
hit **every** route: the generated root layout imports the app store, so the shared UI graph is in every client
|
|
1499
|
+
entry's SSR chunk graph.
|
|
1500
|
+
|
|
1501
|
+
`ClientEntriesBundler` now takes the target as an option — still `"browser"` for the browser bundle — and
|
|
1502
|
+
`resolveSsrClientBundleOptions` (was `resolveSsrClientExternalOptions`) carries `target: "bun"` for `start` and
|
|
1503
|
+
`build` alike, so neither `client-ssr` call site can drift. `conditions: ["node"]` is not a fix: Bun _adds_
|
|
1504
|
+
conditions to the target's defaults, so `browser` still wins. The target itself has to say server.
|
|
1505
|
+
|
|
1506
|
+
- ccf2ae2: feat(dev): SSR pages load their client code from a dev module registry
|
|
1507
|
+
|
|
1508
|
+
- Under `akan start`, an SSR page's `"use client"` code now comes from a module registry under
|
|
1509
|
+
`.akan/artifact/ssr-dev`, the same machinery the dev CSR registry uses, instead of the route chunks. React and the
|
|
1510
|
+
akanjs vendor facets stay the import map's, so the page keeps one React and one store registry. The route chunks and
|
|
1511
|
+
the `client-refresh` that re-imported all of them are gone from dev; `akan build` is unchanged.
|
|
1512
|
+
- A save patches the changed module in place with React Fast Refresh: state stays, and on minimal a component edit
|
|
1513
|
+
shows in about 110ms instead of 900ms. Undoing an edit now shows too; with chunks the browser kept the module it had
|
|
1514
|
+
already imported for that URL. A client module the RSC payload names that also exports constants re-runs in place.
|
|
1515
|
+
- A save that changes nothing the server renders (a `"use client"` module that keeps its export names, or a module only
|
|
1516
|
+
client code imports) keeps the page's build id and refetches no RSC payload. One that does — a component both sides
|
|
1517
|
+
render, a constant — holds its client patch until that save's pages build lands, then sends the patch and the RSC
|
|
1518
|
+
refresh together, so the page never runs new client code against old server props. A constant edit that reloads
|
|
1519
|
+
waits for that build too, and a backend restarted by a server-only save renders from the latest pages build.
|
|
1520
|
+
- The registry builds once in a build worker after the dev server boots, beside the builder's slow lane, and each route
|
|
1521
|
+
build adds the entries its page names that the registry lacks; a page served before the registry exists waits for
|
|
1522
|
+
it in the browser, in short holds. Route builds no longer bundle browser chunks in dev, which took about 240MB off
|
|
1523
|
+
the builder's peak on apps/akan.
|
|
1524
|
+
- Most saves are patched in the resident builder: a new file, a new import from a package something already resolved,
|
|
1525
|
+
a moved or renamed file (a case-only rename, `Foo.tsx` to `Foo/index.tsx`, a file created beside a folder an import
|
|
1526
|
+
resolved to), a deleted module brought back. A whole build in a build worker is left for a new npm package, a signal
|
|
1527
|
+
save (its metadata is inlined into the client runtime; a dictionary save does not rebuild the SSR registry, which
|
|
1528
|
+
inlines none), and a registry whose last whole build was cut short. A save that does not compile — a typo in an
|
|
1529
|
+
import included — is reported by the builder at once, and the save that fixes it patches in about 120ms.
|
|
1530
|
+
- A failed build keeps the registry the tabs and the next save read. Its error stays on the overlay, is shown to a tab
|
|
1531
|
+
opened while it stands, and clears with the save that fixes it — for a route that failed on a shared module too,
|
|
1532
|
+
which is built again once a newer build goes green. A broken file no page imports any more stops failing saves.
|
|
1533
|
+
- A client module whose top level threw, or a store that throws at startup, recovers with the save that fixes it, and
|
|
1534
|
+
the dev server's error page carries the HMR client, so it reloads once the fix lands. A pages bundle that builds but
|
|
1535
|
+
throws while it loads shows its error while the RSC worker keeps serving the bundle it had.
|
|
1536
|
+
- A tab that reconnects after a backend restart loads the patches it missed and refetches its payload instead of
|
|
1537
|
+
reloading; one that reconnects to a new dev session (a new `akan start`, a config restart) reloads onto its registry.
|
|
1538
|
+
A tab that booted beside the vendor file of the build before loads the newer one, so a new npm import in a leaf
|
|
1539
|
+
component works without a manual refresh.
|
|
1540
|
+
- A build worker exits with the builder that spawned it, and a boot build whose worker was killed is retried after 10
|
|
1541
|
+
seconds. On Windows, registry writes retry a rename refused while a reader holds the file, and a build worker hashes
|
|
1542
|
+
the same bundle config as the builder, so saves are not all handed to whole builds.
|
|
1543
|
+
|
|
1544
|
+
- 2d8de1c: fix(code): at most `maxConcurrent` sub-agents run at once across the whole tree
|
|
1545
|
+
|
|
1546
|
+
`/agents` says a profile runs sub-agents "at most N at once", but each `task` pool counted only its own children,
|
|
1547
|
+
so a child agent could open N of its own while its parent's N were still running — with the shipped presets (two
|
|
1548
|
+
levels, three at once) up to 12 sub-agents ran at once instead of 3.
|
|
1549
|
+
|
|
1550
|
+
The running count now travels with the tree's token tally (`SubagentSpend` gained `running`), so every pool of one
|
|
1551
|
+
tree checks the same number. A `task` over the limit is refused, as before, and never queued: every ancestor keeps
|
|
1552
|
+
its slot while it waits on its child, so a queued child would deadlock the tree. The refusal tells an agent that has
|
|
1553
|
+
sub-agents of its own running to wait for one of them or do the work itself, and one that has none to do it itself.
|
|
1554
|
+
|
|
1555
|
+
- 2d8de1c: fix(code): a sub-agent that fails or is stopped still spends from the tree's budget
|
|
1556
|
+
|
|
1557
|
+
A sub-agent's tokens were added to the tree's tally only when its run returned normally. One whose run threw — a
|
|
1558
|
+
provider or engine error after it had already spent tokens — added nothing and said nothing, so a tree of failing
|
|
1559
|
+
sub-agents could spend past the budget without `task` ever refusing; one stopped by its parent's interrupt was
|
|
1560
|
+
counted but announced as "finished".
|
|
1561
|
+
|
|
1562
|
+
Each sub-agent's own tokens are now added exactly once however it ends, and the notice says how it ended:
|
|
1563
|
+
`explore sub-agent finished|failed|stopped, N tokens (M of B spent)`. A sub-agent's own children are still counted
|
|
1564
|
+
by its pool, so nothing is counted twice.
|
|
1565
|
+
|
|
1566
|
+
- 2d8de1c: fix(code): an interrupt that lands before a sub-agent's run is live stops it
|
|
1567
|
+
|
|
1568
|
+
A sub-agent listened for its parent's interrupt only once it had been created, and the engine ignores an abort that
|
|
1569
|
+
arrives before a run is live. So an interrupt pressed while a `task` was still opening its sub-agent — connecting
|
|
1570
|
+
its MCP servers, say — or in the moment between that and the sub-agent's run starting was lost: the sub-agent ran
|
|
1571
|
+
its whole task anyway, and in the second case its notice even said "stopped".
|
|
1572
|
+
|
|
1573
|
+
A sub-agent whose parent was interrupted while it was being created now never starts, and one interrupted after that
|
|
1574
|
+
is stopped as soon as its run is live. Either way it reports `stopped`, and its tokens are added to the tree's tally
|
|
1575
|
+
once.
|
|
1576
|
+
|
|
1577
|
+
- 2d8de1c: fix(code): every sub-agent of one tree draws on one token budget
|
|
1578
|
+
|
|
1579
|
+
A profile's `subagent.budget` is documented, and shown by `/agents`, as the tokens "a whole subagent tree may
|
|
1580
|
+
spend", but each `task` pool kept its own count. A child's pool started from zero against the same ceiling and
|
|
1581
|
+
counted only its own children, so with the shipped presets (two levels, three at once) a tree could spend several
|
|
1582
|
+
times the budget before anything refused.
|
|
1583
|
+
|
|
1584
|
+
The count is now one object for the whole tree: the root pool starts it, hands it to every child agent it opens
|
|
1585
|
+
(`CodeAgentOptions.subagentSpend`, forwarded through `AkanCodePlugins` to the child's pool), and each sub-agent's
|
|
1586
|
+
tokens are added to it when that sub-agent finishes. Once the tree has spent the budget, `task` refuses at every
|
|
1587
|
+
level, and the "sub-agent finished" notice reports the tree's total. Depth and concurrency limits are unchanged.
|
|
1588
|
+
|
|
1589
|
+
- 5f53462: The CLI's AI editor is removed, and with it the `--ai` commands
|
|
1590
|
+
|
|
1591
|
+
`AiSession` — the langchain-backed editor that asked a model to write module constants, dictionaries, scalars,
|
|
1592
|
+
UI components, guideline instructions and abstract files — is deleted, along with every command that drove it.
|
|
1593
|
+
Gone: `akan set-llm`, `akan reset-llm`, `akan ask`, `akan compact`, `akan generate-instruction`, `akan
|
|
1594
|
+
update-instruction`, `akan generate-document`, `akan reapply-instruction`, and the `--ai` flag on `akan
|
|
1595
|
+
create-module` and `akan create-scalar`. The scaffolding halves of those two commands are unchanged and are now
|
|
1596
|
+
what they always do.
|
|
1597
|
+
|
|
1598
|
+
`akan guideline list` and `akan guideline show` are unaffected — guidelines are bundled files the CLI reads and
|
|
1599
|
+
never writes, and MCP `get_guideline` serves the same ones. `akan quality scan` still warns on an abstract past
|
|
1600
|
+
300 lines; there is no longer a command that rewrites one for you.
|
|
1601
|
+
|
|
1602
|
+
The `@langchain/*` dependencies leave `@akanjs/cli`, `@akanjs/devkit` and the workspace root — they were the
|
|
1603
|
+
heaviest lazy-loaded stack the CLI carried, and `EntryModuleGraph` no longer has to guard against one reaching
|
|
1604
|
+
an entry. The workspace-global config at `~/.akan/config.json` no longer holds an LLM key; an existing `llm`
|
|
1605
|
+
entry is ignored and can be deleted.
|
|
1606
|
+
|
|
1607
|
+
This is the dev-time editor only. The in-page agent's runtime `LlmAdaptorRole`, `option.setLlm(...)` and the
|
|
1608
|
+
shipped adaptors are a different surface and are untouched.
|
|
1609
|
+
|
|
1610
|
+
Step-by-step migration: `akan guideline show workspaceRecipes`, Recipe 8.
|
|
1611
|
+
|
|
1612
|
+
- 2d8de1c: fix(build): `export *` in a `"use client"` module is a build error, and a dev build failure says why
|
|
1613
|
+
|
|
1614
|
+
The server replaces a `"use client"` module with one client reference per name the module exports, and the names
|
|
1615
|
+
come from Bun's export scan — which reports none through `export * from "./x"`. So a star re-export in a client
|
|
1616
|
+
module registered nothing: a star-only file was silently no client boundary at all, and a file mixing named exports
|
|
1617
|
+
with a star lost the star's names on the server, where they were `undefined` in a server component while the same
|
|
1618
|
+
import worked in a client one. `libs/shared`'s `User.Template.tsx` did exactly this with the leave-flow components.
|
|
1619
|
+
|
|
1620
|
+
Like Next.js, the build now refuses it, in `akan build` and `akan start` alike, naming the file, the specifiers and
|
|
1621
|
+
the fix:
|
|
1622
|
+
|
|
1623
|
+
```
|
|
1624
|
+
libs/shared/lib/user/User.Template.tsx is a "use client" module, so it cannot `export * from "../../ui/UserLeave"`:
|
|
1625
|
+
the server sees only the names a client module declares, and a star re-export declares none. Re-export them by
|
|
1626
|
+
name — `export { … } from "../../ui/UserLeave"`.
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
`export type * from` and `export * as ns from` stay allowed, and a module without the directive is untouched. A
|
|
1630
|
+
barrel that mixes client-only files with isomorphic ones should drop the directive instead: each client file keeps
|
|
1631
|
+
its own, and the barrel stays importable on the server.
|
|
1632
|
+
|
|
1633
|
+
A failed bundle during `akan start` used to report only the bundler's own message, "Bundle failed". The dev build
|
|
1634
|
+
now prints the reasons nested under it, the way `akan build` already did.
|
|
1635
|
+
|
|
1636
|
+
- 2d8de1c: fix(windows): `akan` commands and the dev build worker no longer stop silently at a missing file
|
|
1637
|
+
|
|
1638
|
+
On Windows, Bun lets a process exit while a `Bun.file` read of a file that does not exist is still pending, if
|
|
1639
|
+
nothing else is keeping it alive. `akan build` stopped right after `sync` without an error, and the dev server's boot
|
|
1640
|
+
build ended with "build worker exited with code 0 before reporting a result" on any app that had no font cache yet.
|
|
1641
|
+
The CLI and its build workers now await their work from the entry module, which keeps the process alive until it
|
|
1642
|
+
settles; a failed command still prints its error once and exits 1.
|
|
1643
|
+
|
|
1644
|
+
- ccf2ae2: fix(cloud): on Windows, a `~/.akan/config.json` write waits for another command's read instead of failing
|
|
1645
|
+
|
|
1646
|
+
Windows refuses to rename over a file another process has open, and every `akan` command reads `config.json`
|
|
1647
|
+
outside its lock. On a Windows VM, a writer racing two readers had 1374 of 1579 renames fail with `EPERM`. A token
|
|
1648
|
+
refresh that lost that race fell back to the stored session. A session in its last hour is sent without
|
|
1649
|
+
`Authorization`, so one of two commands started together saw the cloud as signed out. And when the lost write was
|
|
1650
|
+
the one after the refresh, the rotated refresh token was never saved, so the next command had to sign in again.
|
|
1651
|
+
`GlobalConfig` now retries the rename on `EPERM`, `EACCES` and `EBUSY` for up to about five seconds on Windows.
|
|
1652
|
+
Under the same two readers, every write landed, the slowest after 40 tries (1.9s).
|
|
1653
|
+
|
|
1654
|
+
- Updated dependencies [ccf2ae2]
|
|
1655
|
+
- Updated dependencies [ccf2ae2]
|
|
1656
|
+
- Updated dependencies [ccf2ae2]
|
|
1657
|
+
- Updated dependencies [ccf2ae2]
|
|
1658
|
+
- Updated dependencies [ccf2ae2]
|
|
1659
|
+
- Updated dependencies [ccf2ae2]
|
|
1660
|
+
- Updated dependencies [ccf2ae2]
|
|
1661
|
+
- Updated dependencies [5f53462]
|
|
1662
|
+
- Updated dependencies [ccf2ae2]
|
|
1663
|
+
- Updated dependencies [ccf2ae2]
|
|
1664
|
+
- Updated dependencies [5f53462]
|
|
1665
|
+
- Updated dependencies [ccf2ae2]
|
|
1666
|
+
- Updated dependencies [ccf2ae2]
|
|
1667
|
+
- Updated dependencies [69f7178]
|
|
1668
|
+
- Updated dependencies [69f7178]
|
|
1669
|
+
- Updated dependencies [69f7178]
|
|
1670
|
+
- Updated dependencies [5f53462]
|
|
1671
|
+
- Updated dependencies [5f53462]
|
|
1672
|
+
- Updated dependencies [5f53462]
|
|
1673
|
+
- Updated dependencies [5f53462]
|
|
1674
|
+
- Updated dependencies [5f53462]
|
|
1675
|
+
- Updated dependencies [f882bbc]
|
|
1676
|
+
- Updated dependencies [8c31d54]
|
|
1677
|
+
- Updated dependencies [5f53462]
|
|
1678
|
+
- Updated dependencies [69f7178]
|
|
1679
|
+
- Updated dependencies [5f53462]
|
|
1680
|
+
- Updated dependencies [f882bbc]
|
|
1681
|
+
- Updated dependencies [5f53462]
|
|
1682
|
+
- Updated dependencies [5f53462]
|
|
1683
|
+
- Updated dependencies [f882bbc]
|
|
1684
|
+
- Updated dependencies [f882bbc]
|
|
1685
|
+
- Updated dependencies [5f53462]
|
|
1686
|
+
- Updated dependencies [5f53462]
|
|
1687
|
+
- Updated dependencies [69f7178]
|
|
1688
|
+
- Updated dependencies [5f53462]
|
|
1689
|
+
- Updated dependencies [5f53462]
|
|
1690
|
+
- Updated dependencies [69f7178]
|
|
1691
|
+
- Updated dependencies [f882bbc]
|
|
1692
|
+
- Updated dependencies [5f53462]
|
|
1693
|
+
- Updated dependencies [5f53462]
|
|
1694
|
+
- Updated dependencies [69f7178]
|
|
1695
|
+
- Updated dependencies [69f7178]
|
|
1696
|
+
- Updated dependencies [5f53462]
|
|
1697
|
+
- Updated dependencies [69f7178]
|
|
1698
|
+
- Updated dependencies [5f53462]
|
|
1699
|
+
- Updated dependencies [5f53462]
|
|
1700
|
+
- Updated dependencies [f882bbc]
|
|
1701
|
+
- Updated dependencies [69f7178]
|
|
1702
|
+
- Updated dependencies [69f7178]
|
|
1703
|
+
- Updated dependencies [ccf2ae2]
|
|
1704
|
+
- Updated dependencies [ccf2ae2]
|
|
1705
|
+
- Updated dependencies [ccf2ae2]
|
|
1706
|
+
- Updated dependencies [ccf2ae2]
|
|
1707
|
+
- Updated dependencies [5f53462]
|
|
1708
|
+
- Updated dependencies [ccf2ae2]
|
|
1709
|
+
- Updated dependencies [ccf2ae2]
|
|
1710
|
+
- Updated dependencies [ccf2ae2]
|
|
1711
|
+
- Updated dependencies [ccf2ae2]
|
|
1712
|
+
- Updated dependencies [5f53462]
|
|
1713
|
+
- Updated dependencies [c45ac6c]
|
|
1714
|
+
- Updated dependencies [69f7178]
|
|
1715
|
+
- Updated dependencies [69f7178]
|
|
1716
|
+
- Updated dependencies [5f53462]
|
|
1717
|
+
- Updated dependencies [5f53462]
|
|
1718
|
+
- Updated dependencies [2d8de1c]
|
|
1719
|
+
- Updated dependencies [5f53462]
|
|
1720
|
+
- Updated dependencies [8c31d54]
|
|
1721
|
+
- Updated dependencies [ccf2ae2]
|
|
1722
|
+
- Updated dependencies [5f53462]
|
|
1723
|
+
- Updated dependencies [5f53462]
|
|
1724
|
+
- Updated dependencies [ccf2ae2]
|
|
1725
|
+
- Updated dependencies [ccf2ae2]
|
|
1726
|
+
- Updated dependencies [ccf2ae2]
|
|
1727
|
+
- Updated dependencies [ccf2ae2]
|
|
1728
|
+
- Updated dependencies [ccf2ae2]
|
|
1729
|
+
- Updated dependencies [ccf2ae2]
|
|
1730
|
+
- Updated dependencies [ccf2ae2]
|
|
1731
|
+
- Updated dependencies [ccf2ae2]
|
|
1732
|
+
- Updated dependencies [ccf2ae2]
|
|
1733
|
+
- Updated dependencies [ccf2ae2]
|
|
1734
|
+
- Updated dependencies [ccf2ae2]
|
|
1735
|
+
- Updated dependencies [ccf2ae2]
|
|
1736
|
+
- Updated dependencies [ccf2ae2]
|
|
1737
|
+
- Updated dependencies [ccf2ae2]
|
|
1738
|
+
- Updated dependencies [5f53462]
|
|
1739
|
+
- Updated dependencies [ccf2ae2]
|
|
1740
|
+
- Updated dependencies [ccf2ae2]
|
|
1741
|
+
- Updated dependencies [ccf2ae2]
|
|
1742
|
+
- Updated dependencies [69f7178]
|
|
1743
|
+
- Updated dependencies [69f7178]
|
|
1744
|
+
- Updated dependencies [69f7178]
|
|
1745
|
+
- Updated dependencies [f882bbc]
|
|
1746
|
+
- Updated dependencies [2d8de1c]
|
|
1747
|
+
- Updated dependencies [69f7178]
|
|
1748
|
+
- Updated dependencies [5f53462]
|
|
1749
|
+
- Updated dependencies [22aa04f]
|
|
1750
|
+
- Updated dependencies [69f7178]
|
|
1751
|
+
- Updated dependencies [5f53462]
|
|
1752
|
+
- Updated dependencies [ccf2ae2]
|
|
1753
|
+
- Updated dependencies [2d8de1c]
|
|
1754
|
+
- Updated dependencies [69f7178]
|
|
1755
|
+
- Updated dependencies [2d8de1c]
|
|
1756
|
+
- Updated dependencies [2d8de1c]
|
|
1757
|
+
- Updated dependencies [2d8de1c]
|
|
1758
|
+
- Updated dependencies [f882bbc]
|
|
1759
|
+
- Updated dependencies [69f7178]
|
|
1760
|
+
- Updated dependencies [f882bbc]
|
|
1761
|
+
- Updated dependencies [f882bbc]
|
|
1762
|
+
- Updated dependencies [5f53462]
|
|
1763
|
+
- Updated dependencies [22aa04f]
|
|
1764
|
+
- Updated dependencies [69f7178]
|
|
1765
|
+
- Updated dependencies [2d8de1c]
|
|
1766
|
+
- Updated dependencies [ccf2ae2]
|
|
1767
|
+
- Updated dependencies [5f53462]
|
|
1768
|
+
- Updated dependencies [5f53462]
|
|
1769
|
+
- Updated dependencies [5f53462]
|
|
1770
|
+
- Updated dependencies [ccf2ae2]
|
|
1771
|
+
- Updated dependencies [f882bbc]
|
|
1772
|
+
- Updated dependencies [69f7178]
|
|
1773
|
+
- Updated dependencies [5f53462]
|
|
1774
|
+
- Updated dependencies [5f53462]
|
|
1775
|
+
- Updated dependencies [5f53462]
|
|
1776
|
+
- Updated dependencies [3ebd8e1]
|
|
1777
|
+
- Updated dependencies [326da07]
|
|
1778
|
+
- Updated dependencies [ccf2ae2]
|
|
1779
|
+
- Updated dependencies [ccf2ae2]
|
|
1780
|
+
- Updated dependencies [ccf2ae2]
|
|
1781
|
+
- Updated dependencies [ccf2ae2]
|
|
1782
|
+
- Updated dependencies [5f53462]
|
|
1783
|
+
- Updated dependencies [c45ac6c]
|
|
1784
|
+
- Updated dependencies [2d8de1c]
|
|
1785
|
+
- Updated dependencies [ccf2ae2]
|
|
1786
|
+
- Updated dependencies [ccf2ae2]
|
|
1787
|
+
- Updated dependencies [22aa04f]
|
|
1788
|
+
- Updated dependencies [f882bbc]
|
|
1789
|
+
- Updated dependencies [2d8de1c]
|
|
1790
|
+
- Updated dependencies [5f53462]
|
|
1791
|
+
- Updated dependencies [c45ac6c]
|
|
1792
|
+
- Updated dependencies [f882bbc]
|
|
1793
|
+
- Updated dependencies [2d8de1c]
|
|
1794
|
+
- Updated dependencies [ccf2ae2]
|
|
1795
|
+
- Updated dependencies [2d8de1c]
|
|
1796
|
+
- Updated dependencies [ccf2ae2]
|
|
1797
|
+
- Updated dependencies [f882bbc]
|
|
1798
|
+
- Updated dependencies [326da07]
|
|
1799
|
+
- Updated dependencies [ccf2ae2]
|
|
1800
|
+
- Updated dependencies [5f53462]
|
|
1801
|
+
- Updated dependencies [2d8de1c]
|
|
1802
|
+
- Updated dependencies [5f53462]
|
|
1803
|
+
- Updated dependencies [5f53462]
|
|
1804
|
+
- Updated dependencies [ccf2ae2]
|
|
1805
|
+
- Updated dependencies [2d8de1c]
|
|
1806
|
+
- Updated dependencies [2d8de1c]
|
|
1807
|
+
- Updated dependencies [2d8de1c]
|
|
1808
|
+
- Updated dependencies [f882bbc]
|
|
1809
|
+
- Updated dependencies [f882bbc]
|
|
1810
|
+
- Updated dependencies [69f7178]
|
|
1811
|
+
- Updated dependencies [2d8de1c]
|
|
1812
|
+
- Updated dependencies [5f53462]
|
|
1813
|
+
- Updated dependencies [5f53462]
|
|
1814
|
+
- Updated dependencies [5f53462]
|
|
1815
|
+
- Updated dependencies [ccf2ae2]
|
|
1816
|
+
- Updated dependencies [bf27773]
|
|
1817
|
+
- Updated dependencies [2d8de1c]
|
|
1818
|
+
- Updated dependencies [5f53462]
|
|
1819
|
+
- Updated dependencies [69f7178]
|
|
1820
|
+
- Updated dependencies [5f53462]
|
|
1821
|
+
- Updated dependencies [5f53462]
|
|
1822
|
+
- Updated dependencies [ccf2ae2]
|
|
1823
|
+
- Updated dependencies [ccf2ae2]
|
|
1824
|
+
- Updated dependencies [2d8de1c]
|
|
1825
|
+
- Updated dependencies [2d8de1c]
|
|
1826
|
+
- Updated dependencies [2d8de1c]
|
|
1827
|
+
- Updated dependencies [2d8de1c]
|
|
1828
|
+
- Updated dependencies [ccf2ae2]
|
|
1829
|
+
- Updated dependencies [5f53462]
|
|
1830
|
+
- Updated dependencies [ccf2ae2]
|
|
1831
|
+
- Updated dependencies [5f53462]
|
|
1832
|
+
- Updated dependencies [f882bbc]
|
|
1833
|
+
- akanjs@3.0.0
|
|
1834
|
+
|
|
3
1835
|
## 2.4.2
|
|
4
1836
|
|
|
5
1837
|
### Patch Changes
|