@aion0/forge 0.4.4 → 0.4.5

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,28 +1,11 @@
1
- # Forge v0.4.4
1
+ # Forge v0.4.5
2
2
 
3
3
  Released: 2026-03-21
4
4
 
5
- ## Changes since v0.4.3
6
-
7
- ### Features
8
- - feat: add scheduled pipeline execution for project bindings
9
- - feat: pipeline import/delete + editor cancel confirmation
5
+ ## Changes since v0.4.4
10
6
 
11
7
  ### Bug Fixes
12
- - fix: embed editor in right panel, button nesting, fetchData error handling
13
-
14
- ### Refactoring
15
- - refactor: generic project-pipeline bindings replace hardcoded issue scanner
16
- - refactor: pipeline UI — workflow list in sidebar, project dropdown, per-workflow history
17
- - refactor: merge issue-auto-fix + pr-review into single issue-fix-and-review pipeline
18
-
19
- ### Documentation
20
- - feat: pipeline import/delete + editor cancel confirmation
21
-
22
- ### Other
23
- - fix issue
24
- - fix issue
25
- - fix issues for no any issue on github
8
+ - fix: add drag-to-resize for left sidebars across all split-panel pages (#14)
26
9
 
27
10
 
28
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.3...v0.4.4
11
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.4...v0.4.5
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
4
4
  import type { WebTerminalHandle, WebTerminalProps } from './WebTerminal';
5
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
5
6
 
6
7
  const WebTerminal = lazy(() => import('./WebTerminal'));
7
8
 
@@ -196,6 +197,7 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
196
197
  const [activeSession, setActiveSession] = useState<string | null>(null);
197
198
  const [taskNotification, setTaskNotification] = useState<{ id: string; status: string; prompt: string; sessionId?: string } | null>(null);
198
199
  const dragRef = useRef<{ startY: number; startH: number } | null>(null);
200
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 120, maxWidth: 480 });
199
201
  const lastDirRef = useRef<string | null>(null);
200
202
  const lastTaskCheckRef = useRef<string>('');
201
203
 
@@ -437,7 +439,7 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
437
439
  {codeOpen && <div className="flex-1 flex min-h-0 min-w-0 overflow-hidden">
438
440
  {/* Sidebar */}
439
441
  {sidebarOpen && (
440
- <aside className="w-56 border-r border-[var(--border)] flex flex-col shrink-0">
442
+ <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
441
443
  {/* Directory name + git */}
442
444
  <div className="px-3 py-2 border-b border-[var(--border)]">
443
445
  <div className="flex items-center gap-2">
@@ -611,6 +613,14 @@ export default function CodeViewer({ terminalRef }: { terminalRef: React.RefObje
611
613
  </aside>
612
614
  )}
613
615
 
616
+ {/* Sidebar resize handle */}
617
+ {sidebarOpen && (
618
+ <div
619
+ onMouseDown={onSidebarDragStart}
620
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
621
+ />
622
+ )}
623
+
614
624
  {/* Code viewer */}
615
625
  <main className="flex-1 flex flex-col min-w-0 overflow-hidden" style={{ width: 0 }}>
616
626
  <div className="px-3 py-1.5 border-b border-[var(--border)] shrink-0 flex items-center gap-2">
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
  import MarkdownContent from './MarkdownContent';
5
6
  import TabBar from './TabBar';
6
7
 
@@ -100,6 +101,7 @@ export default function DocsViewer() {
100
101
  const [editContent, setEditContent] = useState('');
101
102
  const [saving, setSaving] = useState(false);
102
103
  const dragRef = useRef<{ startY: number; startH: number } | null>(null);
104
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 120, maxWidth: 480 });
103
105
 
104
106
  // Doc tabs
105
107
  const [docTabs, setDocTabs] = useState<DocTab[]>([]);
@@ -324,7 +326,7 @@ export default function DocsViewer() {
324
326
  <div className="flex-1 flex min-h-0">
325
327
  {/* Collapsible sidebar — file tree */}
326
328
  {sidebarOpen && (
327
- <aside className="w-56 border-r border-[var(--border)] flex flex-col shrink-0">
329
+ <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
328
330
  {/* Root selector */}
329
331
  {roots.length > 0 && (
330
332
  <div className="p-2 border-b border-[var(--border)]">
@@ -390,6 +392,14 @@ export default function DocsViewer() {
390
392
  </aside>
391
393
  )}
392
394
 
395
+ {/* Sidebar resize handle */}
396
+ {sidebarOpen && (
397
+ <div
398
+ onMouseDown={onSidebarDragStart}
399
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
400
+ />
401
+ )}
402
+
393
403
  {/* Main content */}
394
404
  <main className="flex-1 flex flex-col min-w-0">
395
405
  {/* Doc tab bar */}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  const PipelineEditor = lazy(() => import('./PipelineEditor'));
6
7
 
@@ -64,6 +65,7 @@ const STATUS_COLOR: Record<string, string> = {
64
65
  };
65
66
 
66
67
  export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: string) => void }) {
68
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 256, minWidth: 140, maxWidth: 480 });
67
69
  const [pipelines, setPipelines] = useState<Pipeline[]>([]);
68
70
  const [workflows, setWorkflows] = useState<Workflow[]>([]);
69
71
  const [selectedPipeline, setSelectedPipeline] = useState<Pipeline | null>(null);
@@ -158,7 +160,7 @@ export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: str
158
160
  return (
159
161
  <div className="flex-1 flex min-h-0">
160
162
  {/* Left — Workflow list */}
161
- <aside className="w-64 border-r border-[var(--border)] flex flex-col shrink-0">
163
+ <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
162
164
  <div className="px-3 py-2 border-b border-[var(--border)] flex items-center gap-1.5">
163
165
  <span className="text-[11px] font-semibold text-[var(--text-primary)] flex-1">Workflows</span>
164
166
  <button
@@ -369,6 +371,12 @@ export default function PipelineView({ onViewTask }: { onViewTask?: (taskId: str
369
371
  </div>
370
372
  </aside>
371
373
 
374
+ {/* Sidebar resize handle */}
375
+ <div
376
+ onMouseDown={onSidebarDragStart}
377
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
378
+ />
379
+
372
380
  {/* Right — Pipeline detail / Editor */}
373
381
  <main className="flex-1 flex flex-col min-w-0 overflow-hidden">
374
382
  {showEditor ? (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState, useEffect, useCallback, memo } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  // ─── Syntax highlighting ─────────────────────────────────
6
7
  const KEYWORDS = new Set([
@@ -49,6 +50,7 @@ interface GitInfo {
49
50
  }
50
51
 
51
52
  export default memo(function ProjectDetail({ projectPath, projectName, hasGit }: { projectPath: string; projectName: string; hasGit: boolean }) {
53
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 208, minWidth: 120, maxWidth: 400 });
52
54
  const [gitInfo, setGitInfo] = useState<GitInfo | null>(null);
53
55
  const [loading, setLoading] = useState(false);
54
56
  const [commitMsg, setCommitMsg] = useState('');
@@ -490,12 +492,18 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
490
492
  {/* Code content area */}
491
493
  {projectTab === 'code' && <div className="flex-1 flex min-h-0 overflow-hidden">
492
494
  {/* File tree */}
493
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto p-1 shrink-0">
495
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto p-1 shrink-0">
494
496
  {fileTree.map((node: any) => (
495
497
  <FileTreeNode key={node.path} node={node} depth={0} selected={selectedFile} onSelect={openFile} />
496
498
  ))}
497
499
  </div>
498
500
 
501
+ {/* Sidebar resize handle */}
502
+ <div
503
+ onMouseDown={onSidebarDragStart}
504
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
505
+ />
506
+
499
507
  {/* File content */}
500
508
  <div className="flex-1 min-w-0 overflow-auto bg-[var(--bg-primary)]" style={{ width: 0 }}>
501
509
  {/* Diff view */}
@@ -543,7 +551,7 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
543
551
  {projectTab === 'skills' && (
544
552
  <div className="flex-1 flex min-h-0 overflow-hidden">
545
553
  {/* Left: skill/command tree */}
546
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto p-1 shrink-0">
554
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto p-1 shrink-0">
547
555
  {projectSkills.length === 0 ? (
548
556
  <p className="text-[9px] text-[var(--text-secondary)] p-2">No skills or commands installed</p>
549
557
  ) : (
@@ -591,6 +599,12 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
591
599
  )}
592
600
  </div>
593
601
 
602
+ {/* Sidebar resize handle */}
603
+ <div
604
+ onMouseDown={onSidebarDragStart}
605
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
606
+ />
607
+
594
608
  {/* Right: file content / editor */}
595
609
  <div className="flex-1 min-w-0 flex flex-col overflow-hidden bg-[var(--bg-primary)]">
596
610
  {skillActivePath ? (
@@ -658,7 +672,7 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
658
672
  {projectTab === 'claudemd' && (
659
673
  <div className="flex-1 flex min-h-0 overflow-hidden">
660
674
  {/* Left: templates list */}
661
- <div className="w-52 border-r border-[var(--border)] overflow-y-auto shrink-0 flex flex-col">
675
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto shrink-0 flex flex-col">
662
676
  <button
663
677
  onClick={() => { setClaudeSelectedTemplate(null); setClaudeEditing(false); }}
664
678
  className={`w-full px-2 py-1.5 border-b border-[var(--border)] text-[10px] text-left flex items-center gap-1 ${
@@ -703,6 +717,12 @@ export default memo(function ProjectDetail({ projectPath, projectName, hasGit }:
703
717
  </div>
704
718
  </div>
705
719
 
720
+ {/* Sidebar resize handle */}
721
+ <div
722
+ onMouseDown={onSidebarDragStart}
723
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
724
+ />
725
+
706
726
  {/* Right: CLAUDE.md content or template preview */}
707
727
  <div className="flex-1 min-w-0 flex flex-col overflow-hidden bg-[var(--bg-primary)]">
708
728
  {/* Header bar */}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
  import MarkdownContent from './MarkdownContent';
5
6
 
6
7
  interface SessionEntry {
@@ -42,6 +43,7 @@ export default function SessionView({
42
43
  projects: { name: string; path: string; language: string | null }[];
43
44
  onOpenInTerminal?: (sessionId: string, projectPath: string) => void;
44
45
  }) {
46
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 288, minWidth: 160, maxWidth: 480 });
45
47
  // Tree data: project → sessions
46
48
  const [sessionTree, setSessionTree] = useState<Record<string, ClaudeSessionInfo[]>>({});
47
49
  const [expandedProjects, setExpandedProjects] = useState<Set<string>>(new Set());
@@ -285,7 +287,7 @@ export default function SessionView({
285
287
  return (
286
288
  <div className="flex h-full">
287
289
  {/* Left: tree view */}
288
- <div className="w-72 border-r border-[var(--border)] flex flex-col shrink-0">
290
+ <div style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">
289
291
  {/* Header */}
290
292
  <div className="flex items-center justify-between p-2 border-b border-[var(--border)]">
291
293
  <span className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase">Sessions</span>
@@ -451,6 +453,12 @@ export default function SessionView({
451
453
  </div>
452
454
  </div>
453
455
 
456
+ {/* Sidebar resize handle */}
457
+ <div
458
+ onMouseDown={onSidebarDragStart}
459
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
460
+ />
461
+
454
462
  {/* Right: session content */}
455
463
  <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
456
464
  {activeSession && (
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
+ import { useSidebarResize } from '@/hooks/useSidebarResize';
4
5
 
5
6
  type ItemType = 'skill' | 'command';
6
7
 
@@ -28,6 +29,7 @@ interface ProjectInfo {
28
29
  }
29
30
 
30
31
  export default function SkillsPanel({ projectFilter }: { projectFilter?: string }) {
32
+ const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224, minWidth: 140, maxWidth: 400 });
31
33
  const [skills, setSkills] = useState<Skill[]>([]);
32
34
  const [projects, setProjects] = useState<ProjectInfo[]>([]);
33
35
  const [syncing, setSyncing] = useState(false);
@@ -282,7 +284,7 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
282
284
  ) : (
283
285
  <div className="flex-1 flex min-h-0">
284
286
  {/* Left: skill list */}
285
- <div className="w-56 border-r border-[var(--border)] overflow-y-auto shrink-0">
287
+ <div style={{ width: sidebarWidth }} className="overflow-y-auto shrink-0">
286
288
  {/* Registry items */}
287
289
  {filtered.map(skill => {
288
290
  const isInstalled = skill.installedGlobal || skill.installedProjects.length > 0;
@@ -401,6 +403,12 @@ export default function SkillsPanel({ projectFilter }: { projectFilter?: string
401
403
  )}
402
404
  </div>
403
405
 
406
+ {/* Sidebar resize handle */}
407
+ <div
408
+ onMouseDown={onSidebarDragStart}
409
+ className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50 transition-colors"
410
+ />
411
+
404
412
  {/* Right: detail panel */}
405
413
  <div className="flex-1 flex flex-col min-w-0">
406
414
  {expandedSkill ? (() => {
@@ -0,0 +1,52 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+
3
+ interface UseSidebarResizeOptions {
4
+ defaultWidth?: number;
5
+ minWidth?: number;
6
+ maxWidth?: number;
7
+ }
8
+
9
+ /**
10
+ * Provides drag-to-resize behaviour for a vertical split panel sidebar.
11
+ *
12
+ * Usage:
13
+ * const { sidebarWidth, onSidebarDragStart } = useSidebarResize({ defaultWidth: 224 });
14
+ *
15
+ * <aside style={{ width: sidebarWidth }} className="flex flex-col shrink-0 overflow-hidden">…</aside>
16
+ * <div onMouseDown={onSidebarDragStart} className="w-1 cursor-col-resize shrink-0 bg-[var(--border)] hover:bg-[var(--accent)]/50" />
17
+ * <main className="flex-1 min-w-0">…</main>
18
+ */
19
+ export function useSidebarResize({
20
+ defaultWidth = 224,
21
+ minWidth = 120,
22
+ maxWidth = 480,
23
+ }: UseSidebarResizeOptions = {}) {
24
+ const [sidebarWidth, setSidebarWidth] = useState(defaultWidth);
25
+ // Track the in-progress drag without causing re-renders in the move handler
26
+ const dragRef = useRef<{ startX: number; startW: number } | null>(null);
27
+ // Keep a mutable copy so the stable onSidebarDragStart callback always reads the latest width
28
+ const widthRef = useRef(defaultWidth);
29
+
30
+ const onSidebarDragStart = useCallback((e: React.MouseEvent) => {
31
+ e.preventDefault();
32
+ dragRef.current = { startX: e.clientX, startW: widthRef.current };
33
+
34
+ const onMove = (ev: MouseEvent) => {
35
+ if (!dragRef.current) return;
36
+ const next = Math.max(minWidth, Math.min(maxWidth, dragRef.current.startW + ev.clientX - dragRef.current.startX));
37
+ widthRef.current = next;
38
+ setSidebarWidth(next);
39
+ };
40
+
41
+ const onUp = () => {
42
+ dragRef.current = null;
43
+ window.removeEventListener('mousemove', onMove);
44
+ window.removeEventListener('mouseup', onUp);
45
+ };
46
+
47
+ window.addEventListener('mousemove', onMove);
48
+ window.addEventListener('mouseup', onUp);
49
+ }, [minWidth, maxWidth]);
50
+
51
+ return { sidebarWidth, onSidebarDragStart };
52
+ }
package/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {