npm package: @mrsf/markdown-it-mrsf
Description: markdown-it plugin for rendering Sidemark/MRSF (Markdown Review Sidecar Format) review comments
Author: Wictor Wilén
Latest version: 0.4.9

# @mrsf/markdown-it-mrsf

A [markdown-it](https://github.com/markdown-it/markdown-it) plugin for rendering [MRSF (Sidemark)](https://github.com/wictorwilen/MRSF) review comments directly into HTML output.

Renders badges, line highlights, inline text highlights, and tooltips — no client-side JavaScript required for static use. An optional interactive mode adds action buttons for host applications to hook into.

## Install

```bash
npm install @mrsf/markdown-it-mrsf
```

## Browser & Bundler Support

The plugin works seamlessly in both Node.js and browser environments. When bundled with tools like **Vite**, **webpack**, or **esbuild**, the `"browser"` export condition in `package.json` automatically resolves to a browser-safe entry point — no special imports required.

```ts
// Works everywhere — Node.js, Vite, webpack, etc.
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";
```

In browser environments, only the `comments` and `loader` options are available (no file-system access). The `sidecarPath` and `documentPath` options require Node.js.

## Usage

### Inline data (recommended for testing and custom pipelines)

```ts
import MarkdownIt from "markdown-it";
import { mrsfPlugin } from "@mrsf/markdown-it-mrsf";

const md = new MarkdownIt();
md.use(mrsfPlugin, {
  comments: {
    mrsf_version: "1.0",
    document: "guide.md",
    comments: [
      {
        id: "c1",
        author: "Jane",
        timestamp: "2026-01-01T00:00:00Z",
        text: "Can you clarify this section?",
        resolved: false,
        line: 3,
      },
    ],
  },
});

const html = md.render(markdownSource);
```

### Auto-discover from disk

```ts
md.use(mrsfPlugin, {
  documentPath: "docs/guide.md",
});
```

The plugin looks for `docs/guide.md.review.yaml` (or `.review.json`) next to the document.

### Explicit sidecar path

```ts
md.use(mrsfPlugin, {
  sidecarPath: ".reviews/docs/guide.md.review.yaml",
});
```

### Custom loader function

```ts
md.use(mrsfPlugin, {
  loader: () => {
    // Load sidecar data however you like — from a database, API, in-memory store, etc.
    return {
      mrsf_version: "1.0",
      document: "guide.md",
      comments: [/* ... */],
    };
  },
});
```

## Stylesheet

Include the default CSS for badges, tooltips, and highlights:

```ts
import "@mrsf/markdown-it-mrsf/style.css";
```

Or link directly in HTML:

```html
<link rel="stylesheet" href="node_modules/@mrsf/markdown-it-mrsf/dist/style.css">
```

Customise with CSS custom properties:

```css
:root {
  --mrsf-accent: #3794ff;
  --mrsf-badge-bg: #007acc;
  --mrsf-badge-fg: #fff;
  --mrsf-tooltip-bg: #252526;
  --mrsf-tooltip-fg: #cccccc;
  --mrsf-highlight-bg: rgba(255, 213, 79, 0.25);
}
```

## Interactive Mode

Enable `interactive: true` to add action buttons inside tooltips, plus:

- Gutter “Add comment” buttons for each line badge
- An inline “Add comment” floater that appears when users select text

Buttons render as `<button data-mrsf-action="..." data-mrsf-comment-id="...">` elements — inert without JavaScript.

```ts
md.use(mrsfPlugin, {
  comments: sidecarData,
  interactive: true,
});
```

### Controller

Include the optional controller to wire up the inline and gutter actions. By default it shows built-in dialogs for add, reply, edit, resolve, unresolve, and delete, then dispatches events you can persist through your own API:

```ts
import { refreshAll } from "@mrsf/markdown-it-mrsf/controller";

document.addEventListener("mrsf:submit", async (e) => {
  // { action, commentId, text?, line?, end_line?, start_column?, end_column?, selection_text? }
  await saveComment(e.detail);
});

await mermaid.run();
refreshAll();
```

Events fired after user confirmation: `mrsf:add`, `mrsf:reply`, `mrsf:edit`, `mrsf:resolve`, `mrsf:unresolve`, `mrsf:delete`, `mrsf:navigate`, plus `mrsf:submit` with the full payload.

Set `window.mrsfDisableBuiltinUi = true` before loading the controller if you want to render your own UI and only consume the events.

The controller also auto-refreshes gutter positions on DOM mutations inside the rendered container, which covers common async renderers such as Mermaid. Call `refreshAll()` after third-party rendering when you need a deterministic reposition step.

### Shared Gutter Render Hooks

The controller accepts `gutterRenderers` so hosts can override the shared badge and add-button presentation without forking plugin-specific UX:

```ts
import { MrsfController } from "@mrsf/markdown-it-mrsf/controller";

new MrsfController(document.querySelector(".markdown-body")!, {
  interactive: true,
  gutterRenderers: {
    badge: ({ defaultPresentation }) => ({
      label: `🗨 ${defaultPresentation.countText}`,
      icon: "🗨",
      countText: defaultPresentation.countText,
    }),
    addButton: () => ({
      label: "New",
    }),
  },
});
```

The default renderer contract is shared with the Monaco integration, including count capping at `9+` and the standard tooltip/add-button labels.

## Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `comments` | `MrsfDocument` | — | Pre-loaded sidecar data (highest priority) |
| `loader` | `() => MrsfDocument \| null` | — | Custom loader function for sidecar data |
| `documentPath` | `string` | — | Path to Markdown file for auto-discovery |
| `sidecarPath` | `string` | — | Explicit path to sidecar file |
| `showResolved` | `boolean` | `true` | Whether to show resolved comments |
| `interactive` | `boolean` | `false` | Add action buttons for host JS integration |
| `gutterPosition` | `'left' \| 'right'` | `'right'` | Badge position: left margin gutter or floated right |
| `gutterForInline` | `boolean` | `true` | Show gutter badge for comments that also have inline highlights |
| `inlineHighlights` | `boolean` | `true` | Render inline text highlights for `selected_text` |
| `lineHighlight` | `boolean` | `false` | Add background tint on commented lines |
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color scheme hint |
| `cwd` | `string` | `process.cwd()` | Working directory for file resolution |

### Display Modes

The plugin supports several display modes that can be combined:

- **Gutter + inline** (default): Badges appear in the gutter, `selected_text` is highlighted inline with hover tooltips on both.
- **Gutter only** (`inlineHighlights: false`): Only gutter badges shown, no inline highlights.
- **Inline only** (`gutterForInline: false`): Lines with inline-highlighted comments show only the highlight (with tooltip) — no gutter badge. Lines without `selected_text` still show badges.

## DOM Contract

All rendered elements follow a strict data-attribute contract:

- `data-mrsf-line` — source line number (1-based)
- `data-mrsf-comment-id` — comment identifier
- `data-mrsf-action` — action type (`add`, `resolve`, `unresolve`, `reply`, `edit`, `delete`, `navigate`)
- `data-mrsf-start-line`, `data-mrsf-end-line`, `data-mrsf-start-column`, `data-mrsf-end-column` — optional selection anchors when present

## License

[MIT](../LICENSE)