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:
@@ -484,4 +484,28 @@ describe('renderProjectDetail', () => {
|
||||
const root = renderProjectDetail(detail({ isGit: false, branch: undefined }), hooks(), cbs())
|
||||
expect(root.textContent).toContain('Not a git repository')
|
||||
})
|
||||
|
||||
it('shows CLAUDE.md content + an Update button when present', () => {
|
||||
const root = renderProjectDetail(detail({ hasClaudeMd: true, claudeMd: '# Rules\nbe nice' }), hooks(), cbs())
|
||||
expect(root.querySelector('.proj-claudemd')?.textContent).toContain('be nice')
|
||||
expect(root.querySelector('.proj-claudemd-btn')?.textContent).toContain('Update')
|
||||
})
|
||||
|
||||
it('shows a Generate button + empty state when CLAUDE.md is absent', () => {
|
||||
const root = renderProjectDetail(detail({ hasClaudeMd: false }), hooks(), cbs())
|
||||
expect(root.querySelector('.proj-claudemd')).toBeNull()
|
||||
expect(root.textContent).toContain('No CLAUDE.md yet')
|
||||
expect(root.querySelector('.proj-claudemd-btn')?.textContent).toContain('Generate')
|
||||
})
|
||||
|
||||
it('the CLAUDE.md button opens a Claude /init session in the repo', () => {
|
||||
const h = hooks()
|
||||
const root = renderProjectDetail(
|
||||
detail({ name: 'r', path: '/p/r', hasClaudeMd: true, claudeMd: 'x' }),
|
||||
h,
|
||||
cbs(),
|
||||
)
|
||||
;(root.querySelector('.proj-claudemd-btn') as HTMLButtonElement).click()
|
||||
expect(h.onOpenProject).toHaveBeenCalledWith('/p/r', 'r', 'claude "/init"\r')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -398,6 +398,23 @@ describe('buildProjectDetail', () => {
|
||||
expect(d!.sessions.map((s) => s.id).sort()).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
it('includes CLAUDE.md content when present', async () => {
|
||||
const dir = path.join(tmp, 'withmd')
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(path.join(dir, 'CLAUDE.md'), '# Project\nbe excellent')
|
||||
const d = await buildProjectDetail(makeCfg({}), dir, [])
|
||||
expect(d!.hasClaudeMd).toBe(true)
|
||||
expect(d!.claudeMd).toContain('be excellent')
|
||||
})
|
||||
|
||||
it('reports no CLAUDE.md when absent', async () => {
|
||||
const dir = path.join(tmp, 'nomd')
|
||||
await fs.mkdir(dir)
|
||||
const d = await buildProjectDetail(makeCfg({}), dir, [])
|
||||
expect(d!.hasClaudeMd).toBe(false)
|
||||
expect(d!.claudeMd).toBeUndefined()
|
||||
})
|
||||
|
||||
it('lists worktrees for a real git repo (gated on git)', async () => {
|
||||
if (!(await gitAvailable())) return
|
||||
await fs.mkdir(path.join(tmp, 'realrepo'))
|
||||
|
||||
Reference in New Issue
Block a user