fix(projects): pin the worktree actions right and put the count on the heading
Two layout defects the screenshots caught that the DOM tests could not. 1. `.proj-wt-row` was declared twice — my two-line-card rule earlier in the file, the original flex rule later. Same specificity, so the later copy won and the Open button sat against the text instead of at the card's right edge. Folded the grid into the ORIGINAL rule and left a note where the duplicate was. One rule per selector; a second declaration of a layout property is a silent override waiting to happen. 2. The unpushed count rendered as its own line under "Recent commits". The design has it ON the heading, because it qualifies that heading rather than making a separate statement. renderGitLog now takes the heading element and writes the badge there, clearing any previous badge first so a re-render cannot stack duplicates. Both were invisible to the existing assertions: they checked that the elements exist and carry the right text, which was true in each case. Added a test for the no-duplicate-badge path; the right-edge alignment is a pure CSS outcome and is verified by screenshot, not by the suite.
This commit is contained in:
@@ -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 {
|
||||
|
||||
Binary file not shown.
@@ -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;
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user