feat: complete reorganize-long-files cleanup

- Extract tool instance lifecycle endpoints (start/stop/restart/delete) from
  api/tool/tool_instances.py into new api/tool/tool_lifecycle.py.
- Register tool_lifecycle_router in main.py and api/tool/__init__.py.
- Extract inline WorkspaceDetailPage components into
  components/features/workspace/: detail header, tab bars, file/git/tools/settings
  panels. Slim page from ~446 to ~62 lines.
- Update OpenSpec reorganize-long-files tasks to reflect completed work and
  current source state; mark change completed.
- Regenerate project maps.

Quality gates: python3 -m py_compile (backend clean), npm run typecheck,
npm run lint, npm test -- --run (87 passed), pytest workspace integration
and unit tests (27 passed, 1 skipped).
This commit is contained in:
Developer
2026-06-12 18:53:23 +00:00
parent efb62fe41a
commit ce8b5dc86d
45 changed files with 878 additions and 830 deletions
@@ -0,0 +1,81 @@
/** Tools tab for the workspace detail page. */
import { useState } from "react";
import { Icon } from "../../icon";
import { ToolStarter } from "../tool/tool-starter";
import { useWorkspaceInstances } from "../../../hooks/use-workspace-instances";
import type { Workspace } from "../../../types/workspace";
interface WorkspaceToolsPanelProps {
workspace: Workspace;
}
export function WorkspaceToolsPanel({ workspace }: WorkspaceToolsPanelProps) {
const { instances, loading, refresh } = useWorkspaceInstances(workspace.id);
const [showModal, setShowModal] = useState(false);
return (
<div className="tools-tab">
{loading && <p className="muted">Loading instances...</p>}
{instances.length === 0 ? (
<div className="empty-state-card">
<Icon name="terminal" size="lg" />
<h3>No tools running</h3>
<p>Start a tool to begin coding in this workspace</p>
<button
className="btn btn-primary"
onClick={() => setShowModal(true)}
type="button"
>
Start Tool
</button>
</div>
) : (
<>
<div className="instances-grid">
{instances.map((instance) => (
<div
key={instance.id}
className={`instance-card ${instance.status}`}
>
<h4>{instance.display_name}</h4>
<span className="status-badge">{instance.status}</span>
{instance.url && (
<a
href={instance.url}
target={`instance-${instance.id}`}
rel="noreferrer"
>
Open
</a>
)}
</div>
))}
</div>
<button
className="btn btn-primary"
onClick={() => setShowModal(true)}
type="button"
>
Start Another Tool
</button>
</>
)}
{showModal && (
<div className="modal-overlay" onClick={() => setShowModal(false)}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h3>Start Tool</h3>
<ToolStarter
workspace={workspace}
onStarted={() => {
setShowModal(false);
void refresh();
}}
onCancel={() => setShowModal(false)}
/>
</div>
</div>
)}
</div>
);
}