@flanksource/clicky-ui 0.3.31 → 0.3.33

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 (81) hide show
  1. package/dist/comments/CommentCard.cjs +79 -44
  2. package/dist/comments/CommentCard.cjs.map +1 -1
  3. package/dist/comments/CommentCard.d.ts +2 -0
  4. package/dist/comments/CommentCard.d.ts.map +1 -1
  5. package/dist/comments/CommentCard.js +81 -46
  6. package/dist/comments/CommentCard.js.map +1 -1
  7. package/dist/comments/CommentCardParts.cjs +20 -0
  8. package/dist/comments/CommentCardParts.cjs.map +1 -1
  9. package/dist/comments/CommentCardParts.d.ts +4 -0
  10. package/dist/comments/CommentCardParts.d.ts.map +1 -1
  11. package/dist/comments/CommentCardParts.js +22 -2
  12. package/dist/comments/CommentCardParts.js.map +1 -1
  13. package/dist/comments/CommentProvider.cjs +3 -0
  14. package/dist/comments/CommentProvider.cjs.map +1 -1
  15. package/dist/comments/CommentProvider.d.ts +1 -1
  16. package/dist/comments/CommentProvider.d.ts.map +1 -1
  17. package/dist/comments/CommentProvider.js +3 -0
  18. package/dist/comments/CommentProvider.js.map +1 -1
  19. package/dist/comments/CommentSidePanel.cjs +43 -17
  20. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  21. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  22. package/dist/comments/CommentSidePanel.js +44 -18
  23. package/dist/comments/CommentSidePanel.js.map +1 -1
  24. package/dist/comments/CommentThreadList.cjs +2 -0
  25. package/dist/comments/CommentThreadList.cjs.map +1 -1
  26. package/dist/comments/CommentThreadList.d.ts +3 -2
  27. package/dist/comments/CommentThreadList.d.ts.map +1 -1
  28. package/dist/comments/CommentThreadList.js +2 -0
  29. package/dist/comments/CommentThreadList.js.map +1 -1
  30. package/dist/comments/comment-types.cjs +16 -4
  31. package/dist/comments/comment-types.cjs.map +1 -1
  32. package/dist/comments/comment-types.d.ts +11 -1
  33. package/dist/comments/comment-types.d.ts.map +1 -1
  34. package/dist/comments/comment-types.js +16 -4
  35. package/dist/comments/comment-types.js.map +1 -1
  36. package/dist/comments/comment-utils.cjs +31 -1
  37. package/dist/comments/comment-utils.cjs.map +1 -1
  38. package/dist/comments/comment-utils.d.ts +9 -2
  39. package/dist/comments/comment-utils.d.ts.map +1 -1
  40. package/dist/comments/comment-utils.js +31 -1
  41. package/dist/comments/comment-utils.js.map +1 -1
  42. package/dist/comments.cjs +3 -0
  43. package/dist/comments.cjs.map +1 -1
  44. package/dist/comments.js +4 -1
  45. package/dist/components/EndpointSelector.cjs +6 -6
  46. package/dist/components/EndpointSelector.cjs.map +1 -1
  47. package/dist/components/EndpointSelector.d.ts.map +1 -1
  48. package/dist/components/EndpointSelector.js +6 -6
  49. package/dist/components/EndpointSelector.js.map +1 -1
  50. package/dist/components/ErrorWrapper.cjs +3 -2
  51. package/dist/components/ErrorWrapper.cjs.map +1 -1
  52. package/dist/components/ErrorWrapper.js +3 -2
  53. package/dist/components/ErrorWrapper.js.map +1 -1
  54. package/dist/components/RangeSlider.cjs +108 -45
  55. package/dist/components/RangeSlider.cjs.map +1 -1
  56. package/dist/components/RangeSlider.d.ts +3 -1
  57. package/dist/components/RangeSlider.d.ts.map +1 -1
  58. package/dist/components/RangeSlider.js +108 -45
  59. package/dist/components/RangeSlider.js.map +1 -1
  60. package/dist/components/SecretKeySelector.cjs +4 -4
  61. package/dist/components/SecretKeySelector.cjs.map +1 -1
  62. package/dist/components/SecretKeySelector.js +4 -4
  63. package/dist/components/SecretKeySelector.js.map +1 -1
  64. package/dist/data/test-runner/status.cjs +1 -1
  65. package/dist/data/test-runner/status.cjs.map +1 -1
  66. package/dist/data/test-runner/status.js +1 -1
  67. package/dist/data/test-runner/status.js.map +1 -1
  68. package/dist/data/test-runner/types.d.ts +6 -0
  69. package/dist/data/test-runner/types.d.ts.map +1 -1
  70. package/dist/data/verification/verifyReportTests.cjs +1 -0
  71. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  72. package/dist/data/verification/verifyReportTests.d.ts.map +1 -1
  73. package/dist/data/verification/verifyReportTests.js +1 -0
  74. package/dist/data/verification/verifyReportTests.js.map +1 -1
  75. package/dist/data/version-info.cjs +3 -3
  76. package/dist/data/version-info.js +3 -3
  77. package/dist/index.cjs +3 -0
  78. package/dist/index.cjs.map +1 -1
  79. package/dist/index.js +4 -1
  80. package/dist/styles.css +1 -1
  81. package/package.json +1 -1
@@ -1,14 +1,26 @@
1
1
  const DOCUMENT_ANCHOR = "__document__";
2
2
  const DEFAULT_COMMENT_STATUSES = [
3
- { value: "open", label: "Open", tone: "info", unresolved: true },
3
+ {
4
+ value: "open",
5
+ label: "Open",
6
+ tone: "info",
7
+ unresolved: true,
8
+ stage: "active"
9
+ },
4
10
  {
5
11
  value: "in_progress",
6
12
  label: "In progress",
7
13
  tone: "warning",
8
- unresolved: true
14
+ unresolved: true,
15
+ stage: "active"
16
+ },
17
+ {
18
+ value: "resolved",
19
+ label: "Resolved",
20
+ tone: "success",
21
+ stage: "resolved"
9
22
  },
10
- { value: "resolved", label: "Resolved", tone: "success" },
11
- { value: "closed", label: "Closed", tone: "neutral" }
23
+ { value: "closed", label: "Closed", tone: "neutral", stage: "closed" }
12
24
  ];
13
25
  const DEFAULT_CHECKLIST_CYCLE = ["open", "in_progress", "done"];
14
26
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"comment-types.js","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n { value: \"open\", label: \"Open\", tone: \"info\", unresolved: true },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n },\n { value: \"resolved\", label: \"Resolved\", tone: \"success\" },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":"AAyGO,MAAM,kBAAkB;AAiGxB,MAAM,2BAAkD;AAAA,EAC7D,EAAE,OAAO,QAAQ,OAAO,QAAQ,MAAM,QAAQ,YAAY,KAAA;AAAA,EAC1D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,EAAA;AAAA,EAEd,EAAE,OAAO,YAAY,OAAO,YAAY,MAAM,UAAA;AAAA,EAC9C,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,UAAA;AAC5C;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;"}
1
+ {"version":3,"file":"comment-types.js","sources":["../../src/comments/comment-types.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { StaticIconComponent } from \"../data/Icon\";\n\n/** Semantic tone driving a status/facet color (maps onto Badge tones). */\nexport type CommentTone =\n | \"default\"\n | \"info\"\n | \"success\"\n | \"warning\"\n | \"danger\"\n | \"neutral\";\n\n/** Product lifecycle stage represented by one or more stored statuses. */\nexport type CommentStatusStage = \"active\" | \"resolved\" | \"closed\";\n\n/** A single selectable status (e.g. \"open\", \"resolved\"). Consumer-defined. */\nexport type CommentStatusConfig = {\n /** Stable value stored on a comment's `status`. */\n value: string;\n /** Human label shown in menus and badges. */\n label: string;\n /** Color tone for the status chip. */\n tone?: CommentTone;\n /** Optional leading glyph. */\n icon?: string | StaticIconComponent;\n /** Lifecycle stage used by review queues and status actions. */\n stage?: CommentStatusStage;\n /**\n * Whether this status counts as \"unresolved/open\" — drives progress tallies\n * and which groups start expanded. Defaults to false.\n */\n unresolved?: boolean;\n};\n\n/** One option of a generic facet (e.g. a \"category\" value). */\nexport type CommentFacetOption = {\n value: string;\n label: string;\n tone?: CommentTone;\n /** Compact glyph for the collapsed card (e.g. \"BS\" for a category). */\n short?: string;\n};\n\n/**\n * A generic, consumer-supplied classification axis. Replaces the financial\n * category/severity vocabulary — any app declares its own facets.\n */\nexport type CommentFacet = {\n /** Key into `Comment.facets`. */\n key: string;\n label: string;\n options: CommentFacetOption[];\n /** Allow several values to be selected at once in the filter bar. */\n multiple?: boolean;\n};\n\nexport type CommentMentionKind = \"user\" | \"agent\";\n\n/** A person or agent that can be @-mentioned in the composer. */\nexport type CommentMentionable = {\n id: string;\n name: string;\n /** Image URL for `kind: \"user\"`. */\n avatar?: string;\n kind: CommentMentionKind;\n /** Glyph override for `kind: \"agent\"`. */\n icon?: string | StaticIconComponent;\n};\n\n/** A resolved mention emitted when a comment referencing a mentionable is posted. */\nexport type CommentMention = {\n id: string;\n name: string;\n kind: CommentMentionKind;\n};\n\n/** A labelled group of reference chips (e.g. linked ids). */\nexport type CommentRefGroup = {\n label: string;\n items: string[];\n /** Render items in a monospace font. */\n mono?: boolean;\n};\n\n/** One item of a generic checklist; `status` cycles through `checklistStatusCycle`. */\nexport type CommentChecklistItem = {\n label: string;\n status: string;\n};\n\nexport type CommentAuthor = {\n id?: string;\n name: string;\n kind?: CommentMentionKind;\n avatar?: string;\n /** Glyph override for `kind: \"agent\"` authors. */\n icon?: string | StaticIconComponent;\n};\n\n/**\n * An opaque anchor key locating a comment within a document. Not a JSON path —\n * any stable string a consumer registers via the provider. `null`/absent means\n * document-level.\n */\nexport type CommentAnchor = string;\n\n/** Binary review signal attached to a root comment. */\nexport type CommentRating = \"positive\" | \"negative\";\n\n/** Anchor key reserved for document-level (un-anchored) comments. */\nexport const DOCUMENT_ANCHOR = \"__document__\";\n\n/** A single comment. Replies set `parentId`; roots leave it null. */\nexport type Comment = {\n id: string;\n body: string;\n /** ISO-8601 creation time. */\n createdAt: string;\n /** ISO-8601 last-update time. */\n updatedAt?: string;\n author: CommentAuthor | null;\n /** Key into the configured statuses; roots only. */\n status?: string;\n /** Parent comment id when this is a reply. */\n parentId?: string | null;\n /** Where the comment is anchored; null/absent = document-level. */\n anchor?: CommentAnchor | null;\n /** Optional positive/negative review signal; roots only. */\n rating?: CommentRating;\n /** ISO-8601 time when a human closed the resolved comment. */\n closedAt?: string;\n /** Human who closed the comment. */\n closedBy?: CommentAuthor;\n /** facet.key → selected option value. */\n facets?: Record<string, string>;\n refs?: CommentRefGroup[];\n checklist?: CommentChecklistItem[];\n};\n\n/** Payload for creating a new root comment. */\nexport type CommentCreateInput = {\n body: string;\n anchor?: CommentAnchor | null;\n rating?: CommentRating;\n facets?: Record<string, string>;\n mentions?: CommentMention[];\n};\n\n/** Payload for replying to a comment. */\nexport type CommentReplyInput = {\n parentId: string;\n body: string;\n anchor?: CommentAnchor | null;\n mentions?: CommentMention[];\n};\n\n/**\n * Controlled callbacks. The components own no data fetching; the consumer\n * performs the mutation and feeds the updated `comments` back in.\n */\nexport type CommentCallbacks = {\n onCreate?: (input: CommentCreateInput) => void | Promise<void>;\n onReply?: (input: CommentReplyInput) => void | Promise<void>;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n /** Human-only transition from resolved to closed. */\n onClose?: (id: string) => void | Promise<void>;\n onUpdateRating?: (id: string, rating: CommentRating) => void | Promise<void>;\n onDelete?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (\n id: string,\n index: number,\n next: string,\n ) => void | Promise<void>;\n /** Fired when a posted comment references a mentionable. */\n onMention?: (mention: CommentMention, context: { body: string }) => void;\n};\n\n/** Static configuration shared across the comment components. */\nexport type CommentConfig = {\n /** Selectable statuses; the first `unresolved` one is the default for new comments. */\n statuses: CommentStatusConfig[];\n /** Classification axes (category/severity/etc.). */\n facets?: CommentFacet[];\n /** People/agents available for @-mention. */\n mentionables?: CommentMentionable[];\n /** Status values a checklist item cycles through on click. */\n checklistStatusCycle?: string[];\n};\n\n/** Aggregate metadata for one anchor, derived from the comment list. */\nexport type CommentAnchorMeta = {\n count: number;\n authors: string[];\n latestStatus?: string;\n};\n\n/** Selected filter state for the filter bar / `applyCommentFilters`. */\nexport type CommentFilters = {\n /** Selected status values; empty = all statuses. */\n statuses: Set<string>;\n /** facet.key → selected option values; empty set = all values for that facet. */\n facets: Record<string, Set<string>>;\n /** Restrict to an author kind, or \"all\". */\n authorKind?: CommentMentionKind | \"all\";\n};\n\n/** Optional renderers passed down to a card. */\nexport type CommentRenderOptions = {\n /** Custom comment-body renderer (e.g. full Markdown). Defaults to CommentMarkdown. */\n renderBody?: (body: string) => ReactNode;\n};\n\n/** A domain-free default status set: open / in progress / resolved / closed. */\nexport const DEFAULT_COMMENT_STATUSES: CommentStatusConfig[] = [\n {\n value: \"open\",\n label: \"Open\",\n tone: \"info\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"in_progress\",\n label: \"In progress\",\n tone: \"warning\",\n unresolved: true,\n stage: \"active\",\n },\n {\n value: \"resolved\",\n label: \"Resolved\",\n tone: \"success\",\n stage: \"resolved\",\n },\n { value: \"closed\", label: \"Closed\", tone: \"neutral\", stage: \"closed\" },\n];\n\n/** Default checklist status cycle used when a consumer omits one. */\nexport const DEFAULT_CHECKLIST_CYCLE = [\"open\", \"in_progress\", \"done\"];\n"],"names":[],"mappings":"AA8GO,MAAM,kBAAkB;AAuGxB,MAAM,2BAAkD;AAAA,EAC7D;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAET;AAAA,IACE,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,WAAW,OAAO,SAAA;AAC9D;AAGO,MAAM,0BAA0B,CAAC,QAAQ,eAAe,MAAM;"}
@@ -38,6 +38,22 @@ function isUnresolved(config, status) {
38
38
  var _a;
39
39
  return ((_a = resolveStatusConfig(config, status)) == null ? void 0 : _a.unresolved) ?? false;
40
40
  }
41
+ function resolveCommentStage(config, status) {
42
+ if (status == null) return "active";
43
+ const resolved = resolveStatusConfig(config, status);
44
+ if (!resolved) return void 0;
45
+ if (resolved.stage) return resolved.stage;
46
+ if (resolved.unresolved) return "active";
47
+ if (resolved.value.toLowerCase() === "closed" || resolved.label.toLowerCase() === "closed") {
48
+ return "closed";
49
+ }
50
+ return "resolved";
51
+ }
52
+ function statusForCommentStage(config, stage) {
53
+ return config.statuses.find(
54
+ (status) => resolveCommentStage(config, status.value) === stage
55
+ );
56
+ }
41
57
  function getRoots(comments) {
42
58
  return comments.filter((c) => !c.parentId);
43
59
  }
@@ -71,10 +87,20 @@ function selectAnchorThreads(comments, anchor) {
71
87
  }
72
88
  function selectUnresolvedThreads(comments, config) {
73
89
  const keep = new Set(
74
- getRoots(comments).filter((root) => root.status == null || isUnresolved(config, root.status)).map((root) => String(root.id))
90
+ getRoots(comments).filter(
91
+ (root) => root.status == null || isUnresolved(config, root.status)
92
+ ).map((root) => String(root.id))
75
93
  );
76
94
  return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
77
95
  }
96
+ function selectCommentThreadsByStage(comments, config, stage) {
97
+ const keep = new Set(
98
+ getRoots(comments).filter((root) => resolveCommentStage(config, root.status) === stage).map((root) => String(root.id))
99
+ );
100
+ return comments.filter(
101
+ (comment) => keep.has(String(comment.parentId ?? comment.id))
102
+ );
103
+ }
78
104
  function sortReplies(replies) {
79
105
  return [...replies].sort((a, b) => {
80
106
  if (a.createdAt !== b.createdAt)
@@ -187,6 +213,7 @@ function matchMentionsInBody(body, mentionables) {
187
213
  function buildThreadListHandlers(comments, config, cb) {
188
214
  const handlers = {};
189
215
  if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;
216
+ if (cb.onClose) handlers.onClose = cb.onClose;
190
217
  if (cb.onDelete) handlers.onDelete = cb.onDelete;
191
218
  if (cb.onChecklistToggle) {
192
219
  const toggle = cb.onChecklistToggle;
@@ -232,11 +259,14 @@ exports.hasActiveFilters = hasActiveFilters;
232
259
  exports.isUnresolved = isUnresolved;
233
260
  exports.matchMentionsInBody = matchMentionsInBody;
234
261
  exports.nextChecklistStatus = nextChecklistStatus;
262
+ exports.resolveCommentStage = resolveCommentStage;
235
263
  exports.resolveFacetOption = resolveFacetOption;
236
264
  exports.resolveStatusConfig = resolveStatusConfig;
237
265
  exports.selectAnchorThreads = selectAnchorThreads;
266
+ exports.selectCommentThreadsByStage = selectCommentThreadsByStage;
238
267
  exports.selectUnresolvedThreads = selectUnresolvedThreads;
239
268
  exports.sortReplies = sortReplies;
269
+ exports.statusForCommentStage = statusForCommentStage;
240
270
  exports.toneToBadgeTone = toneToBadgeTone;
241
271
  exports.truncatePlain = truncatePlain;
242
272
  //# sourceMappingURL=comment-utils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.cjs","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => root.status == null || isUnresolved(config, root.status))\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":["DOCUMENT_ANCHOR","DEFAULT_CHECKLIST_CYCLE"],"mappings":"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"}
1
+ {"version":3,"file":"comment-utils.cjs","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentStatusStage,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Resolves a stored status to its lifecycle stage. */\nexport function resolveCommentStage(\n config: CommentConfig,\n status: string | undefined,\n): CommentStatusStage | undefined {\n if (status == null) return \"active\";\n const resolved = resolveStatusConfig(config, status);\n if (!resolved) return undefined;\n if (resolved.stage) return resolved.stage;\n if (resolved.unresolved) return \"active\";\n if (\n resolved.value.toLowerCase() === \"closed\" ||\n resolved.label.toLowerCase() === \"closed\"\n ) {\n return \"closed\";\n }\n return \"resolved\";\n}\n\n/** Returns the first configured stored status for a lifecycle stage. */\nexport function statusForCommentStage(\n config: CommentConfig,\n stage: CommentStatusStage,\n): CommentStatusConfig | undefined {\n return config.statuses.find(\n (status) => resolveCommentStage(config, status.value) === stage,\n );\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Selects roots in one lifecycle stage together with every descendant reply. */\nexport function selectCommentThreadsByStage(\n comments: Comment[],\n config: CommentConfig,\n stage: CommentStatusStage,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => resolveCommentStage(config, root.status) === stage)\n .map((root) => String(root.id)),\n );\n return comments.filter((comment) =>\n keep.has(String(comment.parentId ?? comment.id)),\n );\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":["DOCUMENT_ANCHOR","DEFAULT_CHECKLIST_CYCLE"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { BadgeTone } from '../data/Badge';
2
- import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentTone } from './comment-types';
2
+ import { Comment, CommentAnchor, CommentAnchorMeta, CommentAuthor, CommentCallbacks, CommentConfig, CommentFacet, CommentFacetOption, CommentFilters, CommentMention, CommentMentionable, CommentStatusConfig, CommentStatusStage, CommentTone } from './comment-types';
3
3
  /** Human-readable name for an author, falling back to "Unknown". */
4
4
  export declare function authorDisplayName(author: CommentAuthor | null): string;
5
5
  /** Maps a CommentTone onto the Badge component's tone vocabulary. */
@@ -12,6 +12,10 @@ export declare function resolveFacetOption(facet: CommentFacet, value: string |
12
12
  export declare function defaultStatusValue(config: CommentConfig): string | undefined;
13
13
  /** Whether a stored status counts as unresolved/open. */
14
14
  export declare function isUnresolved(config: CommentConfig, status: string | undefined): boolean;
15
+ /** Resolves a stored status to its lifecycle stage. */
16
+ export declare function resolveCommentStage(config: CommentConfig, status: string | undefined): CommentStatusStage | undefined;
17
+ /** Returns the first configured stored status for a lifecycle stage. */
18
+ export declare function statusForCommentStage(config: CommentConfig, stage: CommentStatusStage): CommentStatusConfig | undefined;
15
19
  /** Returns the root (non-reply) comments in input order. */
16
20
  export declare function getRoots(comments: Comment[]): Comment[];
17
21
  /** Indexes replies by their parent id. */
@@ -27,6 +31,8 @@ export declare function selectAnchorThreads(comments: Comment[], anchor: Comment
27
31
  * been acted on, so it counts as unresolved; replies follow their root.
28
32
  */
29
33
  export declare function selectUnresolvedThreads(comments: Comment[], config: CommentConfig): Comment[];
34
+ /** Selects roots in one lifecycle stage together with every descendant reply. */
35
+ export declare function selectCommentThreadsByStage(comments: Comment[], config: CommentConfig, stage: CommentStatusStage): Comment[];
30
36
  /** Stable reply ordering: by creation time, then id. */
31
37
  export declare function sortReplies(replies: Comment[]): Comment[];
32
38
  /** Groups root comments by the value of a facet key (missing → ""). */
@@ -55,7 +61,8 @@ export declare function emptyCommentFilters(): CommentFilters;
55
61
  export declare function matchMentionsInBody(body: string, mentionables: CommentMentionable[]): CommentMention[];
56
62
  /** Per-id handlers consumed by CommentThreadList. */
57
63
  export type ThreadListHandlers = {
58
- onUpdateStatus?: (id: string, status: string) => void;
64
+ onUpdateStatus?: (id: string, status: string) => void | Promise<void>;
65
+ onClose?: (id: string) => void | Promise<void>;
59
66
  onChecklistToggle?: (id: string, index: number) => void;
60
67
  onDelete?: (id: string) => void;
61
68
  onReply?: (parent: Comment, body: string) => void | Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EAEvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAEjB,MAAM,iBAAiB,CAAC;AAEzB,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAEtE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAexE;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,mBAAmB,GAAG,SAAS,CAGjC;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,kBAAkB,GAAG,SAAS,CAGhC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAG5E;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,OAAO,CAET;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAEvD;AAED,0CAA0C;AAC1C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CASzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,EAAE,CAkBX;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,aAAa,GACpB,OAAO,EAAE,CAOX;AAED,wDAAwD;AACxD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAMzD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,GACf,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CASxB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EAAE,GAClB,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAO/B;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,OAAO,EAAE,GAClB,MAAM,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAuB1C;AAED,wFAAwF;AACxF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,SAAS,SAAM,GACd,MAAM,CAMR;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,KAAK,WAA0B,GAC9B,MAAM,CAIR;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,CAG5D;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa,GACpB,OAAO,EAAE,CA+BX;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAIjE;AAED,6BAA6B;AAC7B,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,kBAAkB,EAAE,GACjC,cAAc,EAAE,CAKlB;AAED,qDAAqD;AACrD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,aAAa,EACrB,EAAE,EAAE,gBAAgB,GACnB,kBAAkB,CA+BpB"}
1
+ {"version":3,"file":"comment-utils.d.ts","sourceRoot":"","sources":["../../src/comments/comment-utils.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,22 @@ function isUnresolved(config, status) {
36
36
  var _a;
37
37
  return ((_a = resolveStatusConfig(config, status)) == null ? void 0 : _a.unresolved) ?? false;
38
38
  }
39
+ function resolveCommentStage(config, status) {
40
+ if (status == null) return "active";
41
+ const resolved = resolveStatusConfig(config, status);
42
+ if (!resolved) return void 0;
43
+ if (resolved.stage) return resolved.stage;
44
+ if (resolved.unresolved) return "active";
45
+ if (resolved.value.toLowerCase() === "closed" || resolved.label.toLowerCase() === "closed") {
46
+ return "closed";
47
+ }
48
+ return "resolved";
49
+ }
50
+ function statusForCommentStage(config, stage) {
51
+ return config.statuses.find(
52
+ (status) => resolveCommentStage(config, status.value) === stage
53
+ );
54
+ }
39
55
  function getRoots(comments) {
40
56
  return comments.filter((c) => !c.parentId);
41
57
  }
@@ -69,10 +85,20 @@ function selectAnchorThreads(comments, anchor) {
69
85
  }
70
86
  function selectUnresolvedThreads(comments, config) {
71
87
  const keep = new Set(
72
- getRoots(comments).filter((root) => root.status == null || isUnresolved(config, root.status)).map((root) => String(root.id))
88
+ getRoots(comments).filter(
89
+ (root) => root.status == null || isUnresolved(config, root.status)
90
+ ).map((root) => String(root.id))
73
91
  );
74
92
  return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));
75
93
  }
94
+ function selectCommentThreadsByStage(comments, config, stage) {
95
+ const keep = new Set(
96
+ getRoots(comments).filter((root) => resolveCommentStage(config, root.status) === stage).map((root) => String(root.id))
97
+ );
98
+ return comments.filter(
99
+ (comment) => keep.has(String(comment.parentId ?? comment.id))
100
+ );
101
+ }
76
102
  function sortReplies(replies) {
77
103
  return [...replies].sort((a, b) => {
78
104
  if (a.createdAt !== b.createdAt)
@@ -185,6 +211,7 @@ function matchMentionsInBody(body, mentionables) {
185
211
  function buildThreadListHandlers(comments, config, cb) {
186
212
  const handlers = {};
187
213
  if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;
214
+ if (cb.onClose) handlers.onClose = cb.onClose;
188
215
  if (cb.onDelete) handlers.onDelete = cb.onDelete;
189
216
  if (cb.onChecklistToggle) {
190
217
  const toggle = cb.onChecklistToggle;
@@ -231,11 +258,14 @@ export {
231
258
  isUnresolved,
232
259
  matchMentionsInBody,
233
260
  nextChecklistStatus,
261
+ resolveCommentStage,
234
262
  resolveFacetOption,
235
263
  resolveStatusConfig,
236
264
  selectAnchorThreads,
265
+ selectCommentThreadsByStage,
237
266
  selectUnresolvedThreads,
238
267
  sortReplies,
268
+ statusForCommentStage,
239
269
  toneToBadgeTone,
240
270
  truncatePlain
241
271
  };
@@ -1 +1 @@
1
- {"version":3,"file":"comment-utils.js","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => root.status == null || isUnresolved(config, root.status))\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":[],"mappings":";AAqBO,SAAS,kBAAkB,QAAsC;;AACtE,WAAO,sCAAQ,SAAR,mBAAc,WAAU;AACjC;AAGO,SAAS,gBAAgB,MAA0C;AACxE,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EAAA;AAEb;AAGO,SAAS,oBACd,QACA,OACiC;AACjC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AACtD;AAGO,SAAS,mBACd,OACA,OACgC;AAChC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AACpD;AAGO,SAAS,mBAAmB,QAA2C;;AAC5E,QAAM,aAAa,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU;AAC3D,UAAQ,mBAAc,OAAO,SAAS,CAAC,MAA/B,mBAAmC;AAC7C;AAGO,SAAS,aACd,QACA,QACS;;AACT,WAAO,yBAAoB,QAAQ,MAAM,MAAlC,mBAAqC,eAAc;AAC5D;AAGO,SAAS,SAAS,UAAgC;AACvD,SAAO,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAC3C;AAGO,SAAS,cAAc,UAA6C;AACzE,QAAM,0BAAU,IAAA;AAChB,aAAW,SAAS,UAAU;AAC5B,QAAI,CAAC,MAAM,SAAU;AACrB,UAAM,QAAQ,IAAI,IAAI,MAAM,QAAQ,KAAK,CAAA;AACzC,UAAM,KAAK,KAAK;AAChB,QAAI,IAAI,MAAM,UAAU,KAAK;AAAA,EAC/B;AACA,SAAO;AACT;AAOO,SAAS,oBACd,UACA,QACW;AACX,QAAM,QAAQ,SAAS;AAAA,IACrB,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,UAAU,qBAAqB;AAAA,EAAA;AAE1D,QAAM,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO,EAAE,EAAE,CAAC,CAAC;AAClD,QAAM,cAAyB,CAAA;AAE/B,WAAS,OAAO,MAAM,QAAQ;AAC5B,WAAO;AACP,eAAW,KAAK,UAAU;AACxB,UAAI,CAAC,EAAE,YAAY,IAAI,IAAI,OAAO,EAAE,EAAE,CAAC,EAAG;AAC1C,UAAI,CAAC,IAAI,IAAI,OAAO,EAAE,QAAQ,CAAC,EAAG;AAClC,UAAI,IAAI,OAAO,EAAE,EAAE,CAAC;AACpB,kBAAY,KAAK,CAAC;AAClB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC,GAAG,OAAO,GAAG,WAAW;AAClC;AAMO,SAAS,wBACd,UACA,QACW;AACX,QAAM,OAAO,IAAI;AAAA,IACf,SAAS,QAAQ,EACd,OAAO,CAAC,SAAS,KAAK,UAAU,QAAQ,aAAa,QAAQ,KAAK,MAAM,CAAC,EACzE,IAAI,CAAC,SAAS,OAAO,KAAK,EAAE,CAAC;AAAA,EAAA;AAElC,SAAO,SAAS,OAAO,CAAC,MAAM,KAAK,IAAI,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;AACpE;AAGO,SAAS,YAAY,SAA+B;AACzD,SAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AACjC,QAAI,EAAE,cAAc,EAAE;AACpB,aAAO,EAAE,UAAU,cAAc,EAAE,SAAS;AAC9C,WAAO,OAAO,EAAE,EAAE,EAAE,cAAc,OAAO,EAAE,EAAE,CAAC;AAAA,EAChD,CAAC;AACH;AAGO,SAAS,aACd,UACA,UACwB;;AACxB,QAAM,0BAAU,IAAA;AAChB,aAAW,QAAQ,SAAS,QAAQ,GAAG;AACrC,UAAM,QAAM,UAAK,WAAL,mBAAc,cAAa;AACvC,UAAM,QAAQ,IAAI,IAAI,GAAG,KAAK,CAAA;AAC9B,UAAM,KAAK,IAAI;AACf,QAAI,IAAI,KAAK,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAGO,SAAS,mBACd,UAC+B;AAC/B,QAAM,SAAwC,CAAA;AAC9C,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,EAAE,UAAU;AACxB,WAAO,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK;AAAA,EACrC;AACA,SAAO;AACT;AAGO,SAAS,iBACd,UAC0C;AAC1C,QAAM,UAA4C,CAAA;AAClD,aAAW,KAAK,UAAU;AACxB,UAAM,MAAM,EAAE,UAAU;AACxB,KAAC,gCAAiB,CAAA,IAAI,KAAK,CAAC;AAAA,EAC9B;AACA,QAAM,OAAiD,CAAA;AACvD,aAAW,CAAC,QAAQ,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACrD,UAAM,UAAU;AAAA,MACd,GAAG,IAAI;AAAA,QACL,MAAM,IAAI,CAAC,MAAA;;AAAM,yBAAE,WAAF,mBAAU;AAAA,SAAI,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAAA;AAAA,IACjE;AAEF,UAAM,SAAS,MAAM;AAAA,MAAO,CAAC,GAAG,OAC7B,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,IAAI;AAAA,IAAA;AAEpE,SAAK,MAAM,IAAI;AAAA,MACb,OAAO,MAAM;AAAA,MACb;AAAA,MACA,GAAI,OAAO,UAAU,OAAO,EAAE,cAAc,OAAO,WAAW,CAAA;AAAA,IAAC;AAAA,EAEnE;AACA,SAAO;AACT;AAGO,SAAS,sBACd,QACA,QACA,YAAY,KACJ;AACR,MAAI,QAAQ;AACZ,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,QAAQ,UAAU,IAAI,WAAW,SAAS,SAAS,EAAG,UAAS;AAAA,EACrE;AACA,SAAO;AACT;AAGO,SAAS,oBACd,SACA,QAAQ,yBACA;AACR,QAAM,MAAM,MAAM,QAAQ,OAAO;AACjC,MAAI,QAAQ,GAAI,QAAO,MAAM,CAAC,KAAK;AACnC,SAAO,OAAO,MAAM,KAAK,MAAM,MAAM,KAAK;AAC5C;AAGO,SAAS,cAAc,MAAc,MAAM,IAAY;AAC5D,QAAM,QAAQ,KAAK,QAAQ,mBAAmB,EAAE,EAAE,KAAA;AAClD,SAAO,MAAM,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM;AAC1D;AAEA,SAAS,aAAa,SAAsC;;AAC1D,WAAO,aAAQ,WAAR,mBAAgB,SAAQ;AACjC;AAOO,SAAS,oBACd,UACA,SACA,QACW;AACX,QAAM,aAAa,OAAO,UAAU,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,GAAG;AACxD,QAAM,qCAAqB,IAAA;AAE3B,aAAW,QAAQ,SAAS,QAAQ,GAAG;AACrC,QACE,QAAQ,SAAS,OAAO,MACvB,KAAK,UAAU,QAAQ,CAAC,QAAQ,SAAS,IAAI,KAAK,MAAM,IACzD;AACA;AAAA,IACF;AACA,QACE,QAAQ,cACR,QAAQ,eAAe,SACvB,aAAa,IAAI,MAAM,QAAQ,YAC/B;AACA;AAAA,IACF;AACA,UAAM,aAAa,UAAU,MAAM,CAAC,QAAQ;;AAC1C,YAAM,WAAW,QAAQ,OAAO,GAAG;AACnC,UAAI,CAAC,YAAY,SAAS,SAAS,EAAG,QAAO;AAC7C,YAAM,SAAQ,UAAK,WAAL,mBAAc;AAC5B,aAAO,SAAS,QAAQ,SAAS,IAAI,KAAK;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,WAAY;AACjB,mBAAe,IAAI,KAAK,EAAE;AAAA,EAC5B;AAEA,SAAO,SAAS;AAAA,IAAO,CAAC,MACtB,EAAE,WAAW,eAAe,IAAI,EAAE,QAAQ,IAAI,eAAe,IAAI,EAAE,EAAE;AAAA,EAAA;AAEzE;AAGO,SAAS,iBAAiB,SAAkC;AACjE,MAAI,QAAQ,SAAS,OAAO,EAAG,QAAO;AACtC,MAAI,QAAQ,cAAc,QAAQ,eAAe,MAAO,QAAO;AAC/D,SAAO,OAAO,OAAO,QAAQ,MAAM,EAAE,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC;AACjE;AAGO,SAAS,sBAAsC;AACpD,SAAO,EAAE,UAAU,oBAAI,IAAA,GAAO,QAAQ,CAAA,GAAI,YAAY,MAAA;AACxD;AAGO,SAAS,oBACd,MACA,cACkB;AAClB,QAAM,QAAQ,KAAK,YAAA;AACnB,SAAO,aACJ,OAAO,CAAC,MAAM,MAAM,SAAS,IAAI,EAAE,KAAK,YAAA,CAAa,EAAE,CAAC,EACxD,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,EAAE,KAAA,EAAO;AAC1D;AAgBO,SAAS,wBACd,UACA,QACA,IACoB;AACpB,QAAM,WAA+B,CAAA;AACrC,MAAI,GAAG,eAAgB,UAAS,iBAAiB,GAAG;AACpD,MAAI,GAAG,SAAU,UAAS,WAAW,GAAG;AACxC,MAAI,GAAG,mBAAmB;AACxB,UAAM,SAAS,GAAG;AAClB,aAAS,oBAAoB,CAAC,IAAI,UAAU;;AAC1C,YAAM,QAAO,oBAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAhC,mBAAmC,cAAnC,mBAA+C;AAC5D,UAAI,CAAC,KAAM;AACX,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA,oBAAoB,KAAK,QAAQ,OAAO,oBAAoB;AAAA,MAAA;AAAA,IAEhE;AAAA,EACF;AACA,MAAI,GAAG,SAAS;AACd,UAAM,QAAQ,GAAG;AACjB,UAAM,YAAY,GAAG;AACrB,aAAS,UAAU,OAAO,QAAQ,SAAS;AACzC,YAAM,WAAW,oBAAoB,MAAM,OAAO,gBAAgB,CAAA,CAAE;AACpE,YAAM,MAAM;AAAA,QACV,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,QAAQ,OAAO,UAAU;AAAA,QACzB,GAAI,SAAS,SAAS,IAAI,EAAE,SAAA,IAAa,CAAA;AAAA,MAAC,CAC3C;AACD,iBAAW,WAAW,SAAU,wCAAY,SAAS,EAAE;IACzD;AAAA,EACF;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"comment-utils.js","sources":["../../src/comments/comment-utils.ts"],"sourcesContent":["import type { BadgeTone } from \"../data/Badge\";\nimport {\n DEFAULT_CHECKLIST_CYCLE,\n type Comment,\n type CommentAnchor,\n type CommentAnchorMeta,\n type CommentAuthor,\n type CommentCallbacks,\n type CommentConfig,\n type CommentFacet,\n type CommentFacetOption,\n type CommentFilters,\n type CommentMention,\n type CommentMentionable,\n type CommentMentionKind,\n type CommentStatusConfig,\n type CommentStatusStage,\n type CommentTone,\n DOCUMENT_ANCHOR,\n} from \"./comment-types\";\n\n/** Human-readable name for an author, falling back to \"Unknown\". */\nexport function authorDisplayName(author: CommentAuthor | null): string {\n return author?.name?.trim() || \"Unknown\";\n}\n\n/** Maps a CommentTone onto the Badge component's tone vocabulary. */\nexport function toneToBadgeTone(tone: CommentTone | undefined): BadgeTone {\n switch (tone) {\n case \"success\":\n return \"success\";\n case \"danger\":\n return \"danger\";\n case \"warning\":\n return \"warning\";\n case \"info\":\n return \"info\";\n case \"neutral\":\n case \"default\":\n default:\n return \"neutral\";\n }\n}\n\n/** Looks up the status config for a stored status value. */\nexport function resolveStatusConfig(\n config: CommentConfig,\n value: string | undefined,\n): CommentStatusConfig | undefined {\n if (value == null) return undefined;\n return config.statuses.find((s) => s.value === value);\n}\n\n/** Looks up a facet option for a stored facet value. */\nexport function resolveFacetOption(\n facet: CommentFacet,\n value: string | undefined,\n): CommentFacetOption | undefined {\n if (value == null) return undefined;\n return facet.options.find((o) => o.value === value);\n}\n\n/** The default status value for new comments: first unresolved, else first. */\nexport function defaultStatusValue(config: CommentConfig): string | undefined {\n const unresolved = config.statuses.find((s) => s.unresolved);\n return (unresolved ?? config.statuses[0])?.value;\n}\n\n/** Whether a stored status counts as unresolved/open. */\nexport function isUnresolved(\n config: CommentConfig,\n status: string | undefined,\n): boolean {\n return resolveStatusConfig(config, status)?.unresolved ?? false;\n}\n\n/** Resolves a stored status to its lifecycle stage. */\nexport function resolveCommentStage(\n config: CommentConfig,\n status: string | undefined,\n): CommentStatusStage | undefined {\n if (status == null) return \"active\";\n const resolved = resolveStatusConfig(config, status);\n if (!resolved) return undefined;\n if (resolved.stage) return resolved.stage;\n if (resolved.unresolved) return \"active\";\n if (\n resolved.value.toLowerCase() === \"closed\" ||\n resolved.label.toLowerCase() === \"closed\"\n ) {\n return \"closed\";\n }\n return \"resolved\";\n}\n\n/** Returns the first configured stored status for a lifecycle stage. */\nexport function statusForCommentStage(\n config: CommentConfig,\n stage: CommentStatusStage,\n): CommentStatusConfig | undefined {\n return config.statuses.find(\n (status) => resolveCommentStage(config, status.value) === stage,\n );\n}\n\n/** Returns the root (non-reply) comments in input order. */\nexport function getRoots(comments: Comment[]): Comment[] {\n return comments.filter((c) => !c.parentId);\n}\n\n/** Indexes replies by their parent id. */\nexport function buildReplyMap(comments: Comment[]): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const reply of comments) {\n if (!reply.parentId) continue;\n const items = map.get(reply.parentId) ?? [];\n items.push(reply);\n map.set(reply.parentId, items);\n }\n return map;\n}\n\n/**\n * Comments forming the threads rooted at `anchor`: matching roots plus their\n * descendants. Replies carry no anchor of their own — they belong to a thread,\n * not to a location — so selecting by anchor equality alone would drop them.\n */\nexport function selectAnchorThreads(\n comments: Comment[],\n anchor: CommentAnchor,\n): Comment[] {\n const roots = comments.filter(\n (c) => !c.parentId && (c.anchor ?? DOCUMENT_ANCHOR) === anchor,\n );\n const ids = new Set(roots.map((c) => String(c.id)));\n const descendants: Comment[] = [];\n // Fixed point, so a reply to a reply is kept when replies are not flattened.\n for (let grew = true; grew; ) {\n grew = false;\n for (const c of comments) {\n if (!c.parentId || ids.has(String(c.id))) continue;\n if (!ids.has(String(c.parentId))) continue;\n ids.add(String(c.id));\n descendants.push(c);\n grew = true;\n }\n }\n return [...roots, ...descendants];\n}\n\n/**\n * Threads whose root is not yet resolved. A root carrying no status has not\n * been acted on, so it counts as unresolved; replies follow their root.\n */\nexport function selectUnresolvedThreads(\n comments: Comment[],\n config: CommentConfig,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter(\n (root) => root.status == null || isUnresolved(config, root.status),\n )\n .map((root) => String(root.id)),\n );\n return comments.filter((c) => keep.has(String(c.parentId ?? c.id)));\n}\n\n/** Selects roots in one lifecycle stage together with every descendant reply. */\nexport function selectCommentThreadsByStage(\n comments: Comment[],\n config: CommentConfig,\n stage: CommentStatusStage,\n): Comment[] {\n const keep = new Set(\n getRoots(comments)\n .filter((root) => resolveCommentStage(config, root.status) === stage)\n .map((root) => String(root.id)),\n );\n return comments.filter((comment) =>\n keep.has(String(comment.parentId ?? comment.id)),\n );\n}\n\n/** Stable reply ordering: by creation time, then id. */\nexport function sortReplies(replies: Comment[]): Comment[] {\n return [...replies].sort((a, b) => {\n if (a.createdAt !== b.createdAt)\n return a.createdAt.localeCompare(b.createdAt);\n return String(a.id).localeCompare(String(b.id));\n });\n}\n\n/** Groups root comments by the value of a facet key (missing → \"\"). */\nexport function groupByFacet(\n comments: Comment[],\n facetKey: string,\n): Map<string, Comment[]> {\n const map = new Map<string, Comment[]>();\n for (const root of getRoots(comments)) {\n const key = root.facets?.[facetKey] ?? \"\";\n const items = map.get(key) ?? [];\n items.push(root);\n map.set(key, items);\n }\n return map;\n}\n\n/** Per-anchor comment counts derived from the list (document-level → DOCUMENT_ANCHOR). */\nexport function deriveAnchorCounts(\n comments: Comment[],\n): Record<CommentAnchor, number> {\n const counts: Record<CommentAnchor, number> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n counts[key] = (counts[key] ?? 0) + 1;\n }\n return counts;\n}\n\n/** Per-anchor aggregate metadata (count, distinct authors, latest status). */\nexport function deriveAnchorMeta(\n comments: Comment[],\n): Record<CommentAnchor, CommentAnchorMeta> {\n const grouped: Record<CommentAnchor, Comment[]> = {};\n for (const c of comments) {\n const key = c.anchor ?? DOCUMENT_ANCHOR;\n (grouped[key] ??= []).push(c);\n }\n const meta: Record<CommentAnchor, CommentAnchorMeta> = {};\n for (const [anchor, items] of Object.entries(grouped)) {\n const authors = [\n ...new Set(\n items.map((c) => c.author?.name).filter((n): n is string => !!n),\n ),\n ];\n const latest = items.reduce((a, b) =>\n (a.updatedAt ?? a.createdAt) > (b.updatedAt ?? b.createdAt) ? a : b,\n );\n meta[anchor] = {\n count: items.length,\n authors,\n ...(latest.status != null ? { latestStatus: latest.status } : {}),\n };\n }\n return meta;\n}\n\n/** Sums counts for an anchor and its descendants under a separator-delimited prefix. */\nexport function commentCountForPrefix(\n counts: Record<string, number>,\n prefix: string,\n separator = \".\",\n): number {\n let total = 0;\n for (const [key, count] of Object.entries(counts)) {\n if (key === prefix || key.startsWith(prefix + separator)) total += count;\n }\n return total;\n}\n\n/** Advances a checklist status to the next in the cycle (wrapping). */\nexport function nextChecklistStatus(\n current: string,\n cycle = DEFAULT_CHECKLIST_CYCLE,\n): string {\n const idx = cycle.indexOf(current);\n if (idx === -1) return cycle[0] ?? current;\n return cycle[(idx + 1) % cycle.length] ?? current;\n}\n\n/** Strips inline markdown punctuation and truncates to a plain preview. */\nexport function truncatePlain(body: string, max = 80): string {\n const plain = body.replace(/[#*_`~>[\\]()!]/g, \"\").trim();\n return plain.length > max ? `${plain.slice(0, max)}…` : plain;\n}\n\nfunction authorKindOf(comment: Comment): CommentMentionKind {\n return comment.author?.kind ?? \"user\";\n}\n\n/**\n * Filters a comment list by status / facets / author kind while keeping threads\n * intact: a reply survives iff its root survives. An empty status set or empty\n * per-facet set means \"no constraint\" for that axis.\n */\nexport function applyCommentFilters(\n comments: Comment[],\n filters: CommentFilters,\n config: CommentConfig,\n): Comment[] {\n const facetKeys = (config.facets ?? []).map((f) => f.key);\n const passingRootIds = new Set<string>();\n\n for (const root of getRoots(comments)) {\n if (\n filters.statuses.size > 0 &&\n (root.status == null || !filters.statuses.has(root.status))\n ) {\n continue;\n }\n if (\n filters.authorKind &&\n filters.authorKind !== \"all\" &&\n authorKindOf(root) !== filters.authorKind\n ) {\n continue;\n }\n const facetsPass = facetKeys.every((key) => {\n const selected = filters.facets[key];\n if (!selected || selected.size === 0) return true;\n const value = root.facets?.[key];\n return value != null && selected.has(value);\n });\n if (!facetsPass) continue;\n passingRootIds.add(root.id);\n }\n\n return comments.filter((c) =>\n c.parentId ? passingRootIds.has(c.parentId) : passingRootIds.has(c.id),\n );\n}\n\n/** True when any filter axis constrains the result. */\nexport function hasActiveFilters(filters: CommentFilters): boolean {\n if (filters.statuses.size > 0) return true;\n if (filters.authorKind && filters.authorKind !== \"all\") return true;\n return Object.values(filters.facets).some((set) => set.size > 0);\n}\n\n/** An empty filter state. */\nexport function emptyCommentFilters(): CommentFilters {\n return { statuses: new Set(), facets: {}, authorKind: \"all\" };\n}\n\n/** Finds mentionables whose `@name` token appears in a body (case-insensitive). */\nexport function matchMentionsInBody(\n body: string,\n mentionables: CommentMentionable[],\n): CommentMention[] {\n const lower = body.toLowerCase();\n return mentionables\n .filter((m) => lower.includes(`@${m.name.toLowerCase()}`))\n .map((m) => ({ id: m.id, name: m.name, kind: m.kind }));\n}\n\n/** Per-id handlers consumed by CommentThreadList. */\nexport type ThreadListHandlers = {\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\n/**\n * Adapts high-level {@link CommentCallbacks} into the id-keyed handlers a\n * {@link CommentThreadList} consumes: resolves the next checklist status from the\n * cycle and extracts mentions from a reply body. Shared by `CommentThread` and\n * `CommentSidePanel` so the adaptation lives in one place.\n */\nexport function buildThreadListHandlers(\n comments: Comment[],\n config: CommentConfig,\n cb: CommentCallbacks,\n): ThreadListHandlers {\n const handlers: ThreadListHandlers = {};\n if (cb.onUpdateStatus) handlers.onUpdateStatus = cb.onUpdateStatus;\n if (cb.onClose) handlers.onClose = cb.onClose;\n if (cb.onDelete) handlers.onDelete = cb.onDelete;\n if (cb.onChecklistToggle) {\n const toggle = cb.onChecklistToggle;\n handlers.onChecklistToggle = (id, index) => {\n const item = comments.find((c) => c.id === id)?.checklist?.[index];\n if (!item) return;\n void toggle(\n id,\n index,\n nextChecklistStatus(item.status, config.checklistStatusCycle),\n );\n };\n }\n if (cb.onReply) {\n const reply = cb.onReply;\n const onMention = cb.onMention;\n handlers.onReply = async (parent, body) => {\n const mentions = matchMentionsInBody(body, config.mentionables ?? []);\n await reply({\n parentId: parent.id,\n body,\n anchor: parent.anchor ?? null,\n ...(mentions.length > 0 ? { mentions } : {}),\n });\n for (const mention of mentions) onMention?.(mention, { body });\n };\n }\n return handlers;\n}\n"],"names":[],"mappings":"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"}
package/dist/comments.cjs CHANGED
@@ -33,11 +33,14 @@ exports.hasActiveFilters = commentUtils.hasActiveFilters;
33
33
  exports.isUnresolved = commentUtils.isUnresolved;
34
34
  exports.matchMentionsInBody = commentUtils.matchMentionsInBody;
35
35
  exports.nextChecklistStatus = commentUtils.nextChecklistStatus;
36
+ exports.resolveCommentStage = commentUtils.resolveCommentStage;
36
37
  exports.resolveFacetOption = commentUtils.resolveFacetOption;
37
38
  exports.resolveStatusConfig = commentUtils.resolveStatusConfig;
38
39
  exports.selectAnchorThreads = commentUtils.selectAnchorThreads;
40
+ exports.selectCommentThreadsByStage = commentUtils.selectCommentThreadsByStage;
39
41
  exports.selectUnresolvedThreads = commentUtils.selectUnresolvedThreads;
40
42
  exports.sortReplies = commentUtils.sortReplies;
43
+ exports.statusForCommentStage = commentUtils.statusForCommentStage;
41
44
  exports.toneToBadgeTone = commentUtils.toneToBadgeTone;
42
45
  exports.truncatePlain = commentUtils.truncatePlain;
43
46
  exports.CommentMarkdown = CommentMarkdown.CommentMarkdown;
@@ -1 +1 @@
1
- {"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"comments.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/comments.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_CHECKLIST_CYCLE, DEFAULT_COMMENT_STATUSES, DOCUMENT_ANCHOR } from "./comments/comment-types.js";
2
- import { applyCommentFilters, authorDisplayName, buildReplyMap, buildThreadListHandlers, commentCountForPrefix, defaultStatusValue, deriveAnchorCounts, deriveAnchorMeta, emptyCommentFilters, getRoots, groupByFacet, hasActiveFilters, isUnresolved, matchMentionsInBody, nextChecklistStatus, resolveFacetOption, resolveStatusConfig, selectAnchorThreads, selectUnresolvedThreads, sortReplies, toneToBadgeTone, truncatePlain } from "./comments/comment-utils.js";
2
+ import { applyCommentFilters, authorDisplayName, buildReplyMap, buildThreadListHandlers, commentCountForPrefix, defaultStatusValue, deriveAnchorCounts, deriveAnchorMeta, emptyCommentFilters, getRoots, groupByFacet, hasActiveFilters, isUnresolved, matchMentionsInBody, nextChecklistStatus, resolveCommentStage, resolveFacetOption, resolveStatusConfig, selectAnchorThreads, selectCommentThreadsByStage, selectUnresolvedThreads, sortReplies, statusForCommentStage, toneToBadgeTone, truncatePlain } from "./comments/comment-utils.js";
3
3
  import { CommentMarkdown } from "./comments/CommentMarkdown.js";
4
4
  import { CommentAuthorAvatar } from "./comments/CommentAuthor.js";
5
5
  import { MentionTextarea } from "./comments/MentionTextarea.js";
@@ -46,11 +46,14 @@ export {
46
46
  isUnresolved,
47
47
  matchMentionsInBody,
48
48
  nextChecklistStatus,
49
+ resolveCommentStage,
49
50
  resolveFacetOption,
50
51
  resolveStatusConfig,
51
52
  selectAnchorThreads,
53
+ selectCommentThreadsByStage,
52
54
  selectUnresolvedThreads,
53
55
  sortReplies,
56
+ statusForCommentStage,
54
57
  strictAnchorResolver,
55
58
  toneToBadgeTone,
56
59
  truncatePlain,
@@ -27,10 +27,10 @@ const DEFAULT_MODE_KINDS = {
27
27
  ingress: ["ingress"],
28
28
  "port-forward": ["service", "deployment"]
29
29
  };
30
- const WORKLOAD_FIELD = "max-w-full min-w-0 basis-56 shrink grow";
31
- const SCHEME_FIELD = "max-w-full min-w-0 basis-32 shrink grow-0";
32
- const PORT_FIELD = "max-w-full min-w-0 basis-36 shrink grow-0";
33
- const PATH_FIELD = "max-w-full min-w-0 basis-56 shrink grow";
30
+ const WORKLOAD_FIELD = "col-span-2 max-w-full min-w-0 @min-[40rem]/endpoint:col-span-1";
31
+ const SCHEME_FIELD = "max-w-full min-w-0 w-full";
32
+ const PORT_FIELD = "max-w-full min-w-0 w-full";
33
+ const PATH_FIELD = "col-span-2 max-w-full min-w-0 @min-[40rem]/endpoint:col-span-3 @min-[56rem]/endpoint:col-span-1";
34
34
  function isWorkloadValue(value) {
35
35
  return value !== void 0 && value.mode !== "url";
36
36
  }
@@ -268,7 +268,7 @@ function EndpointSelector({
268
268
  };
269
269
  const workloadValue = isWorkloadValue(value) && value.mode === activeMode ? targetKey(value.target, namespace, selectedResource) : "";
270
270
  const showPort = activeMode !== "url" && (activeMode !== "ingress" || showIngressPort);
271
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("space-y-density-2", className), children: [
271
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("@container/endpoint space-y-density-2", className), children: [
272
272
  /* @__PURE__ */ jsxRuntime.jsx(
273
273
  SegmentedControl.SegmentedControl,
274
274
  {
@@ -294,7 +294,7 @@ function EndpointSelector({
294
294
  })() : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-density-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
295
295
  "div",
296
296
  {
297
- className: "flex w-full max-w-full min-w-0 flex-nowrap items-end gap-density-2",
297
+ className: "grid w-full max-w-full min-w-0 grid-cols-2 items-end gap-density-2 @min-[40rem]/endpoint:grid-cols-[minmax(14rem,1fr)_auto_auto] @min-[56rem]/endpoint:grid-cols-[minmax(14rem,1fr)_auto_auto_minmax(14rem,1fr)]",
298
298
  "data-slot": "endpoint-fields",
299
299
  children: [
300
300
  /* @__PURE__ */ jsxRuntime.jsx(