Disable the fold button when no run is long enough to fold

This commit is contained in:
2026-10-14 22:07:21 +02:00
parent 14e0ddc7b9
commit e09dabd441
3 changed files with 22 additions and 3 deletions
+9 -1
View File
@@ -164,6 +164,7 @@ function toggleRun(hash: string) {
}
function toggleCollapseAll() {
if (!built.value.collapsible) return showToast("nothing to fold: no run of 3 or more plain commits");
collapseAll.value = !collapseAll.value;
toggled.value = new Set();
}
@@ -244,7 +245,13 @@ onUnmounted(() => {
<span v-if="repo" class="path">{{ repo.root }}</span>
<span class="spacer" />
<span v-if="repo" class="stats">{{ visibleCommits.length }} commits · {{ visibleRefs.length }}/{{ repo.refs.length }} refs</span>
<button class="collapse" :class="{ on: collapseAll }" title="c" @click="toggleCollapseAll">
<button
class="collapse"
:class="{ on: collapseAll && built.collapsible }"
:disabled="!built.collapsible"
:title="built.collapsible ? 'c' : 'Nothing to fold: no run of 3 or more commits without a branch, tag or merge'"
@click="toggleCollapseAll"
>
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
<circle cx="8" cy="2.5" r="1.6" />
<circle cx="8" cy="13.5" r="1.6" />
@@ -362,6 +369,7 @@ onUnmounted(() => {
.spacer { flex: 1; }
.stats { color: var(--text-dim); font-size: 12px; }
.collapse { display: inline-flex; align-items: center; gap: 6px; }
.collapse:disabled { opacity: 0.4; cursor: default; background: none; }
.collapse.on { border-color: var(--accent); color: var(--accent); background: #b197fc1a; }
.main { flex: 1; display: flex; min-height: 0; }
.list { flex: 1; overflow: auto; position: relative; }
+8 -1
View File
@@ -38,7 +38,14 @@ describe("buildRows", () => {
it("does not collapse runs shorter than three", () => {
const short = chain("top:p1", "p1:p2", "p2:root", "root");
expect(hashes(buildRows(short, new Set(["top"]), true, new Set()))).toHaveLength(4);
const r = buildRows(short, new Set(["top"]), true, new Set());
expect(hashes(r)).toHaveLength(4);
expect(r.collapsible).toBe(false);
});
it("reports when a run can collapse, whether collapsed or not", () => {
expect(buildRows(commits, labeled, false, new Set()).collapsible).toBe(true);
expect(buildRows(commits, labeled, true, new Set()).collapsible).toBe(true);
});
it("stops a run at merges, forks and labels", () => {
+5 -1
View File
@@ -4,6 +4,8 @@ export interface RowsResult {
rows: DisplayRow[];
/** Commit hash → id of the run of plain commits it belongs to. */
runOf: Map<string, string>;
/** Whether any run is long enough to collapse. */
collapsible: boolean;
}
/**
@@ -35,6 +37,7 @@ export function buildRows(
const hidden = new Set<string>();
/** First commit of a collapsed run → [last commit, number hidden]. */
const jump = new Map<string, [string, number]>();
let collapsible = false;
for (const c of commits) {
if (!plain(c.hash) || plain(childOf.get(c.hash))) continue;
@@ -47,6 +50,7 @@ export function buildRows(
}
const id = c.hash;
for (const h of run) runOf.set(h, id);
if (run.length >= 3) collapsible = true;
if (run.length >= 3 && collapseAll !== toggled.has(id)) {
for (const h of run.slice(1, -1)) hidden.add(h);
jump.set(c.hash, [run[run.length - 1], run.length - 2]);
@@ -63,5 +67,5 @@ export function buildRows(
: { commit: c, parents: c.parents.map((hash) => ({ hash, dotted: false })) },
);
}
return { rows, runOf };
return { rows, runOf, collapsible };
}