feat(messenger): paged history + scrollback (SDK 0.3.0, kernel-client 0.2.0)

CrmMessagingAdapter.historyPage returns {messages, hasMore}. The socket path
(openThread) serves the newest page since it also joins the room; scrollback has
no socket verb, so `before` goes over the data door — by then the room is already
joined, so nothing live is missed either way.

Opening a long thread now loads one page instead of every message ever sent, and
the SDK renders a "Load older messages" control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 14:02:37 +05:30
parent 7b806da14c
commit 954ad20a78
3 changed files with 37 additions and 19 deletions
+9 -9
View File
@@ -11,8 +11,8 @@
"@abe-kap/appshell-sdk": "^0.2.6",
"@huggingface/transformers": "^4.2.0",
"@imgly/background-removal": "^1.7.0",
"@insignia/iios-kernel-client": "^0.1.6",
"@insignia/iios-messaging-ui": "^0.2.0",
"@insignia/iios-kernel-client": "^0.2.0",
"@insignia/iios-messaging-ui": "^0.3.0",
"@photo-gallery/sdk": "file:./vendor/photo-gallery-sdk",
"@tanstack/react-query": "^5.101.4",
"@tensorflow-models/coco-ssd": "^2.2.3",
@@ -1103,20 +1103,20 @@
"integrity": "sha512-+ycbP8ORdwllMtn7dmxIL7lnDpDDkV+gHqT2OKJtMplMivjKU03gsuuEveQxnrE1CMvZ3fcSljmfMs10Cw131g=="
},
"node_modules/@insignia/iios-kernel-client": {
"version": "0.1.6",
"resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-kernel-client/-/0.1.6/iios-kernel-client-0.1.6.tgz",
"integrity": "sha512-QfjqaGj8acKfwWQvYsf6Rxj1TeLQN+3fNTTlL+PkAP2usAxcdSkgRKkfHxy9FrhoxLXahQBuOn3wnUzd6Qh39Q==",
"version": "0.2.0",
"resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-kernel-client/-/0.2.0/iios-kernel-client-0.2.0.tgz",
"integrity": "sha512-/uO/FocOYZpaDvaP08UYX3ISk8bUaSUnNj1mRHfZkE8oBJSBOs1ZPJ21t6V1FFhlFI+FjEMaysJKJi1sRDTl8Q==",
"dependencies": {
"@insignia/iios-contracts": "0.1.0",
"socket.io-client": "^4.8.1"
}
},
"node_modules/@insignia/iios-messaging-ui": {
"version": "0.2.0",
"resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-messaging-ui/-/0.2.0/iios-messaging-ui-0.2.0.tgz",
"integrity": "sha512-eM3K9CMtPumTvpoL7FrX+RcdKylhIDgMDZ4/7GCGoe6P1lj+HcAMmUX38cyLx7qq21DaDMnUcq3RNMW69gsDNw==",
"version": "0.3.0",
"resolved": "https://git.lynkedup.cloud/api/packages/insignia/npm/%40insignia%2Fiios-messaging-ui/-/0.3.0/iios-messaging-ui-0.3.0.tgz",
"integrity": "sha512-LFxqVk+RY6E2foR38yll2Q18Qd1mNl9YTONENw+fy1Dt2IDAVMcfN73DCJ8AQjwAsZoTqjsWDfMxgGKyLWT2Uw==",
"peerDependencies": {
"@insignia/iios-kernel-client": "*",
"@insignia/iios-kernel-client": ">=0.2.0",
"@tanstack/react-query": ">=5",
"react": ">=18",
"react-dom": ">=18"
+2 -2
View File
@@ -13,8 +13,8 @@
"@abe-kap/appshell-sdk": "^0.2.6",
"@huggingface/transformers": "^4.2.0",
"@imgly/background-removal": "^1.7.0",
"@insignia/iios-kernel-client": "^0.1.6",
"@insignia/iios-messaging-ui": "^0.2.0",
"@insignia/iios-kernel-client": "^0.2.0",
"@insignia/iios-messaging-ui": "^0.3.0",
"@photo-gallery/sdk": "file:./vendor/photo-gallery-sdk",
"@tanstack/react-query": "^5.101.4",
"@tensorflow-models/coco-ssd": "^2.2.3",
+26 -8
View File
@@ -136,21 +136,39 @@ export class CrmMessagingAdapter implements MessagingAdapter {
return { threadId: res.threadId };
}
async history(threadId: string): Promise<Message[]> {
if (this.socket) {
// openThread both JOINS the room and returns history, so it is the only call needed on a
// cache miss. The SDK caches per thread, so this no longer fires on every selection.
const res = await this.socket.openThread(threadId);
async history(threadId: string, opts?: { limit?: number; before?: string }): Promise<Message[]> {
return (await this.historyPage(threadId, opts)).messages;
}
/**
* A page of history plus whether older messages exist.
*
* Socket path: openThread both JOINS the room and returns the newest page, so it is the only call
* needed on a cache miss. Scrollback (`before`) has no socket verb, so it goes over the door —
* the room is already joined by then, so nothing live is missed.
*/
async historyPage(
threadId: string,
opts?: { limit?: number; before?: string },
): Promise<{ messages: Message[]; hasMore: boolean }> {
if (this.socket && !opts?.before) {
const res = await this.socket.openThread(threadId, { ...(opts?.limit ? { limit: opts.limit } : {}) });
this.joined.add(threadId);
return Promise.all(
const messages = await Promise.all(
res.history.map((m) => {
this.ingestReactions(m);
return this.toKernelMessage(m);
}),
);
return { messages, hasMore: res.hasMore ?? false };
}
const msgs = await this.sdk.query<MessageDTO[]>("crm.messenger.history", { threadId });
return Promise.all(msgs.map((m) => this.toDtoMessage(m)));
const res = await this.sdk.query<{ messages: MessageDTO[]; hasMore: boolean }>("crm.messenger.history", {
threadId,
...(opts?.limit ? { limit: opts.limit } : {}),
...(opts?.before ? { before: opts.before } : {}),
});
const messages = await Promise.all(res.messages.map((m) => this.toDtoMessage(m)));
return { messages, hasMore: res.hasMore ?? false };
}
async send(threadId: string, content: string, opts?: SendOpts): Promise<Message> {