@bastani/atomic 0.9.40-alpha.1 → 0.9.40

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 (35) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +14 -0
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/CHANGELOG.md +31 -0
  6. package/dist/builtin/subagents/package.json +1 -1
  7. package/dist/builtin/web-access/CHANGELOG.md +12 -0
  8. package/dist/builtin/web-access/README.md +1 -1
  9. package/dist/builtin/web-access/index.bundle.mjs +2 -5
  10. package/dist/builtin/web-access/package.json +1 -1
  11. package/dist/builtin/workflows/CHANGELOG.md +25 -0
  12. package/dist/builtin/workflows/package.json +1 -1
  13. package/dist/builtin/workflows/src/extension/index.bundle.mjs +11 -2
  14. package/dist/cli/args.js +1 -1
  15. package/dist/cli/args.js.map +1 -1
  16. package/dist/core/export-html/index.d.ts.map +1 -1
  17. package/dist/core/export-html/index.js +3 -2
  18. package/dist/core/export-html/index.js.map +1 -1
  19. package/dist/core/export-html/template.html +1 -0
  20. package/dist/core/export-html/template.js +37 -20
  21. package/dist/core/export-html/vendor/marked-cjk-friendly.min.js +6 -0
  22. package/dist/core/export-html/vendor/marked.min.js +76 -3
  23. package/docs/providers.md +1 -1
  24. package/docs/settings.md +1 -1
  25. package/npm-shrinkwrap.json +35 -35
  26. package/package.json +5 -5
  27. package/dist/core/export-html/template-js/data-tree.js +0 -287
  28. package/dist/core/export-html/template-js/entries-navigation.js +0 -434
  29. package/dist/core/export-html/template-js/initialization.js +0 -335
  30. package/dist/core/export-html/template-js/message-tools.js +0 -428
  31. package/dist/core/export-html/template-js/tree-filter-render.js +0 -419
  32. package/dist/core/export-html/template-script.d.ts +0 -3
  33. package/dist/core/export-html/template-script.d.ts.map +0 -1
  34. package/dist/core/export-html/template-script.js +0 -13
  35. package/dist/core/export-html/template-script.js.map +0 -1
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.40-alpha.1",
3
+ "version": "0.9.40",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bastani/atomic",
9
- "version": "0.9.40-alpha.1",
9
+ "version": "0.9.40",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@bastani/atomic-natives": "0.9.40-alpha.1",
13
- "@bastani/pi-ai": "0.9.40-alpha.1",
12
+ "@bastani/atomic-natives": "0.9.40",
13
+ "@bastani/pi-ai": "0.9.40",
14
14
  "@earendil-works/pi-agent-core": "1.3.0",
15
15
  "@earendil-works/pi-codemode": "1.3.0",
16
16
  "@earendil-works/pi-tui": "1.3.0",
@@ -427,18 +427,18 @@
427
427
  }
428
428
  },
429
429
  "node_modules/@bastani/atomic-natives": {
430
- "version": "0.9.40-alpha.1",
431
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.40-alpha.1.tgz",
430
+ "version": "0.9.40",
431
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.40.tgz",
432
432
  "license": "MIT",
433
433
  "optionalDependencies": {
434
- "@bastani/atomic-natives-darwin-arm64": "0.9.40-alpha.1",
435
- "@bastani/atomic-natives-darwin-x64": "0.9.40-alpha.1",
436
- "@bastani/atomic-natives-linux-arm64-gnu": "0.9.40-alpha.1",
437
- "@bastani/atomic-natives-linux-arm64-musl": "0.9.40-alpha.1",
438
- "@bastani/atomic-natives-linux-x64-gnu": "0.9.40-alpha.1",
439
- "@bastani/atomic-natives-linux-x64-musl": "0.9.40-alpha.1",
440
- "@bastani/atomic-natives-win32-arm64-msvc": "0.9.40-alpha.1",
441
- "@bastani/atomic-natives-win32-x64-msvc": "0.9.40-alpha.1"
434
+ "@bastani/atomic-natives-darwin-arm64": "0.9.40",
435
+ "@bastani/atomic-natives-darwin-x64": "0.9.40",
436
+ "@bastani/atomic-natives-linux-arm64-gnu": "0.9.40",
437
+ "@bastani/atomic-natives-linux-arm64-musl": "0.9.40",
438
+ "@bastani/atomic-natives-linux-x64-gnu": "0.9.40",
439
+ "@bastani/atomic-natives-linux-x64-musl": "0.9.40",
440
+ "@bastani/atomic-natives-win32-arm64-msvc": "0.9.40",
441
+ "@bastani/atomic-natives-win32-x64-msvc": "0.9.40"
442
442
  },
443
443
  "engines": {
444
444
  "bun": ">=1.4.2",
@@ -446,8 +446,8 @@
446
446
  }
447
447
  },
448
448
  "node_modules/@bastani/atomic-natives-darwin-arm64": {
449
- "version": "0.9.40-alpha.1",
450
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.40-alpha.1.tgz",
449
+ "version": "0.9.40",
450
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.40.tgz",
451
451
  "license": "MIT",
452
452
  "os": [
453
453
  "darwin"
@@ -458,8 +458,8 @@
458
458
  "optional": true
459
459
  },
460
460
  "node_modules/@bastani/atomic-natives-darwin-x64": {
461
- "version": "0.9.40-alpha.1",
462
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.40-alpha.1.tgz",
461
+ "version": "0.9.40",
462
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.40.tgz",
463
463
  "license": "MIT",
464
464
  "os": [
465
465
  "darwin"
@@ -470,8 +470,8 @@
470
470
  "optional": true
471
471
  },
472
472
  "node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
473
- "version": "0.9.40-alpha.1",
474
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.40-alpha.1.tgz",
473
+ "version": "0.9.40",
474
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.40.tgz",
475
475
  "license": "MIT",
476
476
  "os": [
477
477
  "linux"
@@ -485,8 +485,8 @@
485
485
  "optional": true
486
486
  },
487
487
  "node_modules/@bastani/atomic-natives-linux-arm64-musl": {
488
- "version": "0.9.40-alpha.1",
489
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.40-alpha.1.tgz",
488
+ "version": "0.9.40",
489
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.40.tgz",
490
490
  "license": "MIT",
491
491
  "os": [
492
492
  "linux"
@@ -500,8 +500,8 @@
500
500
  "optional": true
501
501
  },
502
502
  "node_modules/@bastani/atomic-natives-linux-x64-gnu": {
503
- "version": "0.9.40-alpha.1",
504
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.40-alpha.1.tgz",
503
+ "version": "0.9.40",
504
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.40.tgz",
505
505
  "license": "MIT",
506
506
  "os": [
507
507
  "linux"
@@ -515,8 +515,8 @@
515
515
  "optional": true
516
516
  },
517
517
  "node_modules/@bastani/atomic-natives-linux-x64-musl": {
518
- "version": "0.9.40-alpha.1",
519
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.40-alpha.1.tgz",
518
+ "version": "0.9.40",
519
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.40.tgz",
520
520
  "license": "MIT",
521
521
  "os": [
522
522
  "linux"
@@ -530,8 +530,8 @@
530
530
  "optional": true
531
531
  },
532
532
  "node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
533
- "version": "0.9.40-alpha.1",
534
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.40-alpha.1.tgz",
533
+ "version": "0.9.40",
534
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.40.tgz",
535
535
  "license": "MIT",
536
536
  "os": [
537
537
  "win32"
@@ -542,8 +542,8 @@
542
542
  "optional": true
543
543
  },
544
544
  "node_modules/@bastani/atomic-natives-win32-x64-msvc": {
545
- "version": "0.9.40-alpha.1",
546
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.40-alpha.1.tgz",
545
+ "version": "0.9.40",
546
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.40.tgz",
547
547
  "license": "MIT",
548
548
  "os": [
549
549
  "win32"
@@ -554,8 +554,8 @@
554
554
  "optional": true
555
555
  },
556
556
  "node_modules/@bastani/pi-ai": {
557
- "version": "0.9.40-alpha.1",
558
- "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.40-alpha.1.tgz",
557
+ "version": "0.9.40",
558
+ "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.40.tgz",
559
559
  "license": "MIT",
560
560
  "dependencies": {
561
561
  "@anthropic-ai/sdk": "0.129.0",
@@ -2262,9 +2262,9 @@
2262
2262
  }
2263
2263
  },
2264
2264
  "node_modules/hono": {
2265
- "version": "4.13.7",
2266
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz",
2267
- "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==",
2265
+ "version": "4.13.13",
2266
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz",
2267
+ "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==",
2268
2268
  "license": "MIT",
2269
2269
  "engines": {
2270
2270
  "node": ">=16.9.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.40-alpha.1",
3
+ "version": "0.9.40",
4
4
  "description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "atomicConfig": {
@@ -76,8 +76,8 @@
76
76
  "start:fast": "bun run bundle:dev && bun dist-dev/cli.js",
77
77
  "build:binary": "bun run clean && bun run build && bun build --target=bun --format=cjs --minify-syntax --external mupdf --external=*native-modifiers.js ./dist/bun/cli.js --outfile dist/app.js && bun build --target=bun --format=cjs --external mupdf ./src/utils/image-resize-worker.ts --outfile dist/image-resize-worker.js && bun run build:codemode-worker && bun build --compile --bytecode --format=cjs --external mupdf --no-compile-autoload-dotenv --no-compile-autoload-bunfig ./dist/bun/split-loader.js --outfile dist/atomic && bun run copy-binary-assets && bun run assert-binary-assets",
78
78
  "build:codemode-worker": "bun build --target=bun --format=esm ./src/extensions/codemode/worker.ts --outfile dist/codemode-worker.js && shx cp ../../node_modules/quickjs-wasi/quickjs.wasm dist/",
79
- "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor dist/core/export-html/template-js && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/template-js/*.js dist/core/export-html/template-js/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && bun run copy-builtin-packages",
80
- "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor dist/export-html/template-js && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/export-html/ && shx cp src/core/export-html/template-js/*.js dist/export-html/template-js/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-modifiers.js dist/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-module-path.js dist/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-platform.js dist/ && shx mkdir -p dist/native/win32/prebuilds && shx cp -r ../../node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-* dist/native/win32/prebuilds/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/ && bun run copy-builtin-packages",
79
+ "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && bun run copy-builtin-packages",
80
+ "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-modifiers.js dist/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-module-path.js dist/ && shx cp ../../node_modules/@earendil-works/pi-tui/dist/native-platform.js dist/ && shx mkdir -p dist/native/win32/prebuilds && shx cp -r ../../node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-* dist/native/win32/prebuilds/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/ && bun run copy-builtin-packages",
81
81
  "copy-builtin-packages": "bun run scripts/copy-builtin-packages.ts",
82
82
  "copy-builtin-workflows": "bun run copy-builtin-packages",
83
83
  "assert-binary-assets": "bun run scripts/assert-pi-runtime-assets.ts --app dist/app.js",
@@ -89,8 +89,8 @@
89
89
  "prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
90
90
  },
91
91
  "dependencies": {
92
- "@bastani/atomic-natives": "0.9.40-alpha.1",
93
- "@bastani/pi-ai": "0.9.40-alpha.1",
92
+ "@bastani/atomic-natives": "0.9.40",
93
+ "@bastani/pi-ai": "0.9.40",
94
94
  "@earendil-works/pi-agent-core": "1.3.0",
95
95
  "@earendil-works/pi-codemode": "1.3.0",
96
96
  "@earendil-works/pi-tui": "1.3.0",
@@ -1,287 +0,0 @@
1
- (function() {
2
- 'use strict';
3
-
4
- // ============================================================
5
- // DATA LOADING
6
- // ============================================================
7
-
8
- const base64 = document.getElementById('session-data').textContent;
9
- const binary = atob(base64);
10
- const bytes = new Uint8Array(binary.length);
11
- for (let i = 0; i < binary.length; i++) {
12
- bytes[i] = binary.charCodeAt(i);
13
- }
14
- const data = JSON.parse(new TextDecoder('utf-8').decode(bytes));
15
- const { header, entries, leafId: defaultLeafId, systemPrompt, tools, renderedTools } = data;
16
-
17
- // ============================================================
18
- // URL PARAMETER HANDLING
19
- // ============================================================
20
-
21
- // Parse URL parameters for deep linking: leafId and targetId
22
- // Check for injected params (when loaded in iframe via srcdoc) or use window.location
23
- const injectedParams = document.querySelector('meta[name="pi-url-params"]');
24
- const searchString = injectedParams ? injectedParams.content : window.location.search.substring(1);
25
- const urlParams = new URLSearchParams(searchString);
26
- const urlLeafId = urlParams.get('leafId');
27
- const urlTargetId = urlParams.get('targetId');
28
- // Use URL leafId if provided, otherwise fall back to session default
29
- const leafId = urlLeafId || defaultLeafId;
30
-
31
- // ============================================================
32
- // DATA STRUCTURES
33
- // ============================================================
34
-
35
- // Entry lookup by ID
36
- const byId = new Map();
37
- for (const entry of entries) {
38
- byId.set(entry.id, entry);
39
- }
40
-
41
- // Tool call lookup (toolCallId -> {name, arguments})
42
- const toolCallMap = new Map();
43
- for (const entry of entries) {
44
- if (entry.type === 'message' && entry.message.role === 'assistant') {
45
- const content = entry.message.content;
46
- if (Array.isArray(content)) {
47
- for (const block of content) {
48
- if (block.type === 'toolCall') {
49
- toolCallMap.set(block.id, { name: block.name, arguments: block.arguments });
50
- }
51
- }
52
- }
53
- }
54
- }
55
-
56
- // Label lookup (entryId -> label string)
57
- // Labels are stored in 'label' entries that reference their target via targetId
58
- const labelMap = new Map();
59
- for (const entry of entries) {
60
- if (entry.type === 'label' && entry.targetId && entry.label) {
61
- labelMap.set(entry.targetId, entry.label);
62
- }
63
- }
64
-
65
- // ============================================================
66
- // TREE DATA PREPARATION (no DOM, pure data)
67
- // ============================================================
68
-
69
- /**
70
- * Build tree structure from flat entries.
71
- * Returns array of root nodes, each with { entry, children, label }.
72
- */
73
- function buildTree() {
74
- const nodeMap = new Map();
75
- const roots = [];
76
-
77
- // Create nodes
78
- for (const entry of entries) {
79
- nodeMap.set(entry.id, {
80
- entry,
81
- children: [],
82
- label: labelMap.get(entry.id)
83
- });
84
- }
85
-
86
- // Build parent-child relationships
87
- for (const entry of entries) {
88
- const node = nodeMap.get(entry.id);
89
- if (entry.parentId === null || entry.parentId === undefined || entry.parentId === entry.id) {
90
- roots.push(node);
91
- } else {
92
- const parent = nodeMap.get(entry.parentId);
93
- if (parent) {
94
- parent.children.push(node);
95
- } else {
96
- roots.push(node);
97
- }
98
- }
99
- }
100
-
101
- // Sort children by timestamp
102
- function sortChildren(node) {
103
- node.children.sort((a, b) =>
104
- new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime()
105
- );
106
- node.children.forEach(sortChildren);
107
- }
108
- roots.forEach(sortChildren);
109
-
110
- return roots;
111
- }
112
-
113
- /**
114
- * Build set of entry IDs on path from root to target.
115
- */
116
- function buildActivePathIds(targetId) {
117
- const ids = new Set();
118
- let current = byId.get(targetId);
119
- while (current) {
120
- ids.add(current.id);
121
- // Stop if no parent or self-referencing (root)
122
- if (!current.parentId || current.parentId === current.id) {
123
- break;
124
- }
125
- current = byId.get(current.parentId);
126
- }
127
- return ids;
128
- }
129
-
130
- /**
131
- * Get array of entries from root to target (the conversation path).
132
- */
133
- function getPath(targetId) {
134
- const path = [];
135
- let current = byId.get(targetId);
136
- while (current) {
137
- path.unshift(current);
138
- // Stop if no parent or self-referencing (root)
139
- if (!current.parentId || current.parentId === current.id) {
140
- break;
141
- }
142
- current = byId.get(current.parentId);
143
- }
144
- return path;
145
- }
146
-
147
- // Tree node lookup for finding leaves
148
- let treeNodeMap = null;
149
-
150
- /**
151
- * Find the newest leaf node reachable from a given node.
152
- * This allows clicking any node in a branch to show the full branch.
153
- * Children are sorted by timestamp, so the newest is always last.
154
- */
155
- function findNewestLeaf(nodeId) {
156
- // Build tree node map lazily
157
- if (!treeNodeMap) {
158
- treeNodeMap = new Map();
159
- const tree = buildTree();
160
- function mapNodes(node) {
161
- treeNodeMap.set(node.entry.id, node);
162
- node.children.forEach(mapNodes);
163
- }
164
- tree.forEach(mapNodes);
165
- }
166
-
167
- const node = treeNodeMap.get(nodeId);
168
- if (!node) return nodeId;
169
-
170
- // Follow the newest (last) child at each level
171
- let current = node;
172
- while (current.children.length > 0) {
173
- current = current.children[current.children.length - 1];
174
- }
175
- return current.entry.id;
176
- }
177
-
178
- /**
179
- * Flatten tree into list with indentation and connector info.
180
- * Returns array of { node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots }.
181
- * Matches tree-selector.ts logic exactly.
182
- */
183
- function flattenTree(roots, activePathIds) {
184
- const result = [];
185
- const multipleRoots = roots.length > 1;
186
-
187
- // Mark which subtrees contain the active leaf
188
- const containsActive = new Map();
189
- function markActive(node) {
190
- let has = activePathIds.has(node.entry.id);
191
- for (const child of node.children) {
192
- if (markActive(child)) has = true;
193
- }
194
- containsActive.set(node, has);
195
- return has;
196
- }
197
- roots.forEach(markActive);
198
-
199
- // Stack: [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild]
200
- const stack = [];
201
-
202
- // Add roots (prioritize branch containing active leaf)
203
- const orderedRoots = [...roots].sort((a, b) =>
204
- Number(containsActive.get(b)) - Number(containsActive.get(a))
205
- );
206
- for (let i = orderedRoots.length - 1; i >= 0; i--) {
207
- const isLast = i === orderedRoots.length - 1;
208
- stack.push([orderedRoots[i], multipleRoots ? 1 : 0, multipleRoots, multipleRoots, isLast, [], multipleRoots]);
209
- }
210
-
211
- while (stack.length > 0) {
212
- const [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
213
-
214
- result.push({ node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots });
215
-
216
- const children = node.children;
217
- const multipleChildren = children.length > 1;
218
-
219
- // Order children (active branch first)
220
- const orderedChildren = [...children].sort((a, b) =>
221
- Number(containsActive.get(b)) - Number(containsActive.get(a))
222
- );
223
-
224
- // Calculate child indent (matches tree-selector.ts)
225
- let childIndent;
226
- if (multipleChildren) {
227
- // Parent branches: children get +1
228
- childIndent = indent + 1;
229
- } else if (justBranched && indent > 0) {
230
- // First generation after a branch: +1 for visual grouping
231
- childIndent = indent + 1;
232
- } else {
233
- // Single-child chain: stay flat
234
- childIndent = indent;
235
- }
236
-
237
- // Build gutters for children
238
- const connectorDisplayed = showConnector && !isVirtualRootChild;
239
- const currentDisplayIndent = multipleRoots ? Math.max(0, indent - 1) : indent;
240
- const connectorPosition = Math.max(0, currentDisplayIndent - 1);
241
- const childGutters = connectorDisplayed
242
- ? [...gutters, { position: connectorPosition, show: !isLast }]
243
- : gutters;
244
-
245
- // Add children in reverse order for stack
246
- for (let i = orderedChildren.length - 1; i >= 0; i--) {
247
- const childIsLast = i === orderedChildren.length - 1;
248
- stack.push([orderedChildren[i], childIndent, multipleChildren, multipleChildren, childIsLast, childGutters, false]);
249
- }
250
- }
251
-
252
- return result;
253
- }
254
-
255
- /**
256
- * Build ASCII prefix string for tree node.
257
- */
258
- function buildTreePrefix(flatNode) {
259
- const { indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots } = flatNode;
260
- const displayIndent = multipleRoots ? Math.max(0, indent - 1) : indent;
261
- const connector = showConnector && !isVirtualRootChild ? (isLast ? '└─ ' : '├─ ') : '';
262
- const connectorPosition = connector ? displayIndent - 1 : -1;
263
-
264
- const totalChars = displayIndent * 3;
265
- const prefixChars = [];
266
- for (let i = 0; i < totalChars; i++) {
267
- const level = Math.floor(i / 3);
268
- const posInLevel = i % 3;
269
-
270
- const gutter = gutters.find(g => g.position === level);
271
- if (gutter) {
272
- prefixChars.push(posInLevel === 0 ? (gutter.show ? '│' : ' ') : ' ');
273
- } else if (connector && level === connectorPosition) {
274
- if (posInLevel === 0) {
275
- prefixChars.push(isLast ? '└' : '├');
276
- } else if (posInLevel === 1) {
277
- prefixChars.push('─');
278
- } else {
279
- prefixChars.push(' ');
280
- }
281
- } else {
282
- prefixChars.push(' ');
283
- }
284
- }
285
- return prefixChars.join('');
286
- }
287
-