Worktree diffs

Open a worktree to read its changes. Keep several open to compare them. These paths, commits and patches are illustrative snapshots.

Keep reply drafts keyed by thread 2 modified · 1 untracked (excluded)

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
c8ebd603a4646f4c771bc7076a2ba4830b2def29
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/drafts.js b/runtime/drafts.js
index 7e24c4f..6b21e75 100644
--- a/runtime/drafts.js
+++ b/runtime/drafts.js
@@ -5,7 +5,7 @@ export function saveDraft(thread, text) {
 }
 
 export function draft(thread) {
-  return activeDraft;
+  return drafts.get(thread.id) ?? "";
 }
 
 export function clearDraft(thread) {
diff --git a/tests/drafts.test.js b/tests/drafts.test.js
index 003e396..8eb2121 100644
--- a/tests/drafts.test.js
+++ b/tests/drafts.test.js
@@ -3,3 +3,9 @@ import { openThread, typeReply } from "./helpers.js";
 test("opens a composer", () => {
   expect(openThread("a").draft).toBe("");
 });
+
+test("keeps each thread draft", () => {
+  typeReply("a", "Keep this text");
+  openThread("b");
+  expect(openThread("a").draft).toBe("Keep this text");
+});

Preserve the selected file after filtering 1 staged · 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
e1550c3bc496aebce09c28eb0e14a45a8e9708ac
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/widgets/file-tree.js b/widgets/file-tree.js
index 1f64b19..b7ca4fe 100644
--- a/widgets/file-tree.js
+++ b/widgets/file-tree.js
@@ -1,3 +1,3 @@
 function select(file) {
-  activePath = file.path;
+  navigateToDatum(target, file.key);
 }
diff --git a/widgets/diff.js b/widgets/diff.js
index 9db353a..e392d0b 100644
--- a/widgets/diff.js
+++ b/widgets/diff.js
@@ -1 +1,2 @@
-const selected = files[0];
+const selected = files.find(file => file.key === current)
+  ?? files[0];

Show added and removed line counts 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/worktrunk
Current commit
243bd04ef5473061535abd2391bf9a26c6c5f0fa
Comparison base
214d8e109fd9abcdac34c52735a359ed64b96c57
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/src/summary.rs b/src/summary.rs
index 0f0860f..9374881 100644
--- a/src/summary.rs
+++ b/src/summary.rs
@@ -1,3 +1,3 @@
 fn summary(added: usize, removed: usize) -> String {
-    format!("{} lines", added - removed)
+    format!("+{added} -{removed}")
 }

Clarify page authoring examples Clean

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
fd2d11d6a89297dd9101a6dddf55977c8bdc33fd
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.

No changes against this base.

Try other sets · one, two, eight, or empty

These examples use the same widget. The larger set includes a binary change, a long branch name, and an unavailable snapshot.

One worktree

Keep reply drafts keyed by thread 2 modified · 1 untracked (excluded)

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
c8ebd603a4646f4c771bc7076a2ba4830b2def29
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/drafts.js b/runtime/drafts.js
index 7e24c4f..6b21e75 100644
--- a/runtime/drafts.js
+++ b/runtime/drafts.js
@@ -5,7 +5,7 @@ export function saveDraft(thread, text) {
 }
 
 export function draft(thread) {
-  return activeDraft;
+  return drafts.get(thread.id) ?? "";
 }
 
 export function clearDraft(thread) {
diff --git a/tests/drafts.test.js b/tests/drafts.test.js
index 003e396..8eb2121 100644
--- a/tests/drafts.test.js
+++ b/tests/drafts.test.js
@@ -3,3 +3,9 @@ import { openThread, typeReply } from "./helpers.js";
 test("opens a composer", () => {
   expect(openThread("a").draft).toBe("");
 });
+
+test("keeps each thread draft", () => {
+  typeReply("a", "Keep this text");
+  openThread("b");
+  expect(openThread("a").draft).toBe("Keep this text");
+});

Two worktrees

Keep reply drafts keyed by thread 2 modified · 1 untracked (excluded)

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
c8ebd603a4646f4c771bc7076a2ba4830b2def29
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/drafts.js b/runtime/drafts.js
index 7e24c4f..6b21e75 100644
--- a/runtime/drafts.js
+++ b/runtime/drafts.js
@@ -5,7 +5,7 @@ export function saveDraft(thread, text) {
 }
 
 export function draft(thread) {
-  return activeDraft;
+  return drafts.get(thread.id) ?? "";
 }
 
 export function clearDraft(thread) {
diff --git a/tests/drafts.test.js b/tests/drafts.test.js
index 003e396..8eb2121 100644
--- a/tests/drafts.test.js
+++ b/tests/drafts.test.js
@@ -3,3 +3,9 @@ import { openThread, typeReply } from "./helpers.js";
 test("opens a composer", () => {
   expect(openThread("a").draft).toBe("");
 });
+
+test("keeps each thread draft", () => {
+  typeReply("a", "Keep this text");
+  openThread("b");
+  expect(openThread("a").draft).toBe("Keep this text");
+});

Preserve the selected file after filtering 1 staged · 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
e1550c3bc496aebce09c28eb0e14a45a8e9708ac
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/widgets/file-tree.js b/widgets/file-tree.js
index 1f64b19..b7ca4fe 100644
--- a/widgets/file-tree.js
+++ b/widgets/file-tree.js
@@ -1,3 +1,3 @@
 function select(file) {
-  activePath = file.path;
+  navigateToDatum(target, file.key);
 }
diff --git a/widgets/diff.js b/widgets/diff.js
index 9db353a..e392d0b 100644
--- a/widgets/diff.js
+++ b/widgets/diff.js
@@ -1 +1,2 @@
-const selected = files[0];
+const selected = files.find(file => file.key === current)
+  ?? files[0];

Eight worktrees

Keep reply drafts keyed by thread 2 modified · 1 untracked (excluded)

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
c8ebd603a4646f4c771bc7076a2ba4830b2def29
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/drafts.js b/runtime/drafts.js
index 7e24c4f..6b21e75 100644
--- a/runtime/drafts.js
+++ b/runtime/drafts.js
@@ -5,7 +5,7 @@ export function saveDraft(thread, text) {
 }
 
 export function draft(thread) {
-  return activeDraft;
+  return drafts.get(thread.id) ?? "";
 }
 
 export function clearDraft(thread) {
diff --git a/tests/drafts.test.js b/tests/drafts.test.js
index 003e396..8eb2121 100644
--- a/tests/drafts.test.js
+++ b/tests/drafts.test.js
@@ -3,3 +3,9 @@ import { openThread, typeReply } from "./helpers.js";
 test("opens a composer", () => {
   expect(openThread("a").draft).toBe("");
 });
+
+test("keeps each thread draft", () => {
+  typeReply("a", "Keep this text");
+  openThread("b");
+  expect(openThread("a").draft).toBe("Keep this text");
+});

Preserve the selected file after filtering 1 staged · 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
e1550c3bc496aebce09c28eb0e14a45a8e9708ac
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/widgets/file-tree.js b/widgets/file-tree.js
index 1f64b19..b7ca4fe 100644
--- a/widgets/file-tree.js
+++ b/widgets/file-tree.js
@@ -1,3 +1,3 @@
 function select(file) {
-  activePath = file.path;
+  navigateToDatum(target, file.key);
 }
diff --git a/widgets/diff.js b/widgets/diff.js
index 9db353a..e392d0b 100644
--- a/widgets/diff.js
+++ b/widgets/diff.js
@@ -1 +1,2 @@
-const selected = files[0];
+const selected = files.find(file => file.key === current)
+  ?? files[0];

Show added and removed line counts 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/worktrunk
Current commit
243bd04ef5473061535abd2391bf9a26c6c5f0fa
Comparison base
214d8e109fd9abcdac34c52735a359ed64b96c57
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/src/summary.rs b/src/summary.rs
index 0f0860f..9374881 100644
--- a/src/summary.rs
+++ b/src/summary.rs
@@ -1,3 +1,3 @@
 fn summary(added: usize, removed: usize) -> String {
-    format!("{} lines", added - removed)
+    format!("+{added} -{removed}")
 }

Clarify page authoring examples Clean

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
fd2d11d6a89297dd9101a6dddf55977c8bdc33fd
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.

No changes against this base.

Refresh the application mark 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
7239fe1a28d2d206f915c9875977a9be2b404014
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.

assets/mark.png changed. Binary content has no line count.

Back off repeated reconnect attempts 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
f5275181a57c3010e3773b646acd818513c21c36
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/reconnect.js b/runtime/reconnect.js
index a939cf9..fcb8367 100644
--- a/runtime/reconnect.js
+++ b/runtime/reconnect.js
@@ -1 +1 @@
-const delay = 1000;
+const delay = nextBackoff(attempt);

Preserve selection across document updates 1 modified

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
171dedea105282ad085989622bc0d35c88126de2
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.
diff --git a/runtime/selection.js b/runtime/selection.js
index dd0ac91..2f292a3 100644
--- a/runtime/selection.js
+++ b/runtime/selection.js
@@ -1 +1 @@
-replace(document);
+reconcile(document, { preserveSelection: true });

Make every control reachable by keyboard Status unavailable

Snapshot details · main merge-base → working tree
Repository
/Users/maximilian/workspace/leaf
Current commit
5bd75b8ace5ecbc5d96cd70a90a37411c9542117
Comparison base
6f6d349144225c431962eb2ddab1cf55a82b6707
Captured
22 Oct 2026, 10:00 PT · illustrative snapshot
Scope
Tracked changes from the merge-base, including staged and unstaged edits. Untracked files excluded.

The worktree is in the supplied set, but its diff was not captured. Its change size is unknown.

Empty set