@palbase/web 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import { u as PalbeConfig, J as PalbeRuntime } from './analytics-facade-CAKBIH_U.cjs';
2
- export { K as buildRuntime } from './analytics-facade-CAKBIH_U.cjs';
1
+ import { u as PalbeConfig, J as PalbeRuntime } from './analytics-facade-Ct3A1zop.cjs';
2
+ export { K as buildRuntime } from './analytics-facade-Ct3A1zop.cjs';
3
3
  import { B as BackendError } from './errors-fDoNdTrJ.cjs';
4
- export { P as PB, c as createBoundClient } from './pb-DioxNuEV.cjs';
4
+ export { P as PB, c as createBoundClient } from './pb-BlIfgBG-.cjs';
5
5
  import 'livekit-client';
6
6
  import './storage-BPaeSG8K.cjs';
7
7
  import './pooled-flags-Bwq4usn0.js';
@@ -1,7 +1,7 @@
1
- import { u as PalbeConfig, J as PalbeRuntime } from './analytics-facade-DLH-KivI.js';
2
- export { K as buildRuntime } from './analytics-facade-DLH-KivI.js';
1
+ import { u as PalbeConfig, J as PalbeRuntime } from './analytics-facade-C93tr7dA.js';
2
+ export { K as buildRuntime } from './analytics-facade-C93tr7dA.js';
3
3
  import { B as BackendError } from './errors-fDoNdTrJ.js';
4
- export { P as PB, c as createBoundClient } from './pb-HegMSSk-.js';
4
+ export { P as PB, c as createBoundClient } from './pb-BtYdWClg.js';
5
5
  import 'livekit-client';
6
6
  import './storage-BPaeSG8K.js';
7
7
  import './pooled-flags-Bwq4usn0.js';
package/dist/internal.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  createBoundClient,
7
7
  getRuntime,
8
8
  onConfigured
9
- } from "./chunk-EMQGOKW6.js";
9
+ } from "./chunk-KJXRY4S3.js";
10
10
  export {
11
11
  __configure,
12
12
  __registerNamespaces,
@@ -2552,6 +2552,18 @@ async function listDevices(rt, userId) {
2552
2552
  }
2553
2553
 
2554
2554
  // src/messaging/group-messaging.ts
2555
+ function encodeReaction(args) {
2556
+ return encodeUtf8(
2557
+ JSON.stringify({
2558
+ v: 1,
2559
+ type: "reaction",
2560
+ client_msg_id: args.clientMsgId,
2561
+ target_client_msg_id: args.targetClientMsgId,
2562
+ emoji: args.emoji,
2563
+ op: args.op
2564
+ })
2565
+ );
2566
+ }
2555
2567
  function encodeEnvelope(args) {
2556
2568
  const env = {
2557
2569
  v: 1,
@@ -2566,18 +2578,32 @@ function decodeEnvelope(bytes) {
2566
2578
  const s = decodeUtf8(bytes);
2567
2579
  try {
2568
2580
  const o = JSON.parse(s);
2581
+ if (typeof o === "object" && o !== null && o.type === "reaction") {
2582
+ return {
2583
+ type: "reaction",
2584
+ text: null,
2585
+ clientMsgId: o.client_msg_id ?? "",
2586
+ replyTo: null,
2587
+ reaction: {
2588
+ targetClientMsgId: o.target_client_msg_id ?? "",
2589
+ emoji: o.emoji ?? "",
2590
+ op: o.op ?? "add"
2591
+ }
2592
+ };
2593
+ }
2569
2594
  if (typeof o === "object" && o !== null && o.type === "text" && typeof o.v === "number") {
2570
2595
  return {
2596
+ type: "text",
2571
2597
  text: o.text ?? null,
2572
2598
  clientMsgId: o.client_msg_id ?? "",
2573
2599
  replyTo: o.reply_to ?? null
2574
2600
  };
2575
2601
  }
2576
2602
  if (typeof o === "object" && o !== null && o.type === "text") {
2577
- return { text: o.text ?? null, clientMsgId: "", replyTo: null };
2603
+ return { type: "text", text: o.text ?? null, clientMsgId: "", replyTo: null };
2578
2604
  }
2579
2605
  if (typeof o === "object" && o !== null && o.type === "media")
2580
- return { text: null, clientMsgId: "", replyTo: null };
2606
+ return { type: "media", text: null, clientMsgId: "", replyTo: null };
2581
2607
  } catch {
2582
2608
  }
2583
2609
  return { text: s, clientMsgId: "", replyTo: null };
@@ -2829,6 +2855,56 @@ var GroupMessaging = class {
2829
2855
  }
2830
2856
  return { receipt: { serverSeq: wire.server_seq, epoch: wire.epoch }, clientMsgId };
2831
2857
  }
2858
+ /** Send a reaction (add/remove of an emoji on a target message). Encrypts a
2859
+ * `type:'reaction'` envelope at the current epoch and sends through the SAME
2860
+ * MLS application path as `sendText` (the server stays blind — a reaction is
2861
+ * just another application message). Persists the outgoing reaction row so it
2862
+ * re-folds onto its target after a reload (the own-send half of the reload
2863
+ * parity). NEVER rebases (epoch-bound like any application message). */
2864
+ async sendReaction(group, args) {
2865
+ const plaintext = encodeReaction({
2866
+ clientMsgId: args.clientMsgId,
2867
+ targetClientMsgId: args.targetClientMsgId,
2868
+ emoji: args.emoji,
2869
+ op: args.op
2870
+ });
2871
+ const ct = await this.engine.encryptApplication(fromBase64(group.rfcGroupId), plaintext);
2872
+ const body = {
2873
+ ciphertext_b64: toBase64(ct),
2874
+ client_idem_key: randomId()
2875
+ };
2876
+ const wire = await palbeRequest(
2877
+ this.rt,
2878
+ "POST",
2879
+ MessagingPaths.groupMessages(group.displayId),
2880
+ { body }
2881
+ );
2882
+ const stored = {
2883
+ id: `${group.rfcGroupId}#${wire.server_seq}`,
2884
+ direction: "outgoing",
2885
+ text: null,
2886
+ senderDeviceId: this.selfDeviceId,
2887
+ epoch: wire.epoch,
2888
+ serverSeq: wire.server_seq,
2889
+ at: Date.now(),
2890
+ clientMsgId: args.clientMsgId,
2891
+ replyTo: null,
2892
+ envelopeType: "reaction",
2893
+ reaction: {
2894
+ targetClientMsgId: args.targetClientMsgId,
2895
+ emoji: args.emoji,
2896
+ op: args.op
2897
+ }
2898
+ };
2899
+ try {
2900
+ await this.messageStore.append(group.rfcGroupId, stored);
2901
+ } catch {
2902
+ }
2903
+ return {
2904
+ receipt: { serverSeq: wire.server_seq, epoch: wire.epoch },
2905
+ clientMsgId: args.clientMsgId
2906
+ };
2907
+ }
2832
2908
  // ── The rebase loop ──
2833
2909
  async commitWithRebase(rfcGroupId, build) {
2834
2910
  const gidBytes = fromBase64(rfcGroupId);
@@ -2890,6 +2966,61 @@ var GroupMessaging = class {
2890
2966
  }
2891
2967
  };
2892
2968
 
2969
+ // src/messaging/reaction-fold.ts
2970
+ function orderLte(aEpoch, aSeq, bEpoch, bSeq) {
2971
+ if (aEpoch !== bEpoch) return aEpoch < bEpoch;
2972
+ return aSeq <= bSeq;
2973
+ }
2974
+ var ReactionFold = class {
2975
+ // Map<targetClientMsgId, Map<actorUserId, UserState>>
2976
+ states = /* @__PURE__ */ new Map();
2977
+ // Dedup set: eventClientMsgId values already applied.
2978
+ seenEvents = /* @__PURE__ */ new Set();
2979
+ ingest(e) {
2980
+ if (this.seenEvents.has(e.eventClientMsgId)) return;
2981
+ this.seenEvents.add(e.eventClientMsgId);
2982
+ let byUser = this.states.get(e.targetClientMsgId);
2983
+ if (byUser === void 0) {
2984
+ byUser = /* @__PURE__ */ new Map();
2985
+ this.states.set(e.targetClientMsgId, byUser);
2986
+ }
2987
+ const prev = byUser.get(e.actorUserId);
2988
+ if (prev !== void 0) {
2989
+ if (orderLte(e.epoch, e.serverSeq, prev.orderEpoch, prev.orderSeq)) return;
2990
+ }
2991
+ byUser.set(e.actorUserId, {
2992
+ orderEpoch: e.epoch,
2993
+ orderSeq: e.serverSeq,
2994
+ emoji: e.op === "add" ? e.emoji : null
2995
+ });
2996
+ }
2997
+ /**
2998
+ * Returns a tally of `{ emoji → sorted userIds[] }` for the given target
2999
+ * message. Users whose last event was a `remove` (emoji === null) are
3000
+ * excluded. userId arrays are sorted for deterministic output (render
3001
+ * stability + golden tests).
3002
+ */
3003
+ tally(targetClientMsgId) {
3004
+ const byUser = this.states.get(targetClientMsgId);
3005
+ if (byUser === void 0) return {};
3006
+ const out = /* @__PURE__ */ new Map();
3007
+ for (const [userId, st] of byUser) {
3008
+ if (st.emoji === null) continue;
3009
+ let users = out.get(st.emoji);
3010
+ if (users === void 0) {
3011
+ users = [];
3012
+ out.set(st.emoji, users);
3013
+ }
3014
+ users.push(userId);
3015
+ }
3016
+ const result = {};
3017
+ for (const [emoji, users] of out) {
3018
+ result[emoji] = users.sort();
3019
+ }
3020
+ return result;
3021
+ }
3022
+ };
3023
+
2893
3024
  // src/messaging/chat.ts
2894
3025
  var Chat = class {
2895
3026
  /** Stable, URL/log-safe id (the grp_ display id once active; a reserved local id while draft). */
@@ -2908,6 +3039,8 @@ var Chat = class {
2908
3039
  seenKeys = /* @__PURE__ */ new Set();
2909
3040
  /** Index: clientMsgId → { text, senderUserId } for resolveReply lookups. */
2910
3041
  byClientMsgId = /* @__PURE__ */ new Map();
3042
+ /** The single authoritative reaction fold for this chat (live + own-send + history). */
3043
+ reactionFold = new ReactionFold();
2911
3044
  loadedEarliestSeq = null;
2912
3045
  historyLoaded = false;
2913
3046
  wired = false;
@@ -3014,7 +3147,7 @@ var Chat = class {
3014
3147
  const key = this.internalKey(m.serverSeq);
3015
3148
  if (this.seenKeys.has(key)) continue;
3016
3149
  this.seenKeys.add(key);
3017
- this.messageList.push(m);
3150
+ this.messageList.push(this.applyReactionTally(m));
3018
3151
  changed = true;
3019
3152
  this.loadedEarliestSeq = Math.min(this.loadedEarliestSeq ?? m.serverSeq, m.serverSeq);
3020
3153
  }
@@ -3039,6 +3172,22 @@ var Chat = class {
3039
3172
  senderUser = await this.backend.userIdForDevice(this._group, incoming.senderDeviceId);
3040
3173
  }
3041
3174
  const direction = senderUser !== null && senderUser === this.backend.selfUserId ? "outgoing" : "incoming";
3175
+ if (incoming.envelopeType === "reaction" && incoming.reaction) {
3176
+ const actorUserId = direction === "outgoing" ? this.backend.selfUserId : senderUser;
3177
+ if (actorUserId !== null) {
3178
+ this.reactionFold.ingest({
3179
+ targetClientMsgId: incoming.reaction.targetClientMsgId,
3180
+ actorUserId,
3181
+ emoji: incoming.reaction.emoji,
3182
+ op: incoming.reaction.op,
3183
+ epoch: incoming.epoch,
3184
+ serverSeq: incoming.serverSeq,
3185
+ eventClientMsgId: incoming.clientMsgId
3186
+ });
3187
+ this.recomputeReactions(incoming.reaction.targetClientMsgId);
3188
+ }
3189
+ return;
3190
+ }
3042
3191
  const incomingClientMsgId = incoming.clientMsgId;
3043
3192
  const incomingReplyRef = incoming.replyRef;
3044
3193
  let resolvedReplyTo = null;
@@ -3054,7 +3203,10 @@ var Chat = class {
3054
3203
  serverSeq: incoming.serverSeq,
3055
3204
  sentAt: incoming.receivedAt,
3056
3205
  clientMsgId: incomingClientMsgId,
3057
- replyTo: resolvedReplyTo
3206
+ replyTo: resolvedReplyTo,
3207
+ // Attach any tally already folded for this message (a reaction that arrived
3208
+ // BEFORE its target — the dangling case — renders the moment the target lands).
3209
+ reactions: incomingClientMsgId ? this.reactionFold.tally(incomingClientMsgId) : {}
3058
3210
  };
3059
3211
  if (incomingClientMsgId && incoming.text !== null) {
3060
3212
  this.byClientMsgId.set(incomingClientMsgId, {
@@ -3070,6 +3222,35 @@ var Chat = class {
3070
3222
  );
3071
3223
  this.emit();
3072
3224
  }
3225
+ /**
3226
+ * Rebuild the target message's `reactions` from the authoritative fold and
3227
+ * re-emit. No-op when the target isn't present yet (its tally is attached the
3228
+ * moment it lands, via the append/merge paths) or when the tally is unchanged.
3229
+ */
3230
+ recomputeReactions(targetClientMsgId) {
3231
+ if (!targetClientMsgId) return;
3232
+ const tally = this.reactionFold.tally(targetClientMsgId);
3233
+ let changed = false;
3234
+ this.messageList = this.messageList.map((m) => {
3235
+ if (m.clientMsgId !== targetClientMsgId) return m;
3236
+ if (sameReactions(m.reactions, tally)) return m;
3237
+ changed = true;
3238
+ return { ...m, reactions: tally };
3239
+ });
3240
+ if (changed) this.emit();
3241
+ }
3242
+ /**
3243
+ * Overlay the authoritative fold's tally onto a message as it is appended/merged.
3244
+ * The Chat fold WINS when it has a non-empty tally; otherwise the tally already
3245
+ * attached upstream (the coordinator's page-local history fold) is preserved.
3246
+ */
3247
+ applyReactionTally(m) {
3248
+ if (!m.clientMsgId) return m;
3249
+ const tally = this.reactionFold.tally(m.clientMsgId);
3250
+ if (Object.keys(tally).length === 0) return m;
3251
+ if (sameReactions(m.reactions, tally)) return m;
3252
+ return { ...m, reactions: tally };
3253
+ }
3073
3254
  /** @internal — called by the backend's conv subscription. */
3074
3255
  applyConv(event, payload) {
3075
3256
  const userId = typeof payload.user_id === "string" ? payload.user_id : null;
@@ -3212,7 +3393,10 @@ var Chat = class {
3212
3393
  serverSeq: receipt.serverSeq,
3213
3394
  sentAt: /* @__PURE__ */ new Date(),
3214
3395
  clientMsgId,
3215
- replyTo: resolvedReplyTo
3396
+ replyTo: resolvedReplyTo,
3397
+ // Attach any tally already folded for this own-sent message (rare, but keeps
3398
+ // the dangling-target invariant uniform across every append path).
3399
+ reactions: clientMsgId ? this.reactionFold.tally(clientMsgId) : {}
3216
3400
  });
3217
3401
  this.messageList.sort((a, b) => a.serverSeq - b.serverSeq);
3218
3402
  this.emit();
@@ -3259,7 +3443,53 @@ var Chat = class {
3259
3443
  this.readWatermark = Math.max(this.readWatermark, message.serverSeq);
3260
3444
  this.emit();
3261
3445
  }
3446
+ // ── Reactions ──
3447
+ /** Add an emoji reaction to a message. No-op if the message isn't reactable
3448
+ * (empty clientMsgId — a legacy/system row). The reaction folds locally with
3449
+ * the server receipt's `(epoch, serverSeq)` so the target's tally updates
3450
+ * instantly; the durable echo on the next pump is a fold no-op (dedup on the
3451
+ * SAME wire clientMsgId). Never appends a bubble. */
3452
+ async react(message, emoji) {
3453
+ await this.sendReaction(message, emoji, "add");
3454
+ }
3455
+ /** Remove this user's emoji reaction from a message (op:'remove'). */
3456
+ async unreact(message, emoji) {
3457
+ await this.sendReaction(message, emoji, "remove");
3458
+ }
3459
+ async sendReaction(message, emoji, op) {
3460
+ if (!message.clientMsgId) return;
3461
+ const group = await this.materializeIfNeeded();
3462
+ const clientMsgId = mintClientMsgId();
3463
+ const { receipt } = await this.backend.sendReaction(group, {
3464
+ clientMsgId,
3465
+ targetClientMsgId: message.clientMsgId,
3466
+ emoji,
3467
+ op
3468
+ });
3469
+ this.reactionFold.ingest({
3470
+ targetClientMsgId: message.clientMsgId,
3471
+ actorUserId: this.backend.selfUserId,
3472
+ emoji,
3473
+ op,
3474
+ epoch: receipt.epoch,
3475
+ serverSeq: receipt.serverSeq,
3476
+ eventClientMsgId: clientMsgId
3477
+ });
3478
+ this.recomputeReactions(message.clientMsgId);
3479
+ }
3262
3480
  };
3481
+ function sameReactions(a, b) {
3482
+ const ak = Object.keys(a);
3483
+ const bk = Object.keys(b);
3484
+ if (ak.length !== bk.length) return false;
3485
+ for (const k of ak) {
3486
+ const av = a[k];
3487
+ const bv = b[k];
3488
+ if (!bv || av === void 0 || av.length !== bv.length) return false;
3489
+ for (let i = 0; i < av.length; i++) if (av[i] !== bv[i]) return false;
3490
+ }
3491
+ return true;
3492
+ }
3263
3493
 
3264
3494
  // src/messaging/delivery-source.ts
3265
3495
  var MessageHub = class {
@@ -3289,6 +3519,11 @@ var MessageHub = class {
3289
3519
  };
3290
3520
  }
3291
3521
  };
3522
+ function decodeSenderDeviceId(sender) {
3523
+ if (sender.length === 0) return null;
3524
+ const id = decodeUtf8(sender);
3525
+ return id.length > 0 ? id : null;
3526
+ }
3292
3527
  var MessageDeliverySource = class {
3293
3528
  constructor(rt, engine, hub, registry, messageStore, deviceId, selfUserId) {
3294
3529
  this.rt = rt;
@@ -3418,12 +3653,15 @@ var MessageDeliverySource = class {
3418
3653
  try {
3419
3654
  const received = await this.engine.processIncoming(fromBase64(group.rfcGroupId), blob);
3420
3655
  if (received.type === "application") {
3421
- const { text, clientMsgId, replyTo } = decodeEnvelope(received.data);
3656
+ const decoded = decodeEnvelope(received.data);
3657
+ const { text, clientMsgId, replyTo } = decoded;
3658
+ const isReaction = decoded.type === "reaction" && decoded.reaction != null;
3659
+ const senderDeviceId = row.sender_device_id ?? decodeSenderDeviceId(received.sender);
3422
3660
  const stored = {
3423
3661
  id: `${group.rfcGroupId}#${row.server_seq}`,
3424
3662
  direction: "incoming",
3425
3663
  text,
3426
- senderDeviceId: row.sender_device_id ?? null,
3664
+ senderDeviceId,
3427
3665
  epoch: row.epoch,
3428
3666
  serverSeq: row.server_seq,
3429
3667
  at: Date.now(),
@@ -3433,7 +3671,19 @@ var MessageDeliverySource = class {
3433
3671
  previewBody: replyTo.preview?.body ?? null,
3434
3672
  previewAuthorUserId: replyTo.preview?.author_user_id ?? null,
3435
3673
  previewKind: replyTo.preview?.kind ?? "text"
3436
- } : null
3674
+ } : null,
3675
+ // Thread the reaction discriminator + fields through the persisted row so
3676
+ // a reaction folded LIVE re-folds onto its target after a reload (the
3677
+ // reload-parity boundary — mirrors iOS T3). Omitted for non-reactions →
3678
+ // old rows hydrate as `'text'`/no-reaction (backward-compat).
3679
+ ...isReaction && decoded.reaction ? {
3680
+ envelopeType: "reaction",
3681
+ reaction: {
3682
+ targetClientMsgId: decoded.reaction.targetClientMsgId,
3683
+ emoji: decoded.reaction.emoji,
3684
+ op: decoded.reaction.op
3685
+ }
3686
+ } : {}
3437
3687
  };
3438
3688
  try {
3439
3689
  await this.messageStore.append(group.rfcGroupId, stored);
@@ -3444,12 +3694,14 @@ var MessageDeliverySource = class {
3444
3694
  kind: "application",
3445
3695
  group,
3446
3696
  text,
3447
- senderDeviceId: row.sender_device_id ?? null,
3697
+ senderDeviceId,
3448
3698
  epoch: row.epoch,
3449
3699
  serverSeq: row.server_seq,
3450
3700
  receivedAt: /* @__PURE__ */ new Date(),
3451
3701
  clientMsgId,
3452
- replyRef: replyTo
3702
+ replyRef: replyTo,
3703
+ envelopeType: decoded.type ?? "text",
3704
+ reaction: isReaction ? decoded.reaction : null
3453
3705
  });
3454
3706
  return true;
3455
3707
  }
@@ -5453,46 +5705,14 @@ var MessagingCoordinator = class {
5453
5705
  const r = await this.resolve();
5454
5706
  return r.groups.sendText(group, text, replyTo);
5455
5707
  }
5708
+ async sendReaction(group, args) {
5709
+ const r = await this.resolve();
5710
+ return r.groups.sendReaction(group, args);
5711
+ }
5456
5712
  async history(group, limit, before) {
5457
5713
  const r = await this.resolve();
5458
5714
  const rows = await r.messageStore.history(group.rfcGroupId, limit, before);
5459
- const lookup = /* @__PURE__ */ new Map();
5460
- for (const s of rows) {
5461
- const cid = s.clientMsgId ?? "";
5462
- if (cid && s.text !== null) {
5463
- const senderUserId = s.direction === "outgoing" ? this.selfUserId : "";
5464
- lookup.set(cid, { text: s.text, senderUserId });
5465
- }
5466
- }
5467
- return rows.map((s) => this.toChatMessage(group, s, (id) => lookup.get(id) ?? null));
5468
- }
5469
- toChatMessage(group, s, lookup) {
5470
- const clientMsgId = s.clientMsgId ?? "";
5471
- let replyTo = null;
5472
- if (s.replyTo && lookup) {
5473
- const ref = {
5474
- v: 1,
5475
- client_msg_id: s.replyTo.clientMsgId,
5476
- preview: {
5477
- kind: s.replyTo.previewKind,
5478
- author_user_id: s.replyTo.previewAuthorUserId ?? "",
5479
- body: s.replyTo.previewBody ?? void 0,
5480
- body_truncated: false
5481
- }
5482
- };
5483
- replyTo = resolveReply(ref, lookup);
5484
- }
5485
- return {
5486
- id: `${group.displayId}#${s.serverSeq}`,
5487
- kind: s.text != null ? "text" : "system",
5488
- direction: s.direction,
5489
- senderUserId: s.direction === "outgoing" ? this.selfUserId : null,
5490
- text: s.text,
5491
- serverSeq: s.serverSeq,
5492
- sentAt: new Date(s.at),
5493
- clientMsgId,
5494
- replyTo
5495
- };
5715
+ return projectHistory(group.displayId, rows, this.selfUserId);
5496
5716
  }
5497
5717
  async members(group) {
5498
5718
  const r = await this.resolve();
@@ -5569,6 +5789,64 @@ var MessagingCoordinator = class {
5569
5789
  return res.devices.map((d) => d.device_id);
5570
5790
  }
5571
5791
  };
5792
+ function projectHistory(displayId, rows, selfUserId, resolveActor) {
5793
+ const fold = new ReactionFold();
5794
+ for (const s of rows) {
5795
+ if (s.envelopeType !== "reaction" || !s.reaction) continue;
5796
+ const actor = s.direction === "outgoing" ? selfUserId : resolveActor?.(s.senderDeviceId ?? null) ?? null;
5797
+ if (actor === null) continue;
5798
+ fold.ingest({
5799
+ targetClientMsgId: s.reaction.targetClientMsgId,
5800
+ actorUserId: actor,
5801
+ emoji: s.reaction.emoji,
5802
+ op: s.reaction.op,
5803
+ epoch: s.epoch,
5804
+ serverSeq: s.serverSeq,
5805
+ eventClientMsgId: s.clientMsgId ?? `${s.id}`
5806
+ });
5807
+ }
5808
+ const lookup = /* @__PURE__ */ new Map();
5809
+ for (const s of rows) {
5810
+ if (s.envelopeType === "reaction") continue;
5811
+ const cid = s.clientMsgId ?? "";
5812
+ if (cid && s.text !== null) {
5813
+ const senderUserId = s.direction === "outgoing" ? selfUserId : "";
5814
+ lookup.set(cid, { text: s.text, senderUserId });
5815
+ }
5816
+ }
5817
+ const out = [];
5818
+ for (const s of rows) {
5819
+ if (s.envelopeType === "reaction") continue;
5820
+ const clientMsgId = s.clientMsgId ?? "";
5821
+ let replyTo = null;
5822
+ if (s.replyTo) {
5823
+ const ref = {
5824
+ v: 1,
5825
+ client_msg_id: s.replyTo.clientMsgId,
5826
+ preview: {
5827
+ kind: s.replyTo.previewKind,
5828
+ author_user_id: s.replyTo.previewAuthorUserId ?? "",
5829
+ body: s.replyTo.previewBody ?? void 0,
5830
+ body_truncated: false
5831
+ }
5832
+ };
5833
+ replyTo = resolveReply(ref, (id) => lookup.get(id) ?? null);
5834
+ }
5835
+ out.push({
5836
+ id: `${displayId}#${s.serverSeq}`,
5837
+ kind: s.text != null ? "text" : "system",
5838
+ direction: s.direction,
5839
+ senderUserId: s.direction === "outgoing" ? selfUserId : null,
5840
+ text: s.text,
5841
+ serverSeq: s.serverSeq,
5842
+ sentAt: new Date(s.at),
5843
+ clientMsgId,
5844
+ replyTo,
5845
+ reactions: clientMsgId ? fold.tally(clientMsgId) : {}
5846
+ });
5847
+ }
5848
+ return out;
5849
+ }
5572
5850
 
5573
5851
  // src/messaging/facade.ts
5574
5852
  var PalbeMessaging = class {
@@ -6302,7 +6580,7 @@ function defaultSessionStorage(key) {
6302
6580
  }
6303
6581
 
6304
6582
  // src/version.ts
6305
- var VERSION = "1.1.1";
6583
+ var VERSION = "1.2.1";
6306
6584
 
6307
6585
  // src/runtime.ts
6308
6586
  function buildRuntime(config) {