Files
hermes-agent/ui-tui/src/domain/paths.ts
T
Brooklyn Nicholson 66a7825ebb feat: unify active-project identity in chat status (supersedes #64721)
Surface the session's first-class Project in both chat surfaces: the
Desktop status bar (project name as the workspace label, full cwd in the
tooltip) and the TUI status label + /status output.

One source of truth. The per-profile projects.db is the authority, read
in tui_gateway via _project_info_for_cwd (backed by
projects_db.project_for_path) and threaded through every session.info
emission path the TUI consumes. The Desktop already caches that truth in
$projectTree, so it DERIVES the label from it (projectNameForCwd) instead
of carrying a second per-session $currentProject atom fed from
session.info.

That drops the parallel state #64721 introduced and the entire
reset/reconciliation surface it required (resume, agentless cwd.set,
gateway-switch, fresh-draft): the label is purely derived, so it stays
correct whenever the cwd or the project tree changes. Only explicit,
named projects resolve on both surfaces, so an auto-discovered repo root
keeps the cwd-leaf label everywhere.

Excludes the unrelated markdown shell-fence change bundled in #64721.
2026-07-18 23:08:13 -04:00

69 lines
2.3 KiB
TypeScript

export const shortCwd = (cwd: string, max = 28) => {
const h = process.env.HOME
const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd
return p.length <= max ? p : `…${p.slice(-(max - 1))}`
}
export const fmtCwdBranch = (cwd: string, branch: null | string, max = 40) => {
if (!branch) {
return shortCwd(cwd, max)
}
const tag = ` (${branch.length > 16 ? `…${branch.slice(-15)}` : branch})`
return `${shortCwd(cwd, Math.max(8, max - tag.length))}${tag}`
}
export const shortProject = (projectName: string, max = 18) => {
const name = projectName.trim()
return name.length <= max ? name : `${name.slice(0, Math.max(1, max - 1))}…`
}
// Status-bar workspace label: the terminal has no hover tooltip, so the project
// name is shown INLINE alongside the cwd/branch (`<project> · ~/cwd (branch)`).
// Falls back to the plain cwd/branch label when the session sits in no named
// project, and when space is tight the project name wins (it's the identity the
// user recognizes) with the cwd/branch dropped.
export const fmtProjectCwdBranch = (cwd: string, branch: null | string, projectName?: null | string, max = 40) => {
const project = shortProject(projectName || '')
if (!project) {
return fmtCwdBranch(cwd, branch, max)
}
const separator = ' · '
const remaining = max - project.length - separator.length
if (remaining < 8) {
return shortProject(project, max)
}
return `${project}${separator}${fmtCwdBranch(cwd, branch, remaining)}`
}
/**
* Compose the terminal titlebar string:
* `<marker> <session name> · <model> · <cwd>`
*
* The session name and cwd are each omitted when empty, and a long session
* name is truncated. The marker is always glued to the first present segment
* with a plain space (not a ` · ` separator). When no model is known yet the
* caller should fall back to a plain brand string instead of calling this.
*/
export const composeTabTitle = (
marker: string,
sessionName: string,
model: string,
cwd: string,
maxName = 28
): string => {
const name = sessionName.trim()
const shortName = name.length > maxName ? `${name.slice(0, maxName - 1)}…` : name
const segments = [shortName, model, cwd].filter(Boolean)
return segments.length ? `${marker} ${segments.join(' · ')}` : marker
}