-
-
-
- {conversation.conversationType === "folder_archive"
- ? conversation.threadTitle
- : conversation.projectTitle}
+
+
+
+
+
+
+
+
+
+
{presentation.title}
+ {isFolderArchive ? (
+
+ {conversation.threadCount ?? 0} 个线程
+
+ ) : (
+
+ {conversation.riskLevel === "high"
+ ? "高风险"
+ : conversation.riskLevel === "medium"
+ ? "关注"
+ : "稳定"}
+
+ )}
+ {conversation.unreadCount > 0 ? (
+
+ {conversation.unreadCount}
+
+ ) : null}
- {conversation.conversationType === "folder_archive" ? (
-
- {conversation.threadCount ?? 0} 个线程
-
- ) : (
-
- {conversation.riskLevel === "high"
- ? "高风险"
- : conversation.riskLevel === "medium"
- ? "关注"
- : "稳定"}
-
- )}
- {conversation.unreadCount > 0 ? (
-
- {conversation.unreadCount}
-
- ) : null}
+
{presentation.subtitle}
+
{conversation.preview}
-
- {conversation.conversationType === "folder_archive"
- ? conversation.folderLabel
- : conversation.deviceNamesPreview.join(" / ")}
-
-
- {conversation.preview}
-
-
-
-
- {conversation.projectId === "master-agent"
- ? "置顶"
- : conversation.manualPinned
+
+
+ {conversation.projectId === "master-agent"
? "置顶"
- : ""}
-
-
- {conversation.latestReplyLabel}
-
- {conversation.contextBudgetIndicator.visible &&
- conversation.contextBudgetIndicator.percent !== undefined ? (
-
- ) : (
-
- {conversation.activeDeviceCount > 1
- ? `${conversation.activeDeviceCount} 台协作`
- : ""}
+ : conversation.manualPinned
+ ? "置顶"
+ : ""}
- )}
+
{conversation.latestReplyLabel}
+ {conversation.contextBudgetIndicator.visible &&
+ conversation.contextBudgetIndicator.percent !== undefined ? (
+
+ ) : (
+
+ {conversation.activeDeviceCount > 1
+ ? `${conversation.activeDeviceCount} 台协作`
+ : ""}
+
+ )}
+
-
-
-
- ))}
+
+
+ );
+ })}
);
}
diff --git a/tests/conversation-home-items.test.ts b/tests/conversation-home-items.test.ts
index 0f0482c..a2c3295 100644
--- a/tests/conversation-home-items.test.ts
+++ b/tests/conversation-home-items.test.ts
@@ -9,6 +9,7 @@ let readState: (typeof import("../src/lib/boss-data"))["readState"];
let getConversationHomeItems: (typeof import("../src/lib/boss-projections"))["getConversationHomeItems"];
let getConversationFolderView: (typeof import("../src/lib/boss-projections"))["getConversationFolderView"];
let formatTimestampLabel: (typeof import("../src/lib/boss-projections"))["formatTimestampLabel"];
+let getConversationListItemPresentation: (typeof import("../src/components/app-ui"))["getConversationListItemPresentation"];
async function setup() {
if (runtimeRoot) return;
@@ -16,14 +17,16 @@ async function setup() {
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
- const [data, projections] = await Promise.all([
+ const [data, projections, ui] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-projections.ts"),
+ import("../src/components/app-ui.tsx"),
]);
readState = data.readState;
getConversationHomeItems = projections.getConversationHomeItems;
getConversationFolderView = projections.getConversationFolderView;
formatTimestampLabel = projections.formatTimestampLabel;
+ getConversationListItemPresentation = ui.getConversationListItemPresentation;
}
test.after(async () => {
@@ -392,6 +395,42 @@ test("conversation home groups multiple imported threads by folder while keeping
);
});
+test("folder archive homepage rows keep the project title, compact subtitle, and folder route", async () => {
+ await setup();
+ const state = await readState();
+
+ state.projects = state.projects.filter((project) => project.id === "master-agent");
+ state.projects.push(
+ buildImportedThreadProject(
+ "mac-studio",
+ "boss-thread-1",
+ "Boss",
+ "boss",
+ "归档确认",
+ "thread-1",
+ "2026-03-30T11:00:00+08:00",
+ ),
+ buildImportedThreadProject(
+ "mac-studio",
+ "boss-thread-2",
+ "Boss",
+ "boss",
+ "发布回滚",
+ "thread-2",
+ "2026-03-30T12:00:00+08:00",
+ ),
+ );
+
+ const folder = getConversationHomeItems(state).find((item) => item.conversationType === "folder_archive");
+
+ assert.ok(folder, "expected grouped folder archive item");
+ const presentation = getConversationListItemPresentation(folder!);
+
+ assert.equal(presentation.title, "Boss");
+ assert.equal(presentation.subtitle, "2 个线程 · 最近:发布回滚");
+ assert.equal(presentation.href, "/conversations/folders/mac-studio%3Aboss");
+});
+
test("conversation items expose context status while keeping idle activity silent", async () => {
await setup();
const state = await readState();