Derive repo name from repoRoot instead of storing it separately

Remove the redundant `repo` field from GlobalSession and compute it
via basename(repoRoot) at render time. Also fix prompt truncation
when terminal is extremely narrow, and simplify backfillPrompts to
avoid an intermediate array allocation.
This commit is contained in:
Chris Wanstrath 2026-03-23 21:20:44 -07:00
parent 99b0fc0f12
commit d402a3f980
3 changed files with 17 additions and 17 deletions

View File

@ -31,7 +31,7 @@ function renderSessions(
const status = statusMap.get(s) ?? "idle"
const { icon, color: bc } = styles[status]
const maxPrompt = cols - prefixWidth
const truncated = prompt.length <= maxPrompt ? prompt : prompt.slice(0, maxPrompt - 3) + "..."
const truncated = maxPrompt <= 3 ? "" : prompt.length <= maxPrompt ? prompt : prompt.slice(0, maxPrompt - 3) + "..."
console.log(`${icon} ${bc}${s.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`)
}
}
@ -61,9 +61,9 @@ async function resolveStatus(
/** Clear in_review flags for sessions where Claude is no longer active. */
async function clearStaleReviews(
sessions: state.GlobalSession[],
results: string[],
statusMap: Map<state.GlobalSession, string>,
) {
const stale = sessions.filter((s, i) => s.in_review && results[i] !== "review")
const stale = sessions.filter(s => s.in_review && statusMap.get(s) !== "review")
if (stale.length === 0) return
const byRepo = Map.groupBy(stale, s => s.repoRoot)
for (const [repoRoot, staleSessions] of byRepo) {
@ -82,13 +82,13 @@ async function backfillPrompts(sessions: { worktree: string; prompt?: string }[]
const result = await vm.exec(homedir() + "/.sandlot", "cat /home/ubuntu/.claude/history.jsonl 2>/dev/null").catch(() => null)
if (!result || result.exitCode !== 0 || !result.stdout) return
const entries = result.stdout.split("\n").filter(Boolean).map(line => {
try { return JSON.parse(line) } catch { return null }
}).filter(Boolean)
const byProject = new Map<string, string>()
for (const e of entries) {
for (const line of result.stdout.split("\n")) {
if (!line) continue
try {
const e = JSON.parse(line)
if (e.project && e.display) byProject.set(e.project, e.display)
} catch {}
}
for (const s of needsPrompt) {
@ -106,7 +106,7 @@ export async function action(opts: { json?: boolean; all?: boolean }) {
} else {
const root = await git.repoRoot()
const st = await state.load(root)
sessions = Object.values(st.sessions).map(s => ({ ...s, repo: basename(root), repoRoot: root }))
sessions = Object.values(st.sessions).map(s => ({ ...s, repoRoot: root }))
}
const vmRunning = (await vm.status()) === "running"
@ -120,7 +120,7 @@ export async function action(opts: { json?: boolean; all?: boolean }) {
const results = await Promise.all(sessions.map(s => resolveStatus(s, vmRunning)))
const statusMap = new Map(sessions.map((s, i) => [s, results[i]]))
await clearStaleReviews(sessions, results)
await clearStaleReviews(sessions, statusMap)
if (opts.json) {
const withStatus = sessions.map(s => ({ ...s, status: statusMap.get(s) ?? "idle" }))

View File

@ -1,3 +1,4 @@
import { basename } from "path"
import type { Command } from "commander"
import * as vm from "../vm.ts"
import * as git from "../git.ts"
@ -67,7 +68,7 @@ export function register(program: Command) {
// Determine status for each session in parallel
const statusEntries = await Promise.all(
sessions.map(async (sess): Promise<[string, string]> => {
const key = `${sess.repo}/${sess.branch}`
const key = `${basename(sess.repoRoot)}/${sess.branch}`
try {
if (await vm.isClaudeActive(sess.worktree, sess.branch)) return [key, "active"]
if (await git.isDirty(sess.worktree)) return [key, "dirty"]
@ -81,7 +82,7 @@ export function register(program: Command) {
const icons: Record<string, string> = { idle: `${dim}${reset}`, active: `${cyan}${reset}`, dirty: `${yellow}${reset}`, saved: `${green}${reset}` }
const branchColors: Record<string, string> = { idle: dim, active: cyan, dirty: yellow, saved: green }
const repoWidth = Math.max(4, ...sessions.map(sess => sess.repo.length))
const repoWidth = Math.max(4, ...sessions.map(sess => basename(sess.repoRoot).length))
const branchWidth = Math.max(6, ...sessions.map(sess => sess.branch.length))
const cols = process.stdout.columns || 80
const prefixWidth = repoWidth + branchWidth + 6
@ -90,13 +91,13 @@ export function register(program: Command) {
for (const sess of sessions) {
const prompt = (sess.prompt ?? "").split("\n")[0]
const key = `${sess.repo}/${sess.branch}`
const key = `${basename(sess.repoRoot)}/${sess.branch}`
const status = statuses[key]
const icon = icons[status]
const bc = branchColors[status]
const maxPrompt = cols - prefixWidth
const truncated = maxPrompt > 3 && prompt.length > maxPrompt ? prompt.slice(0, maxPrompt - 3) + "..." : prompt
console.log(`${icon} ${dim}${sess.repo.padEnd(repoWidth)}${reset} ${bc}${sess.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`)
console.log(`${icon} ${dim}${basename(sess.repoRoot).padEnd(repoWidth)}${reset} ${bc}${sess.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`)
}
console.log(`\n${dim}◯ idle${reset} · ${cyan}◎ active${reset} · ${yellow}◐ unsaved${reset} · ${green}● saved${reset}`)

View File

@ -54,7 +54,6 @@ export async function removeSession(repoRoot: string, branch: string): Promise<v
}
export interface GlobalSession extends Session {
repo: string
repoRoot: string
}
@ -94,7 +93,7 @@ export async function loadAll(): Promise<GlobalSession[]> {
try {
const st = await load(repoRoot)
for (const session of Object.values(st.sessions)) {
all.push({ ...session, repo: entry.name, repoRoot })
all.push({ ...session, repoRoot })
}
} catch {}
}