feat(v0.6): Project Manager — repo discovery + Projects panel + tab wiring
Home screen gains a Sessions↔Projects segmented control. The Projects view discovers the host's git repos and, on click, opens a tab named after the repo, spawned in the repo dir, auto-running `claude` (1:N sessions per project). Backend (P2/P4): - src/http/projects.ts — parseGitHead + buildProjects(cfg, liveSessions): depth-capped BFS for .git (skips node_modules/dotdirs/symlinks, stops descending at a repo), .git/HEAD branch parse, rate-limited `git status` dirty check (execFile, no shell, 2s timeout, concurrency 8), merge of ~/.claude/projects cwds (reuses history.listSessions), cwd-prefix session merge (fresh each call), TTL discovery cache with in-flight dedup. - src/server.ts — read-only GET /projects (no Origin guard, []-fallback). Frontend (P5/P6): - public/projects.ts — mountProjects: 1:N cards (branch chip, dirty dot, session rows, "+ start claude here"), live search, localStorage favourites; pure helpers extracted for unit tests. - public/tabs.ts — openProject (#n suffix for dup names, sends 'claude\r'), countOpenWithTitlePrefix, Sessions↔Projects home toggle (updateHomeView). - public/style.css — Projects panel + .home-seg control styles. Config (P3, hardened): PROJECT_ROOTS/SCAN_DEPTH/SCAN_TTL/DIRTY_CHECK already added; this commit adds fail-fast rejection of relative PROJECT_ROOTS. Review hardening (4 confirmed HIGH + boundary validation): - carriage-return fix so claude auto-executes (A3); seg-control z-order; parallelised history merge; concurrent-cache-miss dedup. - normalizeProject guards the /projects response; getFavs filters non-strings. Tests: +57 (398 total). Backend src/http/projects.ts 93.4%, config.ts 98%. Verified: both typechecks clean, full suite green, build:web OK, coverage ≥80×4, real-browser smoke (83 repos, branch/dirty correct, click→claude tab).
This commit is contained in:
@@ -39,6 +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 { createSessionManager } from './session/manager.js'
|
||||
import { detachWs, writeInput, setClientDims } from './session/session.js'
|
||||
import type { Config } from './types.js'
|
||||
@@ -158,6 +159,16 @@ export function startServer(cfg: Config): { close(): Promise<void> } {
|
||||
res.json(manager.list())
|
||||
})
|
||||
|
||||
// Projects (v0.6 Project Manager) — discovery-only; no Origin guard (read-only, like /live-sessions).
|
||||
app.get('/projects', async (_req, res) => {
|
||||
try {
|
||||
res.json(await buildProjects(cfg, manager.list()))
|
||||
} catch (err) {
|
||||
console.error('[server] /projects failed:', err instanceof Error ? err.message : String(err))
|
||||
res.json([])
|
||||
}
|
||||
})
|
||||
|
||||
// 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