Strip pin metadata from web conversations

This commit is contained in:
kris
2026-04-11 03:05:37 +08:00
parent 5bf745f45f
commit e0c0ea1814
4 changed files with 100 additions and 2 deletions

View File

@@ -7,7 +7,7 @@ import {
StatusBar,
} from "@/components/app-ui";
import { requirePageSession } from "@/lib/boss-auth";
import { getConversationHomeItems } from "@/lib/boss-projections";
import { getConversationWebItems } from "@/lib/boss-projections";
import { readState } from "@/lib/boss-data";
export const dynamic = "force-dynamic";
@@ -15,7 +15,7 @@ export const dynamic = "force-dynamic";
export default async function ConversationsPage() {
await requirePageSession();
const state = await readState();
const conversations = getConversationHomeItems(state);
const conversations = getConversationWebItems(state);
return (
<AppShell>

View File

@@ -660,6 +660,14 @@ export function getConversationHomeItems(state: BossState): ConversationItem[] {
return sortConversationItems(passthrough);
}
export function getConversationWebItems(state: BossState): ConversationItem[] {
return getConversationHomeItems(state).map((item) => ({
...item,
topPinnedLabel: undefined,
manualPinned: false,
}));
}
export function getConversationHomeItemForProject(state: BossState, projectId: string): ConversationItem | null {
const normalizedProjectId = projectId.trim();
if (!normalizedProjectId) {

View File

@@ -8,6 +8,7 @@ let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let updateConversationAction: (typeof import("../src/lib/boss-data"))["updateConversationAction"];
let getConversationHomeItems: (typeof import("../src/lib/boss-projections"))["getConversationHomeItems"];
let getConversationWebItems: (typeof import("../src/lib/boss-projections"))["getConversationWebItems"];
let getConversationHomeItemForProject: (typeof import("../src/lib/boss-projections"))["getConversationHomeItemForProject"];
let getConversationThreadItemForProject: (typeof import("../src/lib/boss-projections"))["getConversationThreadItemForProject"];
let getConversationFolderView: (typeof import("../src/lib/boss-projections"))["getConversationFolderView"];
@@ -31,6 +32,7 @@ async function setup() {
readState = data.readState;
updateConversationAction = data.updateConversationAction;
getConversationHomeItems = projections.getConversationHomeItems;
getConversationWebItems = projections.getConversationWebItems;
getConversationHomeItemForProject = projections.getConversationHomeItemForProject;
getConversationThreadItemForProject = projections.getConversationThreadItemForProject;
getConversationFolderView = projections.getConversationFolderView;
@@ -770,6 +772,65 @@ test("homepage rows do not expose pinned labels in the Web surface", async () =>
assert.equal(getConversationPinnedBadgeLabel(masterAgent!), "");
});
test("web conversation projection strips pin metadata before rendering the Mac/Web surface", async () => {
await setup();
const state = await readState();
state.projects = state.projects.filter((project) => project.id === "master-agent");
state.projects.push(
{
...buildImportedThreadProject(
"mac-studio",
"solo-pinned-thread",
"Solo",
"solo",
"单线程置顶验证",
"solo-thread",
"2026-03-30T13:00:00+08:00",
),
pinned: true,
},
{
...buildImportedThreadProject(
"mac-studio",
"boss-thread-1",
"Boss",
"boss",
"归档确认",
"thread-1",
"2026-03-30T11:00:00+08:00",
),
pinned: true,
},
buildImportedThreadProject(
"mac-studio",
"boss-thread-2",
"Boss",
"boss",
"发布回滚",
"thread-2",
"2026-03-30T12:00:00+08:00",
),
);
const webItems = getConversationWebItems(state);
const pinnedThread = webItems.find((item) => item.projectId === "solo-pinned-thread");
const folder = webItems.find((item) => item.conversationType === "folder_archive");
const masterAgent = webItems.find((item) => item.projectId === "master-agent");
assert.ok(pinnedThread, "expected pinned thread in web projection");
assert.equal(pinnedThread?.topPinnedLabel, undefined);
assert.equal(pinnedThread?.manualPinned, false);
assert.ok(folder, "expected folder archive in web projection");
assert.equal(folder?.topPinnedLabel, undefined);
assert.equal(folder?.manualPinned, false);
assert.ok(masterAgent, "expected master agent in web projection");
assert.equal(masterAgent?.topPinnedLabel, undefined);
assert.equal(masterAgent?.manualPinned, false);
});
test("folder archive action path encodes folder keys with nested path segments", async () => {
await setup();

View File

@@ -0,0 +1,29 @@
import test from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const testsDir = path.dirname(fileURLToPath(import.meta.url));
async function readWorkspaceFile(relativePath: string) {
return readFile(path.join(testsDir, "..", relativePath), "utf8");
}
test("web conversations page uses a web-specific projection without pin metadata", async () => {
const [pageSource, projectionsSource] = await Promise.all([
readWorkspaceFile("src/app/conversations/page.tsx"),
readWorkspaceFile("src/lib/boss-projections.ts"),
]);
assert.match(
projectionsSource,
/export function getConversationWebItems\(/,
"expected a web-specific conversation projection helper",
);
assert.match(
pageSource,
/const conversations = getConversationWebItems\(state\);/,
"expected the web conversations page to use the web-specific projection",
);
});