@aion0/forge 0.5.31 → 0.5.32
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/RELEASE_NOTES.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Forge v0.5.
|
|
1
|
+
# Forge v0.5.32
|
|
2
2
|
|
|
3
3
|
Released: 2026-04-10
|
|
4
4
|
|
|
5
|
-
## Changes since v0.5.
|
|
5
|
+
## Changes since v0.5.31
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.
|
|
8
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.31...v0.5.32
|
|
@@ -28,14 +28,19 @@ interface FileNode {
|
|
|
28
28
|
|
|
29
29
|
// ─── File Tree ───────────────────────────────────────────
|
|
30
30
|
|
|
31
|
-
function TreeNode({ node, depth, selected, onSelect }: {
|
|
31
|
+
function TreeNode({ node, depth, selected, onSelect, collapseVersion = 0 }: {
|
|
32
32
|
node: FileNode;
|
|
33
33
|
depth: number;
|
|
34
34
|
selected: string | null;
|
|
35
35
|
onSelect: (path: string) => void;
|
|
36
|
+
collapseVersion?: number;
|
|
36
37
|
}) {
|
|
37
38
|
const [expanded, setExpanded] = useState(depth < 1);
|
|
38
39
|
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (collapseVersion > 0) setExpanded(false);
|
|
42
|
+
}, [collapseVersion]);
|
|
43
|
+
|
|
39
44
|
if (node.type === 'dir') {
|
|
40
45
|
return (
|
|
41
46
|
<div>
|
|
@@ -48,7 +53,7 @@ function TreeNode({ node, depth, selected, onSelect }: {
|
|
|
48
53
|
<span className="text-[var(--text-primary)]">{node.name}</span>
|
|
49
54
|
</button>
|
|
50
55
|
{expanded && node.children?.map(child => (
|
|
51
|
-
<TreeNode key={child.path} node={child} depth={depth + 1} selected={selected} onSelect={onSelect} />
|
|
56
|
+
<TreeNode key={child.path} node={child} depth={depth + 1} selected={selected} onSelect={onSelect} collapseVersion={collapseVersion} />
|
|
52
57
|
))}
|
|
53
58
|
</div>
|
|
54
59
|
);
|
|
@@ -107,6 +112,7 @@ export default function DocsViewer() {
|
|
|
107
112
|
const [content, setContent] = useState<string | null>(null);
|
|
108
113
|
const [loading, setLoading] = useState(false);
|
|
109
114
|
const [search, setSearch] = useState('');
|
|
115
|
+
const [treeCollapseVersion, setTreeCollapseVersion] = useState(0);
|
|
110
116
|
const [terminalHeight, setTerminalHeight] = useState(250);
|
|
111
117
|
const [docsAgent, setDocsAgent] = useState('');
|
|
112
118
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
@@ -387,14 +393,19 @@ export default function DocsViewer() {
|
|
|
387
393
|
</div>
|
|
388
394
|
|
|
389
395
|
{/* Search */}
|
|
390
|
-
<div className="p-2 border-b border-[var(--border)]">
|
|
396
|
+
<div className="p-2 border-b border-[var(--border)] flex items-center gap-2">
|
|
391
397
|
<input
|
|
392
398
|
type="text"
|
|
393
399
|
placeholder="Search..."
|
|
394
400
|
value={search}
|
|
395
401
|
onChange={e => setSearch(e.target.value)}
|
|
396
|
-
className="
|
|
402
|
+
className="flex-1 text-xs bg-[var(--bg-tertiary)] border border-[var(--border)] rounded px-2 py-1 text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent)]"
|
|
397
403
|
/>
|
|
404
|
+
<button
|
|
405
|
+
onClick={() => setTreeCollapseVersion(v => v + 1)}
|
|
406
|
+
className="text-[10px] px-1.5 py-1 rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] shrink-0"
|
|
407
|
+
title="Collapse all folders"
|
|
408
|
+
>⇱</button>
|
|
398
409
|
</div>
|
|
399
410
|
|
|
400
411
|
{/* Tree / search results */}
|
|
@@ -419,7 +430,7 @@ export default function DocsViewer() {
|
|
|
419
430
|
)
|
|
420
431
|
) : (
|
|
421
432
|
(hideUnsupported ? filterTree(tree) : tree).map(node => (
|
|
422
|
-
<TreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFileInTab} />
|
|
433
|
+
<TreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFileInTab} collapseVersion={treeCollapseVersion} />
|
|
423
434
|
))
|
|
424
435
|
)}
|
|
425
436
|
</div>
|
|
@@ -67,6 +67,7 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
|
|
|
67
67
|
const [gitLoading, setGitLoading] = useState(false);
|
|
68
68
|
const [gitResult, setGitResult] = useState<{ ok?: boolean; error?: string } | null>(null);
|
|
69
69
|
const [fileTree, setFileTree] = useState<any[]>([]);
|
|
70
|
+
const [treeCollapseVersion, setTreeCollapseVersion] = useState(0);
|
|
70
71
|
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
|
71
72
|
const [fileContent, setFileContent] = useState<string | null>(null);
|
|
72
73
|
const [fileImageUrl, setFileImageUrl] = useState<string | null>(null);
|
|
@@ -713,10 +714,19 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
|
|
|
713
714
|
</div>
|
|
714
715
|
)}
|
|
715
716
|
{codeSearching && <div className="px-2 py-1 text-[9px] text-[var(--text-secondary)]">Searching...</div>}
|
|
717
|
+
{/* File tree header */}
|
|
718
|
+
<div className="px-2 py-0.5 border-b border-[var(--border)] flex items-center justify-between">
|
|
719
|
+
<span className="text-[8px] text-[var(--text-secondary)] uppercase">Files</span>
|
|
720
|
+
<button
|
|
721
|
+
onClick={() => setTreeCollapseVersion(v => v + 1)}
|
|
722
|
+
className="text-[8px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
723
|
+
title="Collapse all folders"
|
|
724
|
+
>⇱ collapse all</button>
|
|
725
|
+
</div>
|
|
716
726
|
{/* File tree */}
|
|
717
727
|
<div className="overflow-y-auto flex-1 p-1">
|
|
718
728
|
{fileTree.map((node: any) => (
|
|
719
|
-
<FileTreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFile} />
|
|
729
|
+
<FileTreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFile} collapseVersion={treeCollapseVersion} />
|
|
720
730
|
))}
|
|
721
731
|
</div>
|
|
722
732
|
</div>
|
|
@@ -1468,14 +1478,20 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
|
|
|
1468
1478
|
});
|
|
1469
1479
|
|
|
1470
1480
|
// Simple file tree node
|
|
1471
|
-
const FileTreeNode = memo(function FileTreeNode({ node, depth, selected, onSelect }: {
|
|
1481
|
+
const FileTreeNode = memo(function FileTreeNode({ node, depth, selected, onSelect, collapseVersion }: {
|
|
1472
1482
|
node: { name: string; path: string; type: string; children?: any[] };
|
|
1473
1483
|
depth: number;
|
|
1474
1484
|
selected: string | null;
|
|
1475
1485
|
onSelect: (path: string) => void;
|
|
1486
|
+
collapseVersion: number;
|
|
1476
1487
|
}) {
|
|
1477
1488
|
const [expanded, setExpanded] = useState(depth < 1);
|
|
1478
1489
|
|
|
1490
|
+
// When parent bumps collapseVersion, collapse this node
|
|
1491
|
+
useEffect(() => {
|
|
1492
|
+
if (collapseVersion > 0) setExpanded(false);
|
|
1493
|
+
}, [collapseVersion]);
|
|
1494
|
+
|
|
1479
1495
|
if (node.type === 'dir') {
|
|
1480
1496
|
return (
|
|
1481
1497
|
<div>
|
|
@@ -1488,7 +1504,7 @@ const FileTreeNode = memo(function FileTreeNode({ node, depth, selected, onSelec
|
|
|
1488
1504
|
<span className="text-[var(--text-primary)]">{node.name}</span>
|
|
1489
1505
|
</button>
|
|
1490
1506
|
{expanded && node.children?.map((child: any) => (
|
|
1491
|
-
<FileTreeNode key={child.path} node={child} depth={depth + 1} selected={selected} onSelect={onSelect} />
|
|
1507
|
+
<FileTreeNode key={child.path} node={child} depth={depth + 1} selected={selected} onSelect={onSelect} collapseVersion={collapseVersion} />
|
|
1492
1508
|
))}
|
|
1493
1509
|
</div>
|
|
1494
1510
|
);
|
|
@@ -319,6 +319,13 @@ export default function SessionView({
|
|
|
319
319
|
<div className="flex items-center justify-between p-2 border-b border-[var(--border)]">
|
|
320
320
|
<span className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase">Sessions</span>
|
|
321
321
|
<div className="flex items-center gap-2">
|
|
322
|
+
{!singleProject && (
|
|
323
|
+
<button
|
|
324
|
+
onClick={() => setExpandedProjects(new Set())}
|
|
325
|
+
className="text-[9px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
326
|
+
title="Collapse all projects"
|
|
327
|
+
>⇱</button>
|
|
328
|
+
)}
|
|
322
329
|
<button
|
|
323
330
|
onClick={() => batchMode ? exitBatchMode() : setBatchMode(true)}
|
|
324
331
|
className={`text-[9px] transition-colors ${batchMode ? 'text-[var(--accent)]' : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'}`}
|
|
@@ -64,23 +64,29 @@ function buildTree(files: { name: string; path: string; type: string }[]): TreeN
|
|
|
64
64
|
return root;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function SkillFileTree({ files, activeFile, onSelect }: {
|
|
67
|
+
function SkillFileTree({ files, activeFile, onSelect, collapseVersion = 0 }: {
|
|
68
68
|
files: { name: string; path: string; type: string }[];
|
|
69
69
|
activeFile: string | null;
|
|
70
70
|
onSelect: (path: string) => void;
|
|
71
|
+
collapseVersion?: number;
|
|
71
72
|
}) {
|
|
72
73
|
const tree = buildTree(files);
|
|
73
|
-
return <TreeNodeList nodes={tree} depth={0} activeFile={activeFile} onSelect={onSelect} />;
|
|
74
|
+
return <TreeNodeList nodes={tree} depth={0} activeFile={activeFile} onSelect={onSelect} collapseVersion={collapseVersion} />;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
function TreeNodeList({ nodes, depth, activeFile, onSelect }: {
|
|
77
|
-
nodes: TreeNode[]; depth: number; activeFile: string | null; onSelect: (path: string) => void;
|
|
77
|
+
function TreeNodeList({ nodes, depth, activeFile, onSelect, collapseVersion = 0 }: {
|
|
78
|
+
nodes: TreeNode[]; depth: number; activeFile: string | null; onSelect: (path: string) => void; collapseVersion?: number;
|
|
78
79
|
}) {
|
|
79
80
|
const [expanded, setExpanded] = useState<Set<string>>(new Set(
|
|
80
81
|
// Auto-expand first level
|
|
81
82
|
nodes.filter(n => n.type === 'dir').map(n => n.path)
|
|
82
83
|
));
|
|
83
84
|
|
|
85
|
+
// Collapse all on version bump
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (collapseVersion > 0) setExpanded(new Set());
|
|
88
|
+
}, [collapseVersion]);
|
|
89
|
+
|
|
84
90
|
const toggle = (path: string) => {
|
|
85
91
|
setExpanded(prev => {
|
|
86
92
|
const next = new Set(prev);
|
|
@@ -103,7 +109,7 @@ function TreeNodeList({ nodes, depth, activeFile, onSelect }: {
|
|
|
103
109
|
<span>📁 {node.name}</span>
|
|
104
110
|
</button>
|
|
105
111
|
{expanded.has(node.path) && (
|
|
106
|
-
<TreeNodeList nodes={node.children} depth={depth + 1} activeFile={activeFile} onSelect={onSelect} />
|
|
112
|
+
<TreeNodeList nodes={node.children} depth={depth + 1} activeFile={activeFile} onSelect={onSelect} collapseVersion={collapseVersion} />
|
|
107
113
|
)}
|
|
108
114
|
</div>
|
|
109
115
|
) : (
|
|
@@ -152,6 +158,7 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
|
|
|
152
158
|
const [expandedSkill, setExpandedSkill] = useState<string | null>(null);
|
|
153
159
|
const [skillFiles, setSkillFiles] = useState<{ name: string; path: string; type: string }[]>([]);
|
|
154
160
|
const [activeFile, setActiveFile] = useState<string | null>(null);
|
|
161
|
+
const [skillTreeCollapseVersion, setSkillTreeCollapseVersion] = useState(0);
|
|
155
162
|
const [fileContent, setFileContent] = useState<string>('');
|
|
156
163
|
|
|
157
164
|
const fetchSkills = useCallback(async () => {
|
|
@@ -742,7 +749,18 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
|
|
|
742
749
|
{/* File browser */}
|
|
743
750
|
<div className="flex-1 flex min-h-0 overflow-hidden">
|
|
744
751
|
{/* File tree */}
|
|
745
|
-
<div className="w-36 border-r border-[var(--border)]
|
|
752
|
+
<div className="w-36 border-r border-[var(--border)] flex flex-col shrink-0">
|
|
753
|
+
{skillFiles.length > 0 && (
|
|
754
|
+
<div className="px-2 py-0.5 border-b border-[var(--border)] flex items-center justify-between shrink-0">
|
|
755
|
+
<span className="text-[8px] text-[var(--text-secondary)] uppercase">Files</span>
|
|
756
|
+
<button
|
|
757
|
+
onClick={() => setSkillTreeCollapseVersion(v => v + 1)}
|
|
758
|
+
className="text-[8px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
759
|
+
title="Collapse all"
|
|
760
|
+
>⇱</button>
|
|
761
|
+
</div>
|
|
762
|
+
)}
|
|
763
|
+
<div className="overflow-y-auto flex-1">
|
|
746
764
|
{skillFiles.length === 0 ? (
|
|
747
765
|
<div className="p-2 text-[9px] text-[var(--text-secondary)]">Loading...</div>
|
|
748
766
|
) : (
|
|
@@ -750,8 +768,10 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
|
|
|
750
768
|
files={skillFiles}
|
|
751
769
|
activeFile={activeFile}
|
|
752
770
|
onSelect={(path) => loadFile(itemName, path, isLocal, localItem?.type, localItem?.projectPath)}
|
|
771
|
+
collapseVersion={skillTreeCollapseVersion}
|
|
753
772
|
/>
|
|
754
773
|
)}
|
|
774
|
+
</div>
|
|
755
775
|
{skill?.sourceUrl && (
|
|
756
776
|
<div className="border-t border-[var(--border)] p-2">
|
|
757
777
|
<a
|
|
@@ -126,8 +126,13 @@ export default function WorkspaceTree({
|
|
|
126
126
|
{/* Header */}
|
|
127
127
|
<div className="flex items-center gap-2 px-3 py-2 border-b border-[var(--border)] shrink-0">
|
|
128
128
|
<span className="text-xs font-bold text-[var(--text-primary)]">Workspace</span>
|
|
129
|
+
<button onClick={() => setExpanded(new Set())}
|
|
130
|
+
className="text-[8px] px-1.5 py-0.5 rounded border border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] ml-auto"
|
|
131
|
+
title="Collapse all">
|
|
132
|
+
⇱
|
|
133
|
+
</button>
|
|
129
134
|
<button onClick={onCreateWorkspace}
|
|
130
|
-
className="text-[8px] px-1.5 py-0.5 rounded border border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]
|
|
135
|
+
className="text-[8px] px-1.5 py-0.5 rounded border border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]">
|
|
131
136
|
+
|
|
132
137
|
</button>
|
|
133
138
|
</div>
|
package/package.json
CHANGED