Compare commits

...

1 Commits

Author SHA1 Message Date
J
dab50e617b fix: preserve inbox comment anchors for MUL-3294
Co-authored-by: multica-agent <github@multica.ai>
2026-06-15 15:39:16 +08:00
4 changed files with 163 additions and 3 deletions

View File

@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import type { InboxItem } from "@multica/core/types";
import { deduplicateInboxItems } from "./inbox-display";
function item(overrides: Partial<InboxItem>): InboxItem {
return {
id: "inbox-1",
workspace_id: "workspace-1",
recipient_type: "member",
recipient_id: "member-1",
actor_type: "agent",
actor_id: "agent-1",
type: "new_comment",
severity: "info",
issue_id: "issue-1",
title: "Issue title",
body: null,
issue_status: null,
read: false,
archived: false,
created_at: "2026-06-15T08:00:00Z",
details: null,
...overrides,
};
}
describe("deduplicateInboxItems", () => {
it("keeps the newest issue row while preserving an older comment anchor", () => {
const merged = deduplicateInboxItems([
item({
id: "comment-notification",
created_at: "2026-06-15T08:00:00Z",
details: { comment_id: "comment-1" },
}),
item({
id: "status-notification",
type: "status_changed",
created_at: "2026-06-15T08:01:00Z",
details: { from: "in_progress", to: "in_review" },
}),
]);
expect(merged).toHaveLength(1);
expect(merged[0]).toMatchObject({
id: "status-notification",
type: "status_changed",
details: {
from: "in_progress",
to: "in_review",
comment_id: "comment-1",
},
});
});
});

View File

@@ -62,7 +62,9 @@ export function getInboxDisplayTitle(item: InboxItem): string {
* 2. Group by `issue_id` (fall back to `id` for items with no issue
* attached — e.g. quick_create_failed).
* 3. In each group, keep the newest by `created_at`.
* 4. Sort the result newest-first.
* 4. Preserve the newest grouped `comment_id` anchor when the newest row
* is a later status/metadata event for the same issue.
* 5. Sort the result newest-first.
*/
export function deduplicateInboxItems(items: InboxItem[]): InboxItem[] {
const active = items.filter((i) => !i.archived);
@@ -79,7 +81,22 @@ export function deduplicateInboxItems(items: InboxItem[]): InboxItem[] {
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
if (group[0]) merged.push(group[0]);
const newest = group[0];
if (!newest) continue;
const commentId =
newest.details?.comment_id ??
group.find((item) => item.details?.comment_id)?.details?.comment_id;
if (commentId && newest.details?.comment_id !== commentId) {
merged.push({
...newest,
details: { ...(newest.details ?? {}), comment_id: commentId },
});
continue;
}
merged.push(newest);
}
return merged.sort(
(a, b) =>

View File

@@ -0,0 +1,74 @@
import { describe, expect, it } from "vitest";
import type { InboxItem } from "../types";
import { deduplicateInboxItems } from "./queries";
function item(overrides: Partial<InboxItem>): InboxItem {
return {
id: "inbox-1",
workspace_id: "workspace-1",
recipient_type: "member",
recipient_id: "member-1",
actor_type: "agent",
actor_id: "agent-1",
type: "new_comment",
severity: "info",
issue_id: "issue-1",
title: "Issue title",
body: null,
issue_status: null,
read: false,
archived: false,
created_at: "2026-06-15T08:00:00Z",
details: null,
...overrides,
};
}
describe("deduplicateInboxItems", () => {
it("keeps the newest issue row while preserving an older comment anchor", () => {
const merged = deduplicateInboxItems([
item({
id: "comment-notification",
type: "new_comment",
created_at: "2026-06-15T08:00:00Z",
details: { comment_id: "comment-1" },
}),
item({
id: "status-notification",
type: "status_changed",
created_at: "2026-06-15T08:01:00Z",
details: { from: "in_progress", to: "in_review" },
}),
]);
expect(merged).toHaveLength(1);
expect(merged[0]).toMatchObject({
id: "status-notification",
type: "status_changed",
details: {
from: "in_progress",
to: "in_review",
comment_id: "comment-1",
},
});
});
it("preserves the newest row's own comment anchor", () => {
const merged = deduplicateInboxItems([
item({
id: "older-comment",
created_at: "2026-06-15T08:00:00Z",
details: { comment_id: "comment-1" },
}),
item({
id: "newer-comment",
created_at: "2026-06-15T08:02:00Z",
details: { comment_id: "comment-2" },
}),
]);
expect(merged).toHaveLength(1);
expect(merged[0]?.id).toBe("newer-comment");
expect(merged[0]?.details?.comment_id).toBe("comment-2");
});
});

View File

@@ -50,7 +50,22 @@ export function deduplicateInboxItems(items: InboxItem[]): InboxItem[] {
(a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
);
if (group[0]) merged.push(group[0]);
const newest = group[0];
if (!newest) continue;
const commentId =
newest.details?.comment_id ??
group.find((item) => item.details?.comment_id)?.details?.comment_id;
if (commentId && newest.details?.comment_id !== commentId) {
merged.push({
...newest,
details: { ...(newest.details ?? {}), comment_id: commentId },
});
continue;
}
merged.push(newest);
}
return merged.sort(
(a, b) =>