Skip to content

darko-mesaros/marginalia

Repository files navigation

Marginalia

NOTE: This is very much a work in progress. Use at your own peril

One of the best things you can do these days with LLMs is bridge that skill gap you always had. Learn, experiment, explore, become better. While, straight up asking a model something is good, I found that I tend to get sidetracked from time to ime. I wanted to have the ability to ask a LLM questions about a part of it's answer without having to scroll all the way down and lose my flow. So, because of this Kiro and I built Marginalia - get your knowledge on the margins of an answer!

Marginalia is a web-based LLM explainer tool that renders structured explanations as documents and supports inline margin notes for follow-up questions. Select any passage in the explanation, ask a side question, and get an answer in a margin note, all while the LLM maintains full context of every thread.

This all started with this Excalidraw drawing I made:

Excalidraw drawing

And from there, with some Strands and a whole lot of TypeScript, it sort of became this freakin' thing! 🥳

Marginalia UI

Prerequisites

  • Node.js 18+
  • AWS credentials configured (Bedrock access required)
  • A Bedrock-enabled model (defaults to qwen.qwen3-vl-235b-a22b)

Setup

npm install

Running

npm start

Opens on http://localhost:3000.

Environment Variables

Variable Default Description
PORT 3000 Server port
BEDROCK_MODEL_ID qwen.qwen3-vl-235b-a22b Bedrock model to use
BEDROCK_MAX_TOKENS 8192 Max output tokens per response. Raise for longer answers; some models (e.g. Anthropic Claude) error out if left at their low Bedrock default
MARGINALIA_DATA_DIR ~/.config/marginalia/ Override data/config directory

If you want to start it with a different model you can do:

BEDROCK_MODEL_ID="moonshotai.kimi-k2.5" npm start

Running with just

A justfile is included for convenience. It lets you launch the server with a specific model and pick which AWS profile to authenticate with (handy if you keep your Bedrock access on a personal/named profile):

just run                                  # default model + default AWS profile
just run "moonshotai.kimi-k2.5"           # pick a model
just run "moonshotai.kimi-k2.5" personal  # pick a model + AWS profile

The selected profile is exported as AWS_PROFILE, which the Strands BedrockModel picks up via the standard AWS SDK credential chain — no code changes needed. You can also override the defaults globally:

just model="amazon.nova-pro-v1:0" profile="personal" run

Other recipes: just install, just dev (auto-reload), just build, just test. Run just on its own to list them all.

To raise the output length cap (e.g. for very long answers, or to avoid the "maximum token limit" error some Claude models raise at their low default):

just maxtokens="16384" model="<your-claude-model-id>" profile="personal" run

Development Mode

npm run dev

Uses tsx watch for auto-reload on file changes.

Tests

npm test

Runs 260 tests (property-based + unit) (thank you Kiro ❤️) via Vitest across models, context assembly, conversation ops, validation, agent, retry, SSE, routes, layout, markdown rendering, MCP config management, data directory resolution, system prompt persistence, and conversation export.

How It Works

  1. Type a question in the input bar at the top
  2. The LLM streams a markdown-rendered explanation into the main panel
  3. Select any text in the explanation > a popover appears > ask a side question
  4. The answer appears as a margin note anchored to your selection with some SVG lines
  5. Each margin note supports follow-up questions within its own thread
  6. Continue the main conversation below — your follow-up questions appear as styled cards (blue left border, light background) above each response, and the LLM sees all margin note context
  7. Export your conversation via the export button (top bar) — choose Markdown, HTML, or JSON. The HTML export is a self-contained file with the two-column layout, color-coded highlights, and clickable numbered badges that link inline annotations to their margin notes.

Stack

  • TypeScript / Express backend
  • Strands Agents SDK for Bedrock integration
  • SSE streaming for real-time responses
  • Vanilla HTML/JS frontend (no build step)
  • marked.js, highlight.js, tippy.js (this one caused me troubl), DOMPurify via CDN
  • CSS Custom Highlight API for text anchoring
  • MCP tool integration via settings UI with persistent config
  • Persistent system prompt (survives restarts, stored in system-prompt.md)
  • Conversation export: Markdown (side threads as blockquotes), self-contained HTML (two-column layout, numbered clickable badges), raw JSON
  • Graceful MCP server shutdown on SIGINT/SIGTERM

Data Directory

By default, Marginalia stores all data and config in ~/.config/marginalia/:

~/.config/marginalia/
├── chats/              # Conversation JSON files
│   └── {uuid}.json
├── mcp.json            # MCP server configurations
└── system-prompt.md    # Custom system prompt (optional)

Override with MARGINALIA_DATA_DIR:

MARGINALIA_DATA_DIR=./local-data npm start

MCP Server Configuration

MCP servers can be configured through the settings UI or by editing mcp.json in your data directory (~/.config/marginalia/mcp.json by default). The file follows the VS Code/Cursor/Kiro convention:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "@some/mcp-server"],
      "env": { "API_KEY": "..." },
      "enabled": true
    }
  }
}

Servers can be enabled/disabled individually without removing them. Config is loaded on startup and saved automatically on every change.

TODO

  • Add support to other model providers
  • Improve stability (yeah)
  • The SVG lines get messed all the time up until you scroll
  • Dark mode
  • Make the SVG lines smoother

About

Sometimes answers lie on the margins

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors