feat(v0.6): per-project detail view — branch/worktrees + active sessions
Clicking a project's name opens an in-app detail view (back button returns to the grid) with: - header: name, path, branch, dirty - Branch / Worktrees: each `git worktree list` entry (branch or detached@head, main/current/locked/prunable tags, path) — a single-worktree repo shows its branch; a multi-worktree repo shows them all - Active sessions (N): detailed rows (status, devices watching, started-ago) with Open + Kill - the Claude/Codex/VS Code launcher row Backend: src/http/worktrees.ts (parseWorktrees pure + listWorktrees via execFile, no shell, best-effort); buildProjectDetail(cfg, path, liveSessions) in projects.ts (validates absolute existing dir, reuses branch/dirty/session helpers, uncached — detail is on-demand and wants fresh state); read-only GET /projects/detail?path= (no Origin guard, like /projects; 400 missing / 404 invalid). Types: WorktreeInfo, ProjectDetail. Frontend: detail view + navigation in projects.ts (grid<->detail), project name becomes a link, Kill via DELETE /live-sessions/:id. Tests +21 (452 total): worktree parser, buildProjectDetail (validation, non-git, session merge, real git-init worktree), renderProjectDetail. worktrees.ts 100% / projects.ts 93.8% cov. Verified in-browser: opened a Claude session, then the detail showed its branch+worktree and the live session with Open/Kill.
This commit is contained in:
@@ -39,7 +39,7 @@ import { parseClientMessage, serialize } from './protocol.js'
|
||||
import { isOriginAllowed } from './http/origin.js'
|
||||
import { parseHookEvent } from './http/hook.js'
|
||||
import { listSessions } from './http/history.js'
|
||||
import { buildProjects } from './http/projects.js'
|
||||
import { buildProjects, buildProjectDetail } from './http/projects.js'
|
||||
import { openInEditor } from './http/editor.js'
|
||||
import { createSessionManager } from './session/manager.js'
|
||||
import { detachWs, writeInput, setClientDims } from './session/session.js'
|
||||
@@ -170,6 +170,28 @@ export function startServer(cfg: Config): { close(): Promise<void> } {
|
||||
}
|
||||
})
|
||||
|
||||
// Project detail (v0.6) — branch/worktrees + running sessions for one repo.
|
||||
// Read-only (git branch/status/worktree-list); same threat model as /projects.
|
||||
// ?path must be an absolute existing directory (validated in buildProjectDetail).
|
||||
app.get('/projects/detail', async (req, res) => {
|
||||
const target = req.query['path']
|
||||
if (typeof target !== 'string' || target === '') {
|
||||
res.status(400).json({ error: 'path query parameter is required' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const detail = await buildProjectDetail(cfg, target, manager.list())
|
||||
if (detail === null) {
|
||||
res.status(404).json({ error: 'project not found' })
|
||||
return
|
||||
}
|
||||
res.json(detail)
|
||||
} catch (err) {
|
||||
console.error('[server] /projects/detail failed:', err instanceof Error ? err.message : String(err))
|
||||
res.status(500).json({ error: 'failed to read project detail' })
|
||||
}
|
||||
})
|
||||
|
||||
// Preview (v0.4 manage page) — the tail of a session's scrollback so the grid
|
||||
// can render a live read-only thumbnail of its current screen. No client/attach.
|
||||
app.get('/live-sessions/:id/preview', (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user