66a7825ebb
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.
69 lines
2.3 KiB
TypeScript
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
|
|
}
|