relay-companion 0.1.310 → 0.1.311

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.
Files changed (2) hide show
  1. package/overlay/inbox.html +60 -20
  2. package/package.json +1 -1
@@ -4617,6 +4617,7 @@
4617
4617
  removeQueuedRunMessage(key, messageId);
4618
4618
  requestWorkDrafts.delete(key);
4619
4619
  setRowNote(key, steerNow ? "Steered into the current turn." : "Queued follow-up started.", "ok");
4620
+ fadeRowNoteLater(key, 3000);
4620
4621
  renderAll();
4621
4622
  return;
4622
4623
  }
@@ -5103,11 +5104,13 @@
5103
5104
  const rt = routeFor(id);
5104
5105
  const note = String((b.closest(".ta-dock")?.querySelector(".ta-note") || {}).value || "").trim();
5105
5106
  const ranAlready = requestHasNativeSession(requestRows().find((row) => row.id === id));
5106
- setRowNote(id, ranAlready
5107
- ? `Opening this exact session in ${rt.app}…`
5108
- : `Opening a new ${rt.app} session with the Relay documents${note ? " and your draft note" : ""}.`, "ok");
5109
5107
  if (ranAlready && window.relay.openInCurrent) window.relay.openInCurrent(id, hostKeyFor(rt.app));
5110
5108
  else openRelayFromUI(id, "relay", "fresh", hostKeyFor(rt.app), note);
5109
+ // AFTER the open, never before: openRelayFromUI clears the row's note
5110
+ // first (a stale failure must not outlive the retry), so a note written
5111
+ // above this line vanished the same instant — the trap already found on
5112
+ // the per-relay rows.
5113
+ setOpeningNote(id, `Opening in ${rt.app}…`);
5111
5114
  if (onDone) onDone("open-in");
5112
5115
  });
5113
5116
  }
@@ -5122,7 +5125,7 @@
5122
5125
  const res = await window.relay.openRunSession(id).catch((error) => ({ ok: false, error: String(error?.message || error) }));
5123
5126
  b.disabled = false;
5124
5127
  if (!res?.ok) setRowNote(id, res?.error || "That Claude session is not ready to open yet.", "err");
5125
- else setRowNote(id, "Opened the exact completed session in Claude Code.", "ok");
5128
+ else { setRowNote(id, "Opened in Claude Code.", "ok"); fadeRowNoteLater(id, 3000); }
5126
5129
  if (res?.ok && onDone) onDone("open-in");
5127
5130
  });
5128
5131
  }
@@ -5295,6 +5298,7 @@
5295
5298
  if (box) { box.value = ""; clearStagedFiles(box); }
5296
5299
  requestWorkDrafts.delete(key);
5297
5300
  setRowNote(id, "Queued for when the current turn finishes.", "ok");
5301
+ fadeRowNoteLater(id, 5000);
5298
5302
  renderAll();
5299
5303
  return;
5300
5304
  }
@@ -5308,6 +5312,7 @@
5308
5312
  if (box) { box.value = ""; clearStagedFiles(box); }
5309
5313
  requestWorkDrafts.delete(key);
5310
5314
  setRowNote(id, "Follow-up sent into the session.", "ok");
5315
+ fadeRowNoteLater(id, 3000);
5311
5316
  renderAll();
5312
5317
  }
5313
5318
  else {
@@ -6016,6 +6021,7 @@
6016
6021
  input.value = "";
6017
6022
  readerHumanDrafts.delete(String(r.id));
6018
6023
  setRowNote(r.id, `Sent to ${sender}.`, "ok");
6024
+ fadeRowNoteLater(r.id, 3000);
6019
6025
  renderReader();
6020
6026
  };
6021
6027
  send.addEventListener("click", doSend);
@@ -6028,12 +6034,15 @@
6028
6034
  const n = whole ? threadMessages().filter((m) => m.threadId === r.threadId).length : 0;
6029
6035
  // Continuity is judged BEFORE the hand-off marks the conversation.
6030
6036
  const knows = threadHanded(r.threadId || r.id);
6031
- setRowNote(r.id, whole
6037
+ // An in-chat open answers with its own note (staged / delivered / woken),
6038
+ // which supersedes this one; the paths that answer with nothing used to
6039
+ // leave this line up for good, so it retires with the open too.
6040
+ openRelayFromUI(r.id, r.outbound ? "sent" : "relay", "current");
6041
+ setOpeningNote(r.id, whole
6032
6042
  ? (knows ? `Opening the whole conversation (${n} relays) in the ${agentAppName()} chat that knows it — staged…`
6033
6043
  : `Opening ${agentAppName()} on this whole conversation (${n} relays) — staged, nothing sends until you do…`)
6034
6044
  : (knows ? `Reopening the ${agentAppName()} chat that knows this conversation…`
6035
- : `Opening ${agentAppName()} on this conversation — staged, nothing sends until you do…`), "ok");
6036
- openRelayFromUI(r.id, r.outbound ? "sent" : "relay", "current");
6045
+ : `Opening ${agentAppName()} on this conversation — staged, nothing sends until you do…`));
6037
6046
  renderReader();
6038
6047
  });
6039
6048
  // The reader's old "open in a window" hatch pointed at preview.html too;
@@ -6355,6 +6364,45 @@
6355
6364
  el.classList.remove("ok");
6356
6365
  }
6357
6366
  }
6367
+ // A GREEN NOTE IS TRANSIENT; AN ERROR IS NOT. Every "ok" note narrates an
6368
+ // action the user just took, so it retires when that action lands — main
6369
+ // reports the open home (openDone), or a few seconds pass. An error stays: it
6370
+ // is the only record of a click that went nowhere. Both timers are guarded by
6371
+ // the text that scheduled them, so anything written over the note since is
6372
+ // the newer truth and survives (David, live, on a finished open: the green
6373
+ // line just sat there).
6374
+ const noteTimers = new Map(); // id -> { kind, text, shownAt, timer }
6375
+ const OPENING_NOTE_FALLBACK_MS = 6000; // the safety the row spinner already keeps
6376
+ const OPENING_NOTE_MIN_MS = 900; // a note nobody can read is worse than none
6377
+ function noteStillReads(id, text) { return rowNotes.get(id)?.text === text; }
6378
+ function cancelNoteTimer(id) {
6379
+ const entry = noteTimers.get(id);
6380
+ if (!entry) return;
6381
+ clearTimeout(entry.timer);
6382
+ noteTimers.delete(id);
6383
+ }
6384
+ function scheduleNoteClear(id, { kind, text, shownAt, ms }) {
6385
+ cancelNoteTimer(id);
6386
+ noteTimers.set(id, { kind, text, shownAt, timer: setTimeout(() => {
6387
+ cancelNoteTimer(id);
6388
+ if (noteStillReads(id, text)) clearRowNote(id);
6389
+ }, ms) });
6390
+ }
6391
+ // "Opening in {app}…" — up until the open lands, never longer.
6392
+ function setOpeningNote(id, text) {
6393
+ setRowNote(id, text, "ok");
6394
+ scheduleNoteClear(id, { kind: "opening", text, shownAt: performance.now(), ms: OPENING_NOTE_FALLBACK_MS });
6395
+ }
6396
+ // The open landed: let the line finish being readable, then take it away.
6397
+ function retireOpeningNote(id) {
6398
+ const entry = noteTimers.get(id);
6399
+ if (!entry || entry.kind !== "opening" || !noteStillReads(id, entry.text)) return;
6400
+ scheduleNoteClear(id, { ...entry, ms: Math.max(0, OPENING_NOTE_MIN_MS - (performance.now() - entry.shownAt)) });
6401
+ }
6402
+ // A note that reports a FINISHED action: read it, then it goes.
6403
+ function fadeRowNoteLater(id, ms) {
6404
+ scheduleNoteClear(id, { kind: "fade", text: rowNotes.get(id)?.text, shownAt: performance.now(), ms });
6405
+ }
6358
6406
 
6359
6407
  async function runRowAction(id, btn, fn) {
6360
6408
  if (!btn || btn.disabled) return;
@@ -8480,7 +8528,7 @@
8480
8528
  // outlive the retry), so the answer to THIS click is written after it —
8481
8529
  // written before, it vanished the same instant (found on the clone).
8482
8530
  openRelayFromUI(id, source, "open", host);
8483
- setRowNote(id, `Opening this exact Relay in ${app}…`, "ok");
8531
+ setOpeningNote(id, `Opening in ${app}…`);
8484
8532
  });
8485
8533
  }
8486
8534
  }
@@ -10165,7 +10213,7 @@
10165
10213
  // The OS window was just re-shown and main re-asserted click-through; reset the
10166
10214
  // local flag so the next mousemove re-runs the interactivity handshake cleanly.
10167
10215
  if (window.relay.onShown) window.relay.onShown(() => { interactive = false; });
10168
- if (window.relay.onOpenDone) window.relay.onOpenDone((id) => stopOpening(id));
10216
+ if (window.relay.onOpenDone) window.relay.onOpenDone((id) => { stopOpening(id); retireOpeningNote(id); });
10169
10217
  // Visible confirmation for "Open in current chat". Two honest states:
10170
10218
  // waiting — staged for an idle chat; ONLY that chat's next turn can ingest
10171
10219
  // it, so the note stays up (surviving re-renders) until delivery
@@ -10173,25 +10221,17 @@
10173
10221
  // delivered — main watched the consume-once injection file disappear; the
10174
10222
  // instruction is IN the chat. Green check, then fade.
10175
10223
  // A Codex bridge/automation hand-off keeps the classic brief confirmation.
10176
- const injectedNoteTimers = new Map();
10177
10224
  const deliveredChatNames = new Map(); // packetId -> chat the injection was staged for
10178
- function fadeRowNoteLater(id, ms) {
10179
- if (injectedNoteTimers.has(id)) clearTimeout(injectedNoteTimers.get(id));
10180
- injectedNoteTimers.set(id, setTimeout(() => {
10181
- injectedNoteTimers.delete(id);
10182
- clearRowNote(id);
10183
- }, ms));
10184
- }
10185
10225
  if (window.relay.onInjected) window.relay.onInjected((id, info) => {
10186
10226
  if (info.channel) {
10187
10227
  // Wake path: a channel event starts a real turn even in an idle chat.
10188
- if (injectedNoteTimers.has(id)) { clearTimeout(injectedNoteTimers.get(id)); injectedNoteTimers.delete(id); }
10228
+ cancelNoteTimer(id);
10189
10229
  setRowNote(id, "Pushed to your current Claude chat — waking it now…", "ok");
10190
10230
  return;
10191
10231
  }
10192
10232
  if (info.awaitingTurn) {
10193
10233
  // Persistent until delivery / the auto-fresh hop — never a silent limbo.
10194
- if (injectedNoteTimers.has(id)) { clearTimeout(injectedNoteTimers.get(id)); injectedNoteTimers.delete(id); }
10234
+ cancelNoteTimer(id);
10195
10235
  // Name the destination chat when we know it: "your \u201cgranular-relay\u201d chat"
10196
10236
  // tells the user WHERE to look; "your current chat" leaves them hunting
10197
10237
  // (field report: the injection landed in a chat they never found).
@@ -10219,7 +10259,7 @@
10219
10259
  // the explanation while the user is reading it. Cleared by the next open of the row.
10220
10260
  if (window.relay.onOpenError) window.relay.onOpenError((id, message) => {
10221
10261
  if (!message) return;
10222
- if (injectedNoteTimers.has(id)) { clearTimeout(injectedNoteTimers.get(id)); injectedNoteTimers.delete(id); }
10262
+ cancelNoteTimer(id);
10223
10263
  setRowNote(id, message, "err");
10224
10264
  });
10225
10265
  window.relay.refresh().then((p) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relay-companion",
3
- "version": "0.1.310",
3
+ "version": "0.1.311",
4
4
  "description": "Companion CLI for Relay (sendrelays.com): pairs this machine with your Relay account so coding agents like Claude Code and Codex can send and receive Relay messages.",
5
5
  "homepage": "https://sendrelays.com",
6
6
  "repository": {