diff --git a/public/git-log.ts b/public/git-log.ts index e3a4994..e9efdba 100644 --- a/public/git-log.ts +++ b/public/git-log.ts @@ -100,8 +100,16 @@ function renderCommitRow(c: CommitLogEntry): HTMLElement { } /** Render a GitLogResult into a container (clears first). Empty → an inert note. */ -export function renderGitLog(container: HTMLElement, log: GitLogResult): void { +export function renderGitLog( + container: HTMLElement, + log: GitLogResult, + labelEl?: HTMLElement, +): void { container.textContent = '' + // Design: the count sits ON the section heading, not on a line of its own — + // it qualifies "Recent commits", it is not a separate statement. + const badgeHost = labelEl ?? container + badgeHost.querySelector('.proj-commitlog-count')?.remove() if (log.commits.length === 0) { container.append(el('div', 'proj-empty', 'No commits yet.')) return @@ -110,9 +118,9 @@ export function renderGitLog(container: HTMLElement, log: GitLogResult): void { // readable before the eye has walked the rows looking for rails. const unpushedCount = log.commits.filter((c) => c.unpushed === true).length if (unpushedCount > 0 && log.upstream !== undefined) { - const badge = el('div', 'proj-commitlog-count', `↑ ${unpushedCount} unpushed`) + const badge = el('span', 'proj-commitlog-count', `↑ ${unpushedCount} unpushed`) badge.title = `${unpushedCount} commit(s) not on ${log.upstream}` - container.append(badge) + badgeHost.append(badge) } const list = el('div', 'proj-commitlog-list') @@ -150,7 +158,11 @@ export interface GitLogHandle { * the log, then swap in the rows. A fetch failure degrades to a short inert * message (never throws). destroy() removes the node and cancels the swap. */ -export function mountGitLog(container: HTMLElement, repoPath: string): GitLogHandle { +export function mountGitLog( + container: HTMLElement, + repoPath: string, + labelEl?: HTMLElement, +): GitLogHandle { let destroyed = false container.textContent = '' @@ -164,7 +176,7 @@ export function mountGitLog(container: HTMLElement, repoPath: string): GitLogHan container.append(el('div', 'proj-empty', 'Could not read recent commits.')) return } - renderGitLog(container, log) + renderGitLog(container, log, labelEl) })() return { diff --git a/public/projects.ts b/public/projects.ts index c76b591..c67d211 100644 Binary files a/public/projects.ts and b/public/projects.ts differ diff --git a/public/style.css b/public/style.css index cc93923..0deed6e 100644 --- a/public/style.css +++ b/public/style.css @@ -1725,18 +1725,17 @@ body { .proj-commitlog-count { font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 11.5px; + font-variant-numeric: tabular-nums; color: var(--accent); - margin: -4px 0 6px; + text-transform: none; + letter-spacing: 0; + margin-left: 10px; } /* ── w6/G5: worktree rows as two-line cards ──────────────────────────────── */ -.proj-wt-row { - display: grid; - grid-template-columns: 1fr auto; - gap: 14px; - align-items: center; -} - +/* NOTE: the row's own display/grid lives with the ORIGINAL .proj-wt-row rule + further down. Declaring it twice let the later copy silently win and the + Open button never reached the right edge. One rule per selector. */ .proj-wt-row-current { border-color: rgba(227, 166, 74, 0.36); background: linear-gradient(90deg, var(--accent-soft), transparent 46%); @@ -2349,10 +2348,12 @@ body { gap: 6px; } .proj-wt-row { - display: flex; + /* w6: two-line card — content column, then the actions rail pinned right. */ + display: grid; + grid-template-columns: 1fr auto; align-items: center; - gap: 10px; - padding: 9px 12px; + gap: 14px; + padding: 11px 14px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; diff --git a/test/git-log.test.ts b/test/git-log.test.ts index 97930f5..11b626c 100644 --- a/test/git-log.test.ts +++ b/test/git-log.test.ts @@ -217,14 +217,44 @@ describe('renderGitLog — unpushed count beside the heading (w6 design)', () => return { hash, at: Date.now(), subject, ...(unpushed === true ? { unpushed: true } : {}) } } - it('states the count so it is readable before scanning the rows', () => { + it('puts the count ON the heading when one is supplied, not on its own line', () => { + const host = document.createElement('div') + const label = document.createElement('div') + label.textContent = 'Recent commits' + renderGitLog( + host, + { + commits: [entry('a1', 'x', true), entry('b2', 'y', true), entry('c3', 'z')], + truncated: false, + upstream: 'origin/develop', + }, + label, + ) + expect(label.querySelector('.proj-commitlog-count')!.textContent).toContain('2') + expect(host.querySelector('.proj-commitlog-count')).toBeNull() + }) + + it('does not stack duplicate counts across re-renders', () => { + const host = document.createElement('div') + const label = document.createElement('div') + const log = { + commits: [entry('a1', 'x', true)], + truncated: false, + upstream: 'origin/develop', + } + renderGitLog(host, log, label) + renderGitLog(host, log, label) + expect(label.querySelectorAll('.proj-commitlog-count').length).toBe(1) + }) + + it('falls back to the list container when no heading is given', () => { const host = document.createElement('div') renderGitLog(host, { - commits: [entry('a1', 'x', true), entry('b2', 'y', true), entry('c3', 'z')], + commits: [entry('a1', 'x', true)], truncated: false, upstream: 'origin/develop', }) - expect(host.querySelector('.proj-commitlog-count')!.textContent).toContain('2') + expect(host.querySelector('.proj-commitlog-count')).not.toBeNull() }) it('omits the count when nothing is unpushed', () => {