@akanjs/cli 3.0.0-alpha.87 → 3.0.0-alpha.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.build-stamp +1 -1
  2. package/{abstractCompactor-pwc6d6yk.js → abstractCompactor-cefxzggj.js} +2 -2
  3. package/{agent.command-180hh0ye.js → agent.command-r93wmq7m.js} +6 -6
  4. package/{application.command-v8dywdqh.js → application.command-5nqfqgdn.js} +9 -8
  5. package/{applicationBuildRunner-27wyznpv.js → applicationBuildRunner-b21m07fs.js} +4 -3
  6. package/{applicationReleasePackager-ktjt0erc.js → applicationReleasePackager-3wh58n2m.js} +2 -2
  7. package/buildBatch.proc.js +4 -4
  8. package/{capacitorApp-kwa67kzs.js → capacitorApp-gzcx6txb.js} +3 -3
  9. package/{cloud.command-12k4wy4x.js → cloud.command-g0pk0k60.js} +11 -11
  10. package/{context.command-dk2m2sjx.js → context.command-qd8kqrnh.js} +14 -14
  11. package/{dependencyScanner-c64m6fxp.js → dependencyScanner-shs82ynp.js} +2 -2
  12. package/devTui-2xf0meaq.js +552 -0
  13. package/devTuiView-7jy8f30g.js +9 -0
  14. package/{guideline.command-w6ddv13r.js → guideline.command-f38x8jhw.js} +4 -4
  15. package/guidelines/agentRule/agentRule.instruction.md +9 -1
  16. package/guidelines/conventions/conventions.instruction.md +30 -0
  17. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  18. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  19. package/guidelines/runtimeRule/runtimeRule.instruction.md +37 -0
  20. package/incrementalBuilder.proc.js +8 -5
  21. package/{index-5y7qba1c.js → index-0emb9eat.js} +3 -3
  22. package/{index-cd5x18qh.js → index-3vvbv974.js} +4 -4
  23. package/{index-v0s24t8j.js → index-5xdqxbn9.js} +3 -3
  24. package/{index-k86zc21w.js → index-9bwhxrqf.js} +6 -6
  25. package/{index-ft1sfjz0.js → index-cezy7yb3.js} +66 -7
  26. package/{index-a6c5pc4p.js → index-d61rcfa2.js} +614 -62
  27. package/{index-gz0c482j.js → index-deekbtkj.js} +6 -6
  28. package/{index-saheq2bs.js → index-e82dyeqy.js} +1 -1
  29. package/{index-j9vnq8tr.js → index-h759vjfa.js} +3 -3
  30. package/{index-z3c9mmw4.js → index-jydtyzva.js} +2 -2
  31. package/{index-mfm35dhh.js → index-kcxmv48c.js} +1 -1
  32. package/{index-6c3ex9qw.js → index-kfnr6c19.js} +4 -4
  33. package/{index-1kzsn94d.js → index-mpmyqm1y.js} +1 -1
  34. package/{index-wet2ytnr.js → index-n85fer99.js} +2 -2
  35. package/{index-b2nt0w0k.js → index-pgsswppj.js} +1 -1
  36. package/{index-m9c7k0gm.js → index-pvb4vdwr.js} +1 -1
  37. package/{index-ayy5ecax.js → index-q0y2jq9j.js} +13 -1
  38. package/{index-k1n0h7dk.js → index-q8sn827n.js} +254 -126
  39. package/{index-dq6wq1tv.js → index-r50qen0z.js} +10 -10
  40. package/{index-5n0hf8r9.js → index-rntcdss1.js} +3 -3
  41. package/{index-a8h184wr.js → index-sjak5v5b.js} +6 -6
  42. package/{index-x23688ar.js → index-w8kmx1b6.js} +8 -4
  43. package/{index-qcracvc9.js → index-y74k5anj.js} +3 -3
  44. package/{index-xxe90kqy.js → index-zkvpaggp.js} +2 -2
  45. package/index.js +20 -20
  46. package/{library.command-wpedct6c.js → library.command-em103wtn.js} +4 -4
  47. package/{localRegistry.command-3cew8f75.js → localRegistry.command-gz8apn4y.js} +10 -10
  48. package/{module.command-8n4kct3e.js → module.command-451pztkc.js} +7 -7
  49. package/{package.command-fcbd0b1d.js → package.command-rfhyf38t.js} +5 -5
  50. package/package.json +2 -2
  51. package/{page.command-x4379grq.js → page.command-kt19da5g.js} +4 -4
  52. package/{primitive.command-8etmnqhq.js → primitive.command-pgy69ere.js} +8 -8
  53. package/{quality.command-hfx7z8yr.js → quality.command-gpe2vqb5.js} +3 -3
  54. package/{repair.command-j0fs5qkz.js → repair.command-2sbrz187.js} +6 -6
  55. package/{scalar.command-2emk89eh.js → scalar.command-jcf4e5sv.js} +6 -6
  56. package/{subspace.command-nr32nm35.js → subspace.command-kqzqtk0p.js} +6 -6
  57. package/{workflow.command-cvpmzwjg.js → workflow.command-m4z25a2p.js} +11 -11
  58. package/{workspace.command-rqnms515.js → workspace.command-dqgj9zbk.js} +22 -22
@@ -0,0 +1,552 @@
1
+ // @bun
2
+ import {
3
+ openBrowser
4
+ } from "./index-d61rcfa2.js";
5
+
6
+ // pkgs/@akanjs/cli/application/devTui.tsx
7
+ import { render } from "ink";
8
+
9
+ // pkgs/@akanjs/cli/ui/DevTuiApp.tsx
10
+ import { Box, Text, useInput } from "ink";
11
+ import { useEffect, useState } from "react";
12
+ import { jsxDEV } from "react/jsx-dev-runtime";
13
+ var stateGlyph = {
14
+ starting: { glyph: "\u25D0", color: "yellow" },
15
+ ready: { glyph: "\u25CF", color: "green" },
16
+ restarting: { glyph: "\u25D0", color: "yellow" },
17
+ recovering: { glyph: "\u25D0", color: "yellow" },
18
+ suspended: { glyph: "\u25CC", color: "blue" },
19
+ failed: { glyph: "\u2717", color: "red" },
20
+ stopped: { glyph: "\u25CB", color: "gray" }
21
+ };
22
+ var Rail = ({ rows, selected, readyCount, appCount, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
23
+ flexDirection: "column",
24
+ width,
25
+ height,
26
+ borderStyle: "round",
27
+ borderColor: "gray",
28
+ paddingX: 1,
29
+ children: [
30
+ /* @__PURE__ */ jsxDEV(Text, {
31
+ dimColor: true,
32
+ children: [
33
+ "apps ",
34
+ readyCount,
35
+ "/",
36
+ appCount
37
+ ]
38
+ }, undefined, true, undefined, this),
39
+ /* @__PURE__ */ jsxDEV(Box, {
40
+ flexDirection: "column",
41
+ flexGrow: 1,
42
+ children: rows.map((row, idx) => {
43
+ const marker = row.state ? stateGlyph[row.state] : null;
44
+ return /* @__PURE__ */ jsxDEV(Text, {
45
+ inverse: idx === selected,
46
+ wrap: "truncate",
47
+ children: [
48
+ marker ? /* @__PURE__ */ jsxDEV(Text, {
49
+ color: marker.color,
50
+ children: marker.glyph
51
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
52
+ dimColor: true,
53
+ children: "\u25C7"
54
+ }, undefined, false, undefined, this),
55
+ /* @__PURE__ */ jsxDEV(Text, {
56
+ dimColor: true,
57
+ children: " ".repeat(row.depth * 2 + 1)
58
+ }, undefined, false, undefined, this),
59
+ row.appIndex === null ? null : /* @__PURE__ */ jsxDEV(Text, {
60
+ dimColor: true,
61
+ children: `${row.appIndex} `
62
+ }, undefined, false, undefined, this),
63
+ row.label,
64
+ row.port === null ? null : /* @__PURE__ */ jsxDEV(Text, {
65
+ dimColor: true,
66
+ children: ` ${row.port}`
67
+ }, undefined, false, undefined, this)
68
+ ]
69
+ }, `${row.app ?? "*"}/${row.source ?? "*"}`, true, undefined, this);
70
+ })
71
+ }, undefined, false, undefined, this)
72
+ ]
73
+ }, undefined, true, undefined, this);
74
+ var LogPane = ({ lines, title, above, below, following, prefix, prefixWidth, width, height }) => /* @__PURE__ */ jsxDEV(Box, {
75
+ flexDirection: "column",
76
+ width,
77
+ height,
78
+ borderStyle: "round",
79
+ borderColor: following ? "gray" : "cyan",
80
+ paddingX: 1,
81
+ children: [
82
+ /* @__PURE__ */ jsxDEV(Text, {
83
+ wrap: "truncate",
84
+ children: [
85
+ title,
86
+ following ? null : /* @__PURE__ */ jsxDEV(Text, {
87
+ color: "cyan",
88
+ children: ` \u25B2${above} \u25BC${below} paused`
89
+ }, undefined, false, undefined, this)
90
+ ]
91
+ }, undefined, true, undefined, this),
92
+ /* @__PURE__ */ jsxDEV(Box, {
93
+ flexDirection: "column",
94
+ flexGrow: 1,
95
+ children: lines.map((line) => /* @__PURE__ */ jsxDEV(Text, {
96
+ wrap: "truncate",
97
+ children: [
98
+ prefix === "none" ? null : /* @__PURE__ */ jsxDEV(Text, {
99
+ dimColor: true,
100
+ children: `${(prefix === "app" ? line.app : line.source).padEnd(prefixWidth)} \u2502 `
101
+ }, undefined, false, undefined, this),
102
+ line.text
103
+ ]
104
+ }, line.seq, true, undefined, this))
105
+ }, undefined, false, undefined, this)
106
+ ]
107
+ }, undefined, true, undefined, this);
108
+ var DevTuiApp = ({ actions }) => {
109
+ const [snapshot, setSnapshot] = useState(actions.snapshot);
110
+ useEffect(() => {
111
+ const unsubscribe = actions.subscribe(() => setSnapshot(actions.snapshot()));
112
+ setSnapshot(actions.snapshot());
113
+ return unsubscribe;
114
+ }, [actions]);
115
+ useInput((input, key) => {
116
+ if (snapshot.editingGrep) {
117
+ if (key.escape) {
118
+ actions.setGrep("");
119
+ actions.setEditingGrep(false);
120
+ } else if (key.return)
121
+ actions.setEditingGrep(false);
122
+ else if (key.backspace || key.delete)
123
+ actions.setGrep(snapshot.grep.slice(0, -1));
124
+ else if (input && !key.ctrl && !key.meta)
125
+ actions.setGrep(snapshot.grep + input);
126
+ return;
127
+ }
128
+ const page = Math.max(1, snapshot.logRows - 1);
129
+ if (input === "q" || key.ctrl && input === "c")
130
+ actions.quit();
131
+ else if (key.upArrow)
132
+ actions.scroll(key.shift ? -page : -1);
133
+ else if (key.downArrow)
134
+ actions.scroll(key.shift ? page : 1);
135
+ else if (key.pageUp)
136
+ actions.scroll(-page);
137
+ else if (key.pageDown)
138
+ actions.scroll(page);
139
+ else if (input === "G" || input === "f")
140
+ actions.follow();
141
+ else if (key.tab)
142
+ actions.moveSelection(key.shift ? -1 : 1);
143
+ else if (input === "/")
144
+ actions.setEditingGrep(true);
145
+ else if (input === "e")
146
+ actions.toggleErrorsOnly();
147
+ else if (input === "c")
148
+ actions.clear();
149
+ else if (input === "o")
150
+ actions.openSelected();
151
+ else if (input === "r")
152
+ actions.restartSelected();
153
+ else if (/^[1-9]$/.test(input))
154
+ actions.selectApp(Number(input) - 1);
155
+ });
156
+ return /* @__PURE__ */ jsxDEV(Box, {
157
+ flexDirection: "column",
158
+ width: snapshot.columns,
159
+ height: snapshot.terminalRows,
160
+ children: [
161
+ /* @__PURE__ */ jsxDEV(Box, {
162
+ flexDirection: "row",
163
+ height: snapshot.bodyHeight,
164
+ children: [
165
+ /* @__PURE__ */ jsxDEV(Rail, {
166
+ rows: snapshot.rows,
167
+ selected: snapshot.selected,
168
+ readyCount: snapshot.readyCount,
169
+ appCount: snapshot.appCount,
170
+ width: snapshot.railWidth,
171
+ height: snapshot.bodyHeight
172
+ }, undefined, false, undefined, this),
173
+ /* @__PURE__ */ jsxDEV(LogPane, {
174
+ lines: snapshot.lines,
175
+ title: snapshot.title,
176
+ above: snapshot.above,
177
+ below: snapshot.below,
178
+ following: snapshot.following,
179
+ prefix: snapshot.prefix,
180
+ prefixWidth: snapshot.prefixWidth,
181
+ width: Math.max(20, snapshot.columns - snapshot.railWidth),
182
+ height: snapshot.bodyHeight
183
+ }, undefined, false, undefined, this)
184
+ ]
185
+ }, undefined, true, undefined, this),
186
+ snapshot.editingGrep ? /* @__PURE__ */ jsxDEV(Text, {
187
+ children: [
188
+ /* @__PURE__ */ jsxDEV(Text, {
189
+ color: "cyan",
190
+ children: "grep "
191
+ }, undefined, false, undefined, this),
192
+ snapshot.grep,
193
+ /* @__PURE__ */ jsxDEV(Text, {
194
+ inverse: true,
195
+ children: " "
196
+ }, undefined, false, undefined, this),
197
+ /* @__PURE__ */ jsxDEV(Text, {
198
+ dimColor: true,
199
+ children: " enter to apply \xB7 esc to clear"
200
+ }, undefined, false, undefined, this)
201
+ ]
202
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
203
+ dimColor: true,
204
+ wrap: "truncate",
205
+ children: `Tab next \xB7 1-9 app \xB7 \u2191\u2193 scroll (shift page) \xB7 G follow${snapshot.following ? "" : " (paused)"} \xB7 / grep${snapshot.grep ? ` (${snapshot.grep})` : ""} \xB7 e errors${snapshot.errorsOnly ? " on" : ""} \xB7 c clear \xB7 o open \xB7 r restart \xB7 q quit`
206
+ }, undefined, false, undefined, this)
207
+ ]
208
+ }, undefined, true, undefined, this);
209
+ };
210
+
211
+ // pkgs/@akanjs/cli/application/devLogBuffer.ts
212
+ var ansi = new RegExp(`${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[\\s\\S]*?[@-~])`, "g");
213
+ var stripAnsi = (text) => text.replace(ansi, "");
214
+ var HOST_SOURCE = "host";
215
+ var childPrefix = /^\[child:(\d+) ([a-z-]+)\] \[(?:stdout|stderr)\] /;
216
+ var sourceOf = (text) => {
217
+ const match = childPrefix.exec(text);
218
+ return match ? `#${match[1]} ${match[2]}` : HOST_SOURCE;
219
+ };
220
+
221
+ class DevLogBuffer {
222
+ static defaultLimit = 5000;
223
+ #limit;
224
+ #lines = [];
225
+ #partial = new Map;
226
+ #sources = new Map;
227
+ #seq = 0;
228
+ constructor({ limit = DevLogBuffer.defaultLimit } = {}) {
229
+ this.#limit = Math.max(1, limit);
230
+ }
231
+ get size() {
232
+ return this.#lines.length;
233
+ }
234
+ sourcesOf(app) {
235
+ return [...this.#sources.get(app) ?? []];
236
+ }
237
+ push(app, kind, chunk) {
238
+ const key = `${app}:${kind}`;
239
+ const parts = `${this.#partial.get(key) ?? ""}${chunk}`.split(`
240
+ `);
241
+ this.#partial.set(key, parts.pop() ?? "");
242
+ return this.#append(app, kind, parts);
243
+ }
244
+ flushPartials() {
245
+ const flushed = [];
246
+ for (const [key, remainder] of [...this.#partial]) {
247
+ this.#partial.set(key, "");
248
+ if (!remainder)
249
+ continue;
250
+ const split = key.lastIndexOf(":");
251
+ const app = key.slice(0, split);
252
+ const kind = key.slice(split + 1) === "stderr" ? "stderr" : "stdout";
253
+ flushed.push(...this.#append(app, kind, [remainder]));
254
+ }
255
+ return flushed;
256
+ }
257
+ select({ app = null, source = null, grep, errorsOnly = false } = {}) {
258
+ const needle = grep?.trim().toLowerCase();
259
+ return this.#lines.filter((line) => {
260
+ if (app !== null && line.app !== app)
261
+ return false;
262
+ if (source !== null && line.source !== source)
263
+ return false;
264
+ if (errorsOnly && line.kind !== "stderr")
265
+ return false;
266
+ if (needle && !stripAnsi(line.text).toLowerCase().includes(needle))
267
+ return false;
268
+ return true;
269
+ });
270
+ }
271
+ clear(app = null) {
272
+ const kept = app === null ? [] : this.#lines.filter((line) => line.app !== app);
273
+ this.#lines.length = 0;
274
+ this.#lines.push(...kept);
275
+ }
276
+ #append(app, kind, texts) {
277
+ const known = this.#sources.get(app) ?? new Set;
278
+ this.#sources.set(app, known);
279
+ const added = texts.map((text) => {
280
+ const source = sourceOf(text);
281
+ known.add(source);
282
+ return { seq: ++this.#seq, app, source, kind, text };
283
+ });
284
+ this.#lines.push(...added);
285
+ if (this.#lines.length > this.#limit)
286
+ this.#lines.splice(0, this.#lines.length - this.#limit);
287
+ return added;
288
+ }
289
+ }
290
+
291
+ // pkgs/@akanjs/cli/application/devLogWindow.ts
292
+ var windowOf = (lines, rows, anchor) => {
293
+ const height = Math.max(1, Math.trunc(rows));
294
+ const end = anchor === null ? lines.length : endOf(lines, height, anchor);
295
+ const start = Math.max(0, end - height);
296
+ return {
297
+ lines: lines.slice(start, end),
298
+ above: start,
299
+ below: lines.length - end,
300
+ following: anchor === null
301
+ };
302
+ };
303
+ var scrollAnchor = (lines, rows, anchor, delta) => {
304
+ const height = Math.max(1, Math.trunc(rows));
305
+ if (lines.length <= height)
306
+ return null;
307
+ const end = anchor === null ? lines.length : endOf(lines, height, anchor);
308
+ const next = Math.min(lines.length, Math.max(height, end + Math.trunc(delta)));
309
+ if (next >= lines.length)
310
+ return null;
311
+ return lines[next - 1]?.seq ?? null;
312
+ };
313
+ var endOf = (lines, height, anchor) => {
314
+ for (let idx = lines.length - 1;idx >= 0; idx -= 1) {
315
+ const line = lines[idx];
316
+ if (line && line.seq <= anchor)
317
+ return idx + 1;
318
+ }
319
+ return Math.min(lines.length, height);
320
+ };
321
+
322
+ // pkgs/@akanjs/cli/application/devTui.tsx
323
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
324
+
325
+ class DevTui {
326
+ static frameMs = 34;
327
+ static footerRows = 1;
328
+ static paneChromeRows = 3;
329
+ #buffer = new DevLogBuffer;
330
+ #listeners = new Set;
331
+ #supervisor;
332
+ #instance;
333
+ #statuses = [];
334
+ #target = { app: null, source: null };
335
+ #anchor = null;
336
+ #grep = "";
337
+ #editingGrep = false;
338
+ #errorsOnly = false;
339
+ #frameTimer = null;
340
+ #cachedSnapshot = null;
341
+ constructor(supervisor) {
342
+ this.#supervisor = supervisor;
343
+ this.#statuses = supervisor.statuses;
344
+ process.stdout.on("resize", this.#onResize);
345
+ this.#instance = render(/* @__PURE__ */ jsxDEV2(DevTuiApp, {
346
+ actions: {
347
+ subscribe: this.#subscribe,
348
+ snapshot: this.#snapshot,
349
+ moveSelection: this.#moveSelection,
350
+ selectApp: this.#selectApp,
351
+ scroll: this.#scroll,
352
+ follow: this.#follow,
353
+ setGrep: this.#setGrep,
354
+ setEditingGrep: this.#setEditingGrep,
355
+ toggleErrorsOnly: this.#toggleErrorsOnly,
356
+ clear: this.#clear,
357
+ openSelected: this.#openSelected,
358
+ restartSelected: this.#restartSelected,
359
+ quit: this.#quit
360
+ }
361
+ }, undefined, false, undefined, this), { patchConsole: false });
362
+ }
363
+ onOutput = (app, kind, text) => {
364
+ this.#buffer.push(app, kind, text);
365
+ this.#scheduleFrame();
366
+ };
367
+ onStatus = (statuses) => {
368
+ this.#statuses = statuses;
369
+ this.#scheduleFrame();
370
+ };
371
+ onNote = (text, level) => {
372
+ this.#buffer.push("akan", level === "warn" ? "stderr" : "stdout", `${text}
373
+ `);
374
+ this.#scheduleFrame();
375
+ };
376
+ waitForExit = async () => {
377
+ await this.#instance.waitUntilExit();
378
+ };
379
+ close = () => {
380
+ if (this.#frameTimer)
381
+ clearTimeout(this.#frameTimer);
382
+ this.#frameTimer = null;
383
+ process.stdout.off("resize", this.#onResize);
384
+ this.#instance.unmount();
385
+ for (const line of this.#buffer.flushPartials())
386
+ process.stdout.write(`${line.app} \u2502 ${line.text}
387
+ `);
388
+ for (const status of this.#statuses)
389
+ process.stdout.write(`${status.name} ${status.url} \u2014 ${status.state}
390
+ `);
391
+ };
392
+ #subscribe = (listener) => {
393
+ this.#listeners.add(listener);
394
+ return () => {
395
+ this.#listeners.delete(listener);
396
+ };
397
+ };
398
+ #railRows() {
399
+ const rows = [
400
+ { app: null, source: null, label: "all apps", state: null, port: null, depth: 0, appIndex: null }
401
+ ];
402
+ this.#statuses.forEach((status, idx) => {
403
+ rows.push({
404
+ app: status.name,
405
+ source: null,
406
+ label: status.name,
407
+ state: status.state,
408
+ port: status.port,
409
+ depth: 0,
410
+ appIndex: idx < 9 ? idx + 1 : null
411
+ });
412
+ if (status.name !== this.#target.app)
413
+ return;
414
+ for (const source of this.#buffer.sourcesOf(status.name))
415
+ rows.push({ app: status.name, source, label: source, state: null, port: null, depth: 1, appIndex: null });
416
+ });
417
+ return rows;
418
+ }
419
+ #indexOfTarget(rows) {
420
+ const found = rows.findIndex((row) => row.app === this.#target.app && row.source === this.#target.source);
421
+ return found < 0 ? 0 : found;
422
+ }
423
+ #filtered(target) {
424
+ return this.#buffer.select({
425
+ app: target.app ?? null,
426
+ source: target.source ?? null,
427
+ grep: this.#grep,
428
+ errorsOnly: this.#errorsOnly
429
+ });
430
+ }
431
+ #snapshot = () => {
432
+ if (this.#cachedSnapshot)
433
+ return this.#cachedSnapshot;
434
+ const terminalRows = Math.max(8, process.stdout.rows ?? 24);
435
+ const columns = Math.max(40, process.stdout.columns ?? 80);
436
+ const bodyHeight = Math.max(DevTui.paneChromeRows + 1, terminalRows - DevTui.footerRows);
437
+ const logRows = Math.max(1, bodyHeight - DevTui.paneChromeRows);
438
+ const rows = this.#railRows();
439
+ const view = windowOf(this.#filtered(this.#target), logRows, this.#anchor);
440
+ const status = this.#statuses.find((candidate) => candidate.name === this.#target.app);
441
+ const railLabelWidth = rows.reduce((max, row) => Math.max(max, row.label.length + row.depth * 2), 0);
442
+ this.#cachedSnapshot = {
443
+ rows,
444
+ selected: this.#indexOfTarget(rows),
445
+ title: status ? `${status.name}${this.#target.source ? ` \xB7 ${this.#target.source}` : ""} \xB7 ${status.url} \xB7 ${status.state}${status.detail ? ` (${status.detail})` : ""}` : "all apps",
446
+ lines: view.lines,
447
+ above: view.above,
448
+ below: view.below,
449
+ following: view.following,
450
+ prefix: this.#target.app === null ? "app" : this.#target.source === null ? "source" : "none",
451
+ prefixWidth: this.#target.app === null ? this.#appWidth() : this.#sourceWidth(),
452
+ grep: this.#grep,
453
+ errorsOnly: this.#errorsOnly,
454
+ editingGrep: this.#editingGrep,
455
+ readyCount: this.#statuses.filter((candidate) => candidate.state === "ready").length,
456
+ appCount: this.#statuses.length,
457
+ logRows,
458
+ bodyHeight,
459
+ railWidth: Math.min(34, Math.max(20, railLabelWidth + 10)),
460
+ columns,
461
+ terminalRows
462
+ };
463
+ return this.#cachedSnapshot;
464
+ };
465
+ #appWidth() {
466
+ return this.#statuses.reduce((max, status) => Math.max(max, status.name.length), 0);
467
+ }
468
+ #sourceWidth() {
469
+ let width = HOST_SOURCE.length;
470
+ for (const status of this.#statuses)
471
+ for (const source of this.#buffer.sourcesOf(status.name))
472
+ width = Math.max(width, source.length);
473
+ return width;
474
+ }
475
+ #scheduleFrame() {
476
+ if (this.#frameTimer)
477
+ return;
478
+ this.#frameTimer = setTimeout(() => {
479
+ this.#frameTimer = null;
480
+ this.#renderNow();
481
+ }, DevTui.frameMs);
482
+ }
483
+ #renderNow() {
484
+ this.#cachedSnapshot = null;
485
+ for (const listener of this.#listeners)
486
+ listener();
487
+ }
488
+ #onResize = () => this.#renderNow();
489
+ #moveSelection = (delta) => {
490
+ const rows = this.#railRows();
491
+ const next = ((this.#indexOfTarget(rows) + delta) % rows.length + rows.length) % rows.length;
492
+ const row = rows[next];
493
+ if (row)
494
+ this.#setTarget({ app: row.app, source: row.source });
495
+ };
496
+ #selectApp = (index) => {
497
+ const status = this.#statuses[index];
498
+ if (status)
499
+ this.#setTarget({ app: status.name, source: null });
500
+ };
501
+ #setTarget(target) {
502
+ this.#target = { app: target.app ?? null, source: target.source ?? null };
503
+ this.#anchor = null;
504
+ this.#renderNow();
505
+ }
506
+ #scroll = (delta) => {
507
+ this.#anchor = scrollAnchor(this.#filtered(this.#target), this.#snapshot().logRows, this.#anchor, delta);
508
+ this.#renderNow();
509
+ };
510
+ #follow = () => {
511
+ this.#anchor = null;
512
+ this.#renderNow();
513
+ };
514
+ #setGrep = (grep) => {
515
+ this.#grep = grep;
516
+ this.#anchor = null;
517
+ this.#renderNow();
518
+ };
519
+ #setEditingGrep = (editing) => {
520
+ this.#editingGrep = editing;
521
+ this.#renderNow();
522
+ };
523
+ #toggleErrorsOnly = () => {
524
+ this.#errorsOnly = !this.#errorsOnly;
525
+ this.#anchor = null;
526
+ this.#renderNow();
527
+ };
528
+ #clear = () => {
529
+ this.#buffer.clear(this.#target.app ?? null);
530
+ this.#anchor = null;
531
+ this.#renderNow();
532
+ };
533
+ #openSelected = () => {
534
+ const status = this.#selectedStatus();
535
+ if (status)
536
+ openBrowser(status.url);
537
+ };
538
+ #restartSelected = () => {
539
+ const status = this.#selectedStatus();
540
+ if (status)
541
+ this.#supervisor.restart(status.name);
542
+ };
543
+ #selectedStatus() {
544
+ return this.#statuses.find((candidate) => candidate.name === this.#target.app);
545
+ }
546
+ #quit = () => {
547
+ this.#instance.unmount();
548
+ };
549
+ }
550
+ export {
551
+ DevTui
552
+ };
@@ -0,0 +1,9 @@
1
+ // @bun
2
+ // pkgs/@akanjs/cli/application/devTuiView.ts
3
+ var createDevTuiView = async (supervisor) => {
4
+ const { DevTui } = await import("./devTui-2xf0meaq.js");
5
+ return new DevTui(supervisor);
6
+ };
7
+ export {
8
+ createDevTuiView
9
+ };
@@ -4,11 +4,11 @@ import {
4
4
  runner,
5
5
  script,
6
6
  command
7
- } from "./index-ft1sfjz0.js";
8
- import"./index-k1n0h7dk.js";
7
+ } from "./index-cezy7yb3.js";
9
8
  import {
10
9
  FileSys
11
- } from "./index-ayy5ecax.js";
10
+ } from "./index-q0y2jq9j.js";
11
+ import"./index-q8sn827n.js";
12
12
  import {
13
13
  getDirname2
14
14
  } from "./index-6wwzs1bc.js";
@@ -17,7 +17,7 @@ import {
17
17
  } from "./index-h7yw2z9n.js";
18
18
  import {
19
19
  AiSession
20
- } from "./index-wet2ytnr.js";
20
+ } from "./index-n85fer99.js";
21
21
 
22
22
  // pkgs/@akanjs/cli/guideline/guideline.script.ts
23
23
  import { Logger } from "akanjs/common";
@@ -46,7 +46,7 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
46
46
  already titles, and `defaultDraft` opens the composer with text in it without sending it. A panel driven by a
47
47
  controlled `open` with **no** `onOpenChange` draws no close button at all rather than an inert one. Then the
48
48
  slots: `AgentLauncher`, `AgentBubble`, `AgentComposer`, `AgentApproval`,
49
- `AgentQuestion`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
49
+ `AgentQuestion`, `AgentQueued`, `AgentMenu`, `AgentMarkdown` and `AgentCode` each replace one part in `_overrides.tsx`, and
50
50
  `akanjs/ui` exports every default beside them (`DefaultBubble`, `DefaultComposer`, …) so a skin composes the
51
51
  one it is replacing. `AgentCode` is where a highlighter binds — the fence's language reaches it — and a
52
52
  replacement for `AgentBubble` carries its own `memo`, since the transcript re-renders on every delta. Only then
@@ -59,6 +59,14 @@ apps and libs never import it directly (`no-import-external-library`) — everyt
59
59
  it scrolls. The vertical arrows still walk what was sent, but only from the first or last line — anywhere else
60
60
  the caret is the textarea's own. On a phone the panel is the whole screen (it is a card from `sm:` up) and it
61
61
  lifts above the on-screen keyboard, which only `visualViewport` reports.
62
+ **Enter during a running turn parks the message and sends it the moment the turn ends** — the Queue button
63
+ beside Stop does the same, and the placeholder says so. One slot, not a list: a second send joins the first on
64
+ a new line, so the model is handed one user message. The card above the composer (`AgentQueued`) shows what is
65
+ waiting with two ways out — ✎ takes it back into the composer, ahead of whatever was typed since, and ✕ drops it.
66
+ **Stop hands a parked message back to the composer rather than opening the next turn with it**, so Stop means
67
+ stop; `/new` drops it along with the conversation it was written for, the way it drops staged files. A `/prompt`
68
+ parks like text and runs when its turn comes; the built-ins (`/new`, `/copy`, …) never park, and a question the
69
+ agent is waiting on still takes the composer as its answer.
62
70
  - **The LLM is configured in `option.ts`, never through the environment.** `option.setLlm({ apiKey, model, host })`
63
71
  — or `setLlm((options) => …)` to read the key out of the app's own env object, which is where a secret belongs —
64
72
  fills whichever adaptor holds `LlmAdaptorRole`, reaching it as the `llmOption` use. The settings are the role's
@@ -302,6 +302,14 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
302
302
  The RSC worker is always its own process (`--conditions react-server`). A solo process answers
303
303
  `/_akan/app/health`, `/_akan/app/metrics` and `/_akan/bench/ping` in the gateway's own shape, and owns the
304
304
  rotating log file; nothing supervises it but the orchestrator's probes.
305
+ - **Route prefixes move together or not at all.** `new AkanApp({ prefix, websocketPrefix })` in `main.ts` moves
306
+ the server's routes, the gateway's websocket upgrade and — through the SSR bootstrap script — the
307
+ `fetchClient` in every tab the server renders. A prebuilt CSR shell or a Capacitor bundle is never rendered by
308
+ a server, so `api: { prefix, websocketPrefix }` in `akan.config.ts` is what those follow; declare it there too
309
+ when the app ships either. Read the value with `getApiPrefix()` / `getWsPrefix()` from `akanjs/base` — never
310
+ write `/api` or `/ws` as a literal, and never try to hand it down as a React prop: `FetchClient` fixes its
311
+ origin before the first component renders. A blank prefix, a bare `/`, and one that shadows a basePath are all
312
+ refused.
305
313
  - **`main.ts` imports `AkanApp` from `akanjs/server/akanApp`, not the barrel** — the barrel re-exports
306
314
  `AkanServer`, whose graph the gateway never runs. Keep entrypoint imports at the leaf.
307
315
  - **Every `Logger` call builds a `LogRecord`** carrying `traceId`, `endpoint` and `origin` alongside the message,
@@ -338,6 +346,15 @@ Full contract: `get_guideline` with `runtimeRule`, or `akan guideline show runti
338
346
  - Avoid hooks. `useState` is for modal-open, tab, draft-input, and drag state only — never for server data. `useEffect` must be a genuine effect such as subscribe-with-cleanup or one-shot init. Prefer `Tab` over a `useState` mode switch. `.Template.tsx` files contain zero `useState`.
339
347
  - Forms are entirely store-driven: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, the setter passed by reference. Always use `Field.*`, never a bare `<input>` for a model field. Nested rows use `st.do.writeOnX("payments.3.name", v)` plus the generated `add<Field>OnX` / `sub<Field>OnX`. **Passing the setter by reference is also what makes the framework emit `data-akan-action` / `data-akan-state`** on the control — the annotation an in-page agent, an E2E selector, and an external browser agent all read. Wrapping it in an inline arrow (`onChange={(v) => st.do.setFieldOnX(v)}`) silently drops that: a closure the caller wrote says nothing about what it does. Never hand-write a `data-akan-*` attribute.
340
348
  - Read with `st.use.*` and write with `st.do.*`. Client components do not call `fetch.*`.
349
+ - **An edit shell recovers its own form — never persist form values yourself.** `Load.Edit`, `Model.EditModal` and
350
+ `Model.New` save the whole `<model>Form` as the user types and offer it back on the next open, so a modal closed
351
+ by accident, a route change, or a killed app costs nothing. The scope is the record id for an edit and the seed
352
+ plus the route for a new form, both keyed to the signed-in user, so another record or another parent never loads
353
+ someone else's half-finished form. A new form's draft is applied on open with a "start over" chip; an edit whose
354
+ record moved in the meantime is *not* applied, and asks. Turn it off with `draft={false}`, or name the scope
355
+ yourself with `draft="<scope>"` when the context is in neither the id nor the seed. `field.secret` and
356
+ `field.hidden` values are never saved. The old per-field `cache` / `cacheKey` props are deprecated and store
357
+ nothing — they covered five control types, keyed on the translated label, and restored over server data.
341
358
  - Static class strings stay plain strings. Reach for `cn` only for a conditional or to merge an incoming `className`, and merge the caller last: `cn("base classes", cond && "extra", className)`. `cn` comes from `akanjs/client` (token-aware tailwind-merge) and is the only class-combining function — no `clsx` (removed), no raw `twMerge` imports, no object syntax (`{ x: cond }` → `cond && "x"`).
342
359
  - Multi-slot components take extra named props (`wrapperClassName`, `bodyClassName`), never a `classNames` object.
343
360
  - Hoist enum→class lookups to a module-scope `as const` map typed `{ [key in cnst.XStatus["value"]]: string }`. Do not use `Record<...>`. Escalate the map to `webkit/` when a second module needs it.
@@ -837,6 +854,19 @@ when two shapes disagree.
837
854
  ## Application Test Commands
838
855
 
839
856
  - After changing application source code, test the app with `bun run akan start <appName>`.
857
+ - **`akan start` takes several apps** — `akan start a,b`, `akan start a b`, `akan start all`, or no argument at
858
+ all for a checkbox that remembers the last pick.
859
+ - **It opens a full-screen view by default, at any app count.** A rail of apps with the processes inside each
860
+ one — `host` for the dev host, gateway and RSC worker, plus a row per replica the gateway forwards — and the
861
+ selected one's log beside it. `Tab` walks every row and `1`-`9` jump to that app (the rail shows each
862
+ app's digit), `↑↓` scroll (shift for a page, `G` back to following),
863
+ `/` greps, `e` shows stderr only, `c` clears, `o` opens the browser, `r` restarts that app, `q` quits.
864
+ **`--plain` prints prefixed interleaved lines instead**, and a pipe, a redirect or an unsized terminal
865
+ downgrades to that on its own.
866
+ - `--concurrency <n>` boots more than one app at a time; the default of 1 is what keeps two cold builder peaks
867
+ from overlapping. `--kill` frees the dev ports first — it resolves each port's listener, walks up to the top
868
+ of that akan dev tree and signals it, so another checkout's server or a stale orphan on the same port is
869
+ reclaimed. A holder that is not recognisably an akan process is reported and left alone.
840
870
  - Test production build generation with `bun run akan build <appName>`.
841
871
  - To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
842
872
 
@@ -21,6 +21,18 @@ Generate client state coordination for a module. Store connects generated fetch
21
21
  - Do not perform persistence logic in React components when a store action can own it.
22
22
  - Do not call private or internal signal operations from UI state.
23
23
 
24
+ ## Form Draft Recovery
25
+ - `<model>FormDraft`, `restore<Model>FormDraft`, `discard<Model>FormDraft` and `load<Model>FormDraft` are
26
+ generated. Do not hand-write a draft action, and do not write `<model>Form` to browser storage yourself.
27
+ - The draft key carries the signed-in user, derived by hashing the auth token's payload with `iat`, `exp`, `nbf`
28
+ and `jti` removed. **Do not put a claim that changes on every issue into the JWT.** One would rotate the key on
29
+ every silent token refresh, and the symptom is a form that never recovers anything: the draft was written under
30
+ a key nothing looks for again, and it lingers until the seven-day sweep.
31
+ - `field.secret` and `field.hidden` values are never written to a draft, so they come back empty. That is the
32
+ intended trade — a user retypes a password — and is not something to work around.
33
+ - A model whose form holds a large `field.visual` body can pass the 256KB per-draft cap, and a draft over it is
34
+ dropped with a warning rather than truncated.
35
+
24
36
  ## Review Checklist
25
37
  - The instruction points to current docs pages, not removed docs routes.
26
38
  - Generated examples use current Akan builder APIs and scanner-friendly filenames.