@parley-im/parley 0.2.2 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parley-im/parley",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Send someone a link. They talk to a read-only Claude or Codex in your project, on your computer, and you get everything they said.",
5
5
  "keywords": [
6
6
  "claude",
package/web/app.css CHANGED
@@ -74,6 +74,8 @@ button, input, textarea, select { font: inherit; color: inherit; }
74
74
  .crumbs { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex: 1; }
75
75
  .crumbs .product { color: var(--muted); font-weight: 500; }
76
76
  .crumbs .slash { color: var(--faint); }
77
+ .brand { display: inline-flex; align-items: center; gap: 10px; font-weight: 600; color: inherit; text-decoration: none !important; }
78
+ .brand:hover .mark { filter: brightness(1.1); }
77
79
  .crumbs .here { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
78
80
  .crumbs a { text-decoration: none; }
79
81
  .crumbs a:hover { text-decoration: underline; }
package/web/chat.js CHANGED
@@ -743,6 +743,10 @@
743
743
  clearTimeout(retry);
744
744
  retry = null;
745
745
  ws = new WebSocket(`${location.origin.replace(/^http/, "ws")}${base}/ws`);
746
+ // Keep-alives, answered by parley.im at the edge (no cost), keep phone
747
+ // networks and proxies from dropping a quiet connection.
748
+ const ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send('{"t":"ping"}'), 30_000);
749
+ ws.addEventListener("close", () => clearInterval(ping));
746
750
  ws.onopen = () => {
747
751
  failures = 0;
748
752
  h.offline(false, "connection");
package/web/dashboard.js CHANGED
@@ -1,7 +1,12 @@
1
1
  // Keeps the parley.im dashboard current: who's on each session's page right
2
- // now, when they were last active, and whether a reply is coming in. It
3
- // never shows what was said; parley.im doesn't have that.
2
+ // now, when they were last active, and whether a reply is coming in. Each
3
+ // linked computer pushes news over its own WebSocket, which costs nothing
4
+ // while it's quiet, so nothing here polls. It never shows what was said;
5
+ // parley.im doesn't have that.
4
6
  (() => {
7
+ const REPLYING_MS = 6000;
8
+ const live = new Map(); // session id -> { here, active, replying, online }
9
+
5
10
  const ago = (ms) => {
6
11
  const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
7
12
  if (s < 60) return "just now";
@@ -17,25 +22,57 @@
17
22
  // Matches live() in relay/src/pages.ts.
18
23
  const liveNodes = (s) => {
19
24
  if (s.online === false) return dot("off", "computer offline");
20
- if (s.replying) return dot("busy", "replying");
25
+ if (s.replying && Date.now() - s.replying < REPLYING_MS) return dot("busy", "replying");
21
26
  if (s.here) return dot("on", s.here > 1 ? `${s.here} people here` : "here now");
22
27
  if (s.active) return dot("", `active ${ago(s.active)}`);
23
28
  return dot("", "not opened yet");
24
29
  };
25
30
 
26
- async function refresh() {
27
- if (document.hidden) return;
28
- try {
29
- const res = await fetch("/dashboard.json", { cache: "no-store" });
30
- if (!res.ok) return;
31
- const { sessions } = await res.json();
32
- const rows = [...document.querySelectorAll("[data-session]")];
33
- // A session opened or ended elsewhere: show the page fresh.
34
- if (rows.map((r) => r.dataset.session).sort().join() !== sessions.map((s) => s.id).sort().join()) return location.reload();
35
- for (const s of sessions) rows.find((r) => r.dataset.session === s.id)?.querySelector(".live")?.replaceChildren(...liveNodes(s));
36
- } catch {}
31
+ function paint() {
32
+ for (const row of document.querySelectorAll("[data-session]")) {
33
+ const s = live.get(row.dataset.session);
34
+ if (s) row.querySelector(".live")?.replaceChildren(...liveNodes(s));
35
+ }
36
+ }
37
+
38
+ function apply(computer, m) {
39
+ if (m.t === "changed") return location.reload();
40
+ if (m.t !== "live") return;
41
+ // Times come from parley.im's clock; shift them onto this one.
42
+ const shift = Date.now() - m.now;
43
+ for (const row of document.querySelectorAll(`[data-device="${computer}"]`)) {
44
+ const s = m.sessions[row.dataset.session] ?? {};
45
+ live.set(row.dataset.session, {
46
+ online: m.on,
47
+ here: s.here ?? 0,
48
+ active: s.active ? s.active + shift : null,
49
+ replying: s.replying ? s.replying + shift : null,
50
+ });
51
+ }
52
+ const row = document.querySelector(`[data-computer="${computer}"]`);
53
+ if (row) {
54
+ row.querySelector(".dot").className = `dot${m.on ? " on" : ""}`;
55
+ row.querySelector(".computer-status").textContent = m.on ? `online now${m.version ? ` · parley ${m.version}` : ""}` : "offline";
56
+ }
57
+ paint();
58
+ }
59
+
60
+ function watch(computer, delay = 2000) {
61
+ const ws = new WebSocket(`${location.origin.replace(/^http/, "ws")}/dashboard/live?d=${encodeURIComponent(computer)}`);
62
+ const ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send('{"t":"ping"}'), 30_000);
63
+ ws.onopen = () => (delay = 2000);
64
+ ws.onmessage = (event) => {
65
+ try {
66
+ apply(computer, JSON.parse(event.data));
67
+ } catch {}
68
+ };
69
+ ws.onclose = () => {
70
+ clearInterval(ping);
71
+ setTimeout(() => watch(computer, Math.min(delay * 2, 60_000)), delay);
72
+ };
37
73
  }
38
74
 
39
- setInterval(refresh, 4000);
40
- document.addEventListener("visibilitychange", refresh);
75
+ for (const row of document.querySelectorAll("[data-computer]")) watch(row.dataset.computer);
76
+ // "active 3m ago" and the end of "replying" move on with the clock alone.
77
+ setInterval(paint, 5000);
41
78
  })();