@mrsf/marked-mrsf 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,30 @@ import { refreshAll } from "@mrsf/marked-mrsf/controller";
35
35
 
36
36
  The shared controller wires up gutter buttons, selection actions, and built-in dialogs, then dispatches `mrsf:*` events for your host app to persist.
37
37
 
38
+ ### Shared Gutter Render Hooks
39
+
40
+ The controller also accepts `gutterRenderers` so hosts can override the shared badge and add-button presentation while keeping one UX contract across plugins:
41
+
42
+ ```ts
43
+ import { MrsfController } from "@mrsf/marked-mrsf/controller";
44
+
45
+ new MrsfController(document.querySelector(".markdown-body")!, {
46
+ interactive: true,
47
+ gutterRenderers: {
48
+ badge: ({ defaultPresentation }) => ({
49
+ label: `🗨 ${defaultPresentation.countText}`,
50
+ icon: "🗨",
51
+ countText: defaultPresentation.countText,
52
+ }),
53
+ addButton: () => ({
54
+ label: "New",
55
+ }),
56
+ },
57
+ });
58
+ ```
59
+
60
+ The renderer contract is shared with the HTML and Monaco integrations, including the default `9+` count cap and add-button labels.
61
+
38
62
  ## Options
39
63
 
40
64
  The plugin accepts the same `MrsfPluginOptions` surface as the markdown-it and rehype packages: `comments`, `loader`, `documentPath`, `sidecarPath`, `showResolved`, `dataContainer`, `dataElementId`, `interactive`, `gutterPosition`, `gutterForInline`, `inlineHighlights`, `lineHighlight`, `theme`, and `cwd`.
package/dist/browser.js CHANGED
@@ -2,6 +2,15 @@
2
2
  import { Marked } from "marked";
3
3
 
4
4
  // ../shared/dist/comments.js
5
+ function getExtensionFields(comment) {
6
+ const extensions = {};
7
+ for (const [key, value] of Object.entries(comment)) {
8
+ if (key.startsWith("x_")) {
9
+ extensions[key] = value;
10
+ }
11
+ }
12
+ return extensions;
13
+ }
5
14
  function toSlimComments(doc) {
6
15
  return doc.comments.map((c) => ({
7
16
  id: c.id,
@@ -16,7 +25,8 @@ function toSlimComments(doc) {
16
25
  reply_to: c.reply_to || null,
17
26
  severity: c.severity || null,
18
27
  type: c.type || null,
19
- timestamp: c.timestamp || null
28
+ timestamp: c.timestamp || null,
29
+ ...getExtensionFields(c)
20
30
  }));
21
31
  }
22
32
  function groupByLine(comments) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/shared.ts", "../../shared/src/comments.ts", "../src/browser.ts"],
4
- "sourcesContent": ["/**\n * Shared logic for @mrsf/marked-mrsf.\n */\n\nimport { Marked, type MarkedExtension, type Token, type Tokens } from \"marked\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, CommentThread, LineMap, MrsfPluginOptions } from \"./types.js\";\n\ntype TokenWithMrsf = Token & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n mrsfHeaderLine?: number;\n mrsfRowLines?: number[];\n};\n\ntype ListItemWithMrsf = Tokens.ListItem & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n};\n\nconst markedRuntime = new Marked();\nconst BaseRenderer = markedRuntime.Renderer;\n\nconst blockquoteRenderer = BaseRenderer.prototype.blockquote;\nconst codeRenderer = BaseRenderer.prototype.code;\nconst headingRenderer = BaseRenderer.prototype.heading;\nconst hrRenderer = BaseRenderer.prototype.hr;\nconst listItemRenderer = BaseRenderer.prototype.listitem;\nconst paragraphRenderer = BaseRenderer.prototype.paragraph;\nconst tableRenderer = BaseRenderer.prototype.table;\n\nfunction countLines(value: string): number {\n if (!value) return 1;\n return value.split(\"\\n\").length;\n}\n\nfunction trimTrailingBlankLines(value: string): string {\n return value.replace(/\\n+$/g, \"\");\n}\n\nfunction firstCommentLine(lineMap: LineMap, startLine: number, endLine: number): number {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return line;\n }\n }\n return startLine;\n}\n\nfunction hasCommentInRange(lineMap: LineMap, startLine: number, endLine: number): boolean {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return true;\n }\n }\n return false;\n}\n\nfunction stampBlock(token: TokenWithMrsf, startLine: number, lineMap: LineMap): number {\n const raw = typeof token.raw === \"string\" ? token.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = startLine + countLines(semantic) - 1;\n\n token.mrsfStartLine = startLine;\n token.mrsfEndLine = endLine;\n token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);\n token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);\n\n if (token.type === \"table\") {\n const tableToken = token as Tokens.Table & TokenWithMrsf;\n tableToken.mrsfHeaderLine = startLine;\n tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);\n }\n\n return startLine + countLines(raw) - 1;\n}\n\nfunction stampListItems(token: Tokens.List & TokenWithMrsf, lineMap: LineMap): void {\n let currentLine = token.mrsfStartLine ?? 1;\n\n for (const item of token.items as ListItemWithMrsf[]) {\n const raw = typeof item.raw === \"string\" ? item.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = currentLine + countLines(semantic) - 1;\n item.mrsfStartLine = currentLine;\n item.mrsfEndLine = endLine;\n item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);\n item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);\n\n stampTokens(item.tokens as TokenWithMrsf[], currentLine, lineMap);\n currentLine += countLines(raw) - 1;\n }\n}\n\nfunction stampTokens(tokens: TokenWithMrsf[], startLine: number, lineMap: LineMap): number {\n let currentLine = startLine;\n\n for (const token of tokens) {\n currentLine = stampBlock(token, currentLine, lineMap);\n\n if (token.type === \"blockquote\") {\n stampTokens((token as Tokens.Blockquote).tokens as TokenWithMrsf[], token.mrsfStartLine ?? currentLine, lineMap);\n } else if (token.type === \"list\") {\n stampListItems(token as Tokens.List & TokenWithMrsf, lineMap);\n }\n }\n\n return currentLine;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction buildAttrs(token: { mrsfStartLine?: number; mrsfEndLine?: number; mrsfLine?: number; mrsfLineHighlight?: boolean }, lineHighlight: boolean): string {\n if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {\n return \"\";\n }\n\n const attrs = [\n `data-mrsf-line=\"${token.mrsfLine}\"`,\n `data-mrsf-start-line=\"${token.mrsfStartLine}\"`,\n `data-mrsf-end-line=\"${token.mrsfEndLine}\"`,\n ];\n\n if (lineHighlight && token.mrsfLineHighlight) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\nfunction addAttrs(html: string, attrs: string, tagName?: string): string {\n if (!attrs) return html;\n const pattern = tagName\n ? new RegExp(`^<${tagName}(?=[\\\\s>])`, \"i\")\n : /^<([a-z0-9-]+)(?=[\\s>])/i;\n return html.replace(pattern, (match) => match + attrs);\n}\n\nfunction flattenThreads(lineMap: LineMap): CommentThread[] {\n const threads: CommentThread[] = [];\n for (const lineThreads of lineMap.values()) {\n threads.push(...lineThreads);\n }\n return threads;\n}\n\nfunction createDataContainer(options: MrsfPluginOptions, threads: CommentThread[]): string {\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n return `<script type=\"application/mrsf+json\">${payload.replace(/</g, \"\\\\u003c\")}</script>`;\n}\n\nfunction renderAnnotatedTable(\n this: { parser: { parseInline: (tokens: Token[]) => string } },\n token: Tokens.Table & TokenWithMrsf,\n lineMap: LineMap,\n lineHighlight: boolean,\n): string {\n const baseHtml = tableRenderer.call(this, token);\n const rowLines = [token.mrsfHeaderLine, ...(token.mrsfRowLines ?? [])];\n let rowIndex = 0;\n\n return baseHtml.replace(/<tr>/g, () => {\n const line = rowLines[rowIndex++];\n if (line == null) return \"<tr>\";\n\n const hasComment = hasCommentInRange(lineMap, line, line);\n\n const attrs = [\n `data-mrsf-line=\"${line}\"`,\n `data-mrsf-start-line=\"${line}\"`,\n `data-mrsf-end-line=\"${line}\"`,\n ];\n if (lineHighlight && hasComment) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n return `<tr ${attrs.join(\" \")}>`;\n });\n}\n\nexport function createMarkedMrsf(loader: CommentLoader) {\n return function markedMrsf(options: MrsfPluginOptions = {}): MarkedExtension {\n let currentLineMap: LineMap | null = null;\n let currentThreads: CommentThread[] = [];\n\n return {\n hooks: {\n processAllTokens(tokens) {\n const result = resolveComments(loader, options);\n if (!result) {\n currentLineMap = null;\n currentThreads = [];\n return tokens;\n }\n\n currentLineMap = result.lineMap;\n currentThreads = flattenThreads(result.lineMap);\n stampTokens(tokens as TokenWithMrsf[], 1, result.lineMap);\n return tokens;\n },\n postprocess(html) {\n if (!currentLineMap || currentThreads.length === 0) {\n return html;\n }\n return html + createDataContainer(options, currentThreads);\n },\n },\n renderer: {\n heading(token) {\n return addAttrs(headingRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), `h${token.depth}`);\n },\n paragraph(token) {\n return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"p\");\n },\n code(token) {\n return addAttrs(codeRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"pre\");\n },\n blockquote(token) {\n return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"blockquote\");\n },\n listitem(token) {\n return addAttrs(listItemRenderer.call(this, token), buildAttrs(token as ListItemWithMrsf, options.lineHighlight ?? false), \"li\");\n },\n hr(token) {\n return addAttrs(hrRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"hr\");\n },\n table(token) {\n return renderAnnotatedTable.call(\n this,\n token as Tokens.Table & TokenWithMrsf,\n currentLineMap ?? new Map(),\n options.lineHighlight ?? false,\n );\n },\n },\n };\n };\n}", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "/**\n * Browser-safe entry point for @mrsf/marked-mrsf.\n */\n\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarkedMrsf } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport const markedMrsf = createMarkedMrsf((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath || options.documentPath) {\n console.warn(\n \"[@mrsf/marked-mrsf] sidecarPath and documentPath require Node.js. \" +\n \"Use `comments` or `loader` options in browser environments.\",\n );\n }\n\n return null;\n});\n\nexport default markedMrsf;"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Shared logic for @mrsf/marked-mrsf.\n */\n\nimport { Marked, type MarkedExtension, type Token, type Tokens } from \"marked\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, CommentThread, LineMap, MrsfPluginOptions } from \"./types.js\";\n\ntype TokenWithMrsf = Token & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n mrsfHeaderLine?: number;\n mrsfRowLines?: number[];\n};\n\ntype ListItemWithMrsf = Tokens.ListItem & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n};\n\nconst markedRuntime = new Marked();\nconst BaseRenderer = markedRuntime.Renderer;\n\nconst blockquoteRenderer = BaseRenderer.prototype.blockquote;\nconst codeRenderer = BaseRenderer.prototype.code;\nconst headingRenderer = BaseRenderer.prototype.heading;\nconst hrRenderer = BaseRenderer.prototype.hr;\nconst listItemRenderer = BaseRenderer.prototype.listitem;\nconst paragraphRenderer = BaseRenderer.prototype.paragraph;\nconst tableRenderer = BaseRenderer.prototype.table;\n\nfunction countLines(value: string): number {\n if (!value) return 1;\n return value.split(\"\\n\").length;\n}\n\nfunction trimTrailingBlankLines(value: string): string {\n return value.replace(/\\n+$/g, \"\");\n}\n\nfunction firstCommentLine(lineMap: LineMap, startLine: number, endLine: number): number {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return line;\n }\n }\n return startLine;\n}\n\nfunction hasCommentInRange(lineMap: LineMap, startLine: number, endLine: number): boolean {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return true;\n }\n }\n return false;\n}\n\nfunction stampBlock(token: TokenWithMrsf, startLine: number, lineMap: LineMap): number {\n const raw = typeof token.raw === \"string\" ? token.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = startLine + countLines(semantic) - 1;\n\n token.mrsfStartLine = startLine;\n token.mrsfEndLine = endLine;\n token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);\n token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);\n\n if (token.type === \"table\") {\n const tableToken = token as Tokens.Table & TokenWithMrsf;\n tableToken.mrsfHeaderLine = startLine;\n tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);\n }\n\n return startLine + countLines(raw) - 1;\n}\n\nfunction stampListItems(token: Tokens.List & TokenWithMrsf, lineMap: LineMap): void {\n let currentLine = token.mrsfStartLine ?? 1;\n\n for (const item of token.items as ListItemWithMrsf[]) {\n const raw = typeof item.raw === \"string\" ? item.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = currentLine + countLines(semantic) - 1;\n item.mrsfStartLine = currentLine;\n item.mrsfEndLine = endLine;\n item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);\n item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);\n\n stampTokens(item.tokens as TokenWithMrsf[], currentLine, lineMap);\n currentLine += countLines(raw) - 1;\n }\n}\n\nfunction stampTokens(tokens: TokenWithMrsf[], startLine: number, lineMap: LineMap): number {\n let currentLine = startLine;\n\n for (const token of tokens) {\n currentLine = stampBlock(token, currentLine, lineMap);\n\n if (token.type === \"blockquote\") {\n stampTokens((token as Tokens.Blockquote).tokens as TokenWithMrsf[], token.mrsfStartLine ?? currentLine, lineMap);\n } else if (token.type === \"list\") {\n stampListItems(token as Tokens.List & TokenWithMrsf, lineMap);\n }\n }\n\n return currentLine;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction buildAttrs(token: { mrsfStartLine?: number; mrsfEndLine?: number; mrsfLine?: number; mrsfLineHighlight?: boolean }, lineHighlight: boolean): string {\n if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {\n return \"\";\n }\n\n const attrs = [\n `data-mrsf-line=\"${token.mrsfLine}\"`,\n `data-mrsf-start-line=\"${token.mrsfStartLine}\"`,\n `data-mrsf-end-line=\"${token.mrsfEndLine}\"`,\n ];\n\n if (lineHighlight && token.mrsfLineHighlight) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\nfunction addAttrs(html: string, attrs: string, tagName?: string): string {\n if (!attrs) return html;\n const pattern = tagName\n ? new RegExp(`^<${tagName}(?=[\\\\s>])`, \"i\")\n : /^<([a-z0-9-]+)(?=[\\s>])/i;\n return html.replace(pattern, (match) => match + attrs);\n}\n\nfunction flattenThreads(lineMap: LineMap): CommentThread[] {\n const threads: CommentThread[] = [];\n for (const lineThreads of lineMap.values()) {\n threads.push(...lineThreads);\n }\n return threads;\n}\n\nfunction createDataContainer(options: MrsfPluginOptions, threads: CommentThread[]): string {\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n return `<script type=\"application/mrsf+json\">${payload.replace(/</g, \"\\\\u003c\")}</script>`;\n}\n\nfunction renderAnnotatedTable(\n this: { parser: { parseInline: (tokens: Token[]) => string } },\n token: Tokens.Table & TokenWithMrsf,\n lineMap: LineMap,\n lineHighlight: boolean,\n): string {\n const baseHtml = tableRenderer.call(this, token);\n const rowLines = [token.mrsfHeaderLine, ...(token.mrsfRowLines ?? [])];\n let rowIndex = 0;\n\n return baseHtml.replace(/<tr>/g, () => {\n const line = rowLines[rowIndex++];\n if (line == null) return \"<tr>\";\n\n const hasComment = hasCommentInRange(lineMap, line, line);\n\n const attrs = [\n `data-mrsf-line=\"${line}\"`,\n `data-mrsf-start-line=\"${line}\"`,\n `data-mrsf-end-line=\"${line}\"`,\n ];\n if (lineHighlight && hasComment) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n return `<tr ${attrs.join(\" \")}>`;\n });\n}\n\nexport function createMarkedMrsf(loader: CommentLoader) {\n return function markedMrsf(options: MrsfPluginOptions = {}): MarkedExtension {\n let currentLineMap: LineMap | null = null;\n let currentThreads: CommentThread[] = [];\n\n return {\n hooks: {\n processAllTokens(tokens) {\n const result = resolveComments(loader, options);\n if (!result) {\n currentLineMap = null;\n currentThreads = [];\n return tokens;\n }\n\n currentLineMap = result.lineMap;\n currentThreads = flattenThreads(result.lineMap);\n stampTokens(tokens as TokenWithMrsf[], 1, result.lineMap);\n return tokens;\n },\n postprocess(html) {\n if (!currentLineMap || currentThreads.length === 0) {\n return html;\n }\n return html + createDataContainer(options, currentThreads);\n },\n },\n renderer: {\n heading(token) {\n return addAttrs(headingRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), `h${token.depth}`);\n },\n paragraph(token) {\n return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"p\");\n },\n code(token) {\n return addAttrs(codeRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"pre\");\n },\n blockquote(token) {\n return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"blockquote\");\n },\n listitem(token) {\n return addAttrs(listItemRenderer.call(this, token), buildAttrs(token as ListItemWithMrsf, options.lineHighlight ?? false), \"li\");\n },\n hr(token) {\n return addAttrs(hrRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"hr\");\n },\n table(token) {\n return renderAnnotatedTable.call(\n this,\n token as Tokens.Table & TokenWithMrsf,\n currentLineMap ?? new Map(),\n options.lineHighlight ?? false,\n );\n },\n },\n };\n };\n}", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\n const extensions: Record<`x_${string}`, unknown> = {};\n\n for (const [key, value] of Object.entries(comment)) {\n if (key.startsWith(\"x_\")) {\n extensions[key as `x_${string}`] = value;\n }\n }\n\n return extensions;\n}\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n ...getExtensionFields(c as Record<string, unknown>),\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "/**\n * Browser-safe entry point for @mrsf/marked-mrsf.\n */\n\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarkedMrsf } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport const markedMrsf = createMarkedMrsf((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath || options.documentPath) {\n console.warn(\n \"[@mrsf/marked-mrsf] sidecarPath and documentPath require Node.js. \" +\n \"Use `comments` or `loader` options in browser environments.\",\n );\n }\n\n return null;\n});\n\nexport default markedMrsf;"],
5
+ "mappings": "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",
6
6
  "names": ["markedMrsf"]
7
7
  }
@@ -27,6 +27,7 @@
27
27
  * - mrsf:submit { action, commentId, text?, line?, ... }
28
28
  */
29
29
  import type { CommentThread } from "./types.js";
30
+ import type { MrsfGutterRenderers } from "./gutter.js";
30
31
  export type MrsfAction = "resolve" | "unresolve" | "reply" | "edit" | "delete" | "navigate" | "add";
31
32
  export interface MrsfActionDetail {
32
33
  commentId: string | null;
@@ -63,6 +64,7 @@ export interface MrsfControllerOptions {
63
64
  * Default: true.
64
65
  */
65
66
  inlineHighlights?: boolean;
67
+ gutterRenderers?: MrsfGutterRenderers;
66
68
  }
67
69
  export declare class MrsfController {
68
70
  private container;
@@ -103,6 +105,8 @@ export declare class MrsfController {
103
105
  private addVisibleLinesForElement;
104
106
  /** Collect all unique line numbers from data-mrsf-line elements, expanding multi-line ranges. */
105
107
  private collectLines;
108
+ private resolveThreadDisplayLine;
109
+ private collectThreadDisplayMap;
106
110
  private createBadgeItem;
107
111
  private createAddItem;
108
112
  private createAddButton;
@@ -1,3 +1,59 @@
1
+ // src/gutter.ts
2
+ function formatMrsfCount(count, max = 9) {
3
+ return count > max ? `${max}+` : String(count);
4
+ }
5
+ function createMrsfGutterBadgePresentation(context) {
6
+ const icon = context.resolvedState === "resolved" ? "\u2713" : "\u{1F4AC}";
7
+ const countText = formatMrsfCount(context.commentCount);
8
+ const label = `${icon} ${countText}`;
9
+ const threadSummary = context.threadCount === 1 ? "1 thread" : `${context.threadCount} threads`;
10
+ const commentSummary = context.commentCount === 1 ? "1 comment" : `${context.commentCount} comments`;
11
+ return {
12
+ icon,
13
+ countText,
14
+ label,
15
+ title: `${threadSummary}, ${commentSummary}`,
16
+ ariaLabel: `${label} on line ${context.line}`
17
+ };
18
+ }
19
+ function createMrsfGutterAddButtonPresentation(_context) {
20
+ return {
21
+ label: "Add",
22
+ title: "Add comment thread",
23
+ ariaLabel: "Add comment thread"
24
+ };
25
+ }
26
+ function resolveMrsfGutterBadgePresentation(context, renderer) {
27
+ const defaultPresentation = createMrsfGutterBadgePresentation(context);
28
+ const override = renderer?.({
29
+ ...context,
30
+ defaultPresentation
31
+ });
32
+ return {
33
+ ...defaultPresentation,
34
+ ...override,
35
+ attributes: {
36
+ ...defaultPresentation.attributes ?? {},
37
+ ...override?.attributes ?? {}
38
+ }
39
+ };
40
+ }
41
+ function resolveMrsfGutterAddButtonPresentation(context, renderer) {
42
+ const defaultPresentation = createMrsfGutterAddButtonPresentation(context);
43
+ const override = renderer?.({
44
+ ...context,
45
+ defaultPresentation
46
+ });
47
+ return {
48
+ ...defaultPresentation,
49
+ ...override,
50
+ attributes: {
51
+ ...defaultPresentation.attributes ?? {},
52
+ ...override?.attributes ?? {}
53
+ }
54
+ };
55
+ }
56
+
1
57
  // src/html.ts
2
58
  function escapeHtml(str) {
3
59
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -96,7 +152,8 @@ var MrsfController = class _MrsfController {
96
152
  gutterPosition: options.gutterPosition ?? "right",
97
153
  interactive: options.interactive ?? false,
98
154
  comments: options.comments ?? [],
99
- inlineHighlights: options.inlineHighlights ?? true
155
+ inlineHighlights: options.inlineHighlights ?? true,
156
+ gutterRenderers: options.gutterRenderers ?? {}
100
157
  };
101
158
  this.loadCommentData();
102
159
  this.setupOverlayStructure();
@@ -206,10 +263,11 @@ var MrsfController = class _MrsfController {
206
263
  /** Build badge/add-button elements for each line in the gutter(s). */
207
264
  renderGutterItems() {
208
265
  const lines = this.collectLines();
266
+ const displayMap = this.collectThreadDisplayMap(lines);
209
267
  const gutter = this.primaryGutter();
210
268
  if (!gutter) return;
211
269
  for (const line of lines) {
212
- const threads = this.threads.get(line);
270
+ const threads = displayMap.get(line);
213
271
  if (threads && threads.length > 0) {
214
272
  const item = this.createBadgeItem(line, threads);
215
273
  gutter.appendChild(item);
@@ -256,6 +314,31 @@ var MrsfController = class _MrsfController {
256
314
  }
257
315
  return [...seen].sort((a, b) => a - b);
258
316
  }
317
+ resolveThreadDisplayLine(thread, availableLines) {
318
+ const startLine = thread.comment.line;
319
+ if (startLine == null) return null;
320
+ const endLine = thread.comment.end_line != null && thread.comment.end_line >= startLine ? thread.comment.end_line : startLine;
321
+ for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
322
+ if (availableLines.has(currentLine)) {
323
+ return currentLine;
324
+ }
325
+ }
326
+ return null;
327
+ }
328
+ collectThreadDisplayMap(lines) {
329
+ const availableLines = new Set(lines);
330
+ const displayMap = /* @__PURE__ */ new Map();
331
+ for (const threads of this.threads.values()) {
332
+ for (const thread of threads) {
333
+ const displayLine = this.resolveThreadDisplayLine(thread, availableLines);
334
+ if (displayLine == null) continue;
335
+ const existing = displayMap.get(displayLine) ?? [];
336
+ existing.push(thread);
337
+ displayMap.set(displayLine, existing);
338
+ }
339
+ }
340
+ return displayMap;
341
+ }
259
342
  createBadgeItem(line, threads) {
260
343
  const item = document.createElement("div");
261
344
  item.className = "mrsf-gutter-item";
@@ -274,14 +357,32 @@ var MrsfController = class _MrsfController {
274
357
  if (highestSeverity === "high" || highestSeverity === "medium") {
275
358
  classes.push(`mrsf-badge-severity-${highestSeverity}`);
276
359
  }
277
- const icon = allResolved ? "\u2713" : "\u{1F4AC}";
360
+ const presentation = resolveMrsfGutterBadgePresentation(
361
+ {
362
+ line,
363
+ commentCount: total,
364
+ threadCount: threads.length,
365
+ resolvedState: allResolved ? "resolved" : threads.some((thread) => thread.comment.resolved) ? "mixed" : "open",
366
+ highestSeverity,
367
+ isActive: this.activeTooltip?.parentElement === item
368
+ },
369
+ this.opts.gutterRenderers.badge
370
+ );
278
371
  const badge = document.createElement("span");
279
372
  badge.className = classes.join(" ");
373
+ if (presentation.className) {
374
+ badge.classList.add(...presentation.className.split(/\s+/).filter(Boolean));
375
+ }
280
376
  badge.dataset.mrsfLine = String(line);
281
377
  badge.dataset.mrsfAction = "navigate";
282
378
  badge.dataset.mrsfCommentId = displayThreads[0].comment.id;
283
379
  badge.tabIndex = 0;
284
- badge.textContent = `${icon} ${total}`;
380
+ badge.textContent = presentation.label;
381
+ badge.title = presentation.title;
382
+ badge.setAttribute("aria-label", presentation.ariaLabel);
383
+ for (const [name, value] of Object.entries(presentation.attributes ?? {})) {
384
+ badge.setAttribute(name, value);
385
+ }
285
386
  badge.addEventListener("click", (e) => {
286
387
  e.stopPropagation();
287
388
  this.toggleTooltip(item, line, displayThreads);
@@ -298,15 +399,29 @@ var MrsfController = class _MrsfController {
298
399
  return item;
299
400
  }
300
401
  createAddButton(line) {
402
+ const presentation = resolveMrsfGutterAddButtonPresentation(
403
+ {
404
+ line,
405
+ isActive: false
406
+ },
407
+ this.opts.gutterRenderers.addButton
408
+ );
301
409
  const btn = document.createElement("button");
302
410
  btn.className = "mrsf-gutter-add";
411
+ if (presentation.className) {
412
+ btn.classList.add(...presentation.className.split(/\s+/).filter(Boolean));
413
+ }
303
414
  btn.type = "button";
304
415
  btn.dataset.mrsfAction = "add";
305
416
  btn.dataset.mrsfLine = String(line);
306
417
  btn.dataset.mrsfStartLine = String(line);
307
418
  btn.dataset.mrsfEndLine = String(line);
308
- btn.setAttribute("aria-label", "Add comment");
309
- btn.textContent = "Add";
419
+ btn.title = presentation.title;
420
+ btn.setAttribute("aria-label", presentation.ariaLabel);
421
+ btn.textContent = presentation.label;
422
+ for (const [name, value] of Object.entries(presentation.attributes ?? {})) {
423
+ btn.setAttribute(name, value);
424
+ }
310
425
  return btn;
311
426
  }
312
427
  // ── Positioning ─────────────────────────────────────────
@@ -442,9 +557,11 @@ var MrsfController = class _MrsfController {
442
557
  const lines = this.collectLines();
443
558
  const lineSet = new Set(lines);
444
559
  const orphanedThreads = [];
445
- for (const [line, threads] of this.threads) {
446
- if (!lineSet.has(line)) {
447
- orphanedThreads.push(...threads);
560
+ for (const threads of this.threads.values()) {
561
+ for (const thread of threads) {
562
+ if (this.resolveThreadDisplayLine(thread, lineSet) == null) {
563
+ orphanedThreads.push(thread);
564
+ }
448
565
  }
449
566
  }
450
567
  if (orphanedThreads.length === 0) return;
@@ -472,15 +589,18 @@ var MrsfController = class _MrsfController {
472
589
  */
473
590
  renderInlineHighlights() {
474
591
  if (!this.opts.inlineHighlights) return;
475
- for (const [line, threads] of this.threads) {
592
+ const lineSet = new Set(this.collectLines());
593
+ for (const threads of this.threads.values()) {
476
594
  for (const thread of threads) {
477
595
  const comment = thread.comment;
478
596
  if (!comment.selected_text) continue;
597
+ const displayLine = this.resolveThreadDisplayLine(thread, lineSet);
598
+ if (displayLine == null) continue;
479
599
  const el = this.container.querySelector(
480
- `[data-mrsf-line="${line}"]:not(script):not(.mrsf-gutter):not(.mrsf-gutter-item)`
600
+ `[data-mrsf-line="${displayLine}"]:not(script):not(.mrsf-gutter):not(.mrsf-gutter-item)`
481
601
  );
482
602
  if (!el) continue;
483
- this.wrapSelectedText(el, comment.selected_text, thread);
603
+ this.wrapSelectedText(el, comment.selected_text, thread, displayLine);
484
604
  }
485
605
  }
486
606
  }
@@ -502,7 +622,7 @@ var MrsfController = class _MrsfController {
502
622
  * Walk text nodes inside `root` to find `text`, then wrap the matching
503
623
  * range in a `<mark>` element. Falls back to markdown-stripped matching.
504
624
  */
505
- wrapSelectedText(root, selectedText, thread) {
625
+ wrapSelectedText(root, selectedText, thread, displayLine) {
506
626
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
507
627
  let accumulated = "";
508
628
  const textNodes = [];
@@ -539,7 +659,7 @@ var MrsfController = class _MrsfController {
539
659
  const mark = document.createElement("mark");
540
660
  mark.className = "mrsf-inline-highlight";
541
661
  mark.dataset.mrsfCommentId = thread.comment.id;
542
- mark.dataset.mrsfLine = String(thread.comment.line);
662
+ mark.dataset.mrsfLine = String(displayLine);
543
663
  try {
544
664
  range.surroundContents(mark);
545
665
  } catch {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/html.ts", "../src/controller.ts"],
4
- "sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/html.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Shared HTML rendering helpers for MRSF rendering plugins.\n *\n * These produce the HTML strings used in tooltips, badges, and comment\n * rendering. Both the markdown-it and rehype plugins use these functions\n * to ensure identical visual output.\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function formatTime(iso: string | null): string {\n if (!iso) return \"\";\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n } catch {\n return \"\";\n }\n}\n\nexport function renderCommentHtml(\n comment: SlimComment,\n isReply: boolean,\n interactive: boolean,\n): string {\n const resolvedClass = comment.resolved ? \" mrsf-resolved\" : \"\";\n const replyClass = isReply ? \" mrsf-reply\" : \"\";\n let html = `<div class=\"mrsf-comment${resolvedClass}${replyClass}\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\">`;\n\n // Header\n html += `<div class=\"mrsf-comment-header\">`;\n html += `<span class=\"mrsf-author\">${escapeHtml(comment.author)}</span>`;\n if (comment.timestamp) {\n html += `<span class=\"mrsf-date\">${escapeHtml(formatTime(comment.timestamp))}</span>`;\n }\n if (comment.severity) {\n html += `<span class=\"mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}\">${escapeHtml(comment.severity)}</span>`;\n }\n if (comment.type) {\n html += `<span class=\"mrsf-type\">${escapeHtml(comment.type)}</span>`;\n }\n if (comment.resolved) {\n html += `<span class=\"mrsf-resolved-badge\">\u2713 resolved</span>`;\n }\n html += `</div>`;\n\n // Selected text quote (collapsible)\n if (comment.selected_text) {\n html += `<details class=\"mrsf-selected-text\"><summary class=\"mrsf-selected-text-summary\">${escapeHtml(comment.selected_text)}</summary><div class=\"mrsf-selected-text-full\">${escapeHtml(comment.selected_text)}</div></details>`;\n }\n\n // Body\n html += `<div class=\"mrsf-comment-body\">${escapeHtml(comment.text)}</div>`;\n\n // Action buttons (interactive mode)\n if (interactive) {\n const line = comment.line != null ? String(comment.line) : \"\";\n html += `<div class=\"mrsf-actions\">`;\n if (comment.resolved) {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"unresolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Unresolve</button>`;\n } else {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"resolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Resolve</button>`;\n }\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"reply\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Reply</button>`;\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"edit\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Edit</button>`;\n html += `<button class=\"mrsf-action-btn mrsf-action-danger\" data-mrsf-action=\"delete\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Delete</button>`;\n html += `</div>`;\n }\n\n html += `</div>`;\n return html;\n}\n\nexport function renderThreadHtml(thread: CommentThread, interactive: boolean): string {\n let html = `<div class=\"mrsf-thread\">`;\n html += renderCommentHtml(thread.comment, false, interactive);\n if (thread.replies.length > 0) {\n html += `<div class=\"mrsf-replies\">`;\n for (const reply of thread.replies) {\n html += renderCommentHtml(reply, true, interactive);\n }\n html += `</div>`;\n }\n html += `</div>`;\n return html;\n}\n", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/controller.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Sidemark \u2014 MrsfController: overlay gutter architecture.\n *\n * The controller is the runtime engine for interactive MRSF rendering.\n * It reads comment data from the DOM (embedded `<script type=\"application/mrsf+json\">`)\n * or constructor options, creates gutter overlay columns, positions\n * badges/buttons at the correct vertical offsets, and handles all\n * user interactions (tooltips, selection, action buttons).\n *\n * Usage:\n * import { MrsfController } from \"@mrsf/plugin-shared/controller\";\n *\n * const ctrl = new MrsfController(document.querySelector(\".my-content\")!, {\n * interactive: true,\n * gutterPosition: \"left\",\n * });\n * // ctrl.destroy() to clean up\n *\n * Events dispatched on document:\n * - mrsf:resolve { commentId, line, ... }\n * - mrsf:unresolve { commentId, line, ... }\n * - mrsf:reply { commentId, line, ... }\n * - mrsf:edit { commentId, line, ... }\n * - mrsf:delete { commentId, line, ... }\n * - mrsf:navigate { commentId, line, ... }\n * - mrsf:add { commentId: null, line, selectionText?, ... }\n * - mrsf:submit { action, commentId, text?, line?, ... }\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\nimport { renderThreadHtml, escapeHtml } from \"./html.js\";\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MrsfAction = \"resolve\" | \"unresolve\" | \"reply\" | \"edit\" | \"delete\" | \"navigate\" | \"add\";\n\nexport interface MrsfActionDetail {\n commentId: string | null;\n line: number | null;\n action: MrsfAction;\n selectionText?: string | null;\n start_line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n}\n\nexport interface MrsfSubmitDetail {\n action: \"add\" | \"edit\" | \"reply\" | \"resolve\" | \"unresolve\" | \"delete\";\n commentId: string | null;\n text: string;\n type?: string | null;\n severity?: \"low\" | \"medium\" | \"high\" | null;\n line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n selection_text?: string | null;\n}\n\nexport interface MrsfControllerOptions {\n /** Show gutter on left or right side. Default: \"right\". */\n gutterPosition?: \"left\" | \"right\";\n /** Enable interactive actions (add, resolve, reply, etc.). Default: false. */\n interactive?: boolean;\n /** Comment data passed directly (overrides embedded script). */\n comments?: CommentThread[];\n /**\n * Render inline text highlights for comments that have `selected_text`.\n * Wraps matching text in `<mark>` elements with hover tooltips.\n * Default: true.\n */\n inlineHighlights?: boolean;\n}\n\n// \u2500\u2500 MrsfController \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MrsfController {\n private container: HTMLElement;\n private opts: Required<MrsfControllerOptions>;\n private threads: Map<number, CommentThread[]> = new Map();\n private gutterLeft: HTMLDivElement | null = null;\n private gutterRight: HTMLDivElement | null = null;\n private activeTooltip: HTMLElement | null = null;\n private floatingAddButton: HTMLButtonElement | null = null;\n private overlayEl: HTMLDivElement | null = null;\n private lastSelectionText: string | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private styleInjected = false;\n private inlineMarks: HTMLElement[] = [];\n private inlineTooltipEl: HTMLElement | null = null;\n private orphanedSection: HTMLDivElement | null = null;\n private refreshQueued = false;\n\n private handleResizeBound = this.positionGutterItems.bind(this);\n private handleMutationBound = this.handleMutations.bind(this);\n private handleClickBound = this.handleClick.bind(this);\n private handleSelectionBound = this.handleSelectionChange.bind(this);\n\n constructor(container: HTMLElement, options: MrsfControllerOptions = {}) {\n this.container = container;\n this.opts = {\n gutterPosition: options.gutterPosition ?? \"right\",\n interactive: options.interactive ?? false,\n comments: options.comments ?? [],\n inlineHighlights: options.inlineHighlights ?? true,\n };\n\n this.loadCommentData();\n this.setupOverlayStructure();\n this.renderGutterItems();\n this.positionGutterItems();\n this.renderInlineHighlights();\n this.renderOrphanedSection();\n\n controllerRegistry.add(this);\n\n // Listeners\n this.resizeObserver = new ResizeObserver(this.handleResizeBound);\n this.resizeObserver.observe(this.container);\n if (typeof MutationObserver !== \"undefined\") {\n this.mutationObserver = new MutationObserver(this.handleMutationBound);\n this.mutationObserver.observe(this.container, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n document.addEventListener(\"click\", this.handleClickBound);\n if (this.opts.interactive) {\n document.addEventListener(\"selectionchange\", this.handleSelectionBound);\n }\n }\n\n /** Remove all controller DOM and listeners. */\n destroy(): void {\n this.resizeObserver?.disconnect();\n this.mutationObserver?.disconnect();\n controllerRegistry.delete(this);\n document.removeEventListener(\"click\", this.handleClickBound);\n document.removeEventListener(\"selectionchange\", this.handleSelectionBound);\n this.removeInlineHighlights();\n this.orphanedSection?.remove();\n this.gutterLeft?.remove();\n this.gutterRight?.remove();\n this.floatingAddButton?.remove();\n this.closeOverlay();\n this.container.classList.remove(\"mrsf-overlay-root\");\n }\n\n /** Recalculate gutter positions after async layout changes such as Mermaid renders. */\n refresh(): void {\n this.positionGutterItems();\n }\n\n // \u2500\u2500 Data loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private loadCommentData(): void {\n // Priority: constructor options > embedded script\n if (this.opts.comments.length > 0) {\n this.buildThreadMap(this.opts.comments);\n return;\n }\n\n const script = this.container.querySelector('script[type=\"application/mrsf+json\"]');\n if (!script?.textContent) return;\n\n try {\n const data = JSON.parse(script.textContent) as { threads?: CommentThread[] };\n if (data.threads) {\n this.buildThreadMap(data.threads);\n }\n } catch {\n // silently ignore malformed data\n }\n }\n\n private buildThreadMap(threads: CommentThread[]): void {\n this.threads.clear();\n for (const t of threads) {\n const line = t.comment.line;\n if (line == null) continue;\n const arr = this.threads.get(line) ?? [];\n arr.push(t);\n this.threads.set(line, arr);\n }\n }\n\n private findCommentById(commentId: string | null): SlimComment | null {\n if (!commentId) return null;\n\n for (const threadList of this.threads.values()) {\n for (const thread of threadList) {\n if (thread.comment.id === commentId) {\n return thread.comment;\n }\n const reply = thread.replies.find((item) => item.id === commentId);\n if (reply) {\n return reply;\n }\n }\n }\n\n return null;\n }\n\n private orderThreadsForDisplay(threads: CommentThread[]): CommentThread[] {\n return [...threads].sort((left, right) => {\n if (left.comment.resolved === right.comment.resolved) return 0;\n return left.comment.resolved ? 1 : -1;\n });\n }\n\n // \u2500\u2500 Overlay structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setupOverlayStructure(): void {\n this.container.classList.add(\"mrsf-overlay-root\");\n\n const pos = this.opts.gutterPosition;\n if (pos === \"left\") {\n this.gutterLeft = this.createGutter(\"mrsf-gutter-left\");\n } else {\n this.gutterRight = this.createGutter(\"mrsf-gutter-right\");\n }\n }\n\n private createGutter(cls: string): HTMLDivElement {\n const gutter = document.createElement(\"div\");\n gutter.className = `mrsf-gutter ${cls}`;\n this.container.appendChild(gutter);\n return gutter;\n }\n\n // \u2500\u2500 Gutter rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Build badge/add-button elements for each line in the gutter(s). */\n private renderGutterItems(): void {\n const lines = this.collectLines();\n const gutter = this.primaryGutter();\n if (!gutter) return;\n\n for (const line of lines) {\n const threads = this.threads.get(line);\n if (threads && threads.length > 0) {\n const item = this.createBadgeItem(line, threads);\n gutter.appendChild(item);\n } else if (this.opts.interactive) {\n const item = this.createAddItem(line);\n gutter.appendChild(item);\n }\n }\n }\n\n private primaryGutter(): HTMLDivElement | null {\n return this.gutterLeft ?? this.gutterRight;\n }\n\n private shouldExpandRange(el: HTMLElement): boolean {\n return el.tagName === \"BLOCKQUOTE\" || el.tagName === \"PRE\";\n }\n\n private addVisibleLinesForElement(el: HTMLElement, seen: Set<number>): void {\n const line = parseInt(el.dataset.mrsfLine ?? \"\", 10);\n const startLine = parseInt(el.dataset.mrsfStartLine ?? \"\", 10);\n const endLine = parseInt(el.dataset.mrsfEndLine ?? \"\", 10);\n\n if (el.tagName === \"PRE\" && !isNaN(startLine) && !isNaN(endLine) && endLine > startLine) {\n const visibleStart = startLine + 1;\n const visibleEnd = endLine - 1;\n for (let currentLine = visibleStart; currentLine <= visibleEnd; currentLine++) {\n seen.add(currentLine);\n }\n return;\n }\n\n if (!isNaN(line)) {\n seen.add(line);\n }\n\n if (\n this.shouldExpandRange(el) &&\n !isNaN(startLine) &&\n !isNaN(endLine) &&\n endLine > startLine\n ) {\n for (let currentLine = startLine; currentLine <= endLine; currentLine++) {\n seen.add(currentLine);\n }\n }\n }\n\n /** Collect all unique line numbers from data-mrsf-line elements, expanding multi-line ranges. */\n private collectLines(): number[] {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-line]\");\n const seen = new Set<number>();\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n this.addVisibleLinesForElement(el, seen);\n }\n\n return [...seen].sort((a, b) => a - b);\n }\n\n private createBadgeItem(line: number, threads: CommentThread[]): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const displayThreads = this.orderThreadsForDisplay(threads);\n\n const total = threads.reduce((n, t) => n + 1 + t.replies.length, 0);\n const allResolved = threads.every((t) => t.comment.resolved);\n const highestSeverity = threads.reduce<string | null>((sev, t) => {\n if (t.comment.severity === \"high\" || sev === \"high\") return \"high\";\n if (t.comment.severity === \"medium\" || sev === \"medium\") return \"medium\";\n if (t.comment.severity === \"low\" || sev === \"low\") return \"low\";\n return sev;\n }, null);\n\n const classes = [\"mrsf-badge\"];\n if (allResolved) classes.push(\"mrsf-badge-resolved\");\n if (highestSeverity === \"high\" || highestSeverity === \"medium\") {\n classes.push(`mrsf-badge-severity-${highestSeverity}`);\n }\n\n const icon = allResolved ? \"\u2713\" : \"\uD83D\uDCAC\";\n const badge = document.createElement(\"span\");\n badge.className = classes.join(\" \");\n badge.dataset.mrsfLine = String(line);\n badge.dataset.mrsfAction = \"navigate\";\n badge.dataset.mrsfCommentId = displayThreads[0].comment.id;\n badge.tabIndex = 0;\n badge.textContent = `${icon} ${total}`;\n badge.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleTooltip(item, line, displayThreads);\n });\n\n item.appendChild(badge);\n\n return item;\n }\n\n private createAddItem(line: number): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const addBtn = this.createAddButton(line);\n item.appendChild(addBtn);\n return item;\n }\n\n private createAddButton(line: number): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.className = \"mrsf-gutter-add\";\n btn.type = \"button\";\n btn.dataset.mrsfAction = \"add\";\n btn.dataset.mrsfLine = String(line);\n btn.dataset.mrsfStartLine = String(line);\n btn.dataset.mrsfEndLine = String(line);\n btn.setAttribute(\"aria-label\", \"Add comment\");\n btn.textContent = \"Add\";\n return btn;\n }\n\n // \u2500\u2500 Positioning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Measure [data-mrsf-line] elements and set gutter item Y offsets. */\n positionGutterItems(): void {\n const containerRect = this.container.getBoundingClientRect();\n\n const gutters = [this.gutterLeft, this.gutterRight].filter(Boolean) as HTMLDivElement[];\n for (const gutter of gutters) {\n const items = gutter.querySelectorAll<HTMLDivElement>(\".mrsf-gutter-item\");\n const positioned: Array<{ item: HTMLDivElement; target: HTMLElement; kind: \"badge\" | \"add\" }> = [];\n for (const item of items) {\n const line = parseInt(item.dataset.mrsfGutterLine!, 10);\n // For expanded multi-line elements, find the rendered content element whose line data contains this line.\n let target = this.findDirectElementForLine(line);\n if (!target) {\n target = this.findElementForLine(line);\n }\n if (!target) {\n item.style.display = \"none\";\n continue;\n }\n const top = this.calculateItemTop(target, line, containerRect);\n item.style.top = `${top}px`;\n item.style.display = \"\";\n positioned.push({\n item,\n target,\n kind: item.querySelector(\".mrsf-badge\") ? \"badge\" : \"add\",\n });\n }\n\n this.suppressAddItemsThatShareBadgeTargets(positioned);\n }\n }\n\n private suppressAddItemsThatShareBadgeTargets(\n positioned: Array<{ item: HTMLDivElement; target: HTMLElement; kind: \"badge\" | \"add\" }>,\n ): void {\n const badgeTargets = new Set(\n positioned\n .filter((entry) => entry.kind === \"badge\")\n .map((entry) => entry.target),\n );\n\n for (const entry of positioned) {\n if (entry.kind === \"add\" && badgeTargets.has(entry.target)) {\n entry.item.style.display = \"none\";\n }\n }\n }\n\n private handleMutations(records: MutationRecord[]): void {\n if (!records.some((record) => this.isExternalContentMutation(record))) {\n return;\n }\n this.queueRefresh();\n }\n\n private isExternalContentMutation(record: MutationRecord): boolean {\n const target = record.target instanceof Node ? record.target : null;\n if (target && this.isControllerOwnedNode(target)) {\n return false;\n }\n\n for (const node of [...record.addedNodes, ...record.removedNodes]) {\n if (!this.isControllerOwnedNode(node)) {\n return true;\n }\n }\n\n return record.type === \"characterData\";\n }\n\n private isControllerOwnedNode(node: Node): boolean {\n if (!(node instanceof Element)) {\n return false;\n }\n return Boolean(\n node.closest(\".mrsf-gutter\") ||\n node.closest(\".mrsf-orphaned-section\") ||\n node.closest(\"script[type=\\\"application/mrsf+json\\\"]\"),\n );\n }\n\n private queueRefresh(): void {\n if (this.refreshQueued) return;\n this.refreshQueued = true;\n queueMicrotask(() => {\n this.refreshQueued = false;\n this.refresh();\n });\n }\n\n private findDirectElementForLine(line: number): HTMLElement | null {\n const els = this.container.querySelectorAll<HTMLElement>(`[data-mrsf-line=\"${line}\"]`);\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n if (el.closest(\".mrsf-gutter\")) continue;\n if (el.classList.contains(\"mrsf-gutter-item\")) continue;\n return el;\n }\n return null;\n }\n\n private calculateItemTop(target: HTMLElement, line: number, containerRect: DOMRect): number {\n const targetRect = target.getBoundingClientRect();\n const rangeTop = targetRect.top - containerRect.top + this.container.scrollTop;\n\n if (target.tagName !== \"PRE\") {\n return rangeTop;\n }\n\n const startLine = parseInt(target.dataset.mrsfStartLine ?? \"\", 10);\n const endLine = parseInt(target.dataset.mrsfEndLine ?? \"\", 10);\n const visibleStart = startLine + 1;\n const visibleEnd = endLine - 1;\n const visibleLineCount = visibleEnd - visibleStart + 1;\n\n if (\n isNaN(startLine) ||\n isNaN(endLine) ||\n visibleLineCount <= 0 ||\n line < visibleStart ||\n line > visibleEnd\n ) {\n return rangeTop;\n }\n\n const styles = window.getComputedStyle(target);\n const paddingTop = parseFloat(styles.paddingTop) || 0;\n const paddingBottom = parseFloat(styles.paddingBottom) || 0;\n const contentHeight = Math.max(targetRect.height - paddingTop - paddingBottom, 0);\n\n let lineHeight = parseFloat(styles.lineHeight);\n if (!Number.isFinite(lineHeight) || lineHeight <= 0) {\n lineHeight = visibleLineCount > 0 ? contentHeight / visibleLineCount : 0;\n }\n\n return rangeTop + paddingTop + (line - visibleStart) * lineHeight;\n }\n\n /**\n * Find the element whose start-line/end-line range contains the given line.\n * Used for positioning gutter items on expanded multi-line elements.\n */\n private findElementForLine(line: number): HTMLElement | null {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-start-line][data-mrsf-end-line]\");\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n const start = parseInt(el.dataset.mrsfStartLine!, 10);\n const end = parseInt(el.dataset.mrsfEndLine!, 10);\n if (!isNaN(start) && !isNaN(end) && line >= start && line <= end) {\n return el;\n }\n }\n return null;\n }\n\n // \u2500\u2500 Orphaned comments section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Render orphaned comments (whose line doesn't match any DOM element)\n * in a dedicated section at the bottom of the container.\n */\n private renderOrphanedSection(): void {\n const lines = this.collectLines();\n const lineSet = new Set(lines);\n const orphanedThreads: CommentThread[] = [];\n\n for (const [line, threads] of this.threads) {\n if (!lineSet.has(line)) {\n orphanedThreads.push(...threads);\n }\n }\n\n if (orphanedThreads.length === 0) return;\n\n const section = document.createElement(\"div\");\n section.className = \"mrsf-orphaned-section\";\n\n const heading = document.createElement(\"div\");\n heading.className = \"mrsf-orphaned-heading\";\n heading.textContent = `Orphaned Comments (${orphanedThreads.length})`;\n section.appendChild(heading);\n\n const interactive = this.opts.interactive;\n for (const thread of this.orderThreadsForDisplay(orphanedThreads)) {\n const wrapper = document.createElement(\"div\");\n wrapper.className = interactive\n ? \"mrsf-orphaned-thread mrsf-interactive\"\n : \"mrsf-orphaned-thread\";\n wrapper.innerHTML = renderThreadHtml(thread, interactive);\n section.appendChild(wrapper);\n }\n\n this.container.appendChild(section);\n this.orphanedSection = section;\n }\n\n // \u2500\u2500 Inline text highlights \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * For comments with `selected_text`, find the matching text in the DOM\n * and wrap it in a `<mark class=\"mrsf-inline-highlight\">` element with\n * hover/click behaviour to show the comment tooltip.\n */\n private renderInlineHighlights(): void {\n if (!this.opts.inlineHighlights) return;\n\n for (const [line, threads] of this.threads) {\n for (const thread of threads) {\n const comment = thread.comment;\n if (!comment.selected_text) continue;\n\n const el = this.container.querySelector<HTMLElement>(\n `[data-mrsf-line=\"${line}\"]:not(script):not(.mrsf-gutter):not(.mrsf-gutter-item)`,\n );\n if (!el) continue;\n\n this.wrapSelectedText(el, comment.selected_text, thread);\n }\n }\n }\n\n /**\n * Strip common inline markdown syntax so `selected_text` from source\n * can be matched against rendered text content.\n */\n private static stripInlineMarkdown(text: string): string {\n let s = text;\n // Backtick code spans: `code` \u2192 code\n s = s.replace(/`([^`]+)`/g, \"$1\");\n // Bold: **text** or __text__\n s = s.replace(/\\*\\*(.+?)\\*\\*/g, \"$1\");\n s = s.replace(/__(.+?)__/g, \"$1\");\n // Italic: *text* or _text_\n s = s.replace(/\\*(.+?)\\*/g, \"$1\");\n s = s.replace(/_(.+?)_/g, \"$1\");\n // Strikethrough: ~~text~~\n s = s.replace(/~~(.+?)~~/g, \"$1\");\n return s;\n }\n\n /**\n * Walk text nodes inside `root` to find `text`, then wrap the matching\n * range in a `<mark>` element. Falls back to markdown-stripped matching.\n */\n private wrapSelectedText(\n root: HTMLElement,\n selectedText: string,\n thread: CommentThread,\n ): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n let accumulated = \"\";\n const textNodes: { node: Text; start: number; end: number }[] = [];\n\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null)) {\n const start = accumulated.length;\n accumulated += node.textContent || \"\";\n textNodes.push({ node, start, end: accumulated.length });\n }\n\n // Try exact match first, then stripped markdown\n let matchStart = accumulated.indexOf(selectedText);\n let matchLen = selectedText.length;\n if (matchStart === -1) {\n const stripped = MrsfController.stripInlineMarkdown(selectedText);\n if (stripped !== selectedText) {\n matchStart = accumulated.indexOf(stripped);\n matchLen = stripped.length;\n }\n }\n if (matchStart === -1) return;\n\n const matchEnd = matchStart + matchLen;\n\n // Build a Range spanning the matched text nodes\n const range = document.createRange();\n let startSet = false;\n\n for (const tn of textNodes) {\n if (!startSet && tn.end > matchStart) {\n range.setStart(tn.node, matchStart - tn.start);\n startSet = true;\n }\n if (startSet && tn.end >= matchEnd) {\n range.setEnd(tn.node, matchEnd - tn.start);\n break;\n }\n }\n\n if (!startSet) return;\n\n const mark = document.createElement(\"mark\");\n mark.className = \"mrsf-inline-highlight\";\n mark.dataset.mrsfCommentId = thread.comment.id;\n mark.dataset.mrsfLine = String(thread.comment.line);\n\n try {\n range.surroundContents(mark);\n } catch {\n // Range crosses element boundaries \u2014 extract and re-insert\n const fragment = range.extractContents();\n mark.appendChild(fragment);\n range.insertNode(mark);\n }\n\n this.inlineMarks.push(mark);\n\n // Hover shows tooltip inline\n mark.addEventListener(\"mouseenter\", () => {\n this.showInlineTooltip(mark, thread);\n });\n mark.addEventListener(\"mouseleave\", (e) => {\n // Don't hide if moving into the tooltip itself\n const related = (e as MouseEvent).relatedTarget as HTMLElement | null;\n if (related && this.inlineTooltipEl?.contains(related)) return;\n this.scheduleHideInlineTooltip();\n });\n\n // Click toggles tooltip (for touch / accessibility)\n mark.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (this.inlineTooltipEl && this.inlineTooltipEl.dataset.mrsfForMark === thread.comment.id) {\n this.hideInlineTooltip();\n } else {\n this.showInlineTooltip(mark, thread);\n }\n });\n }\n\n private showInlineTooltip(mark: HTMLElement, thread: CommentThread): void {\n this.hideInlineTooltip();\n\n const tooltip = document.createElement(\"div\");\n tooltip.className = this.opts.interactive\n ? \"mrsf-inline-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-inline-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfForMark = thread.comment.id;\n tooltip.innerHTML = renderThreadHtml(thread, this.opts.interactive);\n this.applyThemeVariables(tooltip);\n\n // Let user mouse into tooltip without it disappearing\n tooltip.addEventListener(\"mouseenter\", () => {\n this.cancelHideInlineTooltip();\n });\n tooltip.addEventListener(\"mouseleave\", () => {\n this.hideInlineTooltip();\n });\n\n // Append to body with fixed positioning to avoid clipping\n document.body.appendChild(tooltip);\n this.inlineTooltipEl = tooltip;\n\n // Position relative to the mark element\n const rect = mark.getBoundingClientRect();\n const margin = 4;\n const tooltipH = tooltip.offsetHeight;\n\n // Prefer below; flip above if not enough space at bottom\n if (rect.bottom + margin + tooltipH > window.innerHeight) {\n tooltip.style.top = `${rect.top - tooltipH - margin}px`;\n } else {\n tooltip.style.top = `${rect.bottom + margin}px`;\n }\n tooltip.style.left = `${Math.max(0, rect.left)}px`;\n }\n\n private applyThemeVariables(el: HTMLElement): void {\n const styles = window.getComputedStyle(this.container);\n const themeVars = [\n \"--mrsf-accent\",\n \"--mrsf-badge-bg\",\n \"--mrsf-badge-fg\",\n \"--mrsf-badge-resolved-bg\",\n \"--mrsf-add-bg\",\n \"--mrsf-add-fg\",\n \"--mrsf-add-border\",\n \"--mrsf-tooltip-bg\",\n \"--mrsf-tooltip-fg\",\n \"--mrsf-tooltip-border\",\n \"--mrsf-highlight-bg\",\n \"--mrsf-highlight-border\",\n \"--mrsf-severity-high\",\n \"--mrsf-severity-medium\",\n \"--mrsf-severity-low\",\n \"--mrsf-font-family\",\n ];\n\n for (const name of themeVars) {\n const value = styles.getPropertyValue(name).trim();\n if (value) {\n el.style.setProperty(name, value);\n }\n }\n }\n\n private hideInlineTimeout: ReturnType<typeof setTimeout> | null = null;\n\n private scheduleHideInlineTooltip(): void {\n this.hideInlineTimeout = setTimeout(() => this.hideInlineTooltip(), 120);\n }\n\n private cancelHideInlineTooltip(): void {\n if (this.hideInlineTimeout) {\n clearTimeout(this.hideInlineTimeout);\n this.hideInlineTimeout = null;\n }\n }\n\n private hideInlineTooltip(): void {\n this.cancelHideInlineTooltip();\n if (this.inlineTooltipEl) {\n this.inlineTooltipEl.remove();\n this.inlineTooltipEl = null;\n }\n }\n\n /** Remove all inline marks, unwrapping their contents back to text. */\n private removeInlineHighlights(): void {\n this.hideInlineTooltip();\n for (const mark of this.inlineMarks) {\n const parent = mark.parentNode;\n if (!parent) continue;\n while (mark.firstChild) {\n parent.insertBefore(mark.firstChild, mark);\n }\n parent.removeChild(mark);\n }\n this.inlineMarks = [];\n }\n\n // \u2500\u2500 Tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private toggleTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n // If already visible on this anchor, hide it\n if (this.activeTooltip && this.activeTooltip.parentElement === anchor) {\n this.hideTooltip();\n return;\n }\n this.hideTooltip();\n this.showTooltip(anchor, line, threads);\n }\n\n private showTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n const tooltip = document.createElement(\"div\");\n const interactive = this.opts.interactive;\n tooltip.className = interactive\n ? \"mrsf-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfLine = String(line);\n\n let html = \"\";\n for (const thread of this.orderThreadsForDisplay(threads)) {\n html += renderThreadHtml(thread, interactive);\n }\n if (interactive) {\n html += `<div class=\"mrsf-tooltip-actions\"><button class=\"mrsf-action-btn\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\">Add comment</button></div>`;\n }\n tooltip.innerHTML = html;\n\n anchor.appendChild(tooltip);\n this.activeTooltip = tooltip;\n }\n\n private hideTooltip(): void {\n if (this.activeTooltip) {\n this.activeTooltip.remove();\n this.activeTooltip = null;\n }\n }\n\n // \u2500\u2500 Click handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleClick(e: Event): void {\n const target = (e.target as HTMLElement).closest<HTMLElement>(\"[data-mrsf-action]\");\n\n // Close tooltip when clicking outside\n if (!target && this.activeTooltip) {\n const tooltipClick = (e.target as HTMLElement).closest(\".mrsf-tooltip\");\n if (!tooltipClick) {\n this.hideTooltip();\n }\n return;\n }\n if (!target) return;\n\n const action = target.dataset.mrsfAction as MrsfAction | undefined;\n if (!action) return;\n\n const commentId = target.dataset.mrsfCommentId ?? null;\n const lineStr = target.dataset.mrsfLine;\n const line = lineStr ? parseInt(lineStr, 10) : null;\n const selectionText = target.dataset.mrsfSelection ?? this.lastSelectionText ?? null;\n\n const startLineStr = target.dataset.mrsfStartLine;\n const endLineStr = target.dataset.mrsfEndLine;\n const startColStr = target.dataset.mrsfStartColumn;\n const endColStr = target.dataset.mrsfEndColumn;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : (line ?? null);\n const endLine = endLineStr ? parseInt(endLineStr, 10) : (line ?? null);\n const startColumn = startColStr ? parseInt(startColStr, 10) : null;\n const endColumn = endColStr ? parseInt(endColStr, 10) : null;\n\n e.preventDefault();\n e.stopPropagation();\n\n const detail: MrsfActionDetail = {\n commentId,\n line,\n action,\n selectionText,\n start_line: startLine,\n end_line: endLine,\n start_column: startColumn,\n end_column: endColumn,\n };\n\n if (action === \"add\" || action === \"edit\" || action === \"reply\") {\n if (action === \"add\") {\n this.hideFloatingAddButton();\n }\n this.openForm(action, detail);\n return;\n }\n\n if (action === \"resolve\" || action === \"unresolve\" || action === \"delete\") {\n this.openConfirm(action, detail);\n return;\n }\n\n document.dispatchEvent(\n new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }),\n );\n }\n\n // \u2500\u2500 Selection handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleSelectionChange(): void {\n const sel = document.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0) {\n this.hideFloatingAddButton();\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n this.hideFloatingAddButton();\n return;\n }\n\n const range = sel.getRangeAt(0);\n if (!this.selectionBelongsToContainer(range)) {\n this.hideFloatingAddButton();\n return;\n }\n\n const rect = range.getBoundingClientRect();\n if (!rect || (rect.width === 0 && rect.height === 0)) {\n this.hideFloatingAddButton();\n return;\n }\n\n const startAnchor = this.findSelectionAnchor(range.startContainer);\n const endAnchor = this.findSelectionAnchor(range.endContainer);\n if (!startAnchor || !endAnchor) {\n this.hideFloatingAddButton();\n return;\n }\n\n const startLineStr = startAnchor?.dataset.mrsfStartLine ?? startAnchor?.dataset.mrsfLine;\n const endLineStr = endAnchor?.dataset.mrsfEndLine ?? endAnchor?.dataset.mrsfLine ?? startLineStr;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : null;\n const endLine = endLineStr ? parseInt(endLineStr, 10) : startLine;\n\n const startColumn = range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startOffset : null;\n const endColumn = range.endContainer.nodeType === Node.TEXT_NODE\n ? range.endOffset : null;\n\n this.showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, text);\n }\n\n private selectionBelongsToContainer(range: Range): boolean {\n return this.container.contains(range.commonAncestorContainer)\n && this.container.contains(range.startContainer)\n && this.container.contains(range.endContainer);\n }\n\n private findSelectionAnchor(node: Node): HTMLElement | null {\n const element = node instanceof Element ? node : node.parentElement;\n const anchor = element?.closest<HTMLElement>(\"[data-mrsf-line]\") ?? null;\n return anchor && this.container.contains(anchor) ? anchor : null;\n }\n\n private ensureFloatingAddButton(): HTMLButtonElement {\n if (this.floatingAddButton) return this.floatingAddButton;\n const btn = document.createElement(\"button\");\n btn.textContent = \"Add comment\";\n btn.className = \"mrsf-add-inline-button\";\n btn.dataset.mrsfAction = \"add\";\n btn.style.display = \"none\";\n btn.style.position = \"absolute\";\n btn.style.zIndex = \"1200\";\n this.container.appendChild(btn);\n this.floatingAddButton = btn;\n return btn;\n }\n\n private hideFloatingAddButton(): void {\n if (!this.floatingAddButton) return;\n this.floatingAddButton.style.display = \"none\";\n this.floatingAddButton.dataset.mrsfLine = \"\";\n this.floatingAddButton.dataset.mrsfStartLine = \"\";\n this.floatingAddButton.dataset.mrsfEndLine = \"\";\n this.lastSelectionText = null;\n }\n\n private showFloatingAddButton(\n startLine: number | null,\n endLine: number | null,\n startColumn: number | null,\n endColumn: number | null,\n rect: DOMRect,\n selectionText: string,\n ): void {\n const btn = this.ensureFloatingAddButton();\n if (startLine != null) {\n btn.dataset.mrsfLine = String(startLine);\n btn.dataset.mrsfStartLine = String(startLine);\n btn.dataset.mrsfEndLine = String(endLine ?? startLine);\n } else {\n delete btn.dataset.mrsfLine;\n delete btn.dataset.mrsfStartLine;\n delete btn.dataset.mrsfEndLine;\n }\n if (startColumn != null) {\n btn.dataset.mrsfStartColumn = String(startColumn);\n } else {\n delete btn.dataset.mrsfStartColumn;\n }\n if (endColumn != null) {\n btn.dataset.mrsfEndColumn = String(endColumn);\n } else {\n delete btn.dataset.mrsfEndColumn;\n }\n this.lastSelectionText = selectionText;\n\n const margin = 6;\n const containerRect = this.container.getBoundingClientRect();\n btn.style.visibility = \"hidden\";\n btn.style.display = \"block\";\n const width = btn.offsetWidth || 0;\n const height = btn.offsetHeight || 0;\n\n const minTop = this.container.scrollTop;\n const maxTop = Math.max(minTop, minTop + this.container.clientHeight - height);\n const minLeft = this.container.scrollLeft;\n const maxLeft = Math.max(minLeft, minLeft + this.container.clientWidth - width);\n\n const preferredTop = rect.top - containerRect.top + this.container.scrollTop - height - margin;\n const fallbackTop = rect.bottom - containerRect.top + this.container.scrollTop + margin;\n const unclampedTop = preferredTop < minTop ? fallbackTop : preferredTop;\n const unclampedLeft = rect.left - containerRect.left + this.container.scrollLeft;\n\n const top = Math.min(Math.max(unclampedTop, minTop), maxTop);\n const left = Math.min(Math.max(unclampedLeft, minLeft), maxLeft);\n\n btn.style.top = `${top}px`;\n btn.style.left = `${left}px`;\n btn.style.visibility = \"visible\";\n }\n\n // \u2500\u2500 Dialog: form (add/edit/reply) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private injectStyles(): void {\n if (this.styleInjected) return;\n const css = `\n.mrsf-overlay { position: fixed; inset: 0; background: var(--mrsf-dialog-backdrop, rgba(15, 23, 42, 0.28)); z-index: 2000; display: flex; align-items: center; justify-content: center; padding: 12px; }\n.mrsf-dialog { background: var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); border-radius: 10px; width: min(420px, calc(100vw - 24px)); box-shadow: 0 18px 48px rgba(15, 23, 42, 0.24); font-family: var(--mrsf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif); font-size: 13px; overflow: hidden; }\n.mrsf-dialog header { padding: 10px 12px; font-weight: 600; line-height: 1.35; border-bottom: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); }\n.mrsf-dialog form { padding: 12px; display: flex; flex-direction: column; gap: 10px; }\n.mrsf-dialog-body { padding: 12px; line-height: 1.45; }\n.mrsf-field { display: flex; flex-direction: column; gap: 4px; }\n.mrsf-field label { font-size: 12px; color: var(--mrsf-dialog-muted, color-mix(in srgb, var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)) 72%, transparent)); }\n.mrsf-field input, .mrsf-field select, .mrsf-field textarea, .mrsf-field pre { background: var(--mrsf-field-bg, color-mix(in srgb, var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)) 88%, white)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); border-radius: 6px; padding: 7px 9px; font-size: 12px; line-height: 1.45; }\n.mrsf-field textarea { min-height: 76px; resize: vertical; }\n.mrsf-field select { min-height: 34px; }\n.mrsf-field pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }\n.mrsf-field input:focus, .mrsf-field select:focus, .mrsf-field textarea:focus { outline: 2px solid color-mix(in srgb, var(--mrsf-accent, #2563eb) 38%, transparent); outline-offset: 1px; }\n.mrsf-actions-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 0; padding: 10px 12px 12px; border-top: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); }\n.mrsf-btn { padding: 5px 10px; border-radius: 999px; border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); background: var(--mrsf-button-bg, color-mix(in srgb, var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)) 82%, white)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); cursor: pointer; font: inherit; line-height: 1.2; }\n.mrsf-btn-primary { background: var(--mrsf-button-primary-bg, var(--mrsf-accent, #2563eb)); border-color: var(--mrsf-button-primary-bg, var(--mrsf-accent, #2563eb)); color: #fff; }\n.mrsf-helper { font-size: 11px; color: var(--mrsf-dialog-muted, color-mix(in srgb, var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)) 72%, transparent)); }\n`;\n const style = document.createElement(\"style\");\n style.textContent = css;\n document.head.appendChild(style);\n this.styleInjected = true;\n }\n\n private closeOverlay(): void {\n if (this.overlayEl?.parentElement) {\n this.overlayEl.parentElement.removeChild(this.overlayEl);\n }\n this.overlayEl = null;\n }\n\n private openForm(action: \"add\" | \"edit\" | \"reply\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const sourceComment = action === \"edit\" ? this.findCommentById(detail.commentId) : null;\n const selText = detail.selectionText ?? sourceComment?.selected_text ?? \"\";\n const line = detail.line ?? detail.start_line ?? sourceComment?.line ?? null;\n const endLine = detail.end_line ?? detail.line ?? sourceComment?.end_line ?? sourceComment?.line ?? null;\n const startCol = detail.start_column ?? sourceComment?.start_column ?? null;\n const endCol = detail.end_column ?? sourceComment?.end_column ?? null;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n this.applyThemeVariables(overlay);\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent =\n action === \"add\" ? \"Add comment\" : action === \"edit\" ? \"Edit comment\" : \"Reply\";\n dialog.appendChild(header);\n\n const form = document.createElement(\"form\");\n\n const field = (labelText: string, inputEl: HTMLElement, helper?: string) => {\n const wrap = document.createElement(\"div\");\n wrap.className = \"mrsf-field\";\n const label = document.createElement(\"label\");\n label.textContent = labelText;\n wrap.appendChild(label);\n wrap.appendChild(inputEl);\n if (helper) {\n const h = document.createElement(\"div\");\n h.className = \"mrsf-helper\";\n h.textContent = helper;\n wrap.appendChild(h);\n }\n form.appendChild(wrap);\n };\n\n const textArea = document.createElement(\"textarea\");\n textArea.name = \"text\";\n textArea.required = true;\n textArea.value = action === \"edit\" ? (sourceComment?.text ?? \"\") : \"\";\n field(\"Comment text\", textArea);\n\n const typeSelect = document.createElement(\"select\");\n typeSelect.name = \"type\";\n [\"\", \"suggestion\", \"issue\", \"question\", \"accuracy\", \"style\", \"clarity\"].forEach((t) => {\n const opt = document.createElement(\"option\");\n opt.value = t;\n opt.textContent = t || \"(none)\";\n typeSelect.appendChild(opt);\n });\n typeSelect.value = action === \"edit\" ? (sourceComment?.type ?? \"\") : \"\";\n field(\"Type\", typeSelect, \"Optional\");\n\n const severitySelect = document.createElement(\"select\");\n severitySelect.name = \"severity\";\n [\"\", \"low\", \"medium\", \"high\"].forEach((s) => {\n const opt = document.createElement(\"option\");\n opt.value = s;\n opt.textContent = s || \"(none)\";\n severitySelect.appendChild(opt);\n });\n severitySelect.value = action === \"edit\" ? (sourceComment?.severity ?? \"\") : \"\";\n field(\"Severity\", severitySelect, \"Optional\");\n\n if (selText) {\n const pre = document.createElement(\"pre\");\n pre.textContent = selText;\n field(\"Selected text\", pre as unknown as HTMLElement, \"Captured automatically\");\n }\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const submitBtn = document.createElement(\"button\");\n submitBtn.type = \"submit\";\n submitBtn.className = \"mrsf-btn mrsf-btn-primary\";\n submitBtn.textContent = action === \"add\" ? \"Add\" : action === \"reply\" ? \"Reply\" : \"Save\";\n actions.appendChild(submitBtn);\n\n form.appendChild(actions);\n\n form.addEventListener(\"submit\", (ev) => {\n ev.preventDefault();\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: textArea.value.trim(),\n type: typeSelect.value || null,\n severity: (severitySelect.value as MrsfSubmitDetail[\"severity\"]) || null,\n line,\n end_line: endLine,\n start_column: startCol,\n end_column: endCol,\n selection_text: selText || null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n\n dialog.appendChild(form);\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n\n // \u2500\u2500 Dialog: confirm (resolve / unresolve / delete) \u2500\u2500\u2500\u2500\u2500\u2500\n\n private openConfirm(action: \"resolve\" | \"unresolve\" | \"delete\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n this.applyThemeVariables(overlay);\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent = action === \"delete\" ? \"Delete comment\" : \"Change status\";\n dialog.appendChild(header);\n\n const body = document.createElement(\"div\");\n body.className = \"mrsf-dialog-body\";\n body.textContent =\n action === \"delete\"\n ? \"Delete this comment?\"\n : action === \"resolve\"\n ? \"Mark this comment as resolved?\"\n : \"Mark this comment as unresolved?\";\n dialog.appendChild(body);\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const confirmBtn = document.createElement(\"button\");\n confirmBtn.type = \"button\";\n confirmBtn.className = \"mrsf-btn mrsf-btn-primary\";\n confirmBtn.textContent = action === \"delete\" ? \"Delete\" : \"Confirm\";\n confirmBtn.addEventListener(\"click\", () => {\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: \"\",\n type: null,\n severity: null,\n line: detail.line,\n end_line: detail.end_line ?? detail.line ?? null,\n start_column: detail.start_column ?? null,\n end_column: detail.end_column ?? null,\n selection_text: detail.selectionText ?? null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n actions.appendChild(confirmBtn);\n\n dialog.appendChild(actions);\n\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n}\n\n// \u2500\u2500 Auto-init convenience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Scan for containers with [data-mrsf-controller] and auto-init.\n\nconst controllerRegistry = new Set<MrsfController>();\nlet autoInitDone = false;\n\nexport function refreshAll(): void {\n for (const controller of controllerRegistry) {\n controller.refresh();\n }\n}\n\nexport function autoInit(): void {\n if (autoInitDone) return;\n autoInitDone = true;\n\n const containers = document.querySelectorAll<HTMLElement>(\"[data-mrsf-controller]\");\n for (const container of containers) {\n const pos = container.dataset.mrsfGutterPosition as MrsfControllerOptions[\"gutterPosition\"] | undefined;\n const interactive = container.dataset.mrsfInteractive === \"true\";\n new MrsfController(container, { gutterPosition: pos ?? \"right\", interactive });\n }\n}\n\n// Auto-init on DOMContentLoaded if in browser context\nif (typeof document !== \"undefined\") {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", autoInit);\n } else {\n autoInit();\n }\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/gutter.ts", "../src/html.ts", "../src/controller.ts"],
4
+ "sourcesContent": ["/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/gutter.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\nexport type MrsfResolvedState = \"open\" | \"resolved\" | \"mixed\";\n\nexport interface MrsfGutterBadgePresentation {\n icon: string;\n countText: string;\n label: string;\n title: string;\n ariaLabel: string;\n className?: string;\n attributes?: Record<string, string>;\n}\n\nexport interface MrsfGutterBadgeContext {\n line: number;\n commentCount: number;\n threadCount: number;\n resolvedState: MrsfResolvedState;\n highestSeverity: string | null;\n isActive: boolean;\n}\n\nexport interface MrsfGutterBadgeRenderContext extends MrsfGutterBadgeContext {\n defaultPresentation: MrsfGutterBadgePresentation;\n}\n\nexport interface MrsfGutterAddButtonPresentation {\n label: string;\n title: string;\n ariaLabel: string;\n className?: string;\n attributes?: Record<string, string>;\n}\n\nexport interface MrsfGutterAddButtonContext {\n line: number;\n isActive: boolean;\n}\n\nexport interface MrsfGutterAddButtonRenderContext extends MrsfGutterAddButtonContext {\n defaultPresentation: MrsfGutterAddButtonPresentation;\n}\n\nexport interface MrsfGutterRenderers {\n badge?: (\n context: MrsfGutterBadgeRenderContext,\n ) => Partial<MrsfGutterBadgePresentation> | null | undefined;\n addButton?: (\n context: MrsfGutterAddButtonRenderContext,\n ) => Partial<MrsfGutterAddButtonPresentation> | null | undefined;\n}\n\nexport interface MrsfBadgeSource {\n commentCount: number;\n threadCount: number;\n resolvedState: MrsfResolvedState;\n}\n\nexport function formatMrsfCount(count: number, max = 9): string {\n return count > max ? `${max}+` : String(count);\n}\n\nexport function createMrsfGutterBadgePresentation(\n context: MrsfGutterBadgeContext,\n): MrsfGutterBadgePresentation {\n const icon = context.resolvedState === \"resolved\" ? \"\u2713\" : \"\uD83D\uDCAC\";\n const countText = formatMrsfCount(context.commentCount);\n const label = `${icon} ${countText}`;\n const threadSummary = context.threadCount === 1 ? \"1 thread\" : `${context.threadCount} threads`;\n const commentSummary = context.commentCount === 1 ? \"1 comment\" : `${context.commentCount} comments`;\n\n return {\n icon,\n countText,\n label,\n title: `${threadSummary}, ${commentSummary}`,\n ariaLabel: `${label} on line ${context.line}`,\n };\n}\n\nexport function createMrsfGutterAddButtonPresentation(\n _context: MrsfGutterAddButtonContext,\n): MrsfGutterAddButtonPresentation {\n return {\n label: \"Add\",\n title: \"Add comment thread\",\n ariaLabel: \"Add comment thread\",\n };\n}\n\nexport function resolveMrsfGutterBadgePresentation(\n context: MrsfGutterBadgeContext,\n renderer?: MrsfGutterRenderers[\"badge\"],\n): MrsfGutterBadgePresentation {\n const defaultPresentation = createMrsfGutterBadgePresentation(context);\n const override = renderer?.({\n ...context,\n defaultPresentation,\n });\n\n return {\n ...defaultPresentation,\n ...override,\n attributes: {\n ...(defaultPresentation.attributes ?? {}),\n ...(override?.attributes ?? {}),\n },\n };\n}\n\nexport function resolveMrsfGutterAddButtonPresentation(\n context: MrsfGutterAddButtonContext,\n renderer?: MrsfGutterRenderers[\"addButton\"],\n): MrsfGutterAddButtonPresentation {\n const defaultPresentation = createMrsfGutterAddButtonPresentation(context);\n const override = renderer?.({\n ...context,\n defaultPresentation,\n });\n\n return {\n ...defaultPresentation,\n ...override,\n attributes: {\n ...(defaultPresentation.attributes ?? {}),\n ...(override?.attributes ?? {}),\n },\n };\n}", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/html.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Shared HTML rendering helpers for MRSF rendering plugins.\n *\n * These produce the HTML strings used in tooltips, badges, and comment\n * rendering. Both the markdown-it and rehype plugins use these functions\n * to ensure identical visual output.\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function formatTime(iso: string | null): string {\n if (!iso) return \"\";\n try {\n const d = new Date(iso);\n return d.toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n } catch {\n return \"\";\n }\n}\n\nexport function renderCommentHtml(\n comment: SlimComment,\n isReply: boolean,\n interactive: boolean,\n): string {\n const resolvedClass = comment.resolved ? \" mrsf-resolved\" : \"\";\n const replyClass = isReply ? \" mrsf-reply\" : \"\";\n let html = `<div class=\"mrsf-comment${resolvedClass}${replyClass}\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\">`;\n\n // Header\n html += `<div class=\"mrsf-comment-header\">`;\n html += `<span class=\"mrsf-author\">${escapeHtml(comment.author)}</span>`;\n if (comment.timestamp) {\n html += `<span class=\"mrsf-date\">${escapeHtml(formatTime(comment.timestamp))}</span>`;\n }\n if (comment.severity) {\n html += `<span class=\"mrsf-severity mrsf-severity-${escapeHtml(comment.severity)}\">${escapeHtml(comment.severity)}</span>`;\n }\n if (comment.type) {\n html += `<span class=\"mrsf-type\">${escapeHtml(comment.type)}</span>`;\n }\n if (comment.resolved) {\n html += `<span class=\"mrsf-resolved-badge\">\u2713 resolved</span>`;\n }\n html += `</div>`;\n\n // Selected text quote (collapsible)\n if (comment.selected_text) {\n html += `<details class=\"mrsf-selected-text\"><summary class=\"mrsf-selected-text-summary\">${escapeHtml(comment.selected_text)}</summary><div class=\"mrsf-selected-text-full\">${escapeHtml(comment.selected_text)}</div></details>`;\n }\n\n // Body\n html += `<div class=\"mrsf-comment-body\">${escapeHtml(comment.text)}</div>`;\n\n // Action buttons (interactive mode)\n if (interactive) {\n const line = comment.line != null ? String(comment.line) : \"\";\n html += `<div class=\"mrsf-actions\">`;\n if (comment.resolved) {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"unresolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Unresolve</button>`;\n } else {\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"resolve\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Resolve</button>`;\n }\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"reply\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Reply</button>`;\n html += `<button class=\"mrsf-action-btn\" data-mrsf-action=\"edit\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Edit</button>`;\n html += `<button class=\"mrsf-action-btn mrsf-action-danger\" data-mrsf-action=\"delete\" data-mrsf-comment-id=\"${escapeHtml(comment.id)}\" data-mrsf-line=\"${line}\">Delete</button>`;\n html += `</div>`;\n }\n\n html += `</div>`;\n return html;\n}\n\nexport function renderThreadHtml(thread: CommentThread, interactive: boolean): string {\n let html = `<div class=\"mrsf-thread\">`;\n html += renderCommentHtml(thread.comment, false, interactive);\n if (thread.replies.length > 0) {\n html += `<div class=\"mrsf-replies\">`;\n for (const reply of thread.replies) {\n html += renderCommentHtml(reply, true, interactive);\n }\n html += `</div>`;\n }\n html += `</div>`;\n return html;\n}\n", "/* ---------------------------------------------------------------\n * AUTO-GENERATED \u2014 DO NOT EDIT\n *\n * Source: plugins/shared/src/controller.ts\n * Run `node plugins/sync-types.mjs` to regenerate.\n * --------------------------------------------------------------- */\n\n/**\n * Sidemark \u2014 MrsfController: overlay gutter architecture.\n *\n * The controller is the runtime engine for interactive MRSF rendering.\n * It reads comment data from the DOM (embedded `<script type=\"application/mrsf+json\">`)\n * or constructor options, creates gutter overlay columns, positions\n * badges/buttons at the correct vertical offsets, and handles all\n * user interactions (tooltips, selection, action buttons).\n *\n * Usage:\n * import { MrsfController } from \"@mrsf/plugin-shared/controller\";\n *\n * const ctrl = new MrsfController(document.querySelector(\".my-content\")!, {\n * interactive: true,\n * gutterPosition: \"left\",\n * });\n * // ctrl.destroy() to clean up\n *\n * Events dispatched on document:\n * - mrsf:resolve { commentId, line, ... }\n * - mrsf:unresolve { commentId, line, ... }\n * - mrsf:reply { commentId, line, ... }\n * - mrsf:edit { commentId, line, ... }\n * - mrsf:delete { commentId, line, ... }\n * - mrsf:navigate { commentId, line, ... }\n * - mrsf:add { commentId: null, line, selectionText?, ... }\n * - mrsf:submit { action, commentId, text?, line?, ... }\n */\n\nimport type { CommentThread, SlimComment } from \"./types.js\";\nimport type { MrsfGutterRenderers } from \"./gutter.js\";\nimport {\n resolveMrsfGutterAddButtonPresentation,\n resolveMrsfGutterBadgePresentation,\n} from \"./gutter.js\";\nimport { renderThreadHtml, escapeHtml } from \"./html.js\";\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type MrsfAction = \"resolve\" | \"unresolve\" | \"reply\" | \"edit\" | \"delete\" | \"navigate\" | \"add\";\n\nexport interface MrsfActionDetail {\n commentId: string | null;\n line: number | null;\n action: MrsfAction;\n selectionText?: string | null;\n start_line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n}\n\nexport interface MrsfSubmitDetail {\n action: \"add\" | \"edit\" | \"reply\" | \"resolve\" | \"unresolve\" | \"delete\";\n commentId: string | null;\n text: string;\n type?: string | null;\n severity?: \"low\" | \"medium\" | \"high\" | null;\n line?: number | null;\n end_line?: number | null;\n start_column?: number | null;\n end_column?: number | null;\n selection_text?: string | null;\n}\n\nexport interface MrsfControllerOptions {\n /** Show gutter on left or right side. Default: \"right\". */\n gutterPosition?: \"left\" | \"right\";\n /** Enable interactive actions (add, resolve, reply, etc.). Default: false. */\n interactive?: boolean;\n /** Comment data passed directly (overrides embedded script). */\n comments?: CommentThread[];\n /**\n * Render inline text highlights for comments that have `selected_text`.\n * Wraps matching text in `<mark>` elements with hover tooltips.\n * Default: true.\n */\n inlineHighlights?: boolean;\n gutterRenderers?: MrsfGutterRenderers;\n}\n\n// \u2500\u2500 MrsfController \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class MrsfController {\n private container: HTMLElement;\n private opts: Required<MrsfControllerOptions>;\n private threads: Map<number, CommentThread[]> = new Map();\n private gutterLeft: HTMLDivElement | null = null;\n private gutterRight: HTMLDivElement | null = null;\n private activeTooltip: HTMLElement | null = null;\n private floatingAddButton: HTMLButtonElement | null = null;\n private overlayEl: HTMLDivElement | null = null;\n private lastSelectionText: string | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private mutationObserver: MutationObserver | null = null;\n private styleInjected = false;\n private inlineMarks: HTMLElement[] = [];\n private inlineTooltipEl: HTMLElement | null = null;\n private orphanedSection: HTMLDivElement | null = null;\n private refreshQueued = false;\n\n private handleResizeBound = this.positionGutterItems.bind(this);\n private handleMutationBound = this.handleMutations.bind(this);\n private handleClickBound = this.handleClick.bind(this);\n private handleSelectionBound = this.handleSelectionChange.bind(this);\n\n constructor(container: HTMLElement, options: MrsfControllerOptions = {}) {\n this.container = container;\n this.opts = {\n gutterPosition: options.gutterPosition ?? \"right\",\n interactive: options.interactive ?? false,\n comments: options.comments ?? [],\n inlineHighlights: options.inlineHighlights ?? true,\n gutterRenderers: options.gutterRenderers ?? {},\n };\n\n this.loadCommentData();\n this.setupOverlayStructure();\n this.renderGutterItems();\n this.positionGutterItems();\n this.renderInlineHighlights();\n this.renderOrphanedSection();\n\n controllerRegistry.add(this);\n\n // Listeners\n this.resizeObserver = new ResizeObserver(this.handleResizeBound);\n this.resizeObserver.observe(this.container);\n if (typeof MutationObserver !== \"undefined\") {\n this.mutationObserver = new MutationObserver(this.handleMutationBound);\n this.mutationObserver.observe(this.container, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n }\n document.addEventListener(\"click\", this.handleClickBound);\n if (this.opts.interactive) {\n document.addEventListener(\"selectionchange\", this.handleSelectionBound);\n }\n }\n\n /** Remove all controller DOM and listeners. */\n destroy(): void {\n this.resizeObserver?.disconnect();\n this.mutationObserver?.disconnect();\n controllerRegistry.delete(this);\n document.removeEventListener(\"click\", this.handleClickBound);\n document.removeEventListener(\"selectionchange\", this.handleSelectionBound);\n this.removeInlineHighlights();\n this.orphanedSection?.remove();\n this.gutterLeft?.remove();\n this.gutterRight?.remove();\n this.floatingAddButton?.remove();\n this.closeOverlay();\n this.container.classList.remove(\"mrsf-overlay-root\");\n }\n\n /** Recalculate gutter positions after async layout changes such as Mermaid renders. */\n refresh(): void {\n this.positionGutterItems();\n }\n\n // \u2500\u2500 Data loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private loadCommentData(): void {\n // Priority: constructor options > embedded script\n if (this.opts.comments.length > 0) {\n this.buildThreadMap(this.opts.comments);\n return;\n }\n\n const script = this.container.querySelector('script[type=\"application/mrsf+json\"]');\n if (!script?.textContent) return;\n\n try {\n const data = JSON.parse(script.textContent) as { threads?: CommentThread[] };\n if (data.threads) {\n this.buildThreadMap(data.threads);\n }\n } catch {\n // silently ignore malformed data\n }\n }\n\n private buildThreadMap(threads: CommentThread[]): void {\n this.threads.clear();\n for (const t of threads) {\n const line = t.comment.line;\n if (line == null) continue;\n const arr = this.threads.get(line) ?? [];\n arr.push(t);\n this.threads.set(line, arr);\n }\n }\n\n private findCommentById(commentId: string | null): SlimComment | null {\n if (!commentId) return null;\n\n for (const threadList of this.threads.values()) {\n for (const thread of threadList) {\n if (thread.comment.id === commentId) {\n return thread.comment;\n }\n const reply = thread.replies.find((item) => item.id === commentId);\n if (reply) {\n return reply;\n }\n }\n }\n\n return null;\n }\n\n private orderThreadsForDisplay(threads: CommentThread[]): CommentThread[] {\n return [...threads].sort((left, right) => {\n if (left.comment.resolved === right.comment.resolved) return 0;\n return left.comment.resolved ? 1 : -1;\n });\n }\n\n // \u2500\u2500 Overlay structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setupOverlayStructure(): void {\n this.container.classList.add(\"mrsf-overlay-root\");\n\n const pos = this.opts.gutterPosition;\n if (pos === \"left\") {\n this.gutterLeft = this.createGutter(\"mrsf-gutter-left\");\n } else {\n this.gutterRight = this.createGutter(\"mrsf-gutter-right\");\n }\n }\n\n private createGutter(cls: string): HTMLDivElement {\n const gutter = document.createElement(\"div\");\n gutter.className = `mrsf-gutter ${cls}`;\n this.container.appendChild(gutter);\n return gutter;\n }\n\n // \u2500\u2500 Gutter rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Build badge/add-button elements for each line in the gutter(s). */\n private renderGutterItems(): void {\n const lines = this.collectLines();\n const displayMap = this.collectThreadDisplayMap(lines);\n const gutter = this.primaryGutter();\n if (!gutter) return;\n\n for (const line of lines) {\n const threads = displayMap.get(line);\n if (threads && threads.length > 0) {\n const item = this.createBadgeItem(line, threads);\n gutter.appendChild(item);\n } else if (this.opts.interactive) {\n const item = this.createAddItem(line);\n gutter.appendChild(item);\n }\n }\n }\n\n private primaryGutter(): HTMLDivElement | null {\n return this.gutterLeft ?? this.gutterRight;\n }\n\n private shouldExpandRange(el: HTMLElement): boolean {\n return el.tagName === \"BLOCKQUOTE\" || el.tagName === \"PRE\";\n }\n\n private addVisibleLinesForElement(el: HTMLElement, seen: Set<number>): void {\n const line = parseInt(el.dataset.mrsfLine ?? \"\", 10);\n const startLine = parseInt(el.dataset.mrsfStartLine ?? \"\", 10);\n const endLine = parseInt(el.dataset.mrsfEndLine ?? \"\", 10);\n\n if (el.tagName === \"PRE\" && !isNaN(startLine) && !isNaN(endLine) && endLine > startLine) {\n const visibleStart = startLine + 1;\n const visibleEnd = endLine - 1;\n for (let currentLine = visibleStart; currentLine <= visibleEnd; currentLine++) {\n seen.add(currentLine);\n }\n return;\n }\n\n if (!isNaN(line)) {\n seen.add(line);\n }\n\n if (\n this.shouldExpandRange(el) &&\n !isNaN(startLine) &&\n !isNaN(endLine) &&\n endLine > startLine\n ) {\n for (let currentLine = startLine; currentLine <= endLine; currentLine++) {\n seen.add(currentLine);\n }\n }\n }\n\n /** Collect all unique line numbers from data-mrsf-line elements, expanding multi-line ranges. */\n private collectLines(): number[] {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-line]\");\n const seen = new Set<number>();\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n this.addVisibleLinesForElement(el, seen);\n }\n\n return [...seen].sort((a, b) => a - b);\n }\n\n private resolveThreadDisplayLine(thread: CommentThread, availableLines: Set<number>): number | null {\n const startLine = thread.comment.line;\n if (startLine == null) return null;\n\n const endLine = thread.comment.end_line != null && thread.comment.end_line >= startLine\n ? thread.comment.end_line\n : startLine;\n\n for (let currentLine = startLine; currentLine <= endLine; currentLine++) {\n if (availableLines.has(currentLine)) {\n return currentLine;\n }\n }\n\n return null;\n }\n\n private collectThreadDisplayMap(lines: number[]): Map<number, CommentThread[]> {\n const availableLines = new Set(lines);\n const displayMap = new Map<number, CommentThread[]>();\n\n for (const threads of this.threads.values()) {\n for (const thread of threads) {\n const displayLine = this.resolveThreadDisplayLine(thread, availableLines);\n if (displayLine == null) continue;\n\n const existing = displayMap.get(displayLine) ?? [];\n existing.push(thread);\n displayMap.set(displayLine, existing);\n }\n }\n\n return displayMap;\n }\n\n private createBadgeItem(line: number, threads: CommentThread[]): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const displayThreads = this.orderThreadsForDisplay(threads);\n\n const total = threads.reduce((n, t) => n + 1 + t.replies.length, 0);\n const allResolved = threads.every((t) => t.comment.resolved);\n const highestSeverity = threads.reduce<string | null>((sev, t) => {\n if (t.comment.severity === \"high\" || sev === \"high\") return \"high\";\n if (t.comment.severity === \"medium\" || sev === \"medium\") return \"medium\";\n if (t.comment.severity === \"low\" || sev === \"low\") return \"low\";\n return sev;\n }, null);\n\n const classes = [\"mrsf-badge\"];\n if (allResolved) classes.push(\"mrsf-badge-resolved\");\n if (highestSeverity === \"high\" || highestSeverity === \"medium\") {\n classes.push(`mrsf-badge-severity-${highestSeverity}`);\n }\n\n const presentation = resolveMrsfGutterBadgePresentation(\n {\n line,\n commentCount: total,\n threadCount: threads.length,\n resolvedState: allResolved ? \"resolved\" : threads.some((thread) => thread.comment.resolved) ? \"mixed\" : \"open\",\n highestSeverity,\n isActive: this.activeTooltip?.parentElement === item,\n },\n this.opts.gutterRenderers.badge,\n );\n const badge = document.createElement(\"span\");\n badge.className = classes.join(\" \");\n if (presentation.className) {\n badge.classList.add(...presentation.className.split(/\\s+/).filter(Boolean));\n }\n badge.dataset.mrsfLine = String(line);\n badge.dataset.mrsfAction = \"navigate\";\n badge.dataset.mrsfCommentId = displayThreads[0].comment.id;\n badge.tabIndex = 0;\n badge.textContent = presentation.label;\n badge.title = presentation.title;\n badge.setAttribute(\"aria-label\", presentation.ariaLabel);\n for (const [name, value] of Object.entries(presentation.attributes ?? {})) {\n badge.setAttribute(name, value);\n }\n badge.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n this.toggleTooltip(item, line, displayThreads);\n });\n\n item.appendChild(badge);\n\n return item;\n }\n\n private createAddItem(line: number): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"mrsf-gutter-item\";\n item.dataset.mrsfGutterLine = String(line);\n\n const addBtn = this.createAddButton(line);\n item.appendChild(addBtn);\n return item;\n }\n\n private createAddButton(line: number): HTMLButtonElement {\n const presentation = resolveMrsfGutterAddButtonPresentation(\n {\n line,\n isActive: false,\n },\n this.opts.gutterRenderers.addButton,\n );\n const btn = document.createElement(\"button\");\n btn.className = \"mrsf-gutter-add\";\n if (presentation.className) {\n btn.classList.add(...presentation.className.split(/\\s+/).filter(Boolean));\n }\n btn.type = \"button\";\n btn.dataset.mrsfAction = \"add\";\n btn.dataset.mrsfLine = String(line);\n btn.dataset.mrsfStartLine = String(line);\n btn.dataset.mrsfEndLine = String(line);\n btn.title = presentation.title;\n btn.setAttribute(\"aria-label\", presentation.ariaLabel);\n btn.textContent = presentation.label;\n for (const [name, value] of Object.entries(presentation.attributes ?? {})) {\n btn.setAttribute(name, value);\n }\n return btn;\n }\n\n // \u2500\u2500 Positioning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Measure [data-mrsf-line] elements and set gutter item Y offsets. */\n positionGutterItems(): void {\n const containerRect = this.container.getBoundingClientRect();\n\n const gutters = [this.gutterLeft, this.gutterRight].filter(Boolean) as HTMLDivElement[];\n for (const gutter of gutters) {\n const items = gutter.querySelectorAll<HTMLDivElement>(\".mrsf-gutter-item\");\n const positioned: Array<{ item: HTMLDivElement; target: HTMLElement; kind: \"badge\" | \"add\" }> = [];\n for (const item of items) {\n const line = parseInt(item.dataset.mrsfGutterLine!, 10);\n // For expanded multi-line elements, find the rendered content element whose line data contains this line.\n let target = this.findDirectElementForLine(line);\n if (!target) {\n target = this.findElementForLine(line);\n }\n if (!target) {\n item.style.display = \"none\";\n continue;\n }\n const top = this.calculateItemTop(target, line, containerRect);\n item.style.top = `${top}px`;\n item.style.display = \"\";\n positioned.push({\n item,\n target,\n kind: item.querySelector(\".mrsf-badge\") ? \"badge\" : \"add\",\n });\n }\n\n this.suppressAddItemsThatShareBadgeTargets(positioned);\n }\n }\n\n private suppressAddItemsThatShareBadgeTargets(\n positioned: Array<{ item: HTMLDivElement; target: HTMLElement; kind: \"badge\" | \"add\" }>,\n ): void {\n const badgeTargets = new Set(\n positioned\n .filter((entry) => entry.kind === \"badge\")\n .map((entry) => entry.target),\n );\n\n for (const entry of positioned) {\n if (entry.kind === \"add\" && badgeTargets.has(entry.target)) {\n entry.item.style.display = \"none\";\n }\n }\n }\n\n private handleMutations(records: MutationRecord[]): void {\n if (!records.some((record) => this.isExternalContentMutation(record))) {\n return;\n }\n this.queueRefresh();\n }\n\n private isExternalContentMutation(record: MutationRecord): boolean {\n const target = record.target instanceof Node ? record.target : null;\n if (target && this.isControllerOwnedNode(target)) {\n return false;\n }\n\n for (const node of [...record.addedNodes, ...record.removedNodes]) {\n if (!this.isControllerOwnedNode(node)) {\n return true;\n }\n }\n\n return record.type === \"characterData\";\n }\n\n private isControllerOwnedNode(node: Node): boolean {\n if (!(node instanceof Element)) {\n return false;\n }\n return Boolean(\n node.closest(\".mrsf-gutter\") ||\n node.closest(\".mrsf-orphaned-section\") ||\n node.closest(\"script[type=\\\"application/mrsf+json\\\"]\"),\n );\n }\n\n private queueRefresh(): void {\n if (this.refreshQueued) return;\n this.refreshQueued = true;\n queueMicrotask(() => {\n this.refreshQueued = false;\n this.refresh();\n });\n }\n\n private findDirectElementForLine(line: number): HTMLElement | null {\n const els = this.container.querySelectorAll<HTMLElement>(`[data-mrsf-line=\"${line}\"]`);\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n if (el.closest(\".mrsf-gutter\")) continue;\n if (el.classList.contains(\"mrsf-gutter-item\")) continue;\n return el;\n }\n return null;\n }\n\n private calculateItemTop(target: HTMLElement, line: number, containerRect: DOMRect): number {\n const targetRect = target.getBoundingClientRect();\n const rangeTop = targetRect.top - containerRect.top + this.container.scrollTop;\n\n if (target.tagName !== \"PRE\") {\n return rangeTop;\n }\n\n const startLine = parseInt(target.dataset.mrsfStartLine ?? \"\", 10);\n const endLine = parseInt(target.dataset.mrsfEndLine ?? \"\", 10);\n const visibleStart = startLine + 1;\n const visibleEnd = endLine - 1;\n const visibleLineCount = visibleEnd - visibleStart + 1;\n\n if (\n isNaN(startLine) ||\n isNaN(endLine) ||\n visibleLineCount <= 0 ||\n line < visibleStart ||\n line > visibleEnd\n ) {\n return rangeTop;\n }\n\n const styles = window.getComputedStyle(target);\n const paddingTop = parseFloat(styles.paddingTop) || 0;\n const paddingBottom = parseFloat(styles.paddingBottom) || 0;\n const contentHeight = Math.max(targetRect.height - paddingTop - paddingBottom, 0);\n\n let lineHeight = parseFloat(styles.lineHeight);\n if (!Number.isFinite(lineHeight) || lineHeight <= 0) {\n lineHeight = visibleLineCount > 0 ? contentHeight / visibleLineCount : 0;\n }\n\n return rangeTop + paddingTop + (line - visibleStart) * lineHeight;\n }\n\n /**\n * Find the element whose start-line/end-line range contains the given line.\n * Used for positioning gutter items on expanded multi-line elements.\n */\n private findElementForLine(line: number): HTMLElement | null {\n const els = this.container.querySelectorAll<HTMLElement>(\"[data-mrsf-start-line][data-mrsf-end-line]\");\n for (const el of els) {\n if (el.tagName === \"SCRIPT\") continue;\n const start = parseInt(el.dataset.mrsfStartLine!, 10);\n const end = parseInt(el.dataset.mrsfEndLine!, 10);\n if (!isNaN(start) && !isNaN(end) && line >= start && line <= end) {\n return el;\n }\n }\n return null;\n }\n\n // \u2500\u2500 Orphaned comments section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Render orphaned comments (whose line doesn't match any DOM element)\n * in a dedicated section at the bottom of the container.\n */\n private renderOrphanedSection(): void {\n const lines = this.collectLines();\n const lineSet = new Set(lines);\n const orphanedThreads: CommentThread[] = [];\n\n for (const threads of this.threads.values()) {\n for (const thread of threads) {\n if (this.resolveThreadDisplayLine(thread, lineSet) == null) {\n orphanedThreads.push(thread);\n }\n }\n }\n\n if (orphanedThreads.length === 0) return;\n\n const section = document.createElement(\"div\");\n section.className = \"mrsf-orphaned-section\";\n\n const heading = document.createElement(\"div\");\n heading.className = \"mrsf-orphaned-heading\";\n heading.textContent = `Orphaned Comments (${orphanedThreads.length})`;\n section.appendChild(heading);\n\n const interactive = this.opts.interactive;\n for (const thread of this.orderThreadsForDisplay(orphanedThreads)) {\n const wrapper = document.createElement(\"div\");\n wrapper.className = interactive\n ? \"mrsf-orphaned-thread mrsf-interactive\"\n : \"mrsf-orphaned-thread\";\n wrapper.innerHTML = renderThreadHtml(thread, interactive);\n section.appendChild(wrapper);\n }\n\n this.container.appendChild(section);\n this.orphanedSection = section;\n }\n\n // \u2500\u2500 Inline text highlights \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * For comments with `selected_text`, find the matching text in the DOM\n * and wrap it in a `<mark class=\"mrsf-inline-highlight\">` element with\n * hover/click behaviour to show the comment tooltip.\n */\n private renderInlineHighlights(): void {\n if (!this.opts.inlineHighlights) return;\n\n const lineSet = new Set(this.collectLines());\n\n for (const threads of this.threads.values()) {\n for (const thread of threads) {\n const comment = thread.comment;\n if (!comment.selected_text) continue;\n\n const displayLine = this.resolveThreadDisplayLine(thread, lineSet);\n if (displayLine == null) continue;\n\n const el = this.container.querySelector<HTMLElement>(\n `[data-mrsf-line=\"${displayLine}\"]:not(script):not(.mrsf-gutter):not(.mrsf-gutter-item)`,\n );\n if (!el) continue;\n\n this.wrapSelectedText(el, comment.selected_text, thread, displayLine);\n }\n }\n }\n\n /**\n * Strip common inline markdown syntax so `selected_text` from source\n * can be matched against rendered text content.\n */\n private static stripInlineMarkdown(text: string): string {\n let s = text;\n // Backtick code spans: `code` \u2192 code\n s = s.replace(/`([^`]+)`/g, \"$1\");\n // Bold: **text** or __text__\n s = s.replace(/\\*\\*(.+?)\\*\\*/g, \"$1\");\n s = s.replace(/__(.+?)__/g, \"$1\");\n // Italic: *text* or _text_\n s = s.replace(/\\*(.+?)\\*/g, \"$1\");\n s = s.replace(/_(.+?)_/g, \"$1\");\n // Strikethrough: ~~text~~\n s = s.replace(/~~(.+?)~~/g, \"$1\");\n return s;\n }\n\n /**\n * Walk text nodes inside `root` to find `text`, then wrap the matching\n * range in a `<mark>` element. Falls back to markdown-stripped matching.\n */\n private wrapSelectedText(\n root: HTMLElement,\n selectedText: string,\n thread: CommentThread,\n displayLine: number,\n ): void {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n let accumulated = \"\";\n const textNodes: { node: Text; start: number; end: number }[] = [];\n\n let node: Text | null;\n while ((node = walker.nextNode() as Text | null)) {\n const start = accumulated.length;\n accumulated += node.textContent || \"\";\n textNodes.push({ node, start, end: accumulated.length });\n }\n\n // Try exact match first, then stripped markdown\n let matchStart = accumulated.indexOf(selectedText);\n let matchLen = selectedText.length;\n if (matchStart === -1) {\n const stripped = MrsfController.stripInlineMarkdown(selectedText);\n if (stripped !== selectedText) {\n matchStart = accumulated.indexOf(stripped);\n matchLen = stripped.length;\n }\n }\n if (matchStart === -1) return;\n\n const matchEnd = matchStart + matchLen;\n\n // Build a Range spanning the matched text nodes\n const range = document.createRange();\n let startSet = false;\n\n for (const tn of textNodes) {\n if (!startSet && tn.end > matchStart) {\n range.setStart(tn.node, matchStart - tn.start);\n startSet = true;\n }\n if (startSet && tn.end >= matchEnd) {\n range.setEnd(tn.node, matchEnd - tn.start);\n break;\n }\n }\n\n if (!startSet) return;\n\n const mark = document.createElement(\"mark\");\n mark.className = \"mrsf-inline-highlight\";\n mark.dataset.mrsfCommentId = thread.comment.id;\n mark.dataset.mrsfLine = String(displayLine);\n\n try {\n range.surroundContents(mark);\n } catch {\n // Range crosses element boundaries \u2014 extract and re-insert\n const fragment = range.extractContents();\n mark.appendChild(fragment);\n range.insertNode(mark);\n }\n\n this.inlineMarks.push(mark);\n\n // Hover shows tooltip inline\n mark.addEventListener(\"mouseenter\", () => {\n this.showInlineTooltip(mark, thread);\n });\n mark.addEventListener(\"mouseleave\", (e) => {\n // Don't hide if moving into the tooltip itself\n const related = (e as MouseEvent).relatedTarget as HTMLElement | null;\n if (related && this.inlineTooltipEl?.contains(related)) return;\n this.scheduleHideInlineTooltip();\n });\n\n // Click toggles tooltip (for touch / accessibility)\n mark.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (this.inlineTooltipEl && this.inlineTooltipEl.dataset.mrsfForMark === thread.comment.id) {\n this.hideInlineTooltip();\n } else {\n this.showInlineTooltip(mark, thread);\n }\n });\n }\n\n private showInlineTooltip(mark: HTMLElement, thread: CommentThread): void {\n this.hideInlineTooltip();\n\n const tooltip = document.createElement(\"div\");\n tooltip.className = this.opts.interactive\n ? \"mrsf-inline-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-inline-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfForMark = thread.comment.id;\n tooltip.innerHTML = renderThreadHtml(thread, this.opts.interactive);\n this.applyThemeVariables(tooltip);\n\n // Let user mouse into tooltip without it disappearing\n tooltip.addEventListener(\"mouseenter\", () => {\n this.cancelHideInlineTooltip();\n });\n tooltip.addEventListener(\"mouseleave\", () => {\n this.hideInlineTooltip();\n });\n\n // Append to body with fixed positioning to avoid clipping\n document.body.appendChild(tooltip);\n this.inlineTooltipEl = tooltip;\n\n // Position relative to the mark element\n const rect = mark.getBoundingClientRect();\n const margin = 4;\n const tooltipH = tooltip.offsetHeight;\n\n // Prefer below; flip above if not enough space at bottom\n if (rect.bottom + margin + tooltipH > window.innerHeight) {\n tooltip.style.top = `${rect.top - tooltipH - margin}px`;\n } else {\n tooltip.style.top = `${rect.bottom + margin}px`;\n }\n tooltip.style.left = `${Math.max(0, rect.left)}px`;\n }\n\n private applyThemeVariables(el: HTMLElement): void {\n const styles = window.getComputedStyle(this.container);\n const themeVars = [\n \"--mrsf-accent\",\n \"--mrsf-badge-bg\",\n \"--mrsf-badge-fg\",\n \"--mrsf-badge-resolved-bg\",\n \"--mrsf-add-bg\",\n \"--mrsf-add-fg\",\n \"--mrsf-add-border\",\n \"--mrsf-tooltip-bg\",\n \"--mrsf-tooltip-fg\",\n \"--mrsf-tooltip-border\",\n \"--mrsf-highlight-bg\",\n \"--mrsf-highlight-border\",\n \"--mrsf-severity-high\",\n \"--mrsf-severity-medium\",\n \"--mrsf-severity-low\",\n \"--mrsf-font-family\",\n ];\n\n for (const name of themeVars) {\n const value = styles.getPropertyValue(name).trim();\n if (value) {\n el.style.setProperty(name, value);\n }\n }\n }\n\n private hideInlineTimeout: ReturnType<typeof setTimeout> | null = null;\n\n private scheduleHideInlineTooltip(): void {\n this.hideInlineTimeout = setTimeout(() => this.hideInlineTooltip(), 120);\n }\n\n private cancelHideInlineTooltip(): void {\n if (this.hideInlineTimeout) {\n clearTimeout(this.hideInlineTimeout);\n this.hideInlineTimeout = null;\n }\n }\n\n private hideInlineTooltip(): void {\n this.cancelHideInlineTooltip();\n if (this.inlineTooltipEl) {\n this.inlineTooltipEl.remove();\n this.inlineTooltipEl = null;\n }\n }\n\n /** Remove all inline marks, unwrapping their contents back to text. */\n private removeInlineHighlights(): void {\n this.hideInlineTooltip();\n for (const mark of this.inlineMarks) {\n const parent = mark.parentNode;\n if (!parent) continue;\n while (mark.firstChild) {\n parent.insertBefore(mark.firstChild, mark);\n }\n parent.removeChild(mark);\n }\n this.inlineMarks = [];\n }\n\n // \u2500\u2500 Tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private toggleTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n // If already visible on this anchor, hide it\n if (this.activeTooltip && this.activeTooltip.parentElement === anchor) {\n this.hideTooltip();\n return;\n }\n this.hideTooltip();\n this.showTooltip(anchor, line, threads);\n }\n\n private showTooltip(anchor: HTMLElement, line: number, threads: CommentThread[]): void {\n const tooltip = document.createElement(\"div\");\n const interactive = this.opts.interactive;\n tooltip.className = interactive\n ? \"mrsf-tooltip mrsf-interactive mrsf-tooltip-visible\"\n : \"mrsf-tooltip mrsf-tooltip-visible\";\n tooltip.dataset.mrsfLine = String(line);\n\n let html = \"\";\n for (const thread of this.orderThreadsForDisplay(threads)) {\n html += renderThreadHtml(thread, interactive);\n }\n if (interactive) {\n html += `<div class=\"mrsf-tooltip-actions\"><button class=\"mrsf-action-btn\" data-mrsf-action=\"add\" data-mrsf-line=\"${line}\" data-mrsf-start-line=\"${line}\" data-mrsf-end-line=\"${line}\">Add comment</button></div>`;\n }\n tooltip.innerHTML = html;\n\n anchor.appendChild(tooltip);\n this.activeTooltip = tooltip;\n }\n\n private hideTooltip(): void {\n if (this.activeTooltip) {\n this.activeTooltip.remove();\n this.activeTooltip = null;\n }\n }\n\n // \u2500\u2500 Click handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleClick(e: Event): void {\n const target = (e.target as HTMLElement).closest<HTMLElement>(\"[data-mrsf-action]\");\n\n // Close tooltip when clicking outside\n if (!target && this.activeTooltip) {\n const tooltipClick = (e.target as HTMLElement).closest(\".mrsf-tooltip\");\n if (!tooltipClick) {\n this.hideTooltip();\n }\n return;\n }\n if (!target) return;\n\n const action = target.dataset.mrsfAction as MrsfAction | undefined;\n if (!action) return;\n\n const commentId = target.dataset.mrsfCommentId ?? null;\n const lineStr = target.dataset.mrsfLine;\n const line = lineStr ? parseInt(lineStr, 10) : null;\n const selectionText = target.dataset.mrsfSelection ?? this.lastSelectionText ?? null;\n\n const startLineStr = target.dataset.mrsfStartLine;\n const endLineStr = target.dataset.mrsfEndLine;\n const startColStr = target.dataset.mrsfStartColumn;\n const endColStr = target.dataset.mrsfEndColumn;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : (line ?? null);\n const endLine = endLineStr ? parseInt(endLineStr, 10) : (line ?? null);\n const startColumn = startColStr ? parseInt(startColStr, 10) : null;\n const endColumn = endColStr ? parseInt(endColStr, 10) : null;\n\n e.preventDefault();\n e.stopPropagation();\n\n const detail: MrsfActionDetail = {\n commentId,\n line,\n action,\n selectionText,\n start_line: startLine,\n end_line: endLine,\n start_column: startColumn,\n end_column: endColumn,\n };\n\n if (action === \"add\" || action === \"edit\" || action === \"reply\") {\n if (action === \"add\") {\n this.hideFloatingAddButton();\n }\n this.openForm(action, detail);\n return;\n }\n\n if (action === \"resolve\" || action === \"unresolve\" || action === \"delete\") {\n this.openConfirm(action, detail);\n return;\n }\n\n document.dispatchEvent(\n new CustomEvent(`mrsf:${action}`, { detail, bubbles: true }),\n );\n }\n\n // \u2500\u2500 Selection handling \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private handleSelectionChange(): void {\n const sel = document.getSelection();\n if (!sel || sel.isCollapsed || sel.rangeCount === 0) {\n this.hideFloatingAddButton();\n return;\n }\n\n const text = sel.toString().trim();\n if (!text) {\n this.hideFloatingAddButton();\n return;\n }\n\n const range = sel.getRangeAt(0);\n if (!this.selectionBelongsToContainer(range)) {\n this.hideFloatingAddButton();\n return;\n }\n\n const rect = range.getBoundingClientRect();\n if (!rect || (rect.width === 0 && rect.height === 0)) {\n this.hideFloatingAddButton();\n return;\n }\n\n const startAnchor = this.findSelectionAnchor(range.startContainer);\n const endAnchor = this.findSelectionAnchor(range.endContainer);\n if (!startAnchor || !endAnchor) {\n this.hideFloatingAddButton();\n return;\n }\n\n const startLineStr = startAnchor?.dataset.mrsfStartLine ?? startAnchor?.dataset.mrsfLine;\n const endLineStr = endAnchor?.dataset.mrsfEndLine ?? endAnchor?.dataset.mrsfLine ?? startLineStr;\n const startLine = startLineStr ? parseInt(startLineStr, 10) : null;\n const endLine = endLineStr ? parseInt(endLineStr, 10) : startLine;\n\n const startColumn = range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startOffset : null;\n const endColumn = range.endContainer.nodeType === Node.TEXT_NODE\n ? range.endOffset : null;\n\n this.showFloatingAddButton(startLine, endLine, startColumn, endColumn, rect, text);\n }\n\n private selectionBelongsToContainer(range: Range): boolean {\n return this.container.contains(range.commonAncestorContainer)\n && this.container.contains(range.startContainer)\n && this.container.contains(range.endContainer);\n }\n\n private findSelectionAnchor(node: Node): HTMLElement | null {\n const element = node instanceof Element ? node : node.parentElement;\n const anchor = element?.closest<HTMLElement>(\"[data-mrsf-line]\") ?? null;\n return anchor && this.container.contains(anchor) ? anchor : null;\n }\n\n private ensureFloatingAddButton(): HTMLButtonElement {\n if (this.floatingAddButton) return this.floatingAddButton;\n const btn = document.createElement(\"button\");\n btn.textContent = \"Add comment\";\n btn.className = \"mrsf-add-inline-button\";\n btn.dataset.mrsfAction = \"add\";\n btn.style.display = \"none\";\n btn.style.position = \"absolute\";\n btn.style.zIndex = \"1200\";\n this.container.appendChild(btn);\n this.floatingAddButton = btn;\n return btn;\n }\n\n private hideFloatingAddButton(): void {\n if (!this.floatingAddButton) return;\n this.floatingAddButton.style.display = \"none\";\n this.floatingAddButton.dataset.mrsfLine = \"\";\n this.floatingAddButton.dataset.mrsfStartLine = \"\";\n this.floatingAddButton.dataset.mrsfEndLine = \"\";\n this.lastSelectionText = null;\n }\n\n private showFloatingAddButton(\n startLine: number | null,\n endLine: number | null,\n startColumn: number | null,\n endColumn: number | null,\n rect: DOMRect,\n selectionText: string,\n ): void {\n const btn = this.ensureFloatingAddButton();\n if (startLine != null) {\n btn.dataset.mrsfLine = String(startLine);\n btn.dataset.mrsfStartLine = String(startLine);\n btn.dataset.mrsfEndLine = String(endLine ?? startLine);\n } else {\n delete btn.dataset.mrsfLine;\n delete btn.dataset.mrsfStartLine;\n delete btn.dataset.mrsfEndLine;\n }\n if (startColumn != null) {\n btn.dataset.mrsfStartColumn = String(startColumn);\n } else {\n delete btn.dataset.mrsfStartColumn;\n }\n if (endColumn != null) {\n btn.dataset.mrsfEndColumn = String(endColumn);\n } else {\n delete btn.dataset.mrsfEndColumn;\n }\n this.lastSelectionText = selectionText;\n\n const margin = 6;\n const containerRect = this.container.getBoundingClientRect();\n btn.style.visibility = \"hidden\";\n btn.style.display = \"block\";\n const width = btn.offsetWidth || 0;\n const height = btn.offsetHeight || 0;\n\n const minTop = this.container.scrollTop;\n const maxTop = Math.max(minTop, minTop + this.container.clientHeight - height);\n const minLeft = this.container.scrollLeft;\n const maxLeft = Math.max(minLeft, minLeft + this.container.clientWidth - width);\n\n const preferredTop = rect.top - containerRect.top + this.container.scrollTop - height - margin;\n const fallbackTop = rect.bottom - containerRect.top + this.container.scrollTop + margin;\n const unclampedTop = preferredTop < minTop ? fallbackTop : preferredTop;\n const unclampedLeft = rect.left - containerRect.left + this.container.scrollLeft;\n\n const top = Math.min(Math.max(unclampedTop, minTop), maxTop);\n const left = Math.min(Math.max(unclampedLeft, minLeft), maxLeft);\n\n btn.style.top = `${top}px`;\n btn.style.left = `${left}px`;\n btn.style.visibility = \"visible\";\n }\n\n // \u2500\u2500 Dialog: form (add/edit/reply) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private injectStyles(): void {\n if (this.styleInjected) return;\n const css = `\n.mrsf-overlay { position: fixed; inset: 0; background: var(--mrsf-dialog-backdrop, rgba(15, 23, 42, 0.28)); z-index: 2000; display: flex; align-items: center; justify-content: center; padding: 12px; }\n.mrsf-dialog { background: var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); border-radius: 10px; width: min(420px, calc(100vw - 24px)); box-shadow: 0 18px 48px rgba(15, 23, 42, 0.24); font-family: var(--mrsf-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif); font-size: 13px; overflow: hidden; }\n.mrsf-dialog header { padding: 10px 12px; font-weight: 600; line-height: 1.35; border-bottom: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); }\n.mrsf-dialog form { padding: 12px; display: flex; flex-direction: column; gap: 10px; }\n.mrsf-dialog-body { padding: 12px; line-height: 1.45; }\n.mrsf-field { display: flex; flex-direction: column; gap: 4px; }\n.mrsf-field label { font-size: 12px; color: var(--mrsf-dialog-muted, color-mix(in srgb, var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)) 72%, transparent)); }\n.mrsf-field input, .mrsf-field select, .mrsf-field textarea, .mrsf-field pre { background: var(--mrsf-field-bg, color-mix(in srgb, var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)) 88%, white)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); border-radius: 6px; padding: 7px 9px; font-size: 12px; line-height: 1.45; }\n.mrsf-field textarea { min-height: 76px; resize: vertical; }\n.mrsf-field select { min-height: 34px; }\n.mrsf-field pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }\n.mrsf-field input:focus, .mrsf-field select:focus, .mrsf-field textarea:focus { outline: 2px solid color-mix(in srgb, var(--mrsf-accent, #2563eb) 38%, transparent); outline-offset: 1px; }\n.mrsf-actions-row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 0; padding: 10px 12px 12px; border-top: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); }\n.mrsf-btn { padding: 5px 10px; border-radius: 999px; border: 1px solid var(--mrsf-dialog-border, var(--mrsf-tooltip-border, rgba(148, 163, 184, 0.35))); background: var(--mrsf-button-bg, color-mix(in srgb, var(--mrsf-dialog-bg, var(--mrsf-tooltip-bg, #0f172a)) 82%, white)); color: var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)); cursor: pointer; font: inherit; line-height: 1.2; }\n.mrsf-btn-primary { background: var(--mrsf-button-primary-bg, var(--mrsf-accent, #2563eb)); border-color: var(--mrsf-button-primary-bg, var(--mrsf-accent, #2563eb)); color: #fff; }\n.mrsf-helper { font-size: 11px; color: var(--mrsf-dialog-muted, color-mix(in srgb, var(--mrsf-dialog-fg, var(--mrsf-tooltip-fg, #e5eefb)) 72%, transparent)); }\n`;\n const style = document.createElement(\"style\");\n style.textContent = css;\n document.head.appendChild(style);\n this.styleInjected = true;\n }\n\n private closeOverlay(): void {\n if (this.overlayEl?.parentElement) {\n this.overlayEl.parentElement.removeChild(this.overlayEl);\n }\n this.overlayEl = null;\n }\n\n private openForm(action: \"add\" | \"edit\" | \"reply\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const sourceComment = action === \"edit\" ? this.findCommentById(detail.commentId) : null;\n const selText = detail.selectionText ?? sourceComment?.selected_text ?? \"\";\n const line = detail.line ?? detail.start_line ?? sourceComment?.line ?? null;\n const endLine = detail.end_line ?? detail.line ?? sourceComment?.end_line ?? sourceComment?.line ?? null;\n const startCol = detail.start_column ?? sourceComment?.start_column ?? null;\n const endCol = detail.end_column ?? sourceComment?.end_column ?? null;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n this.applyThemeVariables(overlay);\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent =\n action === \"add\" ? \"Add comment\" : action === \"edit\" ? \"Edit comment\" : \"Reply\";\n dialog.appendChild(header);\n\n const form = document.createElement(\"form\");\n\n const field = (labelText: string, inputEl: HTMLElement, helper?: string) => {\n const wrap = document.createElement(\"div\");\n wrap.className = \"mrsf-field\";\n const label = document.createElement(\"label\");\n label.textContent = labelText;\n wrap.appendChild(label);\n wrap.appendChild(inputEl);\n if (helper) {\n const h = document.createElement(\"div\");\n h.className = \"mrsf-helper\";\n h.textContent = helper;\n wrap.appendChild(h);\n }\n form.appendChild(wrap);\n };\n\n const textArea = document.createElement(\"textarea\");\n textArea.name = \"text\";\n textArea.required = true;\n textArea.value = action === \"edit\" ? (sourceComment?.text ?? \"\") : \"\";\n field(\"Comment text\", textArea);\n\n const typeSelect = document.createElement(\"select\");\n typeSelect.name = \"type\";\n [\"\", \"suggestion\", \"issue\", \"question\", \"accuracy\", \"style\", \"clarity\"].forEach((t) => {\n const opt = document.createElement(\"option\");\n opt.value = t;\n opt.textContent = t || \"(none)\";\n typeSelect.appendChild(opt);\n });\n typeSelect.value = action === \"edit\" ? (sourceComment?.type ?? \"\") : \"\";\n field(\"Type\", typeSelect, \"Optional\");\n\n const severitySelect = document.createElement(\"select\");\n severitySelect.name = \"severity\";\n [\"\", \"low\", \"medium\", \"high\"].forEach((s) => {\n const opt = document.createElement(\"option\");\n opt.value = s;\n opt.textContent = s || \"(none)\";\n severitySelect.appendChild(opt);\n });\n severitySelect.value = action === \"edit\" ? (sourceComment?.severity ?? \"\") : \"\";\n field(\"Severity\", severitySelect, \"Optional\");\n\n if (selText) {\n const pre = document.createElement(\"pre\");\n pre.textContent = selText;\n field(\"Selected text\", pre as unknown as HTMLElement, \"Captured automatically\");\n }\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const submitBtn = document.createElement(\"button\");\n submitBtn.type = \"submit\";\n submitBtn.className = \"mrsf-btn mrsf-btn-primary\";\n submitBtn.textContent = action === \"add\" ? \"Add\" : action === \"reply\" ? \"Reply\" : \"Save\";\n actions.appendChild(submitBtn);\n\n form.appendChild(actions);\n\n form.addEventListener(\"submit\", (ev) => {\n ev.preventDefault();\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: textArea.value.trim(),\n type: typeSelect.value || null,\n severity: (severitySelect.value as MrsfSubmitDetail[\"severity\"]) || null,\n line,\n end_line: endLine,\n start_column: startCol,\n end_column: endCol,\n selection_text: selText || null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n\n dialog.appendChild(form);\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n\n // \u2500\u2500 Dialog: confirm (resolve / unresolve / delete) \u2500\u2500\u2500\u2500\u2500\u2500\n\n private openConfirm(action: \"resolve\" | \"unresolve\" | \"delete\", detail: MrsfActionDetail): void {\n if ((window as any).mrsfDisableBuiltinUi) return;\n this.injectStyles();\n this.closeOverlay();\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"mrsf-overlay\";\n this.applyThemeVariables(overlay);\n\n const dialog = document.createElement(\"div\");\n dialog.className = \"mrsf-dialog\";\n\n const header = document.createElement(\"header\");\n header.textContent = action === \"delete\" ? \"Delete comment\" : \"Change status\";\n dialog.appendChild(header);\n\n const body = document.createElement(\"div\");\n body.className = \"mrsf-dialog-body\";\n body.textContent =\n action === \"delete\"\n ? \"Delete this comment?\"\n : action === \"resolve\"\n ? \"Mark this comment as resolved?\"\n : \"Mark this comment as unresolved?\";\n dialog.appendChild(body);\n\n const actions = document.createElement(\"div\");\n actions.className = \"mrsf-actions-row\";\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.type = \"button\";\n cancelBtn.className = \"mrsf-btn\";\n cancelBtn.textContent = \"Cancel\";\n cancelBtn.addEventListener(\"click\", () => this.closeOverlay());\n actions.appendChild(cancelBtn);\n\n const confirmBtn = document.createElement(\"button\");\n confirmBtn.type = \"button\";\n confirmBtn.className = \"mrsf-btn mrsf-btn-primary\";\n confirmBtn.textContent = action === \"delete\" ? \"Delete\" : \"Confirm\";\n confirmBtn.addEventListener(\"click\", () => {\n const detailOut: MrsfSubmitDetail = {\n action,\n commentId: detail.commentId,\n text: \"\",\n type: null,\n severity: null,\n line: detail.line,\n end_line: detail.end_line ?? detail.line ?? null,\n start_column: detail.start_column ?? null,\n end_column: detail.end_column ?? null,\n selection_text: detail.selectionText ?? null,\n };\n document.dispatchEvent(new CustomEvent(\"mrsf:submit\", { detail: detailOut, bubbles: true }));\n this.closeOverlay();\n });\n actions.appendChild(confirmBtn);\n\n dialog.appendChild(actions);\n\n overlay.appendChild(dialog);\n overlay.addEventListener(\"click\", (e) => {\n if (e.target === overlay) this.closeOverlay();\n });\n\n document.body.appendChild(overlay);\n this.overlayEl = overlay;\n }\n}\n\n// \u2500\u2500 Auto-init convenience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Scan for containers with [data-mrsf-controller] and auto-init.\n\nconst controllerRegistry = new Set<MrsfController>();\nlet autoInitDone = false;\n\nexport function refreshAll(): void {\n for (const controller of controllerRegistry) {\n controller.refresh();\n }\n}\n\nexport function autoInit(): void {\n if (autoInitDone) return;\n autoInitDone = true;\n\n const containers = document.querySelectorAll<HTMLElement>(\"[data-mrsf-controller]\");\n for (const container of containers) {\n const pos = container.dataset.mrsfGutterPosition as MrsfControllerOptions[\"gutterPosition\"] | undefined;\n const interactive = container.dataset.mrsfInteractive === \"true\";\n new MrsfController(container, { gutterPosition: pos ?? \"right\", interactive });\n }\n}\n\n// Auto-init on DOMContentLoaded if in browser context\nif (typeof document !== \"undefined\") {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", autoInit);\n } else {\n autoInit();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,50 @@
1
+ export type MrsfResolvedState = "open" | "resolved" | "mixed";
2
+ export interface MrsfGutterBadgePresentation {
3
+ icon: string;
4
+ countText: string;
5
+ label: string;
6
+ title: string;
7
+ ariaLabel: string;
8
+ className?: string;
9
+ attributes?: Record<string, string>;
10
+ }
11
+ export interface MrsfGutterBadgeContext {
12
+ line: number;
13
+ commentCount: number;
14
+ threadCount: number;
15
+ resolvedState: MrsfResolvedState;
16
+ highestSeverity: string | null;
17
+ isActive: boolean;
18
+ }
19
+ export interface MrsfGutterBadgeRenderContext extends MrsfGutterBadgeContext {
20
+ defaultPresentation: MrsfGutterBadgePresentation;
21
+ }
22
+ export interface MrsfGutterAddButtonPresentation {
23
+ label: string;
24
+ title: string;
25
+ ariaLabel: string;
26
+ className?: string;
27
+ attributes?: Record<string, string>;
28
+ }
29
+ export interface MrsfGutterAddButtonContext {
30
+ line: number;
31
+ isActive: boolean;
32
+ }
33
+ export interface MrsfGutterAddButtonRenderContext extends MrsfGutterAddButtonContext {
34
+ defaultPresentation: MrsfGutterAddButtonPresentation;
35
+ }
36
+ export interface MrsfGutterRenderers {
37
+ badge?: (context: MrsfGutterBadgeRenderContext) => Partial<MrsfGutterBadgePresentation> | null | undefined;
38
+ addButton?: (context: MrsfGutterAddButtonRenderContext) => Partial<MrsfGutterAddButtonPresentation> | null | undefined;
39
+ }
40
+ export interface MrsfBadgeSource {
41
+ commentCount: number;
42
+ threadCount: number;
43
+ resolvedState: MrsfResolvedState;
44
+ }
45
+ export declare function formatMrsfCount(count: number, max?: number): string;
46
+ export declare function createMrsfGutterBadgePresentation(context: MrsfGutterBadgeContext): MrsfGutterBadgePresentation;
47
+ export declare function createMrsfGutterAddButtonPresentation(_context: MrsfGutterAddButtonContext): MrsfGutterAddButtonPresentation;
48
+ export declare function resolveMrsfGutterBadgePresentation(context: MrsfGutterBadgeContext, renderer?: MrsfGutterRenderers["badge"]): MrsfGutterBadgePresentation;
49
+ export declare function resolveMrsfGutterAddButtonPresentation(context: MrsfGutterAddButtonContext, renderer?: MrsfGutterRenderers["addButton"]): MrsfGutterAddButtonPresentation;
50
+ //# sourceMappingURL=gutter.d.ts.map
package/dist/index.js CHANGED
@@ -7,6 +7,15 @@ import { parseSidecarContent } from "@mrsf/cli";
7
7
  import { Marked } from "marked";
8
8
 
9
9
  // ../shared/dist/comments.js
10
+ function getExtensionFields(comment) {
11
+ const extensions = {};
12
+ for (const [key, value] of Object.entries(comment)) {
13
+ if (key.startsWith("x_")) {
14
+ extensions[key] = value;
15
+ }
16
+ }
17
+ return extensions;
18
+ }
10
19
  function toSlimComments(doc) {
11
20
  return doc.comments.map((c) => ({
12
21
  id: c.id,
@@ -21,7 +30,8 @@ function toSlimComments(doc) {
21
30
  reply_to: c.reply_to || null,
22
31
  severity: c.severity || null,
23
32
  type: c.type || null,
24
- timestamp: c.timestamp || null
33
+ timestamp: c.timestamp || null,
34
+ ...getExtensionFields(c)
25
35
  }));
26
36
  }
27
37
  function groupByLine(comments) {
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/shared.ts", "../../shared/src/comments.ts"],
4
- "sourcesContent": ["/**\n * @mrsf/marked-mrsf \u2014 Marked plugin for MRSF review comments.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarkedMrsf } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport const markedMrsf = createMarkedMrsf((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) return null;\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default markedMrsf;", "/**\n * Shared logic for @mrsf/marked-mrsf.\n */\n\nimport { Marked, type MarkedExtension, type Token, type Tokens } from \"marked\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, CommentThread, LineMap, MrsfPluginOptions } from \"./types.js\";\n\ntype TokenWithMrsf = Token & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n mrsfHeaderLine?: number;\n mrsfRowLines?: number[];\n};\n\ntype ListItemWithMrsf = Tokens.ListItem & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n};\n\nconst markedRuntime = new Marked();\nconst BaseRenderer = markedRuntime.Renderer;\n\nconst blockquoteRenderer = BaseRenderer.prototype.blockquote;\nconst codeRenderer = BaseRenderer.prototype.code;\nconst headingRenderer = BaseRenderer.prototype.heading;\nconst hrRenderer = BaseRenderer.prototype.hr;\nconst listItemRenderer = BaseRenderer.prototype.listitem;\nconst paragraphRenderer = BaseRenderer.prototype.paragraph;\nconst tableRenderer = BaseRenderer.prototype.table;\n\nfunction countLines(value: string): number {\n if (!value) return 1;\n return value.split(\"\\n\").length;\n}\n\nfunction trimTrailingBlankLines(value: string): string {\n return value.replace(/\\n+$/g, \"\");\n}\n\nfunction firstCommentLine(lineMap: LineMap, startLine: number, endLine: number): number {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return line;\n }\n }\n return startLine;\n}\n\nfunction hasCommentInRange(lineMap: LineMap, startLine: number, endLine: number): boolean {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return true;\n }\n }\n return false;\n}\n\nfunction stampBlock(token: TokenWithMrsf, startLine: number, lineMap: LineMap): number {\n const raw = typeof token.raw === \"string\" ? token.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = startLine + countLines(semantic) - 1;\n\n token.mrsfStartLine = startLine;\n token.mrsfEndLine = endLine;\n token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);\n token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);\n\n if (token.type === \"table\") {\n const tableToken = token as Tokens.Table & TokenWithMrsf;\n tableToken.mrsfHeaderLine = startLine;\n tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);\n }\n\n return startLine + countLines(raw) - 1;\n}\n\nfunction stampListItems(token: Tokens.List & TokenWithMrsf, lineMap: LineMap): void {\n let currentLine = token.mrsfStartLine ?? 1;\n\n for (const item of token.items as ListItemWithMrsf[]) {\n const raw = typeof item.raw === \"string\" ? item.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = currentLine + countLines(semantic) - 1;\n item.mrsfStartLine = currentLine;\n item.mrsfEndLine = endLine;\n item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);\n item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);\n\n stampTokens(item.tokens as TokenWithMrsf[], currentLine, lineMap);\n currentLine += countLines(raw) - 1;\n }\n}\n\nfunction stampTokens(tokens: TokenWithMrsf[], startLine: number, lineMap: LineMap): number {\n let currentLine = startLine;\n\n for (const token of tokens) {\n currentLine = stampBlock(token, currentLine, lineMap);\n\n if (token.type === \"blockquote\") {\n stampTokens((token as Tokens.Blockquote).tokens as TokenWithMrsf[], token.mrsfStartLine ?? currentLine, lineMap);\n } else if (token.type === \"list\") {\n stampListItems(token as Tokens.List & TokenWithMrsf, lineMap);\n }\n }\n\n return currentLine;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction buildAttrs(token: { mrsfStartLine?: number; mrsfEndLine?: number; mrsfLine?: number; mrsfLineHighlight?: boolean }, lineHighlight: boolean): string {\n if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {\n return \"\";\n }\n\n const attrs = [\n `data-mrsf-line=\"${token.mrsfLine}\"`,\n `data-mrsf-start-line=\"${token.mrsfStartLine}\"`,\n `data-mrsf-end-line=\"${token.mrsfEndLine}\"`,\n ];\n\n if (lineHighlight && token.mrsfLineHighlight) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\nfunction addAttrs(html: string, attrs: string, tagName?: string): string {\n if (!attrs) return html;\n const pattern = tagName\n ? new RegExp(`^<${tagName}(?=[\\\\s>])`, \"i\")\n : /^<([a-z0-9-]+)(?=[\\s>])/i;\n return html.replace(pattern, (match) => match + attrs);\n}\n\nfunction flattenThreads(lineMap: LineMap): CommentThread[] {\n const threads: CommentThread[] = [];\n for (const lineThreads of lineMap.values()) {\n threads.push(...lineThreads);\n }\n return threads;\n}\n\nfunction createDataContainer(options: MrsfPluginOptions, threads: CommentThread[]): string {\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n return `<script type=\"application/mrsf+json\">${payload.replace(/</g, \"\\\\u003c\")}</script>`;\n}\n\nfunction renderAnnotatedTable(\n this: { parser: { parseInline: (tokens: Token[]) => string } },\n token: Tokens.Table & TokenWithMrsf,\n lineMap: LineMap,\n lineHighlight: boolean,\n): string {\n const baseHtml = tableRenderer.call(this, token);\n const rowLines = [token.mrsfHeaderLine, ...(token.mrsfRowLines ?? [])];\n let rowIndex = 0;\n\n return baseHtml.replace(/<tr>/g, () => {\n const line = rowLines[rowIndex++];\n if (line == null) return \"<tr>\";\n\n const hasComment = hasCommentInRange(lineMap, line, line);\n\n const attrs = [\n `data-mrsf-line=\"${line}\"`,\n `data-mrsf-start-line=\"${line}\"`,\n `data-mrsf-end-line=\"${line}\"`,\n ];\n if (lineHighlight && hasComment) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n return `<tr ${attrs.join(\" \")}>`;\n });\n}\n\nexport function createMarkedMrsf(loader: CommentLoader) {\n return function markedMrsf(options: MrsfPluginOptions = {}): MarkedExtension {\n let currentLineMap: LineMap | null = null;\n let currentThreads: CommentThread[] = [];\n\n return {\n hooks: {\n processAllTokens(tokens) {\n const result = resolveComments(loader, options);\n if (!result) {\n currentLineMap = null;\n currentThreads = [];\n return tokens;\n }\n\n currentLineMap = result.lineMap;\n currentThreads = flattenThreads(result.lineMap);\n stampTokens(tokens as TokenWithMrsf[], 1, result.lineMap);\n return tokens;\n },\n postprocess(html) {\n if (!currentLineMap || currentThreads.length === 0) {\n return html;\n }\n return html + createDataContainer(options, currentThreads);\n },\n },\n renderer: {\n heading(token) {\n return addAttrs(headingRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), `h${token.depth}`);\n },\n paragraph(token) {\n return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"p\");\n },\n code(token) {\n return addAttrs(codeRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"pre\");\n },\n blockquote(token) {\n return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"blockquote\");\n },\n listitem(token) {\n return addAttrs(listItemRenderer.call(this, token), buildAttrs(token as ListItemWithMrsf, options.lineHighlight ?? false), \"li\");\n },\n hr(token) {\n return addAttrs(hrRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"hr\");\n },\n table(token) {\n return renderAnnotatedTable.call(\n this,\n token as Tokens.Table & TokenWithMrsf,\n currentLineMap ?? new Map(),\n options.lineHighlight ?? false,\n );\n },\n },\n };\n };\n}", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @mrsf/marked-mrsf \u2014 Marked plugin for MRSF review comments.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarkedMrsf } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport const markedMrsf = createMarkedMrsf((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) return null;\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default markedMrsf;", "/**\n * Shared logic for @mrsf/marked-mrsf.\n */\n\nimport { Marked, type MarkedExtension, type Token, type Tokens } from \"marked\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, CommentThread, LineMap, MrsfPluginOptions } from \"./types.js\";\n\ntype TokenWithMrsf = Token & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n mrsfHeaderLine?: number;\n mrsfRowLines?: number[];\n};\n\ntype ListItemWithMrsf = Tokens.ListItem & {\n mrsfStartLine?: number;\n mrsfEndLine?: number;\n mrsfLine?: number;\n mrsfLineHighlight?: boolean;\n};\n\nconst markedRuntime = new Marked();\nconst BaseRenderer = markedRuntime.Renderer;\n\nconst blockquoteRenderer = BaseRenderer.prototype.blockquote;\nconst codeRenderer = BaseRenderer.prototype.code;\nconst headingRenderer = BaseRenderer.prototype.heading;\nconst hrRenderer = BaseRenderer.prototype.hr;\nconst listItemRenderer = BaseRenderer.prototype.listitem;\nconst paragraphRenderer = BaseRenderer.prototype.paragraph;\nconst tableRenderer = BaseRenderer.prototype.table;\n\nfunction countLines(value: string): number {\n if (!value) return 1;\n return value.split(\"\\n\").length;\n}\n\nfunction trimTrailingBlankLines(value: string): string {\n return value.replace(/\\n+$/g, \"\");\n}\n\nfunction firstCommentLine(lineMap: LineMap, startLine: number, endLine: number): number {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return line;\n }\n }\n return startLine;\n}\n\nfunction hasCommentInRange(lineMap: LineMap, startLine: number, endLine: number): boolean {\n for (let line = startLine; line <= endLine; line++) {\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n return true;\n }\n }\n return false;\n}\n\nfunction stampBlock(token: TokenWithMrsf, startLine: number, lineMap: LineMap): number {\n const raw = typeof token.raw === \"string\" ? token.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = startLine + countLines(semantic) - 1;\n\n token.mrsfStartLine = startLine;\n token.mrsfEndLine = endLine;\n token.mrsfLine = firstCommentLine(lineMap, startLine, endLine);\n token.mrsfLineHighlight = hasCommentInRange(lineMap, startLine, endLine);\n\n if (token.type === \"table\") {\n const tableToken = token as Tokens.Table & TokenWithMrsf;\n tableToken.mrsfHeaderLine = startLine;\n tableToken.mrsfRowLines = tableToken.rows.map((_, index) => startLine + 2 + index);\n }\n\n return startLine + countLines(raw) - 1;\n}\n\nfunction stampListItems(token: Tokens.List & TokenWithMrsf, lineMap: LineMap): void {\n let currentLine = token.mrsfStartLine ?? 1;\n\n for (const item of token.items as ListItemWithMrsf[]) {\n const raw = typeof item.raw === \"string\" ? item.raw : \"\";\n const semantic = trimTrailingBlankLines(raw);\n const endLine = currentLine + countLines(semantic) - 1;\n item.mrsfStartLine = currentLine;\n item.mrsfEndLine = endLine;\n item.mrsfLine = firstCommentLine(lineMap, currentLine, endLine);\n item.mrsfLineHighlight = hasCommentInRange(lineMap, currentLine, endLine);\n\n stampTokens(item.tokens as TokenWithMrsf[], currentLine, lineMap);\n currentLine += countLines(raw) - 1;\n }\n}\n\nfunction stampTokens(tokens: TokenWithMrsf[], startLine: number, lineMap: LineMap): number {\n let currentLine = startLine;\n\n for (const token of tokens) {\n currentLine = stampBlock(token, currentLine, lineMap);\n\n if (token.type === \"blockquote\") {\n stampTokens((token as Tokens.Blockquote).tokens as TokenWithMrsf[], token.mrsfStartLine ?? currentLine, lineMap);\n } else if (token.type === \"list\") {\n stampListItems(token as Tokens.List & TokenWithMrsf, lineMap);\n }\n }\n\n return currentLine;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction buildAttrs(token: { mrsfStartLine?: number; mrsfEndLine?: number; mrsfLine?: number; mrsfLineHighlight?: boolean }, lineHighlight: boolean): string {\n if (token.mrsfStartLine == null || token.mrsfEndLine == null || token.mrsfLine == null) {\n return \"\";\n }\n\n const attrs = [\n `data-mrsf-line=\"${token.mrsfLine}\"`,\n `data-mrsf-start-line=\"${token.mrsfStartLine}\"`,\n `data-mrsf-end-line=\"${token.mrsfEndLine}\"`,\n ];\n\n if (lineHighlight && token.mrsfLineHighlight) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\nfunction addAttrs(html: string, attrs: string, tagName?: string): string {\n if (!attrs) return html;\n const pattern = tagName\n ? new RegExp(`^<${tagName}(?=[\\\\s>])`, \"i\")\n : /^<([a-z0-9-]+)(?=[\\s>])/i;\n return html.replace(pattern, (match) => match + attrs);\n}\n\nfunction flattenThreads(lineMap: LineMap): CommentThread[] {\n const threads: CommentThread[] = [];\n for (const lineThreads of lineMap.values()) {\n threads.push(...lineThreads);\n }\n return threads;\n}\n\nfunction createDataContainer(options: MrsfPluginOptions, threads: CommentThread[]): string {\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n return `<script type=\"application/mrsf+json\">${payload.replace(/</g, \"\\\\u003c\")}</script>`;\n}\n\nfunction renderAnnotatedTable(\n this: { parser: { parseInline: (tokens: Token[]) => string } },\n token: Tokens.Table & TokenWithMrsf,\n lineMap: LineMap,\n lineHighlight: boolean,\n): string {\n const baseHtml = tableRenderer.call(this, token);\n const rowLines = [token.mrsfHeaderLine, ...(token.mrsfRowLines ?? [])];\n let rowIndex = 0;\n\n return baseHtml.replace(/<tr>/g, () => {\n const line = rowLines[rowIndex++];\n if (line == null) return \"<tr>\";\n\n const hasComment = hasCommentInRange(lineMap, line, line);\n\n const attrs = [\n `data-mrsf-line=\"${line}\"`,\n `data-mrsf-start-line=\"${line}\"`,\n `data-mrsf-end-line=\"${line}\"`,\n ];\n if (lineHighlight && hasComment) {\n attrs.push('class=\"mrsf-line-highlight\"');\n }\n return `<tr ${attrs.join(\" \")}>`;\n });\n}\n\nexport function createMarkedMrsf(loader: CommentLoader) {\n return function markedMrsf(options: MrsfPluginOptions = {}): MarkedExtension {\n let currentLineMap: LineMap | null = null;\n let currentThreads: CommentThread[] = [];\n\n return {\n hooks: {\n processAllTokens(tokens) {\n const result = resolveComments(loader, options);\n if (!result) {\n currentLineMap = null;\n currentThreads = [];\n return tokens;\n }\n\n currentLineMap = result.lineMap;\n currentThreads = flattenThreads(result.lineMap);\n stampTokens(tokens as TokenWithMrsf[], 1, result.lineMap);\n return tokens;\n },\n postprocess(html) {\n if (!currentLineMap || currentThreads.length === 0) {\n return html;\n }\n return html + createDataContainer(options, currentThreads);\n },\n },\n renderer: {\n heading(token) {\n return addAttrs(headingRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), `h${token.depth}`);\n },\n paragraph(token) {\n return addAttrs(paragraphRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"p\");\n },\n code(token) {\n return addAttrs(codeRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"pre\");\n },\n blockquote(token) {\n return addAttrs(blockquoteRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"blockquote\");\n },\n listitem(token) {\n return addAttrs(listItemRenderer.call(this, token), buildAttrs(token as ListItemWithMrsf, options.lineHighlight ?? false), \"li\");\n },\n hr(token) {\n return addAttrs(hrRenderer.call(this, token), buildAttrs(token as TokenWithMrsf, options.lineHighlight ?? false), \"hr\");\n },\n table(token) {\n return renderAnnotatedTable.call(\n this,\n token as Tokens.Table & TokenWithMrsf,\n currentLineMap ?? new Map(),\n options.lineHighlight ?? false,\n );\n },\n },\n };\n };\n}", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\nfunction getExtensionFields(comment: Record<string, unknown>): Record<`x_${string}`, unknown> {\n const extensions: Record<`x_${string}`, unknown> = {};\n\n for (const [key, value] of Object.entries(comment)) {\n if (key.startsWith(\"x_\")) {\n extensions[key as `x_${string}`] = value;\n }\n }\n\n return extensions;\n}\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n ...getExtensionFields(c as Record<string, unknown>),\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["markedMrsf"]
7
7
  }
package/dist/style.css CHANGED
@@ -57,6 +57,11 @@
57
57
  z-index: 10;
58
58
  }
59
59
 
60
+ .mrsf-gutter.is-hidden,
61
+ .mrsf-line-highlight-layer.is-hidden {
62
+ display: none;
63
+ }
64
+
60
65
  .mrsf-gutter-left {
61
66
  left: calc(-1 * var(--mrsf-gutter-width) - 8px);
62
67
  }
@@ -65,6 +70,27 @@
65
70
  right: calc(-1 * var(--mrsf-gutter-width) - 8px);
66
71
  }
67
72
 
73
+ .mrsf-line-highlight-layer {
74
+ position: absolute;
75
+ inset: 0;
76
+ pointer-events: none;
77
+ z-index: 1;
78
+ }
79
+
80
+ .mrsf-line-highlight-overlay {
81
+ position: absolute;
82
+ left: 0;
83
+ right: 0;
84
+ border-left: 3px solid var(--mrsf-accent);
85
+ background: rgba(55, 148, 255, 0.06);
86
+ border-radius: 2px;
87
+ transition: background 0.15s ease;
88
+ }
89
+
90
+ .mrsf-line-highlight-overlay:hover {
91
+ background: rgba(55, 148, 255, 0.12);
92
+ }
93
+
68
94
  /* ── Line highlight (CSS-only tinting for commented lines) ── */
69
95
 
70
96
  .mrsf-line-highlight {
package/dist/types.d.ts CHANGED
@@ -91,6 +91,7 @@ export interface SlimComment {
91
91
  severity: string | null;
92
92
  type: string | null;
93
93
  timestamp: string | null;
94
+ [key: `x_${string}`]: unknown;
94
95
  }
95
96
  /** A root comment with its threaded replies. */
96
97
  export interface CommentThread {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrsf/marked-mrsf",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Marked plugin for rendering Sidemark/MRSF review comments",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",