ryugi-artifacts 1.16.2 → 1.16.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/cli.cjs CHANGED
@@ -281,10 +281,16 @@ async function comment(options, emit) {
281
281
  throw new CliError(`The running artifacts server is older than this CLI. Run \`${invocation()} start --project-dir ${server.project}\` to replace it, then run this again.`);
282
282
  }
283
283
  if (status < 200 || status >= 300) throw new CliError(json.error || `HTTP ${status}`);
284
- if (action === "list") emit({ type: "comments", artifact: options.artifact, threads: json.threads, ...options.all ? { all: true } : {} });
284
+ if (action === "list") emit({ type: "comments", artifact: options.artifact, threads: json.threads, ...json.groups ? { groups: json.groups } : {}, ...options.all ? { all: true } : {} });
285
285
  else emit({ type: WRITTEN[action], artifact: options.artifact, threadId: json.threadId, ...json.messageId ? { messageId: json.messageId } : {} });
286
286
  return 0;
287
287
  }
288
+ var VERDICT_TEXT = { pass: "passed", reject: "rejected", pending: "waiting for a verdict" };
289
+ var plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
290
+ function groupText(group) {
291
+ const parts = [VERDICT_TEXT[group.verdict], group.evidenceUpdated ? "evidence replaced since the last verdict" : "", group.rejectThreads ? `${plural(group.rejectThreads, "open reject thread")}` : ""];
292
+ return ` ${group.id}${group.title ? ` (${group.title})` : ""}: ${parts.filter(Boolean).join(" \xB7 ")}`;
293
+ }
288
294
  var clip = (text2, max) => text2.length > max ? `${text2.slice(0, max)}\u2026` : text2;
289
295
  var percent = (value) => `${Math.round(value * 100)}%`;
290
296
  function anchorLines(thread) {
@@ -312,8 +318,12 @@ function threadText(thread) {
312
318
  function commentText(value) {
313
319
  if (value.type === "comments") {
314
320
  const threads = value.threads ?? [];
315
- if (!threads.length) return value.all ? `No comments on ${value.artifact}.` : `No new comments on ${value.artifact}.`;
316
- return threads.map(threadText).join("\n\n");
321
+ const groups = value.groups ?? [];
322
+ const head = threads.length ? threads.map(threadText).join("\n\n") : value.all ? `No comments on ${value.artifact}.` : `No new comments on ${value.artifact}.`;
323
+ return groups.length ? `${head}
324
+
325
+ Verify groups:
326
+ ${groups.map(groupText).join("\n")}` : head;
317
327
  }
318
328
  if (value.type === "comment-added") return `Added thread ${value.threadId} (message ${value.messageId})`;
319
329
  if (value.type === "comment-replied") return `Replied in thread ${value.threadId} (message ${value.messageId})`;
@@ -339,6 +349,7 @@ var import_node_path2 = __toESM(require("node:path"), 1);
339
349
  // src/shared/validate.ts
340
350
  var BOARD_NUMBERS = ["x", "y", "w", "h"];
341
351
  var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
352
+ var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
342
353
 
343
354
  // src/server/state.ts
344
355
  function writeJsonAtomic(file, value) {
@@ -911,7 +922,7 @@ async function main(argv) {
911
922
  }, emit);
912
923
  }
913
924
  if (command === "--version" || command === "-v") {
914
- console.log("1.16.2");
925
+ console.log("1.16.3");
915
926
  return 0;
916
927
  }
917
928
  if (command === "--help" || command === "-h") {
package/dist/server.cjs CHANGED
@@ -5182,6 +5182,8 @@ var import_node_path20 = __toESM(require("node:path"), 1);
5182
5182
 
5183
5183
  // src/shared/types.ts
5184
5184
  var ARTIFACT_TYPES = ["artboard", "document", "html"];
5185
+ var BOARD_TYPES = ["html", "image", "video"];
5186
+ var isMediaBoard = (board) => board.type === "image" || board.type === "video";
5185
5187
 
5186
5188
  // src/server/artifacts.ts
5187
5189
  var import_node_fs = __toESM(require("node:fs"), 1);
@@ -17961,6 +17963,14 @@ function readBoardAnchor(value) {
17961
17963
  if (!isName(value.board)) return null;
17962
17964
  anchor.board = value.board;
17963
17965
  }
17966
+ if (value.group !== void 0) {
17967
+ if (!isName(value.group)) return null;
17968
+ anchor.group = value.group;
17969
+ }
17970
+ if (value.verdict !== void 0) {
17971
+ if (!anchor.group || value.verdict !== "pass" && value.verdict !== "reject") return null;
17972
+ anchor.verdict = value.verdict;
17973
+ }
17964
17974
  if (value.nodes !== void 0) {
17965
17975
  const nodes = readNodes(value.nodes);
17966
17976
  if (!nodes) return null;
@@ -17980,17 +17990,24 @@ function readBoardAnchor(value) {
17980
17990
  }
17981
17991
  var boardAnchors = {
17982
17992
  read: readBoardAnchor,
17983
- same: (a5, b6) => a5.page === b6.page && a5.board === b6.board && a5.x === b6.x && a5.y === b6.y,
17993
+ same: (a5, b6) => a5.page === b6.page && a5.board === b6.board && a5.group === b6.group && a5.verdict === b6.verdict && a5.x === b6.x && a5.y === b6.y,
17984
17994
  missing: "a new thread needs a board anchor (page, x, y); dropped it"
17985
17995
  };
17986
17996
  var emptyBoardComments = () => ({ version: 1, threads: [] });
17987
17997
  var savedBoardComments = (incoming) => savedComments(incoming, boardAnchors);
17988
17998
  var mergeBoardComments = (base, incoming, origin, now, newId, name) => mergeComments(base, incoming, origin, now, newId, name, boardAnchors);
17989
17999
  function applyBoardHumanOp(file, op, now, newId, who) {
17990
- if (op.op === "create") return { ...file, threads: [...file.threads, openThread(op.anchor, "human", op.text, now, newId, who)] };
17991
- return applyThreadOp(file, op, now, newId, who);
18000
+ if (op.op !== "create") return applyThreadOp(file, op, now, newId, who);
18001
+ const thread = openThread(op.anchor, "human", op.text, now, newId, who);
18002
+ if (op.anchor.verdict !== "pass") return { ...file, threads: [...file.threads, thread] };
18003
+ const closed = { status: "resolved", statusAt: now, statusBy: "human" };
18004
+ const rejected = (t7) => t7.anchor.group === op.anchor.group && t7.anchor.verdict === "reject" && t7.status === "open";
18005
+ return { ...file, threads: [...file.threads.map((t7) => rejected(t7) ? { ...t7, ...closed } : t7), { ...thread, ...closed }] };
17992
18006
  }
17993
18007
  function applyBoardAgentOp(file, op, now, newId, name) {
18008
+ if (op.op === "resolve" && file.threads.find((t7) => t7.id === op.threadId)?.anchor.verdict === "reject") {
18009
+ throw new Error("a reject closes only when your human partner passes the group");
18010
+ }
17994
18011
  if (op.op === "add") {
17995
18012
  const thread = openThread(op.anchor, "agent", op.text, now, newId, { name });
17996
18013
  return { file: { ...file, threads: [...file.threads, thread] }, threadId: thread.id, messageId: thread.messages[0].id };
@@ -17998,6 +18015,19 @@ function applyBoardAgentOp(file, op, now, newId, name) {
17998
18015
  return applyAgentThreadOp(file, op, now, newId, name);
17999
18016
  }
18000
18017
 
18018
+ // src/shared/verdicts.ts
18019
+ var openedAt = (thread) => thread.messages[0]?.at ?? thread.statusAt;
18020
+ function groupVerdict(threads, group, stale) {
18021
+ const own = threads.filter((thread) => thread.anchor.group === group && thread.anchor.verdict && thread.messages.some((m11) => !m11.deleted)).sort((a5, b6) => openedAt(a5) < openedAt(b6) ? -1 : openedAt(a5) > openedAt(b6) ? 1 : 0);
18022
+ const rejectThreads = own.filter((thread) => thread.anchor.verdict === "reject" && thread.status === "open").length;
18023
+ const latest = own.at(-1);
18024
+ if (!latest) return { state: "pending", evidenceUpdated: false, rejectThreads };
18025
+ const at7 = openedAt(latest);
18026
+ if (stale.has(latest.id)) return { state: "pending", at: at7, evidenceUpdated: true, rejectThreads };
18027
+ if (latest.anchor.verdict === "pass") return { state: "pass", at: at7, evidenceUpdated: false, rejectThreads };
18028
+ return { state: latest.status === "open" ? "reject" : "pending", at: at7, evidenceUpdated: false, rejectThreads };
18029
+ }
18030
+
18001
18031
  // src/server/comment-cursors.ts
18002
18032
  var import_node_fs6 = __toESM(require("node:fs"), 1);
18003
18033
  var import_node_path6 = __toESM(require("node:path"), 1);
@@ -18093,6 +18123,9 @@ var intersects = (a5, b6) => a5.x < b6.x + b6.w && b6.x < a5.x + a5.w && a5.y <
18093
18123
  // src/shared/validate.ts
18094
18124
  var BOARD_NUMBERS = ["x", "y", "w", "h"];
18095
18125
  var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
18126
+ var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
18127
+ var fileOf = (board) => isMediaBoard(board) ? board.src ?? "" : board.file;
18128
+ var fileField = (board) => isMediaBoard(board) ? "src" : "file";
18096
18129
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18097
18130
  function structuralWarnings(raw) {
18098
18131
  if (!isObject2(raw)) return ["canvas.json must be a JSON object"];
@@ -18122,6 +18155,16 @@ function structuralWarnings(raw) {
18122
18155
  if (isObject2(board) && board.interactive !== void 0 && typeof board.interactive !== "boolean") {
18123
18156
  warnings.push(`board "${id2}" field "interactive" must be a boolean`);
18124
18157
  }
18158
+ if (isObject2(board) && board.type !== void 0 && !BOARD_TYPES.includes(board.type)) {
18159
+ warnings.push(`board "${id2}" field "type" must be "html", "image" or "video"`);
18160
+ }
18161
+ }
18162
+ }
18163
+ if (isObject2(raw.groups)) {
18164
+ for (const [id2, group] of Object.entries(raw.groups)) {
18165
+ if (isObject2(group) && group.verify !== void 0 && typeof group.verify !== "boolean") {
18166
+ warnings.push(`group "${id2}" field "verify" must be a boolean`);
18167
+ }
18125
18168
  }
18126
18169
  }
18127
18170
  if (warnings.length > 0) return warnings;
@@ -18135,7 +18178,7 @@ function structuralWarnings(raw) {
18135
18178
  warnings.push(`board "${id2}" must be an object`);
18136
18179
  continue;
18137
18180
  }
18138
- for (const key2 of BOARD_REQUIRED) {
18181
+ for (const key2 of isMediaBoard(board) ? MEDIA_REQUIRED : BOARD_REQUIRED) {
18139
18182
  if (board[key2] === void 0) warnings.push(`board "${id2}" is missing "${key2}"`);
18140
18183
  }
18141
18184
  for (const key2 of BOARD_NUMBERS) {
@@ -18143,7 +18186,7 @@ function structuralWarnings(raw) {
18143
18186
  warnings.push(`board "${id2}" field "${key2}" must be a number`);
18144
18187
  }
18145
18188
  }
18146
- for (const key2 of ["file", "page"]) {
18189
+ for (const key2 of ["file", "src", "page"]) {
18147
18190
  if (board[key2] !== void 0 && typeof board[key2] !== "string") {
18148
18191
  warnings.push(`board "${id2}" field "${key2}" must be a string`);
18149
18192
  }
@@ -18174,10 +18217,11 @@ function validateCanvas(raw, fileExists, options = {}) {
18174
18217
  const notes = Object.entries(canvas.notes ?? {});
18175
18218
  for (const [id2, board] of boards) {
18176
18219
  if (!pages.has(board.page)) warnings.push(`board "${id2}" uses unknown page "${board.page}"`);
18177
- if (!board.file.startsWith("pages/")) warnings.push(`board "${id2}" file must start with "pages/": ${board.file}`);
18178
- else if (!fileExists(board.file)) {
18220
+ const file = fileOf(board);
18221
+ if (!file.startsWith("pages/")) warnings.push(`board "${id2}" ${fileField(board)} must start with "pages/": ${file}`);
18222
+ else if (!fileExists(file)) {
18179
18223
  options.pending?.add(id2);
18180
- if (!options.allowPending) warnings.push(`board "${id2}" file not found: ${board.file}`);
18224
+ if (!options.allowPending) warnings.push(`board "${id2}" file not found: ${file}`);
18181
18225
  }
18182
18226
  }
18183
18227
  for (let i6 = 0; i6 < boards.length; i6++) {
@@ -18288,6 +18332,10 @@ function boardVersions(boardDir, canvas) {
18288
18332
  }
18289
18333
  try {
18290
18334
  const stat = import_node_fs5.default.statSync(target);
18335
+ if (isMediaBoard(board)) {
18336
+ versions[board.file] = stat.size > 0 ? stat.mtimeMs : 0;
18337
+ continue;
18338
+ }
18291
18339
  const html = import_node_fs5.default.readFileSync(target, "utf8");
18292
18340
  const document2 = /<(?:!doctype|html)\b/i.test(html);
18293
18341
  const ready = html.trim().length > 0 && (!document2 || /<\/html>\s*(?:<!--[\s\S]*?-->\s*)*$/i.test(html));
@@ -18309,6 +18357,7 @@ function readCanvas(boardDir) {
18309
18357
  const structural = structuralWarnings(raw);
18310
18358
  if (structural.length > 0) return { canvas: null, warnings: structural, pending: [], layout: null, versions: {} };
18311
18359
  const canvas = { ...raw };
18360
+ canvas.boards = Object.fromEntries(Object.entries(canvas.boards).map(([id2, board]) => [id2, isMediaBoard(board) ? { ...board, file: board.src ?? "" } : board]));
18312
18361
  const { versions, writing } = boardVersions(boardDir, canvas);
18313
18362
  const pending = /* @__PURE__ */ new Set();
18314
18363
  for (const [id2, board] of Object.entries(canvas.boards)) {
@@ -18487,6 +18536,7 @@ var boardRules = {
18487
18536
  agent: applyBoardAgentOp
18488
18537
  };
18489
18538
  var withoutHashes = ({ hashes: _hashes, ...anchor }) => anchor;
18539
+ var withoutVerdict = ({ hashes: _hashes, group: _group, verdict: _verdict, ...anchor }) => anchor;
18490
18540
  function asBoardHumanOp(value) {
18491
18541
  if (typeof value !== "object" || value === null) return null;
18492
18542
  const v6 = value;
@@ -18502,22 +18552,30 @@ function asBoardAgentOp(value) {
18502
18552
  const v6 = value;
18503
18553
  if (v6.op === "add") {
18504
18554
  const anchor = readBoardAnchor(v6.anchor);
18505
- return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "add", anchor: withoutHashes(anchor), text: v6.text } : null;
18555
+ return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "add", anchor: withoutVerdict(anchor), text: v6.text } : null;
18506
18556
  }
18507
18557
  if (v6.op === "reply") return isId(v6.threadId) && isText3(v6.text) && v6.text.length > 0 ? { op: "reply", threadId: v6.threadId, text: v6.text } : null;
18508
18558
  if (v6.op === "resolve") return isId(v6.threadId) ? { op: "resolve", threadId: v6.threadId } : null;
18509
18559
  return null;
18510
18560
  }
18511
- function boardSpecs(dir) {
18561
+ function readCanvasJson(dir) {
18512
18562
  try {
18513
- const canvas = JSON.parse(import_node_fs8.default.readFileSync(import_node_path7.default.join(dir, "canvas.json"), "utf8"));
18514
- return Object.fromEntries(Object.entries(canvas.boards ?? {}).flatMap(
18515
- ([id2, board]) => typeof board?.file === "string" ? [[id2, { file: board.file, page: typeof board.page === "string" ? board.page : "", w: typeof board.w === "number" ? board.w : 0 }]] : []
18516
- ));
18563
+ return JSON.parse(import_node_fs8.default.readFileSync(import_node_path7.default.join(dir, "canvas.json"), "utf8"));
18517
18564
  } catch {
18518
18565
  return {};
18519
18566
  }
18520
18567
  }
18568
+ function boardSpecs(dir) {
18569
+ return Object.fromEntries(Object.entries(readCanvasJson(dir).boards ?? {}).flatMap(([id2, board]) => {
18570
+ const file = board?.type === "image" || board?.type === "video" ? board.src : board?.file;
18571
+ return typeof file === "string" ? [[id2, { file, page: typeof board.page === "string" ? board.page : "", w: typeof board.w === "number" ? board.w : 0 }]] : [];
18572
+ }));
18573
+ }
18574
+ function groupSpecs(dir) {
18575
+ return Object.fromEntries(Object.entries(readCanvasJson(dir).groups ?? {}).flatMap(
18576
+ ([id2, group]) => Array.isArray(group?.boards) ? [[id2, { ...typeof group.title === "string" ? { title: group.title } : {}, boards: group.boards.filter((b6) => typeof b6 === "string"), verify: group.verify === true }]] : []
18577
+ ));
18578
+ }
18521
18579
  var boardFiles = (dir) => Object.fromEntries(Object.entries(boardSpecs(dir)).map(([id2, spec]) => [id2, spec.file]));
18522
18580
  function agentAddOnBoard(value, specs) {
18523
18581
  if (typeof value !== "object" || value === null) return null;
@@ -18542,9 +18600,11 @@ function boardHashes(dir) {
18542
18600
  return hash ? [[id2, hash]] : [];
18543
18601
  }));
18544
18602
  }
18545
- var boardsOf = (anchor) => [.../* @__PURE__ */ new Set([...anchor.board ? [anchor.board] : [], ...(anchor.nodes ?? []).map((node) => node.board)])];
18546
- function stamp(anchor, hashes) {
18547
- const boards = boardsOf(anchor);
18603
+ var boardsOf = (anchor, groups = {}) => [
18604
+ .../* @__PURE__ */ new Set([...anchor.board ? [anchor.board] : [], ...(anchor.nodes ?? []).map((node) => node.board), ...anchor.group ? groups[anchor.group]?.boards ?? [] : []])
18605
+ ];
18606
+ function stamp(anchor, hashes, groups = {}) {
18607
+ const boards = boardsOf(anchor, groups);
18548
18608
  if (!boards.length) return withoutHashes(anchor);
18549
18609
  return { ...withoutHashes(anchor), hashes: Object.fromEntries(boards.map((board) => [board, hashes[board] ?? "missing"])) };
18550
18610
  }
@@ -18586,11 +18646,12 @@ function createBoardCommentRegistry(options) {
18586
18646
  };
18587
18647
  const forAgent2 = (id2, staleIds) => {
18588
18648
  const files = boardFiles(dirOf(id2));
18649
+ const groups = groupSpecs(dirOf(id2));
18589
18650
  return (thread) => ({
18590
18651
  ...thread,
18591
18652
  stale: staleIds.has(thread.id),
18592
18653
  waitingOn: waitingOn(thread),
18593
- files: Object.fromEntries(boardsOf(thread.anchor).flatMap((board) => files[board] ? [[board, files[board]]] : [])),
18654
+ files: Object.fromEntries(boardsOf(thread.anchor, groups).flatMap((board) => files[board] ? [[board, files[board]]] : [])),
18594
18655
  messages: thread.messages.map(({ avatar: _avatar, ...message }) => message)
18595
18656
  });
18596
18657
  };
@@ -18599,7 +18660,9 @@ function createBoardCommentRegistry(options) {
18599
18660
  stale,
18600
18661
  warnings: (id2) => store(id2).warnings(),
18601
18662
  apply: (id2, op) => {
18602
- const stamped = op.op === "create" ? { ...op, anchor: stamp(op.anchor, fresh(id2)) } : op;
18663
+ const groups = groupSpecs(dirOf(id2));
18664
+ if (op.op === "create" && op.anchor.verdict && !groups[op.anchor.group]?.verify) throw new Error(`no verify group ${op.anchor.group} in artboard/${id2}`);
18665
+ const stamped = op.op === "create" ? { ...op, anchor: stamp(op.anchor, fresh(id2), groups) } : op;
18603
18666
  store(id2).apply(stamped);
18604
18667
  push2(id2);
18605
18668
  return { comments: store(id2).current(), stale: stale(id2) };
@@ -18620,6 +18683,16 @@ function createBoardCommentRegistry(options) {
18620
18683
  if (newest) cursors.set(id2, agent, newest);
18621
18684
  return threads.map(format);
18622
18685
  },
18686
+ groups: (id2) => {
18687
+ const specs = boardSpecs(dirOf(id2));
18688
+ const threads = store(id2).current().threads;
18689
+ const staleIds = new Set(stale(id2));
18690
+ return Object.entries(groupSpecs(dirOf(id2))).filter(([, group]) => group.verify).map(([groupId, group]) => {
18691
+ const { state, ...verdict } = groupVerdict(threads, groupId, staleIds);
18692
+ const page = group.boards.map((board) => specs[board]?.page).find(Boolean) ?? "";
18693
+ return { id: groupId, page, ...group.title ? { title: group.title } : {}, verdict: state, ...verdict, boards: group.boards };
18694
+ });
18695
+ },
18623
18696
  sync: (id2) => {
18624
18697
  if (store(id2).sync()) push2(id2);
18625
18698
  },
@@ -88750,15 +88823,15 @@ function readAnySelection(value) {
88750
88823
  function createSelectionStore(stateDir, exists) {
88751
88824
  const tabs = /* @__PURE__ */ new Map();
88752
88825
  let clock = 0;
88753
- const fileOf = (kind, id2) => import_node_path12.default.join(stateDir, kind, id2, "selection.json");
88826
+ const fileOf2 = (kind, id2) => import_node_path12.default.join(stateDir, kind, id2, "selection.json");
88754
88827
  const write = (kind, id2) => {
88755
88828
  let winner;
88756
88829
  for (const tab of tabs.values()) {
88757
88830
  if (tab.kind !== kind || tab.id !== id2) continue;
88758
88831
  if (!winner || tab.humanAt > winner.humanAt || tab.humanAt === winner.humanAt && tab.seenAt > winner.seenAt) winner = tab;
88759
88832
  }
88760
- if (!winner?.state) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88761
- else writeJsonAtomic(fileOf(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
88833
+ if (!winner?.state) import_node_fs12.default.rmSync(fileOf2(kind, id2), { force: true });
88834
+ else writeJsonAtomic(fileOf2(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
88762
88835
  };
88763
88836
  return {
88764
88837
  accept(socket, value) {
@@ -88792,7 +88865,7 @@ function createSelectionStore(stateDir, exists) {
88792
88865
  } catch {
88793
88866
  continue;
88794
88867
  }
88795
- for (const id2 of ids) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88868
+ for (const id2 of ids) import_node_fs12.default.rmSync(fileOf2(kind, id2), { force: true });
88796
88869
  }
88797
88870
  }
88798
88871
  };
@@ -90348,7 +90421,11 @@ async function startArtifactsServer(options) {
90348
90421
  sendJson(res, 400, { error: "not a comment op" });
90349
90422
  return;
90350
90423
  }
90351
- sendJson(res, 200, boardComments.apply(id2, op));
90424
+ try {
90425
+ sendJson(res, 200, boardComments.apply(id2, op));
90426
+ } catch (err) {
90427
+ sendJson(res, 400, { error: err.message });
90428
+ }
90352
90429
  };
90353
90430
  const serveAgentBoardComments = async (req, res, id2) => {
90354
90431
  const dir = resolveArtifactDir(root, "artboard", id2);
@@ -90360,7 +90437,8 @@ async function startArtifactsServer(options) {
90360
90437
  const fields = typeof body === "object" && body !== null ? body : {};
90361
90438
  const agent = typeof fields.agent === "string" && fields.agent.length > 0 && fields.agent.length <= 100 ? fields.agent : void 0;
90362
90439
  if (fields.op === "list") {
90363
- sendJson(res, 200, { type: "comments", artifact: `artboard/${id2}`, threads: boardComments.list(id2, agent ?? "Agent", fields.all === true) });
90440
+ const threads = boardComments.list(id2, agent ?? "Agent", fields.all === true);
90441
+ sendJson(res, 200, { type: "comments", artifact: `artboard/${id2}`, threads, groups: boardComments.groups(id2) });
90364
90442
  return;
90365
90443
  }
90366
90444
  const shorthand = agentAddOnBoard(body, boardSpecs(dir));
@@ -90967,7 +91045,7 @@ function openReady(payload) {
90967
91045
  }
90968
91046
 
90969
91047
  // package.json
90970
- var version = "1.16.2";
91048
+ var version = "1.16.3";
90971
91049
 
90972
91050
  // src/server/main.ts
90973
91051
  async function main() {
@@ -1 +1 @@
1
- import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{n as t,t as n}from"./jsx-runtime-CldxteV5.js";import{a as r}from"./useSandboxFrame-N5uRz7Cc.js";import{t as i}from"./index.js";import a from"./CloudDocument-DQ8BLzr7.js";import{DocumentView as o,t as s}from"./DocumentPage-CgCHoFVv.js";var c=e(t(),1),l=`1.16.2`,u=e=>/^(\d+)\.(\d+)\./.exec(e??``)?.slice(1,3).join(`.`)??null;function d(e,t){let n=u(e);return n!==null&&n===u(t)}var f=(e,t)=>`电脑上的 ryugi 是 ${t??`未知版本`},网页是 ${e},版本对上之前只能评论`,p=n();function m({id:e,state:t,contentBase:n,liveContentBase:u,comments:m}){let{transport:h,presence:g}=s(e,u??n??``),_=g?.online??!1;r(e,{online:g?.online??null});let[v,y]=(0,c.useState)(!1);if((0,c.useEffect)(()=>{_&&y(!0)},[_]),!h||!(v||_))return!t||!n?(0,p.jsx)(i,{children:g===null?`正在连接…`:`还没发布。电脑在线时,这里会实时显示。`}):(0,p.jsx)(a,{id:e,state:t,contentBase:n,comments:m,offline:g!==null&&!_});let b=_&&d(l,g?.version??null),x=_&&!b?f(l,g?.version??null):null;return(0,p.jsx)(o,{id:e,transport:h,live:_,cloud:{online:_,canEdit:b,reason:x}})}export{m as default};
1
+ import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{n as t,t as n}from"./jsx-runtime-CldxteV5.js";import{a as r}from"./useSandboxFrame-N5uRz7Cc.js";import{t as i}from"./index.js";import a from"./CloudDocument-DQ8BLzr7.js";import{DocumentView as o,t as s}from"./DocumentPage-CgCHoFVv.js";var c=e(t(),1),l=`1.16.3`,u=e=>/^(\d+)\.(\d+)\./.exec(e??``)?.slice(1,3).join(`.`)??null;function d(e,t){let n=u(e);return n!==null&&n===u(t)}var f=(e,t)=>`电脑上的 ryugi 是 ${t??`未知版本`},网页是 ${e},版本对上之前只能评论`,p=n();function m({id:e,state:t,contentBase:n,liveContentBase:u,comments:m}){let{transport:h,presence:g}=s(e,u??n??``),_=g?.online??!1;r(e,{online:g?.online??null});let[v,y]=(0,c.useState)(!1);if((0,c.useEffect)(()=>{_&&y(!0)},[_]),!h||!(v||_))return!t||!n?(0,p.jsx)(i,{children:g===null?`正在连接…`:`还没发布。电脑在线时,这里会实时显示。`}):(0,p.jsx)(a,{id:e,state:t,contentBase:n,comments:m,offline:g!==null&&!_});let b=_&&d(l,g?.version??null),x=_&&!b?f(l,g?.version??null):null;return(0,p.jsx)(o,{id:e,transport:h,live:_,cloud:{online:_,canEdit:b,reason:x}})}export{m as default};