channelKeyCollision.test.ts3.0 KBView on GitHub import { containerKeyOf, findFeedItem } from '@/modules/inbox/hooks/use-channel-artifact-item';
import type { InboxItem } from '@/modules/inbox/types';
/**
* A container key does not identify a container on its own.
*
* `channel_containers` is keyed on `(organization_id, channel, account_ref, external_id)`, and a
* SLACK CONNECT channel is what exercises the difference: both workspaces sync the same `C…` id
* under their own `account_ref`. Four such pairs are live today (`C0B4ZN3A68Z` is
* `cedar-concentrate` under one workspace and `ext-cedar-concentrate` under the other). They
* straddle two orgs, so no single seat sees both yet — but the feed row and the container read
* both have to answer the ambiguity the SAME way, or the panel and the inbox open different
* chats for one click depending only on whether the feed happened to be loaded.
*
* The rule, on both sides: most recently active wins, id breaks the tie.
*/
function slackItem(workspaceId: string, channelId: string, sortedAt: string): InboxItem {
return {
id: `slack:${workspaceId}:${channelId}`,
channel: 'slack',
sortedAt,
counterpart: { name: 'Slack', subtitle: `#${workspaceId}` },
snippet: '',
unread: false,
starred: false,
hasDraft: false,
done: false,
labelIds: [],
participantCount: 1,
ref: { kind: 'slack', conversationId: '', slackChannelId: channelId, workspaceId },
} as InboxItem;
}
/**
* The real `findFeedItem`, fed the store's own shape — every loaded row, in the order the
* per-channel windows happened to arrive. That order is the variable under test: it is exactly
* what the old `find()` followed.
*/
function pick(items: InboxItem[], channelId: string): InboxItem | undefined {
return findFeedItem(items, 'slack', channelId);
}
describe('a container key shared by two workspaces', () => {
const CHANNEL = 'C0B4ZN3A68Z';
const OURS = slackItem('T08AHSLJBTL', CHANNEL, '2026-08-27T17:58:19.268Z');
const THEIRS = slackItem('T08UTA22H5Y', CHANNEL, '2026-08-14T20:08:33.691Z');
it('reads the same key off both rows — which is the whole problem', () => {
expect(containerKeyOf(OURS)).toBe(CHANNEL);
expect(containerKeyOf(THEIRS)).toBe(CHANNEL);
expect(OURS.id).not.toBe(THEIRS.id);
});
it('resolves to the most recently active one, whichever order the feed loaded them in', () => {
expect(pick([OURS, THEIRS], CHANNEL)).toBe(OURS);
// Load order is what `find()` followed, so the reversed case is the one that used to
// disagree with itself.
expect(pick([THEIRS, OURS], CHANNEL)).toBe(OURS);
});
it('breaks a tie on id, so two idle chats cannot flip between renders', () => {
const a = slackItem('T00000000AA', CHANNEL, '');
const b = slackItem('T00000000BB', CHANNEL, '');
expect(pick([a, b], CHANNEL)).toBe(pick([b, a], CHANNEL));
});
it('still finds a lone row, and still misses a key nothing answers to', () => {
expect(pick([THEIRS], CHANNEL)).toBe(THEIRS);
expect(pick([OURS, THEIRS], 'C_NOT_A_CHANNEL')).toBeUndefined();
});
});