thurview 0.8.1 → 0.8.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/dist/store.js CHANGED
@@ -19,6 +19,15 @@ export function revisionDir(id, n) {
19
19
  export function serverStateFile() {
20
20
  return join(home(), "server.json");
21
21
  }
22
+ /**
23
+ * Heartbeat of an agent draining this review's threads. It lives outside
24
+ * `reviewDir` on purpose: the server watches that directory to push changes to
25
+ * the browser, and a file rewritten every few seconds would reload the page
26
+ * under the reader's hands.
27
+ */
28
+ export function agentFile(id) {
29
+ return join(home(), "agents", `${id}.json`);
30
+ }
22
31
  export function now() {
23
32
  return new Date().toISOString();
24
33
  }
@@ -68,6 +77,7 @@ export async function listReviews() {
68
77
  }
69
78
  export async function deleteReview(id) {
70
79
  await rm(reviewDir(id), { recursive: true, force: true });
80
+ await rm(agentFile(id), { force: true });
71
81
  }
72
82
  export async function readThreads(id) {
73
83
  return ((await readJson(join(reviewDir(id), "threads.json"))) ?? {
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /** Threads the agent must act on: open, submitted, and the last message is from the reviewer. */
2
+ export function needsAgent(th) {
3
+ if (th.status !== "open" || !th.submitted)
4
+ return false;
5
+ const last = th.messages[th.messages.length - 1];
6
+ return !!last && last.role === "reviewer";
7
+ }
8
+ export function deliveryOf(th, agent) {
9
+ if (th.status === "resolved")
10
+ return "closed";
11
+ if (!th.submitted)
12
+ return "held";
13
+ if (!needsAgent(th))
14
+ return "answered";
15
+ return agent.attached ? "listening" : "queued";
16
+ }
17
+ //# sourceMappingURL=thread-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-state.js","sourceRoot":"","sources":["../src/thread-state.ts"],"names":[],"mappings":"AAGA,iGAAiG;AACjG,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,IAAI,EAAE,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC;AAC5C,CAAC;AAeD,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,KAAe;IACpD,IAAI,EAAE,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC;IAC9C,IAAI,CAAC,EAAE,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAAE,OAAO,UAAU,CAAC;IACvC,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;AACjD,CAAC"}
package/dist/threads.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { readThreads, writeThreads, readReview, writeReview, newId, now, } from "./store.js";
2
+ export { needsAgent } from "./thread-state.js";
2
3
  export async function createThread(reviewId, input) {
3
4
  const review = await readReview(reviewId);
4
5
  if (!review)
@@ -28,8 +29,15 @@ export async function replyThread(reviewId, threadId, role, body) {
28
29
  throw new Error(`thread ${threadId} not found`);
29
30
  thread.messages.push({ role, body, at: now() });
30
31
  thread.updatedAt = now();
31
- if (role === "reviewer" && thread.mode === "ask")
32
- thread.submitted = true;
32
+ // A message from the reader is a message that wants an answer. Leaving the
33
+ // thread resolved would drop it: `needsAgent` is false there, so `wait` never
34
+ // reports it and `threads list --open` never shows it. The reader would be
35
+ // writing to nobody, with a Reply button that says otherwise.
36
+ if (role === "reviewer") {
37
+ thread.status = "open";
38
+ if (thread.mode === "ask")
39
+ thread.submitted = true;
40
+ }
33
41
  await writeThreads(reviewId, t);
34
42
  return thread;
35
43
  }
@@ -73,13 +81,6 @@ export async function submitReview(reviewId, decision, body) {
73
81
  await writeReview(review);
74
82
  return t;
75
83
  }
76
- /** Threads the agent must act on: open, submitted, and the last message is from the reviewer. */
77
- export function needsAgent(th) {
78
- if (th.status !== "open" || !th.submitted)
79
- return false;
80
- const last = th.messages[th.messages.length - 1];
81
- return !!last && last.role === "reviewer";
82
- }
83
84
  export function threadSummary(th) {
84
85
  return {
85
86
  id: th.id,
@@ -1 +1 @@
1
- {"version":3,"file":"threads.js","sourceRoot":"","sources":["../src/threads.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"threads.js","sourceRoot":"","sources":["../src/threads.ts"],"names":[],"mappings":"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"}
package/dist/ui/app.css CHANGED
@@ -539,6 +539,24 @@ kbd {
539
539
  .thread-pin.resolved {
540
540
  opacity: 0.55;
541
541
  }
542
+ /* Where the reader's last message stands. `queued` is the one that matters:
543
+ nobody is listening, and the page says so instead of implying delivery. */
544
+ .receipt {
545
+ font-size: 12px;
546
+ line-height: 1.45;
547
+ color: var(--muted);
548
+ margin: 4px 0 2px;
549
+ padding-left: 8px;
550
+ border-left: 2px solid var(--line);
551
+ }
552
+ .receipt.listening {
553
+ color: var(--ok);
554
+ border-left-color: var(--ok);
555
+ }
556
+ .receipt.queued {
557
+ color: var(--warn);
558
+ border-left-color: var(--warn);
559
+ }
542
560
  .comment-popover {
543
561
  border-radius: var(--radius);
544
562
  position: absolute;
package/dist/ui/app.js CHANGED
@@ -143,6 +143,7 @@
143
143
  review: (id, revision) => j(`/api/reviews/${id}${revision ? `?revision=${revision}` : ""}`),
144
144
  revisions: (id) => j(`/api/reviews/${id}/revisions`),
145
145
  commits: (id) => j(`/api/reviews/${id}/commits`),
146
+ presence: (id) => j(`/api/reviews/${id}/presence`),
146
147
  diff: (id, path) => j(`/api/reviews/${id}/diff?path=${encodeURIComponent(path)}`),
147
148
  file: (id, path, graph, from, to) => j(
148
149
  `/api/reviews/${id}/file?path=${encodeURIComponent(path)}&graph=${graph}${from ? `&from=${from}` : ""}${to ? `&to=${to}` : ""}`
@@ -232,32 +233,46 @@
232
233
 
233
234
  // src/ui/threads.ts
234
235
  init_dom();
236
+
237
+ // src/thread-state.ts
238
+ function needsAgent(th) {
239
+ if (th.status !== "open" || !th.submitted) return false;
240
+ const last = th.messages[th.messages.length - 1];
241
+ return !!last && last.role === "reviewer";
242
+ }
243
+ function deliveryOf(th, agent2) {
244
+ if (th.status === "resolved") return "closed";
245
+ if (!th.submitted) return "held";
246
+ if (!needsAgent(th)) return "answered";
247
+ return agent2.attached ? "listening" : "queued";
248
+ }
249
+
250
+ // src/ui/threads.ts
235
251
  async function reload() {
236
252
  state.data = await api.review(state.id, state.viewingRevision ?? void 0);
237
253
  emit("threads");
238
254
  }
255
+ var NOBODY = { attached: false, lastSeen: null };
256
+ function agent() {
257
+ return state.data?.agent ?? NOBODY;
258
+ }
259
+ var RECEIPT = {
260
+ held: "Held. It reaches the agent when you submit.",
261
+ queued: "Queued. Nobody is listening right now \u2014 it is delivered the next time an agent checks this document.",
262
+ listening: "Delivered. An agent is listening and answers here.",
263
+ answered: "The agent answered. Reply, or resolve it.",
264
+ closed: "Resolved. Writing here reopens it."
265
+ };
266
+ function listeningLine() {
267
+ return agent().attached ? "An agent is listening to this document now." : "No agent is listening right now. Anything you send is queued until one checks in.";
268
+ }
239
269
  function commentPopover(target, quote) {
240
270
  const ta = h("textarea", { placeholder: "Comment, or a question for the agent\u2026" });
241
- let mode = "review";
242
- const modeBtns = h("div", { class: "row" });
243
- const b1 = h("button", { class: "small primary", onclick: () => set("review") }, "Add to review");
244
- const b2 = h("button", { class: "small", onclick: () => set("ask") }, "Ask now");
245
- const set = (m2) => {
246
- mode = m2;
247
- b1.className = `small ${m2 === "review" ? "primary" : ""}`;
248
- b2.className = `small ${m2 === "ask" ? "primary" : ""}`;
249
- hint.textContent = m2 === "review" ? "Held until you submit the review." : "Sent to the agent at once; it answers in this thread.";
250
- };
251
- const hint = h(
252
- "span",
253
- { class: "muted", style: { fontSize: "12px" } },
254
- "Held until you submit the review."
255
- );
256
- modeBtns.append(b1, b2, hint);
257
- const submit = async () => {
271
+ const held = kind() === "explainer" ? "Add to my notes" : "Add to the review";
272
+ const submit = async (mode) => {
258
273
  const body = ta.value.trim();
259
274
  if (!body) return;
260
- await api.createThread(state.id, {
275
+ const th = await api.createThread(state.id, {
261
276
  kind: mode === "ask" ? "question" : "comment",
262
277
  mode,
263
278
  target: quote ? { ...target, quote } : target,
@@ -265,9 +280,10 @@
265
280
  });
266
281
  closePopover();
267
282
  await reload();
283
+ focusThread(th.id);
268
284
  };
269
285
  ta.addEventListener("keydown", (e) => {
270
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") void submit();
286
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") void submit("ask");
271
287
  });
272
288
  const el = h(
273
289
  "div",
@@ -279,12 +295,28 @@
279
295
  describeTarget(target)
280
296
  ),
281
297
  ta,
282
- modeBtns,
283
298
  h(
284
299
  "div",
285
- { class: "row", style: { justifyContent: "flex-end" } },
286
- h("button", { class: "small ghost", onclick: () => closePopover() }, "Cancel"),
287
- h("button", { class: "small ok", onclick: submit }, "Save ", h("kbd", null, "\u2318\u21B5"))
300
+ { class: "row" },
301
+ h(
302
+ "button",
303
+ { class: "small ok", onclick: () => void submit("ask") },
304
+ "Send to the agent ",
305
+ h("kbd", null, "\u2318\u21B5")
306
+ ),
307
+ h("button", { class: "small", onclick: () => void submit("review") }, held),
308
+ h("span", { style: { flex: "1" } }),
309
+ h("button", { class: "small ghost", onclick: () => closePopover() }, "Cancel")
310
+ ),
311
+ h(
312
+ "div",
313
+ { class: "muted", style: { fontSize: "12px", marginTop: "6px" } },
314
+ h("div", null, `Send to the agent \u2014 ${listeningLine()}`),
315
+ h(
316
+ "div",
317
+ null,
318
+ `${held} \u2014 held until you ${kind() === "explainer" ? "send the document back or finish" : "submit the review"}.`
319
+ )
288
320
  )
289
321
  );
290
322
  setTimeout(() => ta.focus());
@@ -381,6 +413,14 @@
381
413
  const body = h("div", { class: "side-body" });
382
414
  const draw = () => {
383
415
  body.innerHTML = "";
416
+ if (!readOnly())
417
+ body.appendChild(
418
+ h(
419
+ "div",
420
+ { class: `receipt ${agent().attached ? "listening" : "queued"}` },
421
+ listeningLine()
422
+ )
423
+ );
384
424
  if (pending.length && !readOnly()) {
385
425
  body.appendChild(
386
426
  h(
@@ -414,6 +454,7 @@
414
454
  setTimeout(() => body.querySelector(".thread.active")?.scrollIntoView({ block: "center" }));
415
455
  }
416
456
  function threadCard(t) {
457
+ const delivery = deliveryOf(t, agent());
417
458
  const ta = h("textarea", { placeholder: "Reply\u2026", rows: 2 });
418
459
  const send = async () => {
419
460
  const v = ta.value.trim();
@@ -439,6 +480,7 @@
439
480
  h("span", { class: "target", onclick: () => goToTarget(t) }, describeTarget(t.target)),
440
481
  h("span", { class: `badge ${t.status === "resolved" ? "ok" : ""}` }, t.status)
441
482
  ),
483
+ h("div", { class: `receipt ${delivery}` }, RECEIPT[delivery]),
442
484
  quote ? h("div", { class: "quote" }, `\u201C${quote.length > 160 ? quote.slice(0, 160) + "\u2026" : quote}\u201D`) : null,
443
485
  t.messages.map(
444
486
  (m2) => h(
@@ -471,12 +513,16 @@
471
513
  "button",
472
514
  {
473
515
  class: "small",
516
+ // An unanswered question is not resolved, it is abandoned.
517
+ // Saying so is what stops a reader closing their own
518
+ // question and then waiting for an answer to it.
519
+ title: delivery === "answered" ? "Nothing more is owed here" : "Stop waiting for an answer; no agent will pick this up",
474
520
  onclick: async () => {
475
521
  await api.resolve(state.id, t.id);
476
522
  await reload();
477
523
  }
478
524
  },
479
- "Resolve"
525
+ delivery === "answered" || delivery === "held" ? "Resolve" : "Withdraw"
480
526
  ) : h(
481
527
  "button",
482
528
  {
@@ -2871,6 +2917,17 @@
2871
2917
  banner.append("Closed without approval. This review is complete.");
2872
2918
  }
2873
2919
  }
2920
+ function pollPresence() {
2921
+ setInterval(async () => {
2922
+ const cur = state.data;
2923
+ if (!cur) return;
2924
+ const agent2 = await api.presence(state.id).catch(() => cur.agent);
2925
+ if (agent2.attached !== cur.agent.attached) {
2926
+ state.data = { ...cur, agent: agent2 };
2927
+ emit("threads");
2928
+ }
2929
+ }, 5e3);
2930
+ }
2874
2931
  function connectEvents() {
2875
2932
  const es = new EventSource(`/api/reviews/${state.id}/events`);
2876
2933
  es.onmessage = async (ev) => {
@@ -2941,6 +2998,7 @@
2941
2998
  window.matchMedia(NARROW).addEventListener("change", () => emit("view"));
2942
2999
  emit("data");
2943
3000
  connectEvents();
3001
+ pollPresence();
2944
3002
  }
2945
3003
  var m = /^\/review\/([^/]+)/.exec(location.pathname);
2946
3004
  if (m) void reviewPage(m[1]);