feat(v0.6): project card launchers — Claude/Codex/VS Code logos + active highlight
Replace each card's "+ start claude here" with a row of three brand-logo launcher buttons (logo + caption, touch-friendly, tooltips): - Claude (coral burst) → new terminal tab running `claude` - Codex (OpenAI mark) → new terminal tab running `codex` - VS Code (blue ribbon) → opens the repo in VS Code ON THE HOST VS Code: new guarded POST /open-in-editor runs `<EDITOR_CMD> <path>` (default `code`) on the host via execFile (no shell); path validated as an existing absolute dir; Origin guard (CSRF) like the DELETE routes. EDITOR_CMD config added. Active-session highlight (user request): when a project has a running Claude session (Claude-hook status != unknown — plain shells/codex stay unknown), the Claude logo gets a coral ring + tint, with a count badge when more than one. New: public/icons.ts (simple-icons SVGs, fill=currentColor), src/http/editor.ts. onOpenProject gains an optional cmd; makeProjectCard exported for tests. Tests +16 (431 total): editor validation/launch, EDITOR_CMD config, launcher row (3 buttons, claude/codex commands), and the active-Claude highlight + badge. Verified: coverage >=80x4, build OK, both typechecks clean; in-browser the 3 logos render with brand colours, and POST /open-in-editor actually launched VS Code on the host (403 no-Origin / 400 bad-path / 204 valid-dir all correct).
This commit is contained in:
@@ -1077,35 +1077,83 @@ body {
|
||||
}
|
||||
|
||||
/* "+ New" / "+ start claude here" action button */
|
||||
.proj-new {
|
||||
background: var(--surface-3);
|
||||
/* Launcher row — Claude · Codex · VS Code, each a brand-logo icon button. */
|
||||
.proj-launchers {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.proj-launch {
|
||||
position: relative; /* for the active count badge */
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 9px 4px;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-dim);
|
||||
border-radius: 8px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 9px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
transition: background 0.1s ease, color 0.1s ease, border-color 0.1s ease;
|
||||
color: var(--text-dim); /* drives the logo colour (currentColor) by default */
|
||||
transition: background 0.12s ease, border-color 0.12s ease, transform 0.08s ease;
|
||||
}
|
||||
.proj-new:hover {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
border-color: rgba(227, 166, 74, 0.3);
|
||||
.proj-launch svg {
|
||||
display: block;
|
||||
}
|
||||
.proj-launch-cap {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.proj-launch:hover {
|
||||
background: var(--surface-3);
|
||||
border-color: var(--border-strong);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.proj-launch:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.proj-launch:hover .proj-launch-cap {
|
||||
color: var(--text);
|
||||
}
|
||||
/* Brand colours on the logos (currentColor) */
|
||||
.proj-launch-claude {
|
||||
color: #d97757; /* Claude coral */
|
||||
}
|
||||
.proj-launch-codex {
|
||||
color: #e7e8ec; /* OpenAI mark — near-white on dark */
|
||||
}
|
||||
.proj-launch-vscode {
|
||||
color: #3aa0e3; /* VS Code blue */
|
||||
}
|
||||
|
||||
/* Primary action — no sessions yet; full Amber accent button */
|
||||
.proj-new.proj-new-primary {
|
||||
background: var(--accent);
|
||||
color: var(--on-accent);
|
||||
border-color: var(--accent);
|
||||
font-weight: 600;
|
||||
/* Active state — a matching session is running (Claude logo when a Claude
|
||||
* session is live). Coral ring + faint tint draw the eye without shouting. */
|
||||
.proj-launch-active {
|
||||
background: rgba(217, 119, 87, 0.14);
|
||||
border-color: #d97757;
|
||||
box-shadow: 0 0 0 1px rgba(217, 119, 87, 0.45) inset;
|
||||
}
|
||||
.proj-new.proj-new-primary:hover {
|
||||
background: var(--accent-2);
|
||||
border-color: var(--accent-2);
|
||||
color: var(--on-accent);
|
||||
.proj-launch-active .proj-launch-cap {
|
||||
color: #e9b9a6;
|
||||
}
|
||||
.proj-launch-badge {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
min-width: 15px;
|
||||
height: 15px;
|
||||
padding: 0 3px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background: #d97757;
|
||||
color: #1a1408;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
|
||||
Reference in New Issue
Block a user