@aion0/forge 0.4.3 → 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,11 +1,11 @@
1
- # Forge v0.4.3
1
+ # Forge v0.4.5
2
2
 
3
3
  Released: 2026-03-21
4
4
 
5
- ## Changes since v0.4.2
5
+ ## Changes since v0.4.4
6
6
 
7
7
  ### Bug Fixes
8
- - fix: restore Forge name in header + allow icon.png on login page
8
+ - fix: add drag-to-resize for left sidebars across all split-panel pages (#14)
9
9
 
10
10
 
11
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.2...v0.4.3
11
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.4...v0.4.5
@@ -61,7 +61,7 @@ export async function POST(req: Request) {
61
61
  const config = getConfig(body.projectPath);
62
62
  const projectName = config?.projectName || body.projectName;
63
63
  try {
64
- const pipeline = startPipeline('issue-auto-fix', {
64
+ const pipeline = startPipeline('issue-fix-and-review', {
65
65
  issue_id: String(body.issueId),
66
66
  project: projectName,
67
67
  base_branch: config?.baseBranch || body.baseBranch || 'auto-detect',
@@ -93,7 +93,7 @@ export async function POST(req: Request) {
93
93
  // Reset the processed record first, then re-create with new pipeline
94
94
  resetProcessedIssue(body.projectPath, body.issueId);
95
95
  try {
96
- const pipeline = startPipeline('issue-auto-fix', {
96
+ const pipeline = startPipeline('issue-fix-and-review', {
97
97
  issue_id: String(body.issueId),
98
98
  project: projectName,
99
99
  base_branch: config?.baseBranch || 'auto-detect',
@@ -61,6 +61,20 @@ export async function POST(req: Request) {
61
61
  }
62
62
  }
63
63
 
64
+ // Delete workflow
65
+ if (body.action === 'delete-workflow' && body.name) {
66
+ const { existsSync: ex, unlinkSync: ul } = await import('node:fs');
67
+ const filePath = join(FLOWS_DIR, `${body.name}.yaml`);
68
+ const altPath = join(FLOWS_DIR, `${body.name}.yml`);
69
+ const path = ex(filePath) ? filePath : ex(altPath) ? altPath : null;
70
+ if (!path) return NextResponse.json({ error: 'Not found' }, { status: 404 });
71
+ // Check if built-in
72
+ const w = listWorkflows().find(w => w.name === body.name);
73
+ if (w?.builtin) return NextResponse.json({ error: 'Cannot delete built-in workflow' }, { status: 400 });
74
+ ul(path);
75
+ return NextResponse.json({ ok: true });
76
+ }
77
+
64
78
  // Start pipeline
65
79
  const { workflow, input } = body;
66
80
  if (!workflow) {
@@ -0,0 +1,68 @@
1
+ import { NextResponse } from 'next/server';
2
+ import {
3
+ getBindings,
4
+ addBinding,
5
+ removeBinding,
6
+ updateBinding,
7
+ getRuns,
8
+ deleteRun,
9
+ triggerPipeline,
10
+ getNextRunTime,
11
+ } from '@/lib/pipeline-scheduler';
12
+ import { listWorkflows } from '@/lib/pipeline';
13
+
14
+ // GET /api/project-pipelines?project=PATH
15
+ export async function GET(req: Request) {
16
+ const { searchParams } = new URL(req.url);
17
+ const projectPath = searchParams.get('project');
18
+ if (!projectPath) return NextResponse.json({ error: 'project required' }, { status: 400 });
19
+
20
+ const bindings = getBindings(projectPath).map(b => ({
21
+ ...b,
22
+ nextRunAt: getNextRunTime(b),
23
+ }));
24
+ const runs = getRuns(projectPath);
25
+ const workflows = listWorkflows().map(w => ({ name: w.name, description: w.description, builtin: w.builtin }));
26
+
27
+ return NextResponse.json({ bindings, runs, workflows });
28
+ }
29
+
30
+ // POST /api/project-pipelines
31
+ export async function POST(req: Request) {
32
+ const body = await req.json();
33
+
34
+ if (body.action === 'add') {
35
+ const { projectPath, projectName, workflowName, config } = body;
36
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
37
+ addBinding(projectPath, projectName || projectPath.split('/').pop(), workflowName, config);
38
+ return NextResponse.json({ ok: true });
39
+ }
40
+
41
+ if (body.action === 'remove') {
42
+ removeBinding(body.projectPath, body.workflowName);
43
+ return NextResponse.json({ ok: true });
44
+ }
45
+
46
+ if (body.action === 'update') {
47
+ updateBinding(body.projectPath, body.workflowName, { enabled: body.enabled, config: body.config });
48
+ return NextResponse.json({ ok: true });
49
+ }
50
+
51
+ if (body.action === 'trigger') {
52
+ const { projectPath, projectName, workflowName, input } = body;
53
+ if (!projectPath || !workflowName) return NextResponse.json({ error: 'projectPath and workflowName required' }, { status: 400 });
54
+ try {
55
+ const result = triggerPipeline(projectPath, projectName || projectPath.split('/').pop(), workflowName, input);
56
+ return NextResponse.json({ ok: true, ...result });
57
+ } catch (e: any) {
58
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
59
+ }
60
+ }
61
+
62
+ if (body.action === 'delete-run') {
63
+ deleteRun(body.id);
64
+ return NextResponse.json({ ok: true });
65
+ }
66
+
67
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
68
+ }
@@ -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">
@@ -137,18 +137,16 @@ export default function Dashboard({ user }: { user: any }) {
137
137
  }, []);
138
138
 
139
139
  const fetchData = useCallback(async () => {
140
- const [tasksRes, statusRes, projectsRes] = await Promise.all([
141
- fetch('/api/tasks'),
142
- fetch('/api/status'),
143
- fetch('/api/projects'),
144
- ]);
145
- const tasksData = await tasksRes.json();
146
- const statusData = await statusRes.json();
147
- const projectsData = await projectsRes.json();
148
- setTasks(tasksData);
149
- setProviders(statusData.providers);
150
- setUsage(statusData.usage);
151
- setProjects(projectsData);
140
+ try {
141
+ const [tasksRes, statusRes, projectsRes] = await Promise.all([
142
+ fetch('/api/tasks'),
143
+ fetch('/api/status'),
144
+ fetch('/api/projects'),
145
+ ]);
146
+ if (tasksRes.ok) setTasks(await tasksRes.json());
147
+ if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
148
+ if (projectsRes.ok) setProjects(await projectsRes.json());
149
+ } catch {}
152
150
  }, []);
153
151
 
154
152
  useEffect(() => {
@@ -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 */}
@@ -360,7 +360,9 @@ export default function PipelineEditor({ onSave, onClose, initialYaml }: {
360
360
  Save
361
361
  </button>
362
362
  <button
363
- onClick={onClose}
363
+ onClick={() => {
364
+ if (confirm('Discard unsaved changes?')) onClose();
365
+ }}
364
366
  className="text-xs px-3 py-1 text-gray-400 hover:text-white"
365
367
  >
366
368
  Cancel