inline-chat-kit 0.54.1 → 0.54.3

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,81 @@ The versions before 1.0 follow the pre-release convention: **a breaking change
8
8
  or new public API bumps the minor**, and the patch is for fixes. Anything that would break an
9
9
  existing install is called out under **Breaking**, with what to do about it.
10
10
 
11
+ ## 0.54.3 — 2026-09-13
12
+
13
+ ### Fixed
14
+
15
+ - **The sent message flickered a pixel up and down for the whole length of
16
+ every answer after the first.** Measured while a second answer was written:
17
+ the message sat at y=100 and flipped to 101 and back thirty-one times, with
18
+ the room under the conversation going 418, 379, 374, 372, 362, 360, 351
19
+ underneath it — a new value on every frame.
20
+
21
+ It was a feedback loop. That room is part of `scrollHeight`, the scroll
22
+ target is clamped to `scrollHeight`, and the room was recomputed from
23
+ measurements on every frame: the room moved the view, the view moved the
24
+ measurements, and the measurements moved the room. Sized to be *exactly*
25
+ enough for the anchored turn to reach the anchor, the clamp sat on a knife
26
+ edge and a fraction of a pixel chose the side.
27
+
28
+ A floor does not drop while the thing standing on it is still standing. So
29
+ while a turn is anchored the room only grows, and it is recomputed the
30
+ moment the anchor is let go — a long answer carries some spare room until it
31
+ settles, and nothing carries it afterwards. Growing stays allowed, which is
32
+ what keeps it self-correcting: a first measurement taken before layout is
33
+ final is raised by the next one rather than standing for the whole answer.
34
+
35
+ After: the message decelerates into the anchor and then holds one position
36
+ for the rest of the answer — 473 frames at a 680px viewport, 466 at 1400px,
37
+ not one of them a pixel off.
38
+
39
+ ### Internal
40
+
41
+ - **`follow-check` has a guard for it**, and it had none before: every check
42
+ in there measured a *transition* — where a message lands, where the view
43
+ comes to rest, whether it travelled — and this fault lived in the quiet
44
+ stretch between them, which is why it was reported three times and "fixed"
45
+ twice on measurements that were each true.
46
+
47
+ The window comes from `aria-busy`, which the turn already carries for
48
+ screen readers while its answer arrives, so it is the window the assertion
49
+ names rather than one inferred from the turn's height — that one ran past
50
+ the end of the answer and counted the settle. The fault inside it is a
51
+ **reversal**, because jitter is not motion, it is motion that changes its
52
+ mind; the arrival is required to move and is asserted elsewhere. A creep
53
+ never reverses, so the back half of the window has to be a single number as
54
+ well, and that number the anchor.
55
+
56
+ Watched failing on the fault before it was trusted: 4 reversals at both
57
+ heights with the room recomputing per frame, none with the floor.
58
+
59
+ ## 0.54.2 — 2026-09-12
60
+
61
+ ### Fixed
62
+
63
+ - **The sent message leapt off the top of the view when an answer settled.**
64
+ Measured on the demo at 1100×700, at the frame the turn count went from one
65
+ to two: `scrollTop` 0 → 175, in a single frame, and the message that had
66
+ just been sent went from 100px down the view to −75.
67
+
68
+ It was not the scroll logic. `Conversation` made **exactly one** correction
69
+ in the whole session — a smooth one, arriving after the jump had already
70
+ happened. The jump was `focus()`: when an answer settles the next turn is
71
+ appended, its composer mounts at the bottom of the conversation and focuses
72
+ itself, and focusing an element brings it into view. Instantly, and without
73
+ asking anyone.
74
+
75
+ `preventScroll` now. Where the view goes belongs to `Conversation`, which
76
+ knows about anchors, tails, and whether the reader has scrolled away; focus
77
+ is about the keyboard. The same moment is now eight frames with a largest
78
+ step of 48px.
79
+
80
+ Three traces to find, because every instrument pointed at the scroll logic
81
+ and the scroll logic was innocent. The first two also measured the wrong
82
+ element — during an answer the last editable belongs to the turn being
83
+ written, and afterwards to a different turn entirely, so the trace read a
84
+ change of subject as a movement.
85
+
11
86
  ## 0.54.1 — 2026-09-12
12
87
 
13
88
  ### Fixed
@@ -853,7 +853,7 @@ var Ue = {
853
853
  Le,
854
854
  et
855
855
  ]), o(() => {
856
- (e === "idle" || e === "typing") && F.current?.focus();
856
+ (e === "idle" || e === "typing") && F.current?.focus({ preventScroll: !0 });
857
857
  }, [e]), o(() => {
858
858
  let e = F.current;
859
859
  e && (ve.current = e.scrollHeight || 17);
@@ -4950,17 +4950,18 @@ var Xr = {
4950
4950
  let n = t.lastElementChild, a = n ? ei(n, e) + n.offsetHeight : ei(t, e) + t.offsetHeight - D.current;
4951
4951
  return Math.max(0, a + r - e.clientHeight);
4952
4952
  }, [r, i]), j = a(() => {
4953
- let e = S.current, t = C.current;
4954
- if (!e || !t) return;
4955
- if (i !== "auto") return ni(t, D, Math.max(0, i));
4956
- let a = parseFloat(getComputedStyle(e).paddingBottom) || 0, o = Math.max(0, r - a), s = t.lastElementChild, c = ti(e, O.current);
4957
- if (k.current = !!s && !!c, !k.current || !s || !c) return ni(t, D, o);
4958
- let l = ei(s, e) + s.offsetHeight - ei(c, e);
4959
- ni(t, D, Math.max(o, e.clientHeight - n - l - a));
4953
+ let e = S.current, a = C.current;
4954
+ if (!e || !a) return;
4955
+ if (i !== "auto") return ni(a, D, Math.max(0, i));
4956
+ let o = parseFloat(getComputedStyle(e).paddingBottom) || 0, s = Math.max(0, r - o), c = a.lastElementChild, l = ti(e, O.current);
4957
+ if (k.current = !!c && !!l, !k.current || !c || !l) return ni(a, D, s);
4958
+ let u = ei(c, e) + c.offsetHeight - ei(l, e), d = Math.max(s, e.clientHeight - n - u - o);
4959
+ ni(a, D, t ? Math.max(d, D.current) : d);
4960
4960
  }, [
4961
4961
  i,
4962
4962
  n,
4963
- r
4963
+ r,
4964
+ t
4964
4965
  ]), M = a(() => {
4965
4966
  let e = S.current;
4966
4967
  if (!e) return 0;
@@ -4996,12 +4997,20 @@ var Xr = {
4996
4997
  let e = S.current, n = C.current;
4997
4998
  if (!e || !n || typeof ResizeObserver > "u") return;
4998
4999
  let r = () => {
4999
- let t = M();
5000
+ let n = M();
5000
5001
  if (N.current) {
5001
- if (performance.now() < N.current && Math.abs(e.scrollTop - t) > 1) return;
5002
+ if (performance.now() < N.current && Math.abs(e.scrollTop - n) > 1) return;
5002
5003
  N.current = 0;
5003
5004
  }
5004
- e.scrollTop = t;
5005
+ let r = n - e.scrollTop;
5006
+ if (O.current && !t && Math.abs(r) > s && !K()) {
5007
+ N.current = performance.now() + Qr, e.scrollTo({
5008
+ top: n,
5009
+ behavior: "smooth"
5010
+ });
5011
+ return;
5012
+ }
5013
+ e.scrollTop = n;
5005
5014
  }, i = t != null && t !== P.current;
5006
5015
  t && (P.current = t), i && !K() && Math.abs(e.scrollTop - M()) > s ? (N.current = performance.now() + Qr, e.scrollTo({
5007
5016
  top: M(),