relay-companion 0.1.366 → 0.1.367
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/npm-shrinkwrap.json +2 -2
- package/overlay/inbox.html +38 -21
- package/overlay/main.cjs +21 -19
- package/overlay/preload.cjs +9 -2
- package/package.json +1 -1
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relay-companion",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.367",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "relay-companion",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.367",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/claude-agent-sdk": "0.3.195",
|
package/overlay/inbox.html
CHANGED
|
@@ -94,7 +94,12 @@
|
|
|
94
94
|
Elevation is three tokens: the card's own drop shadow (this one), the
|
|
95
95
|
folded pill's lighter cousin, and one popover shadow — nothing else casts. */
|
|
96
96
|
.card {
|
|
97
|
-
|
|
97
|
+
/* The native window preserves its current screen-space top-right point while
|
|
98
|
+
it changes size. Anchor the visible sheet to that same point so a folded
|
|
99
|
+
pill never lands at the old left edge and then jumps home when AppKit
|
|
100
|
+
trims the transparent remainder. This remains local to the current native
|
|
101
|
+
window, so a user-dragged pill morphs exactly where it was placed. */
|
|
102
|
+
position:absolute; top:0; right:0;
|
|
98
103
|
width:344px; height:524px;
|
|
99
104
|
background:var(--bg);
|
|
100
105
|
border-radius:var(--r-card);
|
|
@@ -2685,15 +2690,25 @@
|
|
|
2685
2690
|
const W = { v: EXPANDED.w, vel: 0, t: EXPANDED.w };
|
|
2686
2691
|
const H = { v: EXPANDED.h, vel: 0, t: EXPANDED.h };
|
|
2687
2692
|
let raf = null, lastT = 0;
|
|
2688
|
-
// Main sizes the native window to this card.
|
|
2689
|
-
//
|
|
2693
|
+
// Main sizes the native window to this card. A morph has two explicit phases:
|
|
2694
|
+
// prepare grows the native surface before the first destination paint; settled
|
|
2695
|
+
// trims it only after the renderer's spring actually finishes. A monotonic id
|
|
2696
|
+
// makes rapid collapse/expand retargeting reject stale completions.
|
|
2690
2697
|
let interactive = false;
|
|
2691
2698
|
let lastSentCardSize = "";
|
|
2692
|
-
|
|
2693
|
-
|
|
2699
|
+
let cardMotionId = 0;
|
|
2700
|
+
// A renderer reload starts a fresh ordering domain. Without this session id,
|
|
2701
|
+
// main could mistake the new renderer's low motion ids for stale messages.
|
|
2702
|
+
const cardMotionSessionId = crypto.randomUUID();
|
|
2703
|
+
function publishCardSize(w, h, { phase = "prepare", motionId = cardMotionId } = {}) {
|
|
2704
|
+
const key = motionId + ":" + phase + ":" + Math.round(w) + "x" + Math.round(h);
|
|
2694
2705
|
if (key === lastSentCardSize) return;
|
|
2695
2706
|
lastSentCardSize = key;
|
|
2696
|
-
if (window.relay.cardSize) window.relay.cardSize(Math.round(w), Math.round(h)
|
|
2707
|
+
if (window.relay.cardSize) window.relay.cardSize(Math.round(w), Math.round(h), {
|
|
2708
|
+
phase,
|
|
2709
|
+
motionId,
|
|
2710
|
+
motionSessionId: cardMotionSessionId,
|
|
2711
|
+
});
|
|
2697
2712
|
}
|
|
2698
2713
|
function step(s, dt) {
|
|
2699
2714
|
const before = s.v - s.t;
|
|
@@ -2719,24 +2734,20 @@
|
|
|
2719
2734
|
for (let i = 0; i < n; i++) { const a = step(W, h), b = step(H, h); moving = moving || a || b; }
|
|
2720
2735
|
cardEl.style.width = W.v.toFixed(2) + "px";
|
|
2721
2736
|
cardEl.style.height = H.v.toFixed(2) + "px";
|
|
2722
|
-
// Native resize only needs to track the animation coarsely; sending it every
|
|
2723
|
-
// frame would compete with the renderer's own motion.
|
|
2724
|
-
publishCardSizeThrottled(W.v, H.v);
|
|
2725
2737
|
if (moving) raf = requestAnimationFrame(frame);
|
|
2726
|
-
else {
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
if (now - lastPublishAt < 80) return;
|
|
2732
|
-
lastPublishAt = now;
|
|
2733
|
-
publishCardSize(w, h);
|
|
2738
|
+
else {
|
|
2739
|
+
raf = null;
|
|
2740
|
+
cardEl.style.willChange = "";
|
|
2741
|
+
publishCardSize(W.t, H.t, { phase:"settled", motionId:cardMotionId });
|
|
2742
|
+
}
|
|
2734
2743
|
}
|
|
2735
2744
|
function springTo(w, h) {
|
|
2736
2745
|
W.t = w; H.t = h;
|
|
2746
|
+
const motionId = ++cardMotionId;
|
|
2737
2747
|
// Publish the destination synchronously so growth is available before paint
|
|
2738
2748
|
// and a delayed animation frame cannot leave a stale native window size.
|
|
2739
|
-
|
|
2749
|
+
// Shrink is only committed by the settled message below.
|
|
2750
|
+
publishCardSize(w, h, { phase:"prepare", motionId });
|
|
2740
2751
|
// The scroller's width is FIXED (containment: the height spring must not
|
|
2741
2752
|
// reflow the rows every frame) — so it must be re-fixed to each new
|
|
2742
2753
|
// TARGET, once per morph. Content lays out at the destination size
|
|
@@ -2744,7 +2755,13 @@
|
|
|
2744
2755
|
// its content in a 344px column and the new width is dead space. The
|
|
2745
2756
|
// folded pill hides .scroll entirely, so never shrink it below EXPANDED.
|
|
2746
2757
|
scrollEl.style.width = Math.max(w, EXPANDED.w) + "px";
|
|
2747
|
-
if (REDUCED) {
|
|
2758
|
+
if (REDUCED) {
|
|
2759
|
+
W.v = w; H.v = h;
|
|
2760
|
+
cardEl.style.width = w + "px";
|
|
2761
|
+
cardEl.style.height = h + "px";
|
|
2762
|
+
publishCardSize(w, h, { phase:"settled", motionId });
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2748
2765
|
// will-change on width/height buys nothing (they are layout, not composited)
|
|
2749
2766
|
// and costs a layer promotion; containment on .scroll is what makes this cheap.
|
|
2750
2767
|
if (!raf) { lastT = performance.now(); raf = requestAnimationFrame(frame); }
|
|
@@ -2970,7 +2987,7 @@
|
|
|
2970
2987
|
if (raf) { cancelAnimationFrame(raf); raf = null; cardEl.style.willChange = ""; }
|
|
2971
2988
|
cardEl.style.width = w + "px";
|
|
2972
2989
|
cardEl.style.height = h + "px";
|
|
2973
|
-
publishCardSize(w, h);
|
|
2990
|
+
publishCardSize(w, h, { phase:"settled", motionId:++cardMotionId });
|
|
2974
2991
|
}
|
|
2975
2992
|
function resetOffstage() {
|
|
2976
2993
|
ghost = false;
|
|
@@ -12021,7 +12038,7 @@
|
|
|
12021
12038
|
// authoritative across reloads and future layout changes. Publish the actual
|
|
12022
12039
|
// initial target before readiness so the native hit rect can never retain a
|
|
12023
12040
|
// larger stale/default size until the first fold-expand transition.
|
|
12024
|
-
publishCardSize(W.t, H.t);
|
|
12041
|
+
publishCardSize(W.t, H.t, { phase:"settled", motionId:cardMotionId });
|
|
12025
12042
|
// LAST signal on purpose: every listener above is registered and the native
|
|
12026
12043
|
// hit rect is synchronized, so main may now deliver queued arrivals without
|
|
12027
12044
|
// a single one being dropped (FM-1).
|
package/overlay/main.cjs
CHANGED
|
@@ -4033,7 +4033,6 @@ function pollHosts({ probeFrontmost = true } = {}) {
|
|
|
4033
4033
|
const CARD_INITIAL = { w: 344, h: 524 }; // EXPANDED in inbox.html; renderer publishes its live size before announcing readiness
|
|
4034
4034
|
const CARD_MAX = { w: 720, h: 800 }; // READER in inbox.html (the pill expanded into the page; PEEK is 400px wide)
|
|
4035
4035
|
let cardSize = { w: CARD_INITIAL.w, h: CARD_INITIAL.h };
|
|
4036
|
-
let overlayFitTimer = null;
|
|
4037
4036
|
|
|
4038
4037
|
function fitOverlayWindowToCard({ settle = false } = {}) {
|
|
4039
4038
|
if (!win || win.isDestroyed()) return;
|
|
@@ -4043,25 +4042,15 @@ function fitOverlayWindowToCard({ settle = false } = {}) {
|
|
|
4043
4042
|
// morphs in place and, importantly, never snaps a user-dragged pill home.
|
|
4044
4043
|
const target = resizedOverlayBounds(current, cardSize, { maximum: CARD_MAX });
|
|
4045
4044
|
if (target.x === current.x && target.y === current.y && target.width === current.width && target.height === current.height) {
|
|
4046
|
-
if (overlayFitTimer) clearTimeout(overlayFitTimer);
|
|
4047
|
-
overlayFitTimer = null;
|
|
4048
4045
|
return;
|
|
4049
4046
|
}
|
|
4050
4047
|
const growing = target.width > current.width || target.height > current.height;
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
}
|
|
4057
|
-
// Let the card's closing animation finish before shrinking the native window;
|
|
4058
|
-
// growing happens first so content is never clipped.
|
|
4059
|
-
if (overlayFitTimer) clearTimeout(overlayFitTimer);
|
|
4060
|
-
overlayFitTimer = setTimeout(() => {
|
|
4061
|
-
overlayFitTimer = null;
|
|
4062
|
-
fitOverlayWindowToCard({ settle: true });
|
|
4063
|
-
}, 420);
|
|
4064
|
-
overlayFitTimer.unref?.();
|
|
4048
|
+
// Growth happens before the renderer paints its larger destination. Shrink
|
|
4049
|
+
// waits for an explicit renderer-settled receipt: no guessed timeout, no
|
|
4050
|
+
// competing AppKit resize during the spring, and no stale collapse after a
|
|
4051
|
+
// rapid retarget.
|
|
4052
|
+
if (!growing && !settle) return;
|
|
4053
|
+
try { win.setBounds(target, false); } catch {}
|
|
4065
4054
|
}
|
|
4066
4055
|
|
|
4067
4056
|
function createWindow() {
|
|
@@ -7374,10 +7363,23 @@ ipcMain.on("relay:stall", (_e, info) => {
|
|
|
7374
7363
|
}, 500).unref?.();
|
|
7375
7364
|
}
|
|
7376
7365
|
|
|
7377
|
-
|
|
7366
|
+
let latestCardMotionId = 0;
|
|
7367
|
+
let latestCardMotionSessionId = "";
|
|
7368
|
+
ipcMain.on("relay:cardSize", (event, w, h, motion = {}) => {
|
|
7369
|
+
if (!win || win.isDestroyed() || event.sender !== win.webContents) return;
|
|
7378
7370
|
if (!Number.isFinite(w) || !Number.isFinite(h)) return;
|
|
7371
|
+
const motionSessionId = typeof motion.motionSessionId === "string"
|
|
7372
|
+
? motion.motionSessionId.slice(0, 64)
|
|
7373
|
+
: "";
|
|
7374
|
+
const motionId = Number.isSafeInteger(motion.motionId) ? motion.motionId : 0;
|
|
7375
|
+
if (motionSessionId !== latestCardMotionSessionId) {
|
|
7376
|
+
latestCardMotionSessionId = motionSessionId;
|
|
7377
|
+
latestCardMotionId = 0;
|
|
7378
|
+
}
|
|
7379
|
+
if (motionId < latestCardMotionId) return;
|
|
7380
|
+
latestCardMotionId = motionId;
|
|
7379
7381
|
cardSize = { w, h };
|
|
7380
|
-
fitOverlayWindowToCard();
|
|
7382
|
+
fitOverlayWindowToCard({ settle: motion.phase === "settled" });
|
|
7381
7383
|
});
|
|
7382
7384
|
ipcMain.handle("relay:prepareCardSize", (event, w, h) => {
|
|
7383
7385
|
if (!win || win.isDestroyed() || event.sender !== win.webContents) return { ok:false };
|
package/overlay/preload.cjs
CHANGED
|
@@ -177,8 +177,15 @@ contextBridge.exposeInMainWorld("relay", {
|
|
|
177
177
|
|
|
178
178
|
// window plumbing
|
|
179
179
|
engage: () => ipcRenderer.send("relay:engage"),
|
|
180
|
-
// The native window tracks the visible card's dimensions.
|
|
181
|
-
|
|
180
|
+
// The native window tracks the visible card's dimensions. `prepare` reserves
|
|
181
|
+
// growth before paint; `settled` commits shrink after the spring completes.
|
|
182
|
+
cardSize: (w, h, motion = {}) => ipcRenderer.send("relay:cardSize", w, h, {
|
|
183
|
+
phase: motion.phase === "settled" ? "settled" : "prepare",
|
|
184
|
+
motionId: Number.isSafeInteger(motion.motionId) ? motion.motionId : 0,
|
|
185
|
+
motionSessionId: typeof motion.motionSessionId === "string"
|
|
186
|
+
? motion.motionSessionId.slice(0, 64)
|
|
187
|
+
: "",
|
|
188
|
+
}),
|
|
182
189
|
// Grow before revealing a larger reader so content is never clipped.
|
|
183
190
|
prepareCardSize: (w, h) => ipcRenderer.invoke("relay:prepareCardSize", w, h),
|
|
184
191
|
setTheme: (t) => ipcRenderer.send("relay:theme", t), // preview window wears the same sheet
|
package/package.json
CHANGED