feat(v0.6): show CLAUDE.md on the project detail page + /init generate/update button
The detail page now reads <repo>/CLAUDE.md and shows it in a scrollable
monospace box, with one smart button: "↻ Update" when it exists / "✨ Generate"
when it doesn't. The button opens an interactive Claude session in the repo
running /init (claude "/init") so you review what it writes — reuses the project
launcher, no new backend orchestration, stays a byte-shuttle.
- ProjectDetail += hasClaudeMd / claudeMd; buildProjectDetail reads CLAUDE.md
(truncated 64KB, best-effort, in the existing Promise.all)
- renderProjectDetail: CLAUDE.md section (content box or empty state) + button
- style.css: .proj-section-row / .proj-claudemd-btn / .proj-claudemd
Tests +5 (458): backend read present/absent; frontend content+Update,
empty+Generate, button opens `claude "/init"`. Verified: tsc clean, full suite
green, build OK, curl returns the content, in-browser shows the box + button.
This commit is contained in:
@@ -464,6 +464,33 @@ export function renderProjectDetail(
|
||||
root.append(list)
|
||||
}
|
||||
|
||||
// CLAUDE.md — view + a smart Generate/Update button that runs /init interactively.
|
||||
const cmHead = el('div', 'proj-section-row')
|
||||
cmHead.append(el('div', 'proj-section-title', 'CLAUDE.md'))
|
||||
const cmBtn = el('button', 'proj-claudemd-btn', detail.hasClaudeMd ? '↻ Update' : '✨ Generate')
|
||||
cmBtn.title = detail.hasClaudeMd
|
||||
? 'Open a Claude session running /init to refresh CLAUDE.md'
|
||||
: 'Open a Claude session running /init to create CLAUDE.md'
|
||||
// Launch claude with /init as the initial prompt, in this repo (interactive,
|
||||
// so you review what it writes). Reuses the project launcher.
|
||||
cmBtn.addEventListener('click', () => hooks.onOpenProject(detail.path, detail.name, 'claude "/init"\r'))
|
||||
cmHead.append(cmBtn)
|
||||
root.append(cmHead)
|
||||
|
||||
if (detail.hasClaudeMd && detail.claudeMd !== undefined) {
|
||||
const pre = el('pre', 'proj-claudemd')
|
||||
pre.textContent = detail.claudeMd
|
||||
root.append(pre)
|
||||
} else {
|
||||
root.append(
|
||||
el(
|
||||
'div',
|
||||
'proj-empty',
|
||||
'No CLAUDE.md yet — generate one to give Claude project-specific instructions.',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Launchers
|
||||
root.append(el('div', 'proj-section-title', 'Start'))
|
||||
root.append(makeLauncherRow(detail, hooks))
|
||||
|
||||
@@ -1278,6 +1278,49 @@ body {
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
/* Section header with an inline action (CLAUDE.md generate/update). */
|
||||
.proj-section-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 22px 0 10px;
|
||||
}
|
||||
.proj-section-row .proj-section-title {
|
||||
margin: 0;
|
||||
}
|
||||
.proj-claudemd-btn {
|
||||
flex: none;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-strong);
|
||||
color: var(--accent);
|
||||
border-radius: 7px;
|
||||
padding: 6px 12px;
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, border-color 0.12s ease;
|
||||
}
|
||||
.proj-claudemd-btn:hover {
|
||||
background: var(--accent-soft);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.proj-claudemd {
|
||||
margin: 0;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
padding: 12px 14px;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
/* Worktrees */
|
||||
.proj-wt-list {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user