diff --git a/src/mobile/views/ThreadsView.test.tsx b/src/mobile/views/ThreadsView.test.tsx
index fb2e2fec..97e3b0a9 100644
--- a/src/mobile/views/ThreadsView.test.tsx
+++ b/src/mobile/views/ThreadsView.test.tsx
@@ -384,6 +384,38 @@ describe("ThreadsView grouping", () => {
expect(container.querySelector(".m-thread-section")).toBeNull();
});
+ it("sinks done threads into a trailing Done section ordered by last update", () => {
+ const { container } = renderView([
+ makeThread({ id: "done-old", title: "Done old", done: true, updatedAt: oldIso() }),
+ makeThread({ id: "live", title: "Live", updatedAt: recentIso() }),
+ makeThread({ id: "done-new", title: "Done new", done: true, updatedAt: recentIso() }),
+ ]);
+
+ expect(rowTitles(container)).toEqual(["Live", "Done new", "Done old"]);
+ expect(
+ [...container.querySelectorAll(".m-thread-section")].map((el) => el.textContent),
+ ).toEqual(["Done"]);
+ });
+
+ it("keeps a mixed worktree group live until every member is done", () => {
+ const worktree = { worktreePath: "/repo/wt", worktreeBranch: "feature/x" };
+ const mixed = renderView([
+ makeThread({ id: "done", title: "Done member", done: true, ...worktree }),
+ makeThread({ id: "live", title: "Live member", ...worktree }),
+ ]);
+
+ expect(mixed.container.querySelector(".m-thread-section")).toBeNull();
+ mixed.unmount();
+
+ const allDone = renderView([
+ makeThread({ id: "done-a", title: "Done A", done: true, ...worktree }),
+ makeThread({ id: "done-b", title: "Done B", done: true, ...worktree }),
+ ]);
+ expect(
+ [...allDone.container.querySelectorAll(".m-thread-section")].map((el) => el.textContent),
+ ).toEqual(["Done"]);
+ });
+
it("filters threads from the touch search box", () => {
renderView([
makeThread({ id: "a", title: "Alpha" }),
diff --git a/src/mobile/views/ThreadsView.tsx b/src/mobile/views/ThreadsView.tsx
index 59550292..cd2d0b87 100644
--- a/src/mobile/views/ThreadsView.tsx
+++ b/src/mobile/views/ThreadsView.tsx
@@ -16,6 +16,7 @@ import { RelativeTime } from "@/renderer/components/common/RelativeTime";
import { ThreadProviderIcon, getStatusTone } from "@/renderer/components/providers";
import { useThreadHasBackgroundActivity } from "@/renderer/hooks/uiSelectors";
import {
+ entryIsDone,
entryIsStarred,
entryLatestDate,
groupThreads,
@@ -523,26 +524,43 @@ export function ThreadsView(props: ThreadsViewProps) {
// header. Standalone threads stay as plain rows. Reuses the desktop sidebar's
// grouping so both surfaces agree on what counts as a group.
const groupedEntries = groupThreads([...visibleThreads]);
- // Split into Pinned / Current (updated < 24h) / Older sections, floated in
- // that order and labeled — mirroring the desktop sidebar. `visibleThreads` is
- // already recency-sorted, so these stable partitions keep that order within
- // each section. Pinning any group member floats the whole group (entryIsStarred).
- const pinnedEntries = groupedEntries.filter(entryIsStarred);
- const unpinnedEntries = groupedEntries.filter((entry) => !entryIsStarred(entry));
+ // Done entries sink below the live list, matching the desktop sidebar. A
+ // mixed group stays live until every member is done.
+ const liveEntries: ThreadListEntry[] = [];
+ const datedDoneEntries: { entry: ThreadListEntry; updatedAt: string }[] = [];
+ for (const entry of groupedEntries) {
+ if (entryIsDone(entry)) {
+ datedDoneEntries.push({ entry, updatedAt: entryLatestDate(entry, "updatedAt") });
+ } else {
+ liveEntries.push(entry);
+ }
+ }
+ const doneEntries = datedDoneEntries
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
+ .map(({ entry }) => entry);
+ // Split the remaining live entries into Pinned / Current (updated < 24h) /
+ // Older sections. `visibleThreads` is already recency-sorted, so these stable
+ // partitions keep that order within each section. Pinning any group member
+ // floats the whole group (entryIsStarred).
+ const pinnedEntries = liveEntries.filter(entryIsStarred);
+ const unpinnedEntries = liveEntries.filter((entry) => !entryIsStarred(entry));
const currentEntries = unpinnedEntries.filter((entry) =>
isRecent(entryLatestDate(entry, "updatedAt")),
);
const olderEntries = unpinnedEntries.filter(
(entry) => !isRecent(entryLatestDate(entry, "updatedAt")),
);
- const sections = [
+ const liveSections = [
{ key: "pinned", label: t`Pinned`, entries: pinnedEntries },
{ key: "current", label: t`Current`, entries: currentEntries },
{ key: "older", label: t`Older`, entries: olderEntries },
].filter((section) => section.entries.length > 0);
- // A lone section spans the whole list, so its label would be noise; only show
- // the headers once there is an actual boundary between two or more sections.
- const showSectionLabels = sections.length > 1;
+ // A lone live section spans the whole live list, so its label would be noise.
+ // Done always keeps its label because it is a distinct trailing state.
+ const showLiveSectionLabels = liveSections.length > 1;
+ const sections = [...liveSections, { key: "done", label: t`Done`, entries: doneEntries }].filter(
+ (section) => section.entries.length > 0,
+ );
// A worktree-group child drops its worktree + git badges (the header carries
// them); any group child drops the project name (the header carries that too).
@@ -633,7 +651,9 @@ export function ThreadsView(props: ThreadsViewProps) {
{sections.map((section) => (
- {showSectionLabels ? {section.label}
: null}
+ {section.key === "done" || showLiveSectionLabels ? (
+ {section.label}
+ ) : null}
{section.entries.map((entry) => renderEntry(entry))}
))}
diff --git a/src/renderer/locales/de/messages.po b/src/renderer/locales/de/messages.po
index 04c08c55..80448c3c 100644
--- a/src/renderer/locales/de/messages.po
+++ b/src/renderer/locales/de/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "erledigt"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/en/messages.po b/src/renderer/locales/en/messages.po
index da2ca53a..b5d23e64 100644
--- a/src/renderer/locales/en/messages.po
+++ b/src/renderer/locales/en/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "done"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/es/messages.po b/src/renderer/locales/es/messages.po
index 2dafa488..42ac33a4 100644
--- a/src/renderer/locales/es/messages.po
+++ b/src/renderer/locales/es/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "listo"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/fr/messages.po b/src/renderer/locales/fr/messages.po
index 220026f9..fbb36112 100644
--- a/src/renderer/locales/fr/messages.po
+++ b/src/renderer/locales/fr/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "fait"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/ja/messages.po b/src/renderer/locales/ja/messages.po
index df93eb6a..d05e5bae 100644
--- a/src/renderer/locales/ja/messages.po
+++ b/src/renderer/locales/ja/messages.po
@@ -3490,6 +3490,7 @@ msgid "done"
msgstr "完了しました"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/ko/messages.po b/src/renderer/locales/ko/messages.po
index 5fcc806e..490f3eee 100644
--- a/src/renderer/locales/ko/messages.po
+++ b/src/renderer/locales/ko/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "완료"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/pl/messages.po b/src/renderer/locales/pl/messages.po
index 417739de..ceaecd93 100644
--- a/src/renderer/locales/pl/messages.po
+++ b/src/renderer/locales/pl/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "zrobione"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/pt-BR/messages.po b/src/renderer/locales/pt-BR/messages.po
index 6056d5e5..7850646d 100644
--- a/src/renderer/locales/pt-BR/messages.po
+++ b/src/renderer/locales/pt-BR/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "feito"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/ru/messages.po b/src/renderer/locales/ru/messages.po
index 56952cf6..a760ff94 100644
--- a/src/renderer/locales/ru/messages.po
+++ b/src/renderer/locales/ru/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "готово"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/tr/messages.po b/src/renderer/locales/tr/messages.po
index 6f48ec0e..dc333b51 100644
--- a/src/renderer/locales/tr/messages.po
+++ b/src/renderer/locales/tr/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "bitti"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/uk/messages.po b/src/renderer/locales/uk/messages.po
index 344336ae..4ff69072 100644
--- a/src/renderer/locales/uk/messages.po
+++ b/src/renderer/locales/uk/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "готово"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/vi/messages.po b/src/renderer/locales/vi/messages.po
index 1e46968e..86ea8fa3 100644
--- a/src/renderer/locales/vi/messages.po
+++ b/src/renderer/locales/vi/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "xong"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts
diff --git a/src/renderer/locales/zh-CN/messages.po b/src/renderer/locales/zh-CN/messages.po
index 1fa158b0..d8fd06eb 100644
--- a/src/renderer/locales/zh-CN/messages.po
+++ b/src/renderer/locales/zh-CN/messages.po
@@ -3491,6 +3491,7 @@ msgid "done"
msgstr "完成"
#. Notification status: thread is done
+#: src/mobile/views/ThreadsView.tsx
#: src/renderer/views/MainView/parts/Sidebar/parts/sidebarProjectRows.ts
#: src/renderer/views/SettingsOverlay/parts/NotificationSettings.tsx
#: src/renderer/views/SettingsOverlay/parts/settingsSearchIndex.ts