Files
hermes-agent/apps/desktop/src/lib/session-project-label.ts
T
Brooklyn Nicholson 45f663b746 feat(desktop): opt-in inbox-style session cards in the sidebar
A new "Inbox style" toggle in the sidebar filter menu renders the flat
recents list as cards: a workspace header line (project when it resolves,
else the cwd leaf, else Home) with the age at its right edge, the title
grouped with a one-line last-message preview, and a model + size footer.
The preview line ships on by default and has its own Show-menu toggle,
offered only while Inbox style is active — the one-line row has nowhere
to put it.

A render variant, deliberately not a grouping — it composes with whichever
grouping is active and only the flat recents list opts in; pinned, project,
and messaging surfaces keep the one-line row. Spacing hangs off a single
--card-gap variable; the title/preview pair is one grouped cell with its own
tighter internal gap. The age and kebab sit in flow inside the header line
rather than a full-height side column, so title, preview, and footer span
the card's entire width.

The card's project label reads through a selector that resolves the label
string, so tree polls with fresh atom identity repaint only rows whose label
actually changed.
2026-08-12 22:24:37 -05:00

41 lines
1.8 KiB
TypeScript

import { liveSessionProjectId } from '@/app/chat/sidebar/projects/workspace-groups'
import { pathLeaf } from '@/lib/display-path'
import type { ProjectInfo, SessionInfo } from '@/types/hermes'
/**
* The PROJECT a session belongs to, as a label for the sidebar card.
*
* Resolution order:
* 1. `liveSessionProjectId` — the same resolver the session COLOR reads, so
* an explicitly-projected row's name and its inherited tint always agree.
* Returns an explicit project id, or a repo root for an auto-project.
* 2. the recorded `git_repo_root` leaf.
* 3. null (a chat with no workspace at all).
*
* Why step 2 exists: `liveSessionProjectId` deliberately refuses to place a
* worktree that lives OUTSIDE its repo root (`bb/<slug>` beside the checkout
* rather than in-tree `.worktrees/`), because for LANE MEMBERSHIP a sibling
* directory genuinely can't be assigned from the row alone. A label has no
* such ambiguity: the backend already recorded which repo the session belongs
* to, so we can name it. Without this, most of a sibling-worktree workflow's
* rows would paint a blank line — and naming the repo is exactly the point of
* showing the project instead of the cwd (`hermes-agent`, not
* `hermes-agent-cwd-copy`).
*
* This never widens membership — it only names a row that placement leaves
* unplaced.
*/
export function sessionProjectLabel(session: SessionInfo, projects: ProjectInfo[]): null | string {
const projectId = liveSessionProjectId(session, projects)
if (projectId) {
const explicit = projects.find(project => project.id === projectId)
// An explicit project shows its user-given name; an auto-project's id IS
// its repo root path, so it shows that folder's leaf.
return (explicit ? explicit.name.trim() : pathLeaf(projectId)) || null
}
return pathLeaf(session.git_repo_root) || null
}