@aion0/forge 0.5.26 → 0.5.27
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/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
- package/RELEASE_NOTES.md +11 -28
- package/app/api/terminal-bell/route.ts +6 -2
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useRef, lazy, Suspense } from 'react';
|
|
4
|
+
import { signOut } from 'next-auth/react';
|
|
5
|
+
import TaskBoard from './TaskBoard';
|
|
6
|
+
import TaskDetail from './TaskDetail';
|
|
7
|
+
import TunnelToggle from './TunnelToggle';
|
|
8
|
+
import type { Task } from '@/src/types';
|
|
9
|
+
import type { WebTerminalHandle } from './WebTerminal';
|
|
10
|
+
|
|
11
|
+
const WebTerminal = lazy(() => import('./WebTerminal'));
|
|
12
|
+
const DocsViewer = lazy(() => import('./DocsViewer'));
|
|
13
|
+
const CodeViewer = lazy(() => import('./CodeViewer'));
|
|
14
|
+
const ProjectManager = lazy(() => import('./ProjectManager'));
|
|
15
|
+
const BrowserPanel = lazy(() => import('./BrowserPanel'));
|
|
16
|
+
const PipelineView = lazy(() => import('./PipelineView'));
|
|
17
|
+
const HelpDialog = lazy(() => import('./HelpDialog'));
|
|
18
|
+
const LogViewer = lazy(() => import('./LogViewer'));
|
|
19
|
+
const SkillsPanel = lazy(() => import('./SkillsPanel'));
|
|
20
|
+
const UsagePanel = lazy(() => import('./UsagePanel'));
|
|
21
|
+
const SessionView = lazy(() => import('./SessionView'));
|
|
22
|
+
const NewTaskModal = lazy(() => import('./NewTaskModal'));
|
|
23
|
+
const SettingsModal = lazy(() => import('./SettingsModal'));
|
|
24
|
+
const MonitorPanel = lazy(() => import('./MonitorPanel'));
|
|
25
|
+
const WorkspaceView = lazy(() => import('./WorkspaceView'));
|
|
26
|
+
// WorkspaceTree moved into ProjectDetail — no longer needed at Dashboard level
|
|
27
|
+
|
|
28
|
+
interface UsageSummary {
|
|
29
|
+
provider: string;
|
|
30
|
+
totalInput: number;
|
|
31
|
+
totalOutput: number;
|
|
32
|
+
totalCost: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ProviderInfo {
|
|
36
|
+
name: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
hasKey: boolean;
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ProjectInfo {
|
|
43
|
+
name: string;
|
|
44
|
+
path: string;
|
|
45
|
+
language: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function FloatingBrowser({ onClose }: { onClose: () => void }) {
|
|
49
|
+
const [pos, setPos] = useState({ x: 60, y: 60 });
|
|
50
|
+
const [size, setSize] = useState({ w: 700, h: 500 });
|
|
51
|
+
const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null);
|
|
52
|
+
const resizeRef = useRef<{ startX: number; startY: number; origW: number; origH: number } | null>(null);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className="fixed z-50 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-2xl flex flex-col overflow-hidden"
|
|
57
|
+
style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
className="flex items-center gap-2 px-3 py-1.5 bg-[var(--bg-tertiary)] border-b border-[var(--border)] cursor-move shrink-0 select-none"
|
|
61
|
+
onMouseDown={(e) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y };
|
|
64
|
+
const onMove = (ev: MouseEvent) => {
|
|
65
|
+
if (!dragRef.current) return;
|
|
66
|
+
setPos({ x: Math.max(0, dragRef.current.origX + ev.clientX - dragRef.current.startX), y: Math.max(0, dragRef.current.origY + ev.clientY - dragRef.current.startY) });
|
|
67
|
+
};
|
|
68
|
+
const onUp = () => { dragRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
69
|
+
window.addEventListener('mousemove', onMove);
|
|
70
|
+
window.addEventListener('mouseup', onUp);
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<span className="text-[11px] font-semibold text-[var(--text-primary)]">Browser</span>
|
|
74
|
+
<button onClick={onClose} className="ml-auto text-[var(--text-secondary)] hover:text-[var(--red)] text-sm leading-none">✕</button>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex-1 min-h-0 flex flex-col">
|
|
77
|
+
<BrowserPanel />
|
|
78
|
+
</div>
|
|
79
|
+
<div
|
|
80
|
+
onMouseDown={(e) => {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
resizeRef.current = { startX: e.clientX, startY: e.clientY, origW: size.w, origH: size.h };
|
|
84
|
+
const onMove = (ev: MouseEvent) => {
|
|
85
|
+
if (!resizeRef.current) return;
|
|
86
|
+
setSize({ w: Math.max(400, resizeRef.current.origW + ev.clientX - resizeRef.current.startX), h: Math.max(300, resizeRef.current.origH + ev.clientY - resizeRef.current.startY) });
|
|
87
|
+
};
|
|
88
|
+
const onUp = () => { resizeRef.current = null; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
89
|
+
window.addEventListener('mousemove', onMove);
|
|
90
|
+
window.addEventListener('mouseup', onUp);
|
|
91
|
+
}}
|
|
92
|
+
className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize"
|
|
93
|
+
style={{ background: 'linear-gradient(135deg, transparent 50%, var(--border) 50%)' }}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default function Dashboard({ user }: { user: any }) {
|
|
100
|
+
const [viewMode, setViewMode] = useState<'tasks' | 'sessions' | 'terminal' | 'docs' | 'projects' | 'pipelines' | 'workspace' | 'skills' | 'logs' | 'usage'>('terminal');
|
|
101
|
+
// workspaceProject state kept for forge:open-terminal event compatibility
|
|
102
|
+
const [workspaceProject, setWorkspaceProject] = useState<{ name: string; path: string } | null>(null);
|
|
103
|
+
const [browserMode, setBrowserMode] = useState<'none' | 'float' | 'right' | 'left'>('none');
|
|
104
|
+
const [showBrowserMenu, setShowBrowserMenu] = useState(false);
|
|
105
|
+
const [browserWidth, setBrowserWidth] = useState(600);
|
|
106
|
+
const browserDragRef = useRef<{ startX: number; startW: number } | null>(null);
|
|
107
|
+
const [browserDragging, setBrowserDragging] = useState(false);
|
|
108
|
+
const [tasks, setTasks] = useState<Task[]>([]);
|
|
109
|
+
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
110
|
+
const [showNewTask, setShowNewTask] = useState(false);
|
|
111
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
112
|
+
const [showMonitor, setShowMonitor] = useState(false);
|
|
113
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
114
|
+
const [usage, setUsage] = useState<UsageSummary[]>([]);
|
|
115
|
+
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
116
|
+
const [projects, setProjects] = useState<ProjectInfo[]>([]);
|
|
117
|
+
const [onlineCount, setOnlineCount] = useState<{ total: number; remote: number }>({ total: 0, remote: 0 });
|
|
118
|
+
const [versionInfo, setVersionInfo] = useState<{ current: string; latest: string; hasUpdate: boolean } | null>(null);
|
|
119
|
+
const [notifications, setNotifications] = useState<any[]>([]);
|
|
120
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
121
|
+
const [showNotifications, setShowNotifications] = useState(false);
|
|
122
|
+
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
123
|
+
const [theme, setTheme] = useState<'dark' | 'light'>('dark');
|
|
124
|
+
const [displayName, setDisplayName] = useState(user?.name || 'Forge');
|
|
125
|
+
const terminalRef = useRef<WebTerminalHandle>(null);
|
|
126
|
+
|
|
127
|
+
// Theme: load from localStorage + apply
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const saved = localStorage.getItem('forge-theme') as 'dark' | 'light' | null;
|
|
130
|
+
if (saved) {
|
|
131
|
+
setTheme(saved);
|
|
132
|
+
document.documentElement.setAttribute('data-theme', saved === 'light' ? 'light' : '');
|
|
133
|
+
}
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
const toggleTheme = () => {
|
|
137
|
+
const next = theme === 'dark' ? 'light' : 'dark';
|
|
138
|
+
setTheme(next);
|
|
139
|
+
document.documentElement.setAttribute('data-theme', next === 'light' ? 'light' : '');
|
|
140
|
+
localStorage.setItem('forge-theme', next);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Fetch display name from settings
|
|
144
|
+
const refreshDisplayName = useCallback(() => {
|
|
145
|
+
fetch('/api/settings').then(r => r.json())
|
|
146
|
+
.then((s: any) => { if (s.displayName) setDisplayName(s.displayName); })
|
|
147
|
+
.catch(() => {});
|
|
148
|
+
}, []);
|
|
149
|
+
useEffect(() => { refreshDisplayName(); }, [refreshDisplayName]);
|
|
150
|
+
|
|
151
|
+
// Listen for open-terminal events from ProjectManager
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const handler = (e: Event) => {
|
|
154
|
+
const { projectPath, projectName, agentId, resumeMode, sessionId, profileEnv } = (e as CustomEvent).detail;
|
|
155
|
+
setViewMode('terminal');
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
terminalRef.current?.openProjectTerminal?.(projectPath, projectName, agentId, resumeMode, sessionId, profileEnv);
|
|
158
|
+
}, 300);
|
|
159
|
+
};
|
|
160
|
+
window.addEventListener('forge:open-terminal', handler);
|
|
161
|
+
return () => window.removeEventListener('forge:open-terminal', handler);
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
// Listen for navigation events (e.g. from ProjectDetail → Pipelines)
|
|
165
|
+
const [pendingPipelineId, setPendingPipelineId] = useState<string | null>(null);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const handler = (e: Event) => {
|
|
168
|
+
const { view, pipelineId } = (e as CustomEvent).detail;
|
|
169
|
+
if (view) setViewMode(view);
|
|
170
|
+
if (pipelineId) setPendingPipelineId(pipelineId);
|
|
171
|
+
};
|
|
172
|
+
window.addEventListener('forge:navigate', handler);
|
|
173
|
+
return () => window.removeEventListener('forge:navigate', handler);
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
// Version check (on mount + every 10 min)
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const check = () => fetch('/api/version').then(r => r.json()).then(setVersionInfo).catch(() => {});
|
|
179
|
+
check();
|
|
180
|
+
const id = setInterval(check, 10 * 60 * 1000);
|
|
181
|
+
return () => clearInterval(id);
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
// Notification polling
|
|
185
|
+
const fetchNotifications = useCallback(() => {
|
|
186
|
+
fetch('/api/notifications').then(r => r.json()).then(data => {
|
|
187
|
+
setNotifications(data.notifications || []);
|
|
188
|
+
setUnreadCount(data.unread || 0);
|
|
189
|
+
}).catch(() => {});
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
fetchNotifications();
|
|
194
|
+
const id = setInterval(fetchNotifications, 10000);
|
|
195
|
+
return () => clearInterval(id);
|
|
196
|
+
}, [fetchNotifications]);
|
|
197
|
+
|
|
198
|
+
// Heartbeat for online user tracking
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
const ping = () => {
|
|
201
|
+
fetch('/api/online', { method: 'POST' })
|
|
202
|
+
.then(r => r.json())
|
|
203
|
+
.then(setOnlineCount)
|
|
204
|
+
.catch(() => {});
|
|
205
|
+
};
|
|
206
|
+
ping();
|
|
207
|
+
const id = setInterval(ping, 15_000); // every 15s
|
|
208
|
+
return () => clearInterval(id);
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const fetchData = useCallback(async () => {
|
|
212
|
+
try {
|
|
213
|
+
const [tasksRes, statusRes, projectsRes] = await Promise.all([
|
|
214
|
+
fetch('/api/tasks'),
|
|
215
|
+
fetch('/api/status'),
|
|
216
|
+
fetch('/api/projects'),
|
|
217
|
+
]);
|
|
218
|
+
if (tasksRes.ok) setTasks(await tasksRes.json());
|
|
219
|
+
if (statusRes.ok) { const s = await statusRes.json(); setProviders(s.providers); setUsage(s.usage); }
|
|
220
|
+
if (projectsRes.ok) setProjects(await projectsRes.json());
|
|
221
|
+
} catch {}
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
fetchData();
|
|
226
|
+
const interval = setInterval(fetchData, 5000);
|
|
227
|
+
return () => clearInterval(interval);
|
|
228
|
+
}, [fetchData]);
|
|
229
|
+
|
|
230
|
+
const activeTask = tasks.find(t => t.id === activeTaskId);
|
|
231
|
+
const running = tasks.filter(t => t.status === 'running');
|
|
232
|
+
const queued = tasks.filter(t => t.status === 'queued');
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className="h-screen flex">
|
|
236
|
+
{/* Browser — left side */}
|
|
237
|
+
{browserMode === 'left' && (
|
|
238
|
+
<>
|
|
239
|
+
<div style={{ width: browserWidth }} className="shrink-0 flex flex-col relative">
|
|
240
|
+
<Suspense fallback={null}><BrowserPanel onClose={() => setBrowserMode('none')} /></Suspense>
|
|
241
|
+
{browserDragging && <div className="absolute inset-0 z-10" />}
|
|
242
|
+
</div>
|
|
243
|
+
<div
|
|
244
|
+
onMouseDown={(e) => {
|
|
245
|
+
e.preventDefault();
|
|
246
|
+
browserDragRef.current = { startX: e.clientX, startW: browserWidth };
|
|
247
|
+
setBrowserDragging(true);
|
|
248
|
+
const onMove = (ev: MouseEvent) => {
|
|
249
|
+
if (!browserDragRef.current) return;
|
|
250
|
+
setBrowserWidth(Math.max(320, Math.min(1200, browserDragRef.current.startW + (ev.clientX - browserDragRef.current.startX))));
|
|
251
|
+
};
|
|
252
|
+
const onUp = () => { browserDragRef.current = null; setBrowserDragging(false); window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
253
|
+
window.addEventListener('mousemove', onMove);
|
|
254
|
+
window.addEventListener('mouseup', onUp);
|
|
255
|
+
}}
|
|
256
|
+
className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50"
|
|
257
|
+
/>
|
|
258
|
+
</>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* Forge main area */}
|
|
262
|
+
<div className="flex-1 flex flex-col min-w-0 min-h-0 overflow-hidden">
|
|
263
|
+
{/* Top bar */}
|
|
264
|
+
<header className="h-12 border-b-2 border-[var(--border)] flex items-center justify-between px-4 shrink-0 bg-[var(--bg-secondary)]">
|
|
265
|
+
<div className="flex items-center gap-4">
|
|
266
|
+
<img src="/icon.png" alt="Forge" width={28} height={28} className="rounded" />
|
|
267
|
+
<span className="text-sm font-bold text-[var(--accent)]">Forge</span>
|
|
268
|
+
{versionInfo && (
|
|
269
|
+
<span className="flex items-center gap-1.5">
|
|
270
|
+
<span className="text-[10px] text-[var(--text-secondary)]">v{versionInfo.current}</span>
|
|
271
|
+
{versionInfo.hasUpdate && (
|
|
272
|
+
<span
|
|
273
|
+
className="text-[9px] px-1.5 py-0.5 bg-[var(--accent)]/15 text-[var(--accent)] rounded cursor-default"
|
|
274
|
+
title={`forge upgrade\nnpm install -g @aion0/forge@latest`}
|
|
275
|
+
>
|
|
276
|
+
v{versionInfo.latest} available
|
|
277
|
+
</span>
|
|
278
|
+
)}
|
|
279
|
+
<button
|
|
280
|
+
onClick={async () => {
|
|
281
|
+
const res = await fetch('/api/version?force=1');
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
setVersionInfo(data);
|
|
284
|
+
if (data.hasUpdate) fetchNotifications();
|
|
285
|
+
}}
|
|
286
|
+
className="text-[9px] px-1 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
287
|
+
title="Check for updates"
|
|
288
|
+
>
|
|
289
|
+
↻
|
|
290
|
+
</button>
|
|
291
|
+
</span>
|
|
292
|
+
)}
|
|
293
|
+
|
|
294
|
+
{/* View mode toggle */}
|
|
295
|
+
<div className="flex items-center bg-[var(--bg-tertiary)] rounded p-0.5">
|
|
296
|
+
{/* Workspace */}
|
|
297
|
+
{(['terminal', 'projects'] as const).map(mode => (
|
|
298
|
+
<button
|
|
299
|
+
key={mode}
|
|
300
|
+
onClick={() => setViewMode(mode)}
|
|
301
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
302
|
+
viewMode === mode
|
|
303
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
304
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
305
|
+
}`}
|
|
306
|
+
>
|
|
307
|
+
{{ terminal: 'Vibe Coding', projects: 'Projects' }[mode]}
|
|
308
|
+
</button>
|
|
309
|
+
))}
|
|
310
|
+
<span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
|
|
311
|
+
{/* Docs */}
|
|
312
|
+
<button
|
|
313
|
+
onClick={() => setViewMode('docs')}
|
|
314
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
315
|
+
viewMode === 'docs'
|
|
316
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
317
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
318
|
+
}`}
|
|
319
|
+
>
|
|
320
|
+
Docs
|
|
321
|
+
</button>
|
|
322
|
+
<span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
|
|
323
|
+
{/* Automation */}
|
|
324
|
+
{(['tasks', 'pipelines'] as const).map(mode => (
|
|
325
|
+
<button
|
|
326
|
+
key={mode}
|
|
327
|
+
onClick={() => setViewMode(mode)}
|
|
328
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
329
|
+
viewMode === mode
|
|
330
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
331
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
332
|
+
}`}
|
|
333
|
+
>
|
|
334
|
+
{{ tasks: 'Tasks', pipelines: 'Pipelines' }[mode]}
|
|
335
|
+
</button>
|
|
336
|
+
))}
|
|
337
|
+
<span className="w-[2px] h-4 bg-[var(--text-secondary)]/30 mx-1.5" />
|
|
338
|
+
{/* Marketplace */}
|
|
339
|
+
<button
|
|
340
|
+
onClick={() => setViewMode('skills')}
|
|
341
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
342
|
+
viewMode === 'skills'
|
|
343
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
344
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
345
|
+
}`}
|
|
346
|
+
>
|
|
347
|
+
Marketplace
|
|
348
|
+
</button>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
{viewMode === 'tasks' && (
|
|
352
|
+
<span className="text-[10px] text-[var(--text-secondary)]">
|
|
353
|
+
{running.length} running · {queued.length} queued · {tasks.filter(t => t.status === 'done').length} done
|
|
354
|
+
</span>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
357
|
+
<div className="flex items-center gap-2.5">
|
|
358
|
+
{viewMode === 'tasks' && (
|
|
359
|
+
<button
|
|
360
|
+
onClick={() => setShowNewTask(true)}
|
|
361
|
+
className="text-[10px] px-2.5 py-1 bg-[var(--accent)] text-white rounded hover:opacity-90"
|
|
362
|
+
>
|
|
363
|
+
+ New Task
|
|
364
|
+
</button>
|
|
365
|
+
)}
|
|
366
|
+
{/* Help */}
|
|
367
|
+
<button
|
|
368
|
+
onClick={() => setShowHelp(v => !v)}
|
|
369
|
+
className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
|
|
370
|
+
showHelp
|
|
371
|
+
? 'border-[var(--accent)] text-[var(--accent)]'
|
|
372
|
+
: 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
|
|
373
|
+
}`}
|
|
374
|
+
>?</button>
|
|
375
|
+
<div className="relative">
|
|
376
|
+
<button
|
|
377
|
+
onClick={() => setShowBrowserMenu(v => !v)}
|
|
378
|
+
className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
|
|
379
|
+
browserMode !== 'none'
|
|
380
|
+
? 'border-blue-500 text-blue-400'
|
|
381
|
+
: 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
|
|
382
|
+
}`}
|
|
383
|
+
>
|
|
384
|
+
Browser
|
|
385
|
+
</button>
|
|
386
|
+
{showBrowserMenu && (
|
|
387
|
+
<>
|
|
388
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowBrowserMenu(false)} />
|
|
389
|
+
<div className="absolute top-full right-0 mt-1 z-50 bg-[var(--bg-secondary)] border border-[var(--border)] rounded shadow-lg py-1 min-w-[140px]">
|
|
390
|
+
{browserMode !== 'none' && (
|
|
391
|
+
<button onClick={() => { setBrowserMode('none'); setShowBrowserMenu(false); }} className="w-full text-left px-3 py-1.5 text-[10px] text-red-400 hover:bg-[var(--bg-tertiary)]">
|
|
392
|
+
Close Browser
|
|
393
|
+
</button>
|
|
394
|
+
)}
|
|
395
|
+
<button onClick={() => { setBrowserMode('float'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'float' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
|
|
396
|
+
Floating Window
|
|
397
|
+
</button>
|
|
398
|
+
<button onClick={() => { setBrowserMode('right'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'right' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
|
|
399
|
+
Right Side
|
|
400
|
+
</button>
|
|
401
|
+
<button onClick={() => { setBrowserMode('left'); setShowBrowserMenu(false); }} className={`w-full text-left px-3 py-1.5 text-[10px] hover:bg-[var(--bg-tertiary)] ${browserMode === 'left' ? 'text-[var(--accent)]' : 'text-[var(--text-primary)]'}`}>
|
|
402
|
+
Left Side
|
|
403
|
+
</button>
|
|
404
|
+
<button onClick={() => {
|
|
405
|
+
const url = localStorage.getItem('forge-browser-url');
|
|
406
|
+
if (url) window.open(url, '_blank');
|
|
407
|
+
else { const u = prompt('Enter URL to open:'); if (u) window.open(u.trim(), '_blank'); }
|
|
408
|
+
setShowBrowserMenu(false);
|
|
409
|
+
}} className="w-full text-left px-3 py-1.5 text-[10px] text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]">
|
|
410
|
+
New Tab
|
|
411
|
+
</button>
|
|
412
|
+
</div>
|
|
413
|
+
</>
|
|
414
|
+
)}
|
|
415
|
+
</div>
|
|
416
|
+
<button
|
|
417
|
+
onClick={() => setViewMode('usage')}
|
|
418
|
+
className={`text-[10px] px-2 py-0.5 border rounded transition-colors ${
|
|
419
|
+
viewMode === 'usage'
|
|
420
|
+
? 'border-[var(--accent)] text-[var(--accent)]'
|
|
421
|
+
: 'border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--text-secondary)]'
|
|
422
|
+
}`}
|
|
423
|
+
>Usage</button>
|
|
424
|
+
<TunnelToggle />
|
|
425
|
+
{onlineCount.total > 0 && (
|
|
426
|
+
<span className="text-[10px] text-[var(--text-secondary)] flex items-center gap-1" title={`${onlineCount.total} online${onlineCount.remote > 0 ? `, ${onlineCount.remote} remote` : ''}`}>
|
|
427
|
+
<span className="text-green-500">●</span>
|
|
428
|
+
{onlineCount.total}
|
|
429
|
+
</span>
|
|
430
|
+
)}
|
|
431
|
+
<span className="w-[2px] h-4 bg-[var(--text-secondary)]/30" />
|
|
432
|
+
{/* Alerts */}
|
|
433
|
+
<div className="relative">
|
|
434
|
+
<button
|
|
435
|
+
onClick={() => { setShowNotifications(v => !v); setShowUserMenu(false); }}
|
|
436
|
+
className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] relative px-1"
|
|
437
|
+
>
|
|
438
|
+
Alerts
|
|
439
|
+
{unreadCount > 0 && (
|
|
440
|
+
<span className="absolute -top-1.5 -right-1.5 min-w-[14px] h-[14px] rounded-full bg-[var(--red)] text-[8px] text-white flex items-center justify-center px-1 font-bold">
|
|
441
|
+
{unreadCount > 99 ? '99+' : unreadCount}
|
|
442
|
+
</span>
|
|
443
|
+
)}
|
|
444
|
+
</button>
|
|
445
|
+
{showNotifications && (
|
|
446
|
+
<>
|
|
447
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
|
|
448
|
+
<div className="absolute right-0 top-8 w-[360px] max-h-[480px] bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-xl z-50 flex flex-col">
|
|
449
|
+
<div className="flex items-center justify-between px-3 py-2 border-b border-[var(--border)]">
|
|
450
|
+
<span className="text-xs font-bold text-[var(--text-primary)]">Notifications</span>
|
|
451
|
+
<div className="flex items-center gap-2">
|
|
452
|
+
{unreadCount > 0 && (
|
|
453
|
+
<button
|
|
454
|
+
onClick={async () => {
|
|
455
|
+
await fetch('/api/notifications', {
|
|
456
|
+
method: 'POST',
|
|
457
|
+
headers: { 'Content-Type': 'application/json' },
|
|
458
|
+
body: JSON.stringify({ action: 'markAllRead' }),
|
|
459
|
+
});
|
|
460
|
+
fetchNotifications();
|
|
461
|
+
}}
|
|
462
|
+
className="text-[9px] text-[var(--accent)] hover:underline"
|
|
463
|
+
>
|
|
464
|
+
Mark all read
|
|
465
|
+
</button>
|
|
466
|
+
)}
|
|
467
|
+
<button
|
|
468
|
+
onClick={() => setShowNotifications(false)}
|
|
469
|
+
className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
470
|
+
>
|
|
471
|
+
Close
|
|
472
|
+
</button>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
<div className="flex-1 overflow-y-auto">
|
|
476
|
+
{notifications.length === 0 ? (
|
|
477
|
+
<div className="p-6 text-center text-xs text-[var(--text-secondary)]">No notifications</div>
|
|
478
|
+
) : (
|
|
479
|
+
notifications.map((n: any) => (
|
|
480
|
+
<div
|
|
481
|
+
key={n.id}
|
|
482
|
+
className={`group px-3 py-2 border-b border-[var(--border)]/50 hover:bg-[var(--bg-tertiary)] ${!n.read ? 'bg-[var(--accent)]/5' : ''}`}
|
|
483
|
+
>
|
|
484
|
+
<div className="flex items-start gap-2">
|
|
485
|
+
<span className="text-[10px] mt-0.5 shrink-0">
|
|
486
|
+
{n.type === 'task_done' ? '✅' : n.type === 'task_failed' ? '❌' : n.type === 'pipeline_done' ? '🔗' : n.type === 'pipeline_failed' ? '💔' : n.type === 'tunnel' ? '🌐' : 'ℹ️'}
|
|
487
|
+
</span>
|
|
488
|
+
<div className="flex-1 min-w-0">
|
|
489
|
+
<div className="flex items-center gap-1">
|
|
490
|
+
<span className={`text-[11px] truncate ${!n.read ? 'font-semibold text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}`}>
|
|
491
|
+
{n.title}
|
|
492
|
+
</span>
|
|
493
|
+
{!n.read && <span className="w-1.5 h-1.5 rounded-full bg-[var(--accent)] shrink-0" />}
|
|
494
|
+
</div>
|
|
495
|
+
{n.body && (
|
|
496
|
+
<p className="text-[9px] text-[var(--text-secondary)] truncate mt-0.5">{n.body}</p>
|
|
497
|
+
)}
|
|
498
|
+
<span className="text-[8px] text-[var(--text-secondary)]">
|
|
499
|
+
{new Date(n.createdAt).toLocaleString()}
|
|
500
|
+
</span>
|
|
501
|
+
</div>
|
|
502
|
+
<div className="hidden group-hover:flex items-center gap-1 shrink-0">
|
|
503
|
+
{!n.read && (
|
|
504
|
+
<button
|
|
505
|
+
onClick={async (e) => {
|
|
506
|
+
e.stopPropagation();
|
|
507
|
+
await fetch('/api/notifications', {
|
|
508
|
+
method: 'POST',
|
|
509
|
+
headers: { 'Content-Type': 'application/json' },
|
|
510
|
+
body: JSON.stringify({ action: 'markRead', id: n.id }),
|
|
511
|
+
});
|
|
512
|
+
fetchNotifications();
|
|
513
|
+
}}
|
|
514
|
+
className="text-[8px] px-1 py-0.5 text-[var(--accent)] hover:underline"
|
|
515
|
+
>
|
|
516
|
+
read
|
|
517
|
+
</button>
|
|
518
|
+
)}
|
|
519
|
+
<button
|
|
520
|
+
onClick={async (e) => {
|
|
521
|
+
e.stopPropagation();
|
|
522
|
+
await fetch('/api/notifications', {
|
|
523
|
+
method: 'POST',
|
|
524
|
+
headers: { 'Content-Type': 'application/json' },
|
|
525
|
+
body: JSON.stringify({ action: 'delete', id: n.id }),
|
|
526
|
+
});
|
|
527
|
+
fetchNotifications();
|
|
528
|
+
}}
|
|
529
|
+
className="text-[8px] px-1 py-0.5 text-red-400 hover:underline"
|
|
530
|
+
>
|
|
531
|
+
del
|
|
532
|
+
</button>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
))
|
|
537
|
+
)}
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
</>
|
|
541
|
+
)}
|
|
542
|
+
</div>
|
|
543
|
+
{/* User menu */}
|
|
544
|
+
<div className="relative">
|
|
545
|
+
<button
|
|
546
|
+
onClick={() => { setShowUserMenu(v => !v); setShowNotifications(false); }}
|
|
547
|
+
className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)] flex items-center gap-1 px-1"
|
|
548
|
+
>
|
|
549
|
+
{displayName} <span className="text-[8px]">▾</span>
|
|
550
|
+
</button>
|
|
551
|
+
{showUserMenu && (
|
|
552
|
+
<>
|
|
553
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowUserMenu(false)} />
|
|
554
|
+
<div className="absolute right-0 top-8 w-[140px] bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-xl z-50 py-1">
|
|
555
|
+
<button
|
|
556
|
+
onClick={() => { setShowMonitor(true); setShowUserMenu(false); }}
|
|
557
|
+
className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
|
558
|
+
>
|
|
559
|
+
Monitor
|
|
560
|
+
</button>
|
|
561
|
+
<button
|
|
562
|
+
onClick={() => { setShowSettings(true); setShowUserMenu(false); }}
|
|
563
|
+
className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
|
564
|
+
>
|
|
565
|
+
Settings
|
|
566
|
+
</button>
|
|
567
|
+
<button
|
|
568
|
+
onClick={() => { setViewMode('logs'); setShowUserMenu(false); }}
|
|
569
|
+
className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
|
570
|
+
>
|
|
571
|
+
Logs
|
|
572
|
+
</button>
|
|
573
|
+
<a
|
|
574
|
+
href="/mobile"
|
|
575
|
+
className="block w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)]"
|
|
576
|
+
>
|
|
577
|
+
Mobile View
|
|
578
|
+
</a>
|
|
579
|
+
<div className="border-t border-[var(--border)] my-1" />
|
|
580
|
+
<button
|
|
581
|
+
onClick={() => signOut({ callbackUrl: '/login' })}
|
|
582
|
+
className="w-full text-left text-[11px] px-3 py-1.5 text-[var(--text-secondary)] hover:text-[var(--red)] hover:bg-[var(--bg-tertiary)]"
|
|
583
|
+
>
|
|
584
|
+
Logout
|
|
585
|
+
</button>
|
|
586
|
+
</div>
|
|
587
|
+
</>
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
</header>
|
|
592
|
+
|
|
593
|
+
{/* Main content */}
|
|
594
|
+
<div className="flex-1 flex min-h-0">
|
|
595
|
+
{viewMode === 'tasks' ? (
|
|
596
|
+
<>
|
|
597
|
+
{/* Left — Task list */}
|
|
598
|
+
<aside className="w-72 border-r border-[var(--border)] flex flex-col shrink-0">
|
|
599
|
+
<TaskBoard tasks={tasks} activeId={activeTaskId} onSelect={setActiveTaskId} onRefresh={fetchData} />
|
|
600
|
+
</aside>
|
|
601
|
+
|
|
602
|
+
{/* Center — Task detail / empty state */}
|
|
603
|
+
<main className="flex-1 flex flex-col min-w-0">
|
|
604
|
+
{activeTask ? (
|
|
605
|
+
<TaskDetail
|
|
606
|
+
task={activeTask}
|
|
607
|
+
onRefresh={fetchData}
|
|
608
|
+
onFollowUp={async (data) => {
|
|
609
|
+
const res = await fetch('/api/tasks', {
|
|
610
|
+
method: 'POST',
|
|
611
|
+
headers: { 'Content-Type': 'application/json' },
|
|
612
|
+
body: JSON.stringify(data),
|
|
613
|
+
});
|
|
614
|
+
const newTask = await res.json();
|
|
615
|
+
setActiveTaskId(newTask.id);
|
|
616
|
+
fetchData();
|
|
617
|
+
}}
|
|
618
|
+
/>
|
|
619
|
+
) : (
|
|
620
|
+
<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">
|
|
621
|
+
<div className="text-center space-y-2">
|
|
622
|
+
<p className="text-lg">Select a task or create a new one</p>
|
|
623
|
+
<p className="text-xs">Submit tasks for Claude Code to work on autonomously</p>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
)}
|
|
627
|
+
</main>
|
|
628
|
+
|
|
629
|
+
{/* Right — Status panel */}
|
|
630
|
+
<aside className="w-56 border-l border-[var(--border)] flex flex-col shrink-0 p-3 space-y-4">
|
|
631
|
+
{/* Providers */}
|
|
632
|
+
<div>
|
|
633
|
+
<h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Providers</h3>
|
|
634
|
+
<div className="space-y-1">
|
|
635
|
+
{providers.map(p => (
|
|
636
|
+
<div key={p.name} className="flex items-center justify-between text-xs">
|
|
637
|
+
<span className={p.hasKey && p.enabled ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}>
|
|
638
|
+
{p.displayName}
|
|
639
|
+
</span>
|
|
640
|
+
<span className={`text-[10px] ${p.hasKey && p.enabled ? 'text-[var(--green)]' : 'text-[var(--red)]'}`}>
|
|
641
|
+
{p.hasKey && p.enabled ? '● active' : '○ off'}
|
|
642
|
+
</span>
|
|
643
|
+
</div>
|
|
644
|
+
))}
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
{/* Usage */}
|
|
649
|
+
{usage.length > 0 && (
|
|
650
|
+
<div>
|
|
651
|
+
<h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Usage (30d)</h3>
|
|
652
|
+
<div className="space-y-1">
|
|
653
|
+
{usage.map((u, i) => (
|
|
654
|
+
<div key={i} className="text-xs">
|
|
655
|
+
<div className="flex justify-between">
|
|
656
|
+
<span className="text-[var(--text-secondary)]">{u.provider}</span>
|
|
657
|
+
<span className="text-[var(--text-primary)]">{((u.totalInput + u.totalOutput) / 1000).toFixed(0)}k tokens</span>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
))}
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
{/* Running tasks */}
|
|
666
|
+
{running.length > 0 && (
|
|
667
|
+
<div>
|
|
668
|
+
<h3 className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-2">Running</h3>
|
|
669
|
+
<div className="space-y-1">
|
|
670
|
+
{running.map(t => (
|
|
671
|
+
<button
|
|
672
|
+
key={t.id}
|
|
673
|
+
onClick={() => { setViewMode('tasks'); setActiveTaskId(t.id); }}
|
|
674
|
+
className="w-full text-left px-2 py-1 rounded text-xs hover:bg-[var(--bg-tertiary)]"
|
|
675
|
+
>
|
|
676
|
+
<span className="text-[var(--green)] text-[10px]">● </span>
|
|
677
|
+
<span className="truncate">{t.projectName}</span>
|
|
678
|
+
</button>
|
|
679
|
+
))}
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
)}
|
|
683
|
+
</aside>
|
|
684
|
+
</>
|
|
685
|
+
) : null}
|
|
686
|
+
|
|
687
|
+
{/* Projects — keep alive to preserve state across tab switches */}
|
|
688
|
+
<div className={`flex-1 flex flex-col min-h-0 ${viewMode !== 'projects' ? 'hidden' : ''}`}>
|
|
689
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
690
|
+
<ProjectManager />
|
|
691
|
+
</Suspense>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
{/* Pipelines */}
|
|
695
|
+
{viewMode === 'pipelines' && (
|
|
696
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
697
|
+
<PipelineView
|
|
698
|
+
onViewTask={(taskId) => { setViewMode('tasks'); setActiveTaskId(taskId); }}
|
|
699
|
+
focusPipelineId={pendingPipelineId}
|
|
700
|
+
onFocusHandled={() => setPendingPipelineId(null)}
|
|
701
|
+
/>
|
|
702
|
+
</Suspense>
|
|
703
|
+
)}
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
{/* Skills */}
|
|
707
|
+
{viewMode === 'skills' && (
|
|
708
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
709
|
+
<SkillsPanel />
|
|
710
|
+
</Suspense>
|
|
711
|
+
)}
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
{/* Usage */}
|
|
715
|
+
{viewMode === 'usage' && (
|
|
716
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
717
|
+
<UsagePanel />
|
|
718
|
+
</Suspense>
|
|
719
|
+
)}
|
|
720
|
+
|
|
721
|
+
{/* Logs */}
|
|
722
|
+
{viewMode === 'logs' && (
|
|
723
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
724
|
+
<LogViewer />
|
|
725
|
+
</Suspense>
|
|
726
|
+
)}
|
|
727
|
+
|
|
728
|
+
{/* Docs — always mounted to keep terminal session alive */}
|
|
729
|
+
<div className={viewMode === 'docs' ? 'flex-1 min-h-0 flex' : 'hidden'}>
|
|
730
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
731
|
+
<DocsViewer />
|
|
732
|
+
</Suspense>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
{/* Code — terminal + file browser, always mounted to keep terminal sessions alive */}
|
|
736
|
+
<div className={viewMode === 'terminal' ? 'flex-1 min-h-0 flex' : 'hidden'}>
|
|
737
|
+
<Suspense fallback={<div className="flex-1 flex items-center justify-center text-[var(--text-secondary)]">Loading...</div>}>
|
|
738
|
+
<CodeViewer terminalRef={terminalRef} />
|
|
739
|
+
</Suspense>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
</div>{/* close Forge main area */}
|
|
743
|
+
|
|
744
|
+
{/* Browser — right side */}
|
|
745
|
+
{browserMode === 'right' && (
|
|
746
|
+
<>
|
|
747
|
+
<div
|
|
748
|
+
onMouseDown={(e) => {
|
|
749
|
+
e.preventDefault();
|
|
750
|
+
browserDragRef.current = { startX: e.clientX, startW: browserWidth };
|
|
751
|
+
setBrowserDragging(true);
|
|
752
|
+
const onMove = (ev: MouseEvent) => {
|
|
753
|
+
if (!browserDragRef.current) return;
|
|
754
|
+
setBrowserWidth(Math.max(320, Math.min(1200, browserDragRef.current.startW - (ev.clientX - browserDragRef.current.startX))));
|
|
755
|
+
};
|
|
756
|
+
const onUp = () => { browserDragRef.current = null; setBrowserDragging(false); window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
757
|
+
window.addEventListener('mousemove', onMove);
|
|
758
|
+
window.addEventListener('mouseup', onUp);
|
|
759
|
+
}}
|
|
760
|
+
className="w-1 bg-[var(--border)] cursor-col-resize shrink-0 hover:bg-[var(--accent)]/50"
|
|
761
|
+
/>
|
|
762
|
+
<div style={{ width: browserWidth }} className="shrink-0 flex flex-col relative">
|
|
763
|
+
<Suspense fallback={null}><BrowserPanel onClose={() => setBrowserMode('none')} /></Suspense>
|
|
764
|
+
{browserDragging && <div className="absolute inset-0 z-10" />}
|
|
765
|
+
</div>
|
|
766
|
+
</>
|
|
767
|
+
)}
|
|
768
|
+
|
|
769
|
+
{/* Browser — floating window */}
|
|
770
|
+
{browserMode === 'float' && (
|
|
771
|
+
<Suspense fallback={null}>
|
|
772
|
+
<FloatingBrowser onClose={() => setBrowserMode('none')} />
|
|
773
|
+
</Suspense>
|
|
774
|
+
)}
|
|
775
|
+
|
|
776
|
+
{showNewTask && (
|
|
777
|
+
<Suspense fallback={null}>
|
|
778
|
+
<NewTaskModal
|
|
779
|
+
onClose={() => setShowNewTask(false)}
|
|
780
|
+
onCreate={async (data) => {
|
|
781
|
+
await fetch('/api/tasks', {
|
|
782
|
+
method: 'POST',
|
|
783
|
+
headers: { 'Content-Type': 'application/json' },
|
|
784
|
+
body: JSON.stringify(data),
|
|
785
|
+
});
|
|
786
|
+
setShowNewTask(false);
|
|
787
|
+
fetchData();
|
|
788
|
+
}}
|
|
789
|
+
/>
|
|
790
|
+
</Suspense>
|
|
791
|
+
)}
|
|
792
|
+
|
|
793
|
+
{showMonitor && <Suspense fallback={null}><MonitorPanel onClose={() => setShowMonitor(false)} /></Suspense>}
|
|
794
|
+
|
|
795
|
+
{showSettings && (
|
|
796
|
+
<Suspense fallback={null}>
|
|
797
|
+
<SettingsModal onClose={() => { setShowSettings(false); fetchData(); refreshDisplayName(); }} />
|
|
798
|
+
</Suspense>
|
|
799
|
+
)}
|
|
800
|
+
{showHelp && (
|
|
801
|
+
<Suspense fallback={null}>
|
|
802
|
+
<HelpDialog onClose={() => setShowHelp(false)} />
|
|
803
|
+
</Suspense>
|
|
804
|
+
)}
|
|
805
|
+
</div>
|
|
806
|
+
);
|
|
807
|
+
}
|