feat(v0.6): kill a session directly from the Projects grid cards

The detail view could kill sessions, but the grid card's session rows were
enter-only. Add a ✕ kill button to each card session row (stopPropagation so it
doesn't also enter the session); makeSessionRow gains an optional onKill,
makeProjectCard an optional onKillSession, and mountProjects wires it to
DELETE /live-sessions/:id followed by a refresh.

Frontend-only. projects-panel tests +1 (51): ✕ present only with the callback,
click kills the right id without entering. Verified in-browser: a card with 2
sessions dropped to 1 after clicking ✕.
This commit is contained in:
Yaojia Wang
2026-06-30 13:35:53 +02:00
parent 99bc6fd9f6
commit 46698b8b5e
4 changed files with 69 additions and 1 deletions

View File

@@ -174,6 +174,7 @@ function sessionDotClass(status: ClaudeStatus): string {
function makeSessionRow(
sess: { id: string; title?: string; status: ClaudeStatus; clientCount: number },
onEnterSession: (id: string) => void,
onKill?: (id: string) => void,
): HTMLElement {
const row = el('div', 'proj-session-row')
row.setAttribute('role', 'button')
@@ -185,6 +186,19 @@ function makeSessionRow(
row.append(dot, title, badge)
if (onKill) {
const kill = el('button', 'proj-session-kill', '✕')
kill.title = 'Kill this session'
kill.setAttribute('aria-label', 'Kill session')
// Don't let the kill button trigger the row's enter handler.
kill.addEventListener('pointerdown', (e) => e.stopPropagation())
kill.addEventListener('click', (e) => {
e.stopPropagation()
onKill(sess.id)
})
row.append(kill)
}
row.addEventListener('click', () => onEnterSession(sess.id))
row.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -283,6 +297,7 @@ export function makeProjectCard(
hooks: ProjectsHooks,
onToggleFav: (path: string) => void,
onOpenDetail?: (path: string) => void,
onKillSession?: (id: string) => void,
): HTMLElement {
const card = el('div', 'proj-card')
@@ -339,7 +354,7 @@ export function makeProjectCard(
if (runningSessions.length > 0) {
const sessionsList = el('div', 'proj-sessions-list')
for (const sess of runningSessions) {
sessionsList.append(makeSessionRow(sess, hooks.onEnterSession))
sessionsList.append(makeSessionRow(sess, hooks.onEnterSession, onKillSession))
}
card.append(sessionsList)
}
@@ -507,6 +522,10 @@ export function mountProjects(host: HTMLElement, hooks: ProjectsHooks): Projects
void refresh()
}
function killAndRefresh(id: string): void {
void killSession(id).then(() => void refresh())
}
function renderGrid(): void {
const filtered = filterProjects(allProjects, searchInput.value)
const sorted = sortProjects(filtered, favs)
@@ -534,6 +553,7 @@ export function mountProjects(host: HTMLElement, hooks: ProjectsHooks): Projects
renderGrid()
},
openDetail,
killAndRefresh,
),
)
}

View File

@@ -1066,6 +1066,26 @@ body {
white-space: nowrap;
}
/* Kill (✕) on a session row — close the session straight from the card. */
.proj-session-kill {
flex: none;
width: 20px;
height: 20px;
line-height: 1;
padding: 0;
border: none;
border-radius: 5px;
background: none;
color: var(--text-faint);
cursor: pointer;
font-size: 11px;
transition: background 0.1s ease, color 0.1s ease;
}
.proj-session-kill:hover {
background: var(--red, #e5484d);
color: #fff;
}
/* Client-count badge (👁 N) */
.proj-session-badge {
font-size: 11px;