Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/mobile/views/ThreadsView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" }),
Expand Down
42 changes: 31 additions & 11 deletions src/mobile/views/ThreadsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -633,7 +651,9 @@ export function ThreadsView(props: ThreadsViewProps) {
<div className="m-thread-list">
{sections.map((section) => (
<Fragment key={section.key}>
{showSectionLabels ? <div className="m-thread-section">{section.label}</div> : null}
{section.key === "done" || showLiveSectionLabels ? (
<div className="m-thread-section">{section.label}</div>
) : null}
{section.entries.map((entry) => renderEntry(entry))}
</Fragment>
))}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/de/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/es/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/ja/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/ko/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/pl/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/pt-BR/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/ru/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/tr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/uk/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/vi/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/zh-CN/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down