@shiyuphan/follow-me 0.5.0 → 0.5.1

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 (95) hide show
  1. package/dist/src/cli.js +17 -3
  2. package/dist/src/comments.js +62 -9
  3. package/dist/src/evolve.js +145 -29
  4. package/dist/src/host.js +12 -3
  5. package/dist/src/plan.js +14 -0
  6. package/dist/src/prompt.js +100 -12
  7. package/dist/src/server.js +106 -29
  8. package/dist/src/session.js +23 -0
  9. package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-BXkLs7mV.js → abnfDiagram-WEYOIA3C-8UsLPznO.js} +1 -1
  10. package/dist/ui/dist/assets/{arc-DNzjw7Ie.js → arc-vovjGalz.js} +1 -1
  11. package/dist/ui/dist/assets/{architecture-WOLXFQ4H-D66kgJJt.js → architecture-WOLXFQ4H-BBMK-PUu.js} +1 -1
  12. package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-D8UWuhQr.js → architectureDiagram-ZHVYKT53-Bw_ehaQk.js} +1 -1
  13. package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-BLkmr62i.js → blockDiagram-RJAE7QA3-CAmD0wKf.js} +1 -1
  14. package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-3rZPYwOc.js → c4Diagram-XDPPPE54-DCP-3zgt.js} +1 -1
  15. package/dist/ui/dist/assets/channel-B7CT_oIp.js +1 -0
  16. package/dist/ui/dist/assets/{chunk-2E7WHVMD-MMnvAgpB.js → chunk-2E7WHVMD-BiHSeJMu.js} +1 -1
  17. package/dist/ui/dist/assets/{chunk-4EMNKR2T-D9FGWA3l.js → chunk-4EMNKR2T-CR3lHKf_.js} +1 -1
  18. package/dist/ui/dist/assets/{chunk-55XCU76K-DuOUaV8F.js → chunk-55XCU76K-CtaoloTT.js} +1 -1
  19. package/dist/ui/dist/assets/{chunk-7S6XSTFO-p3YWPv0m.js → chunk-7S6XSTFO-eABdc7fu.js} +1 -1
  20. package/dist/ui/dist/assets/{chunk-AQDTSKZB-DWfKYjwk.js → chunk-AQDTSKZB-CPlbJWi-.js} +1 -1
  21. package/dist/ui/dist/assets/{chunk-DSNLSHJZ-C3CIwpv0.js → chunk-DSNLSHJZ-YiMuxsb-.js} +1 -1
  22. package/dist/ui/dist/assets/{chunk-HAGFOTED-Bwfl9gme.js → chunk-HAGFOTED-1VcdL78a.js} +1 -1
  23. package/dist/ui/dist/assets/{chunk-HNE4Q36P-0_oeU0le.js → chunk-HNE4Q36P-u-sr_0Qu.js} +1 -1
  24. package/dist/ui/dist/assets/{chunk-NQOKRVIF-z_LPPPx5.js → chunk-NQOKRVIF-C_QlWSze.js} +1 -1
  25. package/dist/ui/dist/assets/{chunk-UHW7PFBP-BH28V2yu.js → chunk-UHW7PFBP-CNuZhsbU.js} +1 -1
  26. package/dist/ui/dist/assets/{chunk-VQ5W62QI-BXDajDf7.js → chunk-VQ5W62QI-DAHWbLqU.js} +1 -1
  27. package/dist/ui/dist/assets/{chunk-WQQL4CDK-BphBlfKj.js → chunk-WQQL4CDK-DWQnxFCd.js} +1 -1
  28. package/dist/ui/dist/assets/{chunk-XXDRQBXY-BiCT5NwV.js → chunk-XXDRQBXY-B0lGgSOR.js} +1 -1
  29. package/dist/ui/dist/assets/{chunk-YZO67HFG-Bg5Pu8BX.js → chunk-YZO67HFG-BDw7efkN.js} +1 -1
  30. package/dist/ui/dist/assets/{chunk-ZAUX2LTX-C_Tgd8tB.js → chunk-ZAUX2LTX-B5wfHsZv.js} +1 -1
  31. package/dist/ui/dist/assets/{chunk-ZCHMQSOR-MBRcMi8-.js → chunk-ZCHMQSOR--rWtYtAG.js} +1 -1
  32. package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-1wyeCe5y.js → classDiagram-v2-4MYWR3UA-BKuQ_xoS.js} +1 -1
  33. package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-BtOYclF6.js → cose-bilkent-JH36ORCC-Dk5fIpU0.js} +1 -1
  34. package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-dJmoOG8g.js → cynefin-EF2NZ3EQ-DIykJb2g.js} +1 -1
  35. package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-AloyqbEN.js → cynefinDiagram-WF24UDRP-Cg8tBqUb.js} +1 -1
  36. package/dist/ui/dist/assets/{dagre-NK4GIZVX-NCZhdnfb.js → dagre-NK4GIZVX-BC_tnnjz.js} +1 -1
  37. package/dist/ui/dist/assets/{diagram-CBK3MWTC-D8w7x5Gb.js → diagram-CBK3MWTC-B_MQGixU.js} +1 -1
  38. package/dist/ui/dist/assets/{diagram-I5XIIV2D-BUFnCGlp.js → diagram-I5XIIV2D-DO681pZ5.js} +1 -1
  39. package/dist/ui/dist/assets/{diagram-ODIL2UCW-BULmHKaW.js → diagram-ODIL2UCW-D14jLzU6.js} +1 -1
  40. package/dist/ui/dist/assets/{diagram-PFBEJUXE-B5MqFo0r.js → diagram-PFBEJUXE-cML48gUS.js} +1 -1
  41. package/dist/ui/dist/assets/{diagram-RJA3I4VV-BIkDLL8E.js → diagram-RJA3I4VV-DSoq8jFk.js} +1 -1
  42. package/dist/ui/dist/assets/{diagram-TYUNFSNR-Ca4gsQV8.js → diagram-TYUNFSNR-CaJ4pvnl.js} +1 -1
  43. package/dist/ui/dist/assets/{dist-G85JKY5k.js → dist-D3j-DODP.js} +1 -1
  44. package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-rVjcFFtb.js → ebnfDiagram-V4P3TTPT-CyRpjSix.js} +1 -1
  45. package/dist/ui/dist/assets/{elk-QONTBCTQ-BWPdvULh.js → elk-QONTBCTQ-ICJdzVFL.js} +1 -1
  46. package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-BPxJJkRl.js → erDiagram-KYQIGQAA-B57hNTfw.js} +1 -1
  47. package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BSB9WYV3.js → eventmodeling-K75KTNOO-BG8tUVIQ.js} +1 -1
  48. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU--HPNoVQJ.js +1 -0
  49. package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-DKps530K.js → ganttDiagram-R7GPNTJY-Czt6Klik.js} +1 -1
  50. package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-D1d4UAFV.js → gitGraph-VSP46ZUC-De_ZWJLj.js} +1 -1
  51. package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-N55yl6hc.js → gitGraphDiagram-PDYL7XHY-BMCJoOnT.js} +1 -1
  52. package/dist/ui/dist/assets/index-CYKbk4QS.js +134 -0
  53. package/dist/ui/dist/assets/{info-OHQRW6UA-CgxE3gf9.js → info-OHQRW6UA-D75H9kUq.js} +1 -1
  54. package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-DOnUrnMf.js → infoDiagram-MVDRKAX5-Bsm_I9dt.js} +1 -1
  55. package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-BIY0fFHB.js → ishikawaDiagram-HPEHV2GE-98xZAJ9y.js} +1 -1
  56. package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-CqA2Ern0.js → journeyDiagram-BOSHW7O7-BWVGuc0P.js} +1 -1
  57. package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-vGrpQKIF.js → kanban-definition-FNSA4HC5-DgUE_KDd.js} +1 -1
  58. package/dist/ui/dist/assets/{line-Brzv3qA6.js → line-i__8Rvas.js} +1 -1
  59. package/dist/ui/dist/assets/{linear-D9uIlqyh.js → linear-BXKj0Fip.js} +1 -1
  60. package/dist/ui/dist/assets/{mermaid-parser.core-Bz9b-Thu.js → mermaid-parser.core-DxvaorTa.js} +3 -3
  61. package/dist/ui/dist/assets/{mermaid.core-BaZipu3b.js → mermaid.core-ujYTsRZz.js} +4 -4
  62. package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-D6aie7uH.js → mindmap-definition-6OY57UG3-B7QD6sjk.js} +1 -1
  63. package/dist/ui/dist/assets/{packet-JDAUHWVQ-28r3jVFY.js → packet-JDAUHWVQ-B6aIwfD1.js} +1 -1
  64. package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-CbGvD4qP.js → pegDiagram-ECIUJNBX-_RStHj-a.js} +1 -1
  65. package/dist/ui/dist/assets/{pie-XZMESJXO-DHpcGnZd.js → pie-XZMESJXO-CNLC-pTe.js} +1 -1
  66. package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-DuhauwnY.js → pieDiagram-GNMPY74T-0uT7MT-u.js} +1 -1
  67. package/dist/ui/dist/assets/{prism-core-CbZMOvrb.js → prism-core-DtxziK1i.js} +1 -1
  68. package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-BfyNmVca.js → quadrantDiagram-D57K4MC3-CTMKb5hl.js} +1 -1
  69. package/dist/ui/dist/assets/{radar-ABXABTNO-Xp1TYwaj.js → radar-ABXABTNO-D53h6I0N.js} +1 -1
  70. package/dist/ui/dist/assets/{railroad-I3PHUGI6-c5FY4wwa.js → railroad-I3PHUGI6-CwjhW4mc.js} +1 -1
  71. package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-RDyWXqcL.js → railroad-abnf-LNEFI6M7-DDbQ3RmB.js} +1 -1
  72. package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-CLD4jVou.js → railroad-ebnf-DWYD2UWJ-Am7YJAqO.js} +1 -1
  73. package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-17EYTA-w.js → railroad-peg-JR7BVNR7-Ci32KoVW.js} +1 -1
  74. package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-C4p2wtKe.js → railroadDiagram-Y72NRBL7-BPU5A6XW.js} +1 -1
  75. package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-DTYIFN8T.js → requirementDiagram-EQYSW5RA-_0Pq4QdV.js} +1 -1
  76. package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-YDYhRyJU.js → sankeyDiagram-SY24JHO5-c9h-7uf8.js} +1 -1
  77. package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-Crvy6IlU.js → sequenceDiagram-BLEHNHQU-B9-S4Rc9.js} +1 -1
  78. package/dist/ui/dist/assets/{src-BPod-Ype.js → src-BZ3kG7lO.js} +1 -1
  79. package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-BWr70YOQ.js → stateDiagram-v2-A6IESKWZ-py1smSen.js} +1 -1
  80. package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-DvbG4I9a.js → swimlanes-B3ENRKFQ-C1E90aH4.js} +1 -1
  81. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-I98Q8AqP.js +8 -0
  82. package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-CR950Bzb.js → timeline-definition-JXYKGLGB-DNe1To6B.js} +1 -1
  83. package/dist/ui/dist/assets/{treeView-D4JQ5SDB-DSDDXGrk.js → treeView-D4JQ5SDB-D6MG89i6.js} +1 -1
  84. package/dist/ui/dist/assets/{treemap-SAJKECNS-Ct5w2_RR.js → treemap-SAJKECNS-CZpt7Mlh.js} +1 -1
  85. package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-_eP0371y.js → usecaseDiagram-WPOLBNNX-CKsv-03M.js} +1 -1
  86. package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-Cu-IA57L.js → vennDiagram-Q3Y3IDCW-DGJ1qlyJ.js} +1 -1
  87. package/dist/ui/dist/assets/{wardley-7MLQ67FV-r8yd6frN.js → wardley-7MLQ67FV-ClOzBemC.js} +1 -1
  88. package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-CBUUiRQm.js → wardleyDiagram-IVAIDKWA-C68ahPyF.js} +1 -1
  89. package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-DEVQjvgG.js → xychartDiagram-4URNHDU4-DWDamCoH.js} +1 -1
  90. package/dist/ui/dist/index.html +1 -1
  91. package/package.json +1 -1
  92. package/dist/ui/dist/assets/channel-B9JVdOBZ.js +0 -1
  93. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-N-Spyk2o.js +0 -1
  94. package/dist/ui/dist/assets/index-BKtWBS7Z.js +0 -134
  95. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-D2wU-jW6.js +0 -8
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
5
5
  import { basename, isAbsolute, join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { isReplyId, parseComment, parseDraft, parseNewDecision, parseNewReply, } from "./comments.js";
8
+ import { carryConcerns, carrySession } from "./evolve.js";
8
9
  import { isConcernId, isPlanVersion } from "./plan.js";
9
10
  import { renderReport, reportItems } from "./prompt.js";
10
11
  import { isLockToken } from "./session.js";
@@ -88,6 +89,8 @@ export async function loadDraft(reviewsDir, version) {
88
89
  resolved: [],
89
90
  excluded: { comments: [], concerns: [] },
90
91
  earlierReplies: [],
92
+ carried: null,
93
+ carriedConcerns: [],
91
94
  };
92
95
  }
93
96
  throw error;
@@ -236,7 +239,7 @@ async function readUi(dir, name) {
236
239
  }
237
240
  const NOT_BUILT = `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="color-scheme" content="light dark" /><title>follow-me</title></head><body style="font-family: system-ui; max-width: 40rem; margin: 4rem auto; padding: 0 1rem"><h1>The review page is not built</h1><p>This follow-me runs from a checkout without <code>ui/dist</code>. Run <code>npm run build</code> (or <code>npm run ui:build</code>) in the checkout, then reload.</p></body></html>
238
241
  `;
239
- function viewOf(registration, loaded, draft) {
242
+ function viewOf(registration, loaded, draft, earlier = loaded.earlier) {
240
243
  const { state, reviewsDir } = loaded;
241
244
  return {
242
245
  registration,
@@ -247,6 +250,8 @@ function viewOf(registration, loaded, draft) {
247
250
  excludedComments: new Set(draft.excluded.comments),
248
251
  excludedConcerns: new Set(draft.excluded.concerns),
249
252
  earlierReplies: new Map(draft.earlierReplies.map((e) => [e.comment, e.replies])),
253
+ earlier: earlier === null ? null : earlier.comments,
254
+ earlierConcerns: earlier === null ? [] : earlier.concerns,
250
255
  unitIds: new Set(state.units.map((u) => u.id)),
251
256
  hunkRows: new Map(state.units.flatMap((u) => u.kind === "hunk" ? [[u.id, u.body.length]] : [])),
252
257
  concernIds: new Set(state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id))),
@@ -258,7 +263,7 @@ function withReplies(comment, replies) {
258
263
  const { replies: _dropped, ...rest } = comment;
259
264
  return replies === undefined || replies.length === 0 ? rest : { ...rest, replies };
260
265
  }
261
- /** A comment of the previous version with the replies it got during this one. */
266
+ /** A comment of an earlier version with the replies it got during this one. */
262
267
  function withEarlier(view, comment) {
263
268
  const later = view.earlierReplies.get(comment.id) ?? [];
264
269
  return later.length === 0
@@ -271,8 +276,39 @@ function previousOf(view) {
271
276
  return null;
272
277
  return { ...previous, comments: previous.comments.map((c) => withEarlier(view, c)) };
273
278
  }
279
+ /** The session's earlier comments with this version's replies, or null outside a session. */
280
+ function sessionOf(view) {
281
+ return view.earlier === null
282
+ ? null
283
+ : view.earlier.map((c) => ({ ...c, comment: withEarlier(view, c.comment) }));
284
+ }
285
+ /**
286
+ * What the page shows carried: the session's earlier comments, then those of the review before
287
+ * this version that the session does not hold (a review that ended before this one began).
288
+ */
274
289
  function carriedOf(view) {
275
- return view.loaded.carried.map((c) => ({ ...c, comment: withEarlier(view, c.comment) }));
290
+ const session = sessionOf(view) ?? [];
291
+ const held = new Set(session.map((c) => c.comment.id));
292
+ const before = view.loaded.carried
293
+ .filter((c) => !held.has(c.comment.id))
294
+ .map((c) => ({ ...c, comment: withEarlier(view, c.comment) }));
295
+ return [...session, ...before];
296
+ }
297
+ /** The session as stored beside this version: comments by id, concerns whole. */
298
+ function carriedRefs(view) {
299
+ if (view.earlier === null)
300
+ return {};
301
+ const carried = view.earlier.map(({ comment, ...at }) => ({ ...at, comment: comment.id }));
302
+ const concerns = view.earlierConcerns;
303
+ return { carried, ...(concerns.length === 0 ? {} : { carriedConcerns: [...concerns] }) };
304
+ }
305
+ /** A comment said on this version or carried into it, which replies and decisions may name. */
306
+ function findSaid(view, id) {
307
+ if (typeof id !== "string")
308
+ return undefined;
309
+ return (view.comments.get(id) ??
310
+ view.loaded.previous?.comments.find((c) => c.id === id) ??
311
+ view.earlier?.find((c) => c.comment.id === id)?.comment);
276
312
  }
277
313
  function earlierOf(view) {
278
314
  return [...view.earlierReplies].map(([comment, replies]) => ({ comment, replies }));
@@ -382,6 +418,7 @@ function hostSession(id, registration, loaded, draft, load) {
382
418
  ...(resolved.length === 0 ? {} : { resolved }),
383
419
  ...(excluded === null ? {} : { excluded }),
384
420
  ...(earlierReplies.length === 0 ? {} : { earlierReplies }),
421
+ ...carriedRefs(view),
385
422
  }, null, 2);
386
423
  const dir = view.loaded.reviewsDir;
387
424
  writes = writes.then(async () => {
@@ -428,16 +465,19 @@ function hostSession(id, registration, loaded, draft, load) {
428
465
  ...(resolved.length === 0 ? {} : { resolved }),
429
466
  ...(excluded === null ? {} : { excluded }),
430
467
  ...(earlierReplies.length === 0 ? {} : { earlierReplies }),
468
+ ...carriedRefs(view),
431
469
  };
432
470
  };
433
- const report = (finished) => {
434
- const { state } = view.loaded;
435
- const units = new Map(state.units.map((u) => [u.id, u]));
436
- return renderReport(finished, state.commits, units, {
437
- branch: state.anchor.branch,
471
+ const reportOptions = () => {
472
+ const comments = sessionOf(view);
473
+ return {
474
+ branch: view.loaded.state.anchor.branch,
438
475
  responses: [],
439
- });
476
+ ...(comments === null ? {} : { earlier: { comments, concerns: view.earlierConcerns } }),
477
+ };
440
478
  };
479
+ const unitsOf = () => new Map(view.loaded.state.units.map((u) => [u.id, u]));
480
+ const report = (finished) => renderReport(finished, view.loaded.state.commits, unitsOf(), reportOptions());
441
481
  /** Writes reviews/vN.json and its report once, and removes the autosave. */
442
482
  const writeReview = async (finished) => {
443
483
  await writes;
@@ -504,19 +544,12 @@ function hostSession(id, registration, loaded, draft, load) {
504
544
  return waitForEvents(since, timeoutMs, signal);
505
545
  },
506
546
  report: () => report(review()),
507
- reportItems: () => {
508
- const { state } = view.loaded;
509
- const units = new Map(state.units.map((u) => [u.id, u]));
510
- return reportItems(review(), state.commits, units, {
511
- branch: state.anchor.branch,
512
- responses: [],
513
- });
514
- },
547
+ reportItems: () => reportItems(review(), view.loaded.state.commits, unitsOf(), reportOptions()),
515
548
  add: async (raw) => {
516
549
  open();
517
550
  if (!isRecord(raw))
518
551
  throw new HttpError(400, "malformed comment");
519
- const commentId = mintCommentId(new Set(view.comments.keys()));
552
+ const commentId = mintCommentId(new Set([...view.comments.keys(), ...(view.earlier ?? []).map((c) => c.comment.id)]));
520
553
  const comment = withReplies(accept(parseComment({ ...raw, id: commentId, createdAt: new Date().toISOString() })), undefined);
521
554
  view.comments.set(commentId, comment);
522
555
  emit({ type: "comment", comment });
@@ -545,18 +578,22 @@ function hostSession(id, registration, loaded, draft, load) {
545
578
  emit({ type: "comment-deleted", id: gone.id });
546
579
  await saveDraft();
547
580
  },
548
- /** Replies under a comment of this version, or of the previous one, as after a swap. */
581
+ /** Replies under a comment of this version, or of an earlier one, as after a swap. */
549
582
  reply: async (target, raw) => {
550
583
  open();
551
584
  const comment = view.comments.get(target);
552
- const earlier = view.loaded.previous?.comments.find((c) => c.id === target);
553
- const owner = comment ?? earlier;
585
+ const owner = findSaid(view, target);
586
+ const earlier = comment === undefined ? owner : undefined;
554
587
  if (owner === undefined)
555
588
  throw new HttpError(404, "no such comment");
556
589
  const parsed = parseNewReply(raw);
557
590
  if (typeof parsed === "string")
558
591
  throw new HttpError(400, `malformed reply: ${parsed}`);
559
- const taken = new Set([...view.comments.values(), ...(previousOf(view)?.comments ?? [])]
592
+ const taken = new Set([
593
+ ...view.comments.values(),
594
+ ...(previousOf(view)?.comments ?? []),
595
+ ...carriedOf(view).map((c) => c.comment),
596
+ ]
560
597
  .flatMap((c) => c.replies ?? [])
561
598
  .map((r) => r.id));
562
599
  const reply = {
@@ -599,16 +636,17 @@ function hostSession(id, registration, loaded, draft, load) {
599
636
  throw new HttpError(400, "expected { comment } or { concern }, and { excluded: boolean }");
600
637
  }
601
638
  if (comment !== undefined) {
602
- if (!isCommentId(comment) || !view.comments.has(comment)) {
639
+ const said = (id) => view.comments.has(id) || (view.earlier ?? []).some((c) => c.comment.id === id);
640
+ if (!isCommentId(comment) || !said(comment))
603
641
  throw new HttpError(404, "no such comment");
604
- }
605
642
  if (excluded)
606
643
  view.excludedComments.add(comment);
607
644
  else
608
645
  view.excludedComments.delete(comment);
609
646
  }
610
647
  else {
611
- if (!isConcernId(concern) || !view.concernIds.has(concern)) {
648
+ const carried = view.earlierConcerns.some((c) => c.concern.id === concern);
649
+ if (!isConcernId(concern) || !(view.concernIds.has(concern) || carried)) {
612
650
  throw new HttpError(404, "no such concern in this plan");
613
651
  }
614
652
  if (excluded)
@@ -632,9 +670,9 @@ function hostSession(id, registration, loaded, draft, load) {
632
670
  open();
633
671
  const body = isRecord(raw) ? raw : {};
634
672
  const target = body["comment"];
635
- const asked = view.loaded.previous?.comments.find((c) => c.id === target);
673
+ const asked = view.comments.has(String(target)) ? undefined : findSaid(view, target);
636
674
  if (asked === undefined || typeof body["resolved"] !== "boolean") {
637
- throw new HttpError(400, "expected { comment: <id of a question of the previous version>, resolved }");
675
+ throw new HttpError(400, "expected { comment: <id of a question of an earlier version>, resolved }");
638
676
  }
639
677
  if (body["resolved"])
640
678
  view.resolved.add(asked.id);
@@ -647,7 +685,9 @@ function hostSession(id, registration, loaded, draft, load) {
647
685
  /**
648
686
  * Moves the session to a newer frozen plan under the same id and URL. The new version loads
649
687
  * first, so a plan that no longer fits leaves the old one untouched; then the old version's
650
- * review is finalized if anything was said, and the comment set starts fresh.
688
+ * review is finalized if anything was said, and the comment set starts fresh. Everything
689
+ * said so far is carried onto the new diff and saved with the new draft at once, so a restart
690
+ * keeps it; decisions, resolutions and exclusions go on applying to what they named.
651
691
  */
652
692
  swap: async (version, hash) => {
653
693
  open();
@@ -664,11 +704,31 @@ function hostSession(id, registration, loaded, draft, load) {
664
704
  try {
665
705
  await writes;
666
706
  const next = await load({ ...view.registration, version, hash, lockToken: null }, view.loaded.release);
707
+ const earlier = continued(view, next);
667
708
  if (hasWork())
668
709
  await writeReview(review());
669
710
  else
670
711
  await rm(view.draftFile, { force: true });
671
- view = viewOf({ ...view.registration, version, hash }, next, await loadDraft(next.reviewsDir, version));
712
+ const nextIds = new Set(next.state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id)));
713
+ const stillNamed = (id) => nextIds.has(id) || earlier.concerns.some((c) => c.concern.id === id);
714
+ const draft = await loadDraft(next.reviewsDir, version);
715
+ view = viewOf({ ...view.registration, version, hash }, next, {
716
+ ...draft,
717
+ concerns: [...view.decisions.values()].filter((d) => nextIds.has(d.concern)),
718
+ resolved: [...view.resolved],
719
+ excluded: {
720
+ comments: [...view.excludedComments],
721
+ concerns: [...view.excludedConcerns].filter(stillNamed),
722
+ },
723
+ }, earlier);
724
+ const carrying = earlier.comments.length +
725
+ earlier.concerns.length +
726
+ view.decisions.size +
727
+ view.resolved.size +
728
+ view.excludedComments.size +
729
+ view.excludedConcerns.size;
730
+ if (carrying > 0)
731
+ await saveDraft();
672
732
  emit({ type: "version", version, hash, carried: carriedOf(view) });
673
733
  }
674
734
  finally {
@@ -692,6 +752,23 @@ function hostSession(id, registration, loaded, draft, load) {
692
752
  },
693
753
  };
694
754
  }
755
+ /**
756
+ * What the session carries from the open view into the next version: what it carried already and
757
+ * what was said on this version, and the kept concerns the next plan lost, all on the next diff.
758
+ */
759
+ function continued(view, next) {
760
+ const { state } = view.loaded;
761
+ const oldUnits = new Map(state.units.map((u) => [u.id, u]));
762
+ const newUnits = new Map(next.state.units.map((u) => [u.id, u]));
763
+ const comments = carrySession(sessionOf(view) ?? [], [...view.comments.values()], state.version, oldUnits, newUnits);
764
+ const kept = state.commits.flatMap((commit, k) => (commit.concerns ?? []).flatMap((concern) => {
765
+ const decision = view.decisions.get(concern.id);
766
+ return decision?.decision === "kept" ? [{ concern, commit: k, decision }] : [];
767
+ }));
768
+ const inNext = new Set(next.state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id)));
769
+ const concerns = carryConcerns(view.earlierConcerns, kept, inNext, state.version, oldUnits, newUnits);
770
+ return { comments, concerns };
771
+ }
695
772
  function sameRegistration(a, b) {
696
773
  return a.dir === b.dir && a.mode === b.mode && a.version === b.version && a.hash === b.hash;
697
774
  }
@@ -449,6 +449,29 @@ export async function sinceLastPlan(session) {
449
449
  const oldDiff = await loadDiff(session.root, latest.file.base, latest.file.tip);
450
450
  return { since: latest.version, ...evolvePlan(latest.file, oldDiff, session.diff) };
451
451
  }
452
+ /**
453
+ * Pure. The comments a review's `carried` names, whole, from the reviews that hold them: each
454
+ * from the review of its version, its thread completed with the replies later versions stored
455
+ * as earlierReplies. `reviews` are the session's reviews up to the one asking; a ref whose review
456
+ * is missing is skipped, so a damaged session still reports the rest.
457
+ */
458
+ export function sessionComments(refs, reviews) {
459
+ return refs.flatMap(({ comment: id, ...at }) => {
460
+ const comment = reviews
461
+ .find((r) => r.planVersion === at.version)
462
+ ?.comments.find((c) => c.id === id);
463
+ if (comment === undefined)
464
+ return [];
465
+ const later = reviews
466
+ .filter((r) => r.planVersion > at.version)
467
+ .sort((a, b) => a.planVersion - b.planVersion)
468
+ .flatMap((r) => (r.earlierReplies ?? []).filter((e) => e.comment === id))
469
+ .flatMap((e) => e.replies);
470
+ const seen = new Set((comment.replies ?? []).map((r) => r.id));
471
+ const replies = [...(comment.replies ?? []), ...later.filter((r) => !seen.has(r.id))];
472
+ return [{ ...at, comment: replies.length === 0 ? comment : { ...comment, replies } }];
473
+ });
474
+ }
452
475
  /** "<name>.<pid>.<random>.tmp" as written by writeJson, the lock and the review server. */
453
476
  function abandonedTemps(dir) {
454
477
  if (!existsSync(dir))
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-Bz9b-Thu.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BPod-Ype.js";import"./chunk-DSNLSHJZ-C3CIwpv0.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-MMnvAgpB.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-BaZipu3b.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
1
+ import{m as e,t}from"./mermaid-parser.core-DxvaorTa.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BZ3kG7lO.js";import"./chunk-DSNLSHJZ-YiMuxsb-.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-BiHSeJMu.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-ujYTsRZz.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
@@ -1 +1 @@
1
- import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-G85JKY5k.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-D3j-DODP.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -1 +1 @@
1
- import"./chunk-NGNAAXSQ-C2Wy9Xmz.js";export{M as createArchitectureServices}from"./mermaid-parser.core-Bz9b-Thu.js";
1
+ import"./chunk-NGNAAXSQ-C2Wy9Xmz.js";export{M as createArchitectureServices}from"./mermaid-parser.core-DxvaorTa.js";