28 KiB
会话首页项目文件夹与抽屉模式 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 让 Boss 首页稳定呈现“单线程项目直显、2 个及以上线程项目显示为项目文件夹”的微信式会话结构,并保证搜索、置顶、排序、文件夹页和前台渲染都一致。
Architecture: 基于现有 folder_archive 半实现继续收口,而不是推翻重做。服务端投影负责把会话项稳定聚合成“单线程/项目文件夹”两种模型,Web 与 Android 首页只消费聚合后的首页项;搜索继续保留线程可达性,但点击多线程项目中的线程时仍先回到项目文件夹语义。
Tech Stack: Next.js App Router、TypeScript、文件型状态存储 data/boss-state.json、Android 原生客户端、Node test runner、Gradle unit tests
文件结构
服务端首页投影与文件夹详情
- Modify:
/Users/kris/code/boss/src/lib/boss-projections.ts - Test:
/Users/kris/code/boss/tests/conversation-home-items.test.ts
职责:
- 收紧
getConversationHomeItems()聚合规则 - 明确文件夹项副标题、时间、预览、上下文环和活动状态的来源
- 保证
1 ↔ 2+线程数变化时首页自动升降级 - 保持
getConversationFolderView()与首页聚合一致
Web 会话首页与搜索
- Modify:
/Users/kris/code/boss/src/components/app-ui.tsx - Test:
/Users/kris/code/boss/tests/conversation-home-items.test.ts
职责:
- 首页渲染项目文件夹会话项
- 搜索保留线程可达性,但不打破文件夹心智
- 文件夹项副标题展示
N 个线程 · 最近:某线程
Android 会话首页与搜索
- Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/MainActivity.java - Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java - Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityConversationSearchTest.java - Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityPinnedConversationsTest.java - Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityRootListAdapterTest.java
职责:
- 首页按聚合后的会话项渲染
- 搜索命中多线程项目里的线程时,结果显示
项目名 / 线程名 - 点击后仍进入文件夹页,不直接打平首页结构
Android 文件夹页与回跳
- Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java - Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ConversationFolderActivityTest.java
职责:
- 把现有线程列表页收成“项目内部线程页”
- 文件夹页标题、副标题、线程数和线程行展示与首页语义一致
- 支持从搜索结果进入时定位到对应线程
文档与回归
- Modify:
/Users/kris/code/boss/README.md - Modify:
/Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md
职责:
- 记录首页项目文件夹规则和搜索/导航行为
- 记录“单线程直显、2+ 线程归档”的正式产品逻辑
Task 1: 收紧服务端首页聚合规则
Files:
-
Modify:
/Users/kris/code/boss/src/lib/boss-projections.ts -
Test:
/Users/kris/code/boss/tests/conversation-home-items.test.ts -
Step 1: 先写失败测试,锁住文件夹会话的副标题、时间、预览和上下文环来源
test("folder archive item uses latest thread preview and most urgent context thread", 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",
"Android UI 收尾",
"thread-1",
"2026-04-05T10:00:00+08:00",
),
buildImportedThreadProject(
"mac-studio",
"boss-thread-2",
"Boss",
"boss",
"发布回滚",
"thread-2",
"2026-04-05T11:00:00+08:00",
),
);
state.projects[0]!.preview = "旧预览";
state.projects[1]!.preview = "最近:发布回滚";
state.threadContextSnapshots = [
{
snapshotId: "snapshot-a",
workerId: "mac-studio",
projectId: "boss-thread-1",
threadId: "thread-1",
title: "Android UI 收尾",
summary: "",
contextBudgetRemainingPct: 72,
contextBudgetLevel: "safe",
mustFinishBeforeCompaction: false,
estimatedRemainingTurns: 20,
estimatedRemainingLargeMessages: 10,
compactionCount: 0,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: [],
capturedAt: "2026-04-05T10:05:00+08:00",
},
{
snapshotId: "snapshot-b",
workerId: "mac-studio",
projectId: "boss-thread-2",
threadId: "thread-2",
title: "发布回滚",
summary: "",
contextBudgetRemainingPct: 18,
contextBudgetLevel: "critical",
mustFinishBeforeCompaction: true,
estimatedRemainingTurns: 2,
estimatedRemainingLargeMessages: 1,
compactionCount: 1,
patchPending: false,
testsPending: false,
evidencePending: false,
checklist: [],
capturedAt: "2026-04-05T11:02:00+08:00",
},
];
const items = getConversationHomeItems(state);
const folder = items.find((item) => item.conversationType === "folder_archive");
assert.ok(folder);
assert.equal(folder?.projectTitle, "发布回滚");
assert.equal(folder?.threadTitle, "Boss");
assert.equal(folder?.threadCount, 2);
assert.equal(folder?.preview, "最近:发布回滚");
assert.match(folder?.folderLabel ?? "", /^2 个线程 · 最近:发布回滚$/);
assert.equal(folder?.latestReplyAt, "2026-04-05T11:00:00+08:00");
assert.equal(folder?.contextBudgetIndicator.percent, 18);
assert.equal(folder?.contextBudgetIndicator.level, "critical");
assert.equal(folder?.mustFinishBeforeCompaction, true);
});
- Step 2: 跑测试,确认先失败
Run:
npx --yes tsx --test /Users/kris/code/boss/tests/conversation-home-items.test.ts
Expected:
-
FAIL,提示
folderLabel、preview、projectTitle或上下文环来源与预期不一致 -
Step 3: 在首页聚合里补全文件夹项来源规则
在 /Users/kris/code/boss/src/lib/boss-projections.ts 的 getConversationHomeItems() 中,把当前文件夹项构造收成:
const latestItem = [...items].sort((a, b) => b.latestReplyAt.localeCompare(a.latestReplyAt))[0];
const topContextItem = [...items]
.filter((item) => item.contextBudgetIndicator.visible)
.sort((a, b) => {
if (a.mustFinishBeforeCompaction !== b.mustFinishBeforeCompaction) {
return a.mustFinishBeforeCompaction ? -1 : 1;
}
const aLevel = a.contextBudgetIndicator.level ?? "safe";
const bLevel = b.contextBudgetIndicator.level ?? "safe";
if (levelPriority[aLevel] !== levelPriority[bLevel]) {
return levelPriority[aLevel] - levelPriority[bLevel];
}
return b.latestReplyAt.localeCompare(a.latestReplyAt);
})[0] ?? latestItem;
const recentThreadLabel = latestItem.threadTitle?.trim();
const folderSubtitle = recentThreadLabel
? `${items.length} 个线程 · 最近:${recentThreadLabel}`
: `${items.length} 个线程`;
passthrough.push({
conversationId: `folder-${folderKey}`,
conversationType: "folder_archive",
projectId: folderKey,
projectTitle: latestItem.projectTitle,
threadTitle: project?.threadMeta?.folderName?.trim() || project?.name || latestItem.folderLabel || "项目文件夹",
folderLabel: folderSubtitle,
folderKey,
threadCount: items.length,
preview: latestItem.preview,
lastMessagePreview: latestItem.lastMessagePreview,
activityIconCount: Math.max(...items.map((item) => item.activityIconCount)),
topPinnedLabel: items.some((item) => item.topPinnedLabel) ? "置顶" : undefined,
manualPinned: items.some((item) => item.manualPinned),
latestReplyAt: latestItem.latestReplyAt,
latestReplyLabel: latestItem.latestReplyLabel,
unreadCount: items.reduce((sum, item) => sum + item.unreadCount, 0),
riskLevel: topContextItem.riskLevel,
activeDeviceCount: latestItem.activeDeviceCount,
deviceNamesPreview: latestItem.deviceNamesPreview,
avatar: latestItem.avatar,
contextBudgetIndicator: topContextItem.contextBudgetIndicator,
contextBudgetSourceNodeId: topContextItem.contextBudgetSourceNodeId,
contextBudgetUpdatedAt: topContextItem.contextBudgetUpdatedAt,
mustFinishBeforeCompaction: topContextItem.mustFinishBeforeCompaction,
});
- Step 4: 再补两个边界测试,锁住 1 ↔ 2+ 升降级
把下面两个测试补到 /Users/kris/code/boss/tests/conversation-home-items.test.ts:
test("single thread project stays direct until a second thread appears", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"talking-thread-1",
"Talking",
"talking",
"树莓派二代查询",
"thread-1",
"2026-04-05T12:00:00+08:00",
),
);
let items = getConversationHomeItems(state);
assert.equal(items.some((item) => item.projectId === "talking-thread-1"), true);
assert.equal(items.some((item) => item.conversationType === "folder_archive"), false);
state.projects.push(
buildImportedThreadProject(
"mac-studio",
"talking-thread-2",
"Talking",
"talking",
"语音桥接回归",
"thread-2",
"2026-04-05T12:05:00+08:00",
),
);
items = getConversationHomeItems(state);
assert.equal(items.some((item) => item.projectId === "talking-thread-1"), false);
assert.equal(items.some((item) => item.projectId === "talking-thread-2"), false);
assert.equal(items.some((item) => item.folderKey === "mac-studio:talking"), true);
});
test("folder archive falls back to direct thread when project shrinks back to one thread", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
const first = buildImportedThreadProject("mac-studio", "talking-thread-1", "Talking", "talking", "树莓派二代查询", "thread-1", "2026-04-05T12:00:00+08:00");
const second = buildImportedThreadProject("mac-studio", "talking-thread-2", "Talking", "talking", "语音桥接回归", "thread-2", "2026-04-05T12:05:00+08:00");
state.projects.push(first, second);
state.projects = state.projects.filter((project) => project.id !== "talking-thread-2");
const items = getConversationHomeItems(state);
const direct = items.find((item) => item.projectId === "talking-thread-1");
assert.ok(direct);
assert.equal(direct?.conversationType, "single_device");
assert.equal(items.some((item) => item.folderKey === "mac-studio:talking"), false);
});
- Step 5: 重新跑投影测试,确认通过
Run:
npx --yes tsx --test /Users/kris/code/boss/tests/conversation-home-items.test.ts
Expected:
-
PASS
-
Step 6: 提交这一小步
git add /Users/kris/code/boss/src/lib/boss-projections.ts /Users/kris/code/boss/tests/conversation-home-items.test.ts
git commit -m "feat: tighten conversation folder archive projections"
Task 2: 收平 Web 首页与搜索的文件夹心智
Files:
-
Modify:
/Users/kris/code/boss/src/components/app-ui.tsx -
Test:
/Users/kris/code/boss/tests/conversation-home-items.test.ts -
Step 1: 先补失败测试,锁住 Web 文件夹副标题和搜索结果标签
在 /Users/kris/code/boss/tests/conversation-home-items.test.ts 里新增一个只关心文案拼装的小测试:
test("folder archive subtitle stays compact and search label preserves project and thread", 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", "Android UI 收尾", "thread-1", "2026-04-05T10:00:00+08:00"),
buildImportedThreadProject("mac-studio", "boss-thread-2", "Boss", "boss", "发布回滚", "thread-2", "2026-04-05T11:00:00+08:00"),
);
const [folder] = getConversationHomeItems(state).filter((item) => item.conversationType === "folder_archive");
assert.equal(folder?.folderLabel, "2 个线程 · 最近:发布回滚");
assert.equal(folder?.threadTitle, "Boss");
});
- Step 2: 跑测试,确认首页文案或搜索标签尚未完全匹配
Run:
npx --yes tsx --test /Users/kris/code/boss/tests/conversation-home-items.test.ts
Expected:
-
FAIL,或当前文案与计划不一致
-
Step 3: 调整 Web 首页文件夹项文案和搜索命中展示
在 /Users/kris/code/boss/src/components/app-ui.tsx 的会话列表渲染里,保持首页仍只吃 ConversationItem[],但把文案收成:
const title =
conversation.conversationType === "folder_archive"
? conversation.threadTitle
: conversation.projectTitle;
const subtitle =
conversation.conversationType === "folder_archive"
? conversation.folderLabel
: conversation.lastMessagePreview;
并在搜索结果命中多线程项目线程时,展示:
const searchLabel =
conversation.conversationType === "folder_archive"
? conversation.folderLabel
: conversation.folderKey && conversation.folderLabel
? `${conversation.folderLabel} / ${conversation.threadTitle}`
: conversation.threadTitle;
同时把多线程项目的点击保持到文件夹页:
const href =
conversation.conversationType === "folder_archive" && conversation.folderKey
? `/conversations/folders/${encodeURIComponent(conversation.folderKey)}`
: `/conversations/${encodeURIComponent(conversation.projectId)}`;
- Step 4: 重新跑投影测试,确认首页文案没回退
Run:
npx --yes tsx --test /Users/kris/code/boss/tests/conversation-home-items.test.ts
Expected:
-
PASS
-
Step 5: 提交这一小步
git add /Users/kris/code/boss/src/components/app-ui.tsx /Users/kris/code/boss/tests/conversation-home-items.test.ts
git commit -m "feat: align web conversation folders with drawer design"
Task 3: 收平 Android 首页文件夹渲染与搜索
Files:
-
Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/MainActivity.java -
Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java -
Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityConversationSearchTest.java -
Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityPinnedConversationsTest.java -
Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityRootListAdapterTest.java -
Step 1: 先写失败测试,锁住多线程项目在首页显示为文件夹而不是平铺线程
在 /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityRootListAdapterTest.java 增加:
@Test
public void folderArchiveRowUsesProjectTitleAndCompactSubtitle() throws Exception {
JSONObject folderItem = new JSONObject()
.put("conversationType", "folder_archive")
.put("projectId", "mac-studio:boss")
.put("projectTitle", "Boss")
.put("threadTitle", "Boss")
.put("folderLabel", "2 个线程 · 最近:发布回滚")
.put("threadCount", 2)
.put("preview", "最近:发布回滚")
.put("latestReplyLabel", "11:00")
.put("activityIconCount", 0)
.put("contextBudgetIndicator", new JSONObject()
.put("visible", true)
.put("style", "ring_percent")
.put("percent", 18)
.put("level", "critical"));
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(folderItem);
assertEquals("Boss", row.threadTitle);
assertEquals("2 个线程 · 最近:发布回滚", row.metaLine);
assertEquals("最近:发布回滚", row.preview);
}
- Step 2: 写失败测试,锁住搜索命中多线程项目线程时保留项目归属
在 /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityConversationSearchTest.java 增加:
@Test
public void searchResultsKeepFolderProjectContextForThreadsInsideArchives() throws Exception {
TestMainActivity activity = Robolectric.buildActivity(TestMainActivity.class)
.setup()
.get();
activity.setConversationItems(new JSONArray()
.put(new JSONObject()
.put("conversationType", "folder_archive")
.put("projectId", "mac-studio:boss")
.put("projectTitle", "Boss")
.put("threadTitle", "Boss")
.put("folderKey", "mac-studio:boss")
.put("folderLabel", "2 个线程 · 最近:发布回滚")
.put("preview", "最近:发布回滚"))
.put(new JSONObject()
.put("conversationType", "single_device")
.put("projectId", "boss-thread-2")
.put("projectTitle", "发布回滚")
.put("threadTitle", "发布回滚")
.put("folderKey", "mac-studio:boss")
.put("folderLabel", "Boss")
.put("preview", "最近:发布回滚")));
activity.enterConversationSearchModeForTest();
activity.setConversationSearchQueryForTest("发布");
assertTrue(activity.latestRenderedSearchLabels().contains("Boss / 发布回滚"));
}
- Step 3: 跑 Android 定向测试,确认先失败
Run:
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.MainActivityConversationSearchTest --tests com.hyzq.boss.MainActivityRootListAdapterTest --tests com.hyzq.boss.MainActivityPinnedConversationsTest --no-daemon
Expected:
-
FAIL,提示行文案或搜索结果标签与新预期不一致
-
Step 4: 调整 Android 首页行文案和搜索标签
在 /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java,让 folder_archive 行走:
if ("folder_archive".equals(type)) {
return new ConversationRow(
item.optString("threadTitle", item.optString("projectTitle", "项目文件夹")),
item.optString("folderLabel", ""),
item.optString("preview", ""),
item.optString("latestReplyLabel", "刚刚"),
contextStatus,
activityVisible,
activityIconCount,
avatarInitials
);
}
在 /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/MainActivity.java 的搜索结果构建里,给多线程项目内线程加归属标签:
private String buildConversationSearchLabel(JSONObject item) {
String conversationType = item.optString("conversationType", "");
if ("folder_archive".equals(conversationType)) {
return item.optString("threadTitle", item.optString("projectTitle", "项目文件夹"));
}
String folderLabel = item.optString("folderLabel", "").trim();
String folderKey = item.optString("folderKey", "").trim();
String threadTitle = item.optString("threadTitle", item.optString("projectTitle", "会话"));
if (!folderLabel.isEmpty() && !folderKey.isEmpty()) {
return folderLabel + " / " + threadTitle;
}
return threadTitle;
}
并保证点击搜索结果时:
if (!folderKey.isEmpty() && !"folder_archive".equals(conversationType) && isArchivedProjectThread(item)) {
openConversationFolder(folderKey, folderLabel, projectId);
return;
}
- Step 5: 重新跑 Android 定向测试,确认通过
Run:
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.MainActivityConversationSearchTest --tests com.hyzq.boss.MainActivityRootListAdapterTest --tests com.hyzq.boss.MainActivityPinnedConversationsTest --no-daemon
Expected:
-
PASS
-
Step 6: 提交这一小步
git add /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/MainActivity.java /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityConversationSearchTest.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityRootListAdapterTest.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/MainActivityPinnedConversationsTest.java
git commit -m "feat: align android conversation folders with drawer design"
Task 4: 把 Android 文件夹页收成项目内部线程页
Files:
-
Modify:
/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java -
Test:
/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ConversationFolderActivityTest.java -
Step 1: 写失败测试,锁住文件夹页标题、副标题和线程列表顺序
在 /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ConversationFolderActivityTest.java 增加:
@Test
public void folderScreenBehavesAsProjectThreadPage() throws Exception {
Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestConversationFolderActivity.class)
.putExtra(ConversationFolderActivity.EXTRA_FOLDER_KEY, "mac-studio:boss")
.putExtra(ConversationFolderActivity.EXTRA_FOLDER_NAME, "Boss");
TestConversationFolderActivity activity = Robolectric
.buildActivity(TestConversationFolderActivity.class, intent)
.setup()
.get();
activity.renderFolderForTest(new JSONObject()
.put("folderLabel", "Boss")
.put("deviceName", "Mac Studio")
.put("threadCount", 2)
.put("threads", new JSONArray()
.put(new JSONObject()
.put("projectId", "boss-thread-2")
.put("conversationType", "single_device")
.put("projectTitle", "发布回滚")
.put("threadTitle", "发布回滚")
.put("preview", "最近:发布回滚")
.put("latestReplyLabel", "11:00"))
.put(new JSONObject()
.put("projectId", "boss-thread-1")
.put("conversationType", "single_device")
.put("projectTitle", "Android UI 收尾")
.put("threadTitle", "Android UI 收尾")
.put("preview", "最近:Android UI 收尾")
.put("latestReplyLabel", "10:00"))));
assertEquals("Boss", activity.topTitleText());
assertEquals("2 个线程", activity.topSubtitleText());
assertEquals(Arrays.asList("发布回滚", "Android UI 收尾"), activity.renderedThreadTitles());
}
- Step 2: 跑文件夹页定向测试,确认先失败
Run:
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.ConversationFolderActivityTest --no-daemon
Expected:
-
FAIL,提示标题、副标题或线程列表语义与新预期不一致
-
Step 3: 把文件夹页标题和副标题改成项目内部线程页
在 /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java 调整:
String resolvedFolderName = folder.optString("folderLabel", folderName == null ? "项目线程" : folderName);
int threadCount = folder.optInt("threadCount", 0);
configureScreen(resolvedFolderName, threadCount + " 个线程");
appendContent(BossUi.buildSoftPanel(
this,
resolvedFolderName,
threadCount + " 个线程",
"选择线程后进入具体聊天窗口。"
));
并在接收搜索跳转时支持可选的目标线程定位:
public static final String EXTRA_TARGET_PROJECT_ID = "target_project_id";
在渲染线程列表时,若命中目标线程:
if (projectId.equals(targetProjectId)) {
row = row.withHighlighted(true);
}
如果当前 ConversationRow 没有高亮能力,本轮先用滚动到命中线程代替:
if (projectId.equals(targetProjectId)) {
matchedThreadView = rowView;
}
...
if (matchedThreadView != null) {
matchedThreadView.post(() -> matchedThreadView.requestFocus());
}
- Step 4: 重新跑文件夹页测试,确认通过
Run:
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.ConversationFolderActivityTest --no-daemon
Expected:
-
PASS
-
Step 5: 提交这一小步
git add /Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ConversationFolderActivity.java /Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ConversationFolderActivityTest.java
git commit -m "feat: refine android folder conversation screen"
Task 5: 文档、全量验证与发布前检查
Files:
-
Modify:
/Users/kris/code/boss/README.md -
Modify:
/Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md -
Step 1: 更新 README,明确首页会话归档规则
在 /Users/kris/code/boss/README.md 增加一段简洁说明:
## 会话首页项目归档规则
- 单线程项目:首页直接显示线程会话
- 2 个及以上线程的项目:首页显示为一个项目文件夹会话
- 文件夹项时间取项目内最新活跃线程
- 文件夹项预览取项目内最新回复
- 文件夹项上下文环取项目内最需要关注的线程
- 搜索仍可命中线程,但多线程项目中的线程会先进入对应项目文件夹页
- Step 2: 更新运行状态文档,记录 Android/Web 均已收成项目文件夹心智
在 /Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md 补一段:
### 会话首页项目文件夹模式
- 单线程项目直接显示线程
- 多线程项目在首页聚合为项目文件夹会话
- Android 与 Web 均使用相同聚合规则
- 会话搜索保留线程可达性,但不再打破首页项目文件夹结构
- Step 3: 跑最终验证
Run:
cd /Users/kris/code/boss && npx --yes tsx --test tests/conversation-home-items.test.ts
cd /Users/kris/code/boss/android && ./gradlew testDebugUnitTest --tests com.hyzq.boss.MainActivityConversationSearchTest --tests com.hyzq.boss.MainActivityPinnedConversationsTest --tests com.hyzq.boss.MainActivityRootListAdapterTest --tests com.hyzq.boss.ConversationFolderActivityTest --no-daemon
cd /Users/kris/code/boss && npm run lint
cd /Users/kris/code/boss && npm run build
cd /Users/kris/code/boss/android && ./gradlew assembleRelease --no-daemon
Expected:
-
全部 PASS
-
没有新的 lint/build 回归
-
Step 4: 提交文档和验证收口
git add /Users/kris/code/boss/README.md /Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md
git commit -m "docs: document conversation folder drawer behavior"
Self-Review
- Spec coverage:
- 首页单线程直显、多线程项目文件夹化:Task 1 / Task 3
- 文件夹项时间、预览、上下文环聚合:Task 1
- 搜索保留线程可达性但不打破文件夹心智:Task 2 / Task 3
- 文件夹页作为项目内部线程页:Task 4
- 置顶、排序、1 ↔ 2+ 升降级:Task 1 / Task 3
- 文档收口:Task 5
- Placeholder scan:
- 已避免
TODO/TBD/自行处理 - 每个任务都给了具体文件、测试和命令
- 已避免
- Type consistency:
- 统一使用现有
ConversationItem、folder_archive、folderKey、threadCount - Android 继续沿用
WechatSurfaceMapper.ConversationRow和ConversationFolderActivity
- 统一使用现有