Angle Guard

approved

by Yuriy Vereshchagin

Captures and wraps HTML tags in pasted text so they don't break the note's formatting. - This plugin has not been manually reviewed by Obsidian staff.

36 downloadsUpdated 28d agoMIT

Angle Guard

Captures and wraps HTML tags in pasted text so they don't break the note's formatting.

Paste a paragraph that contains <name> somewhere in the middle, and everything below it stops rendering. Headings go flat, lists lose their bullets, callouts unroll. The source still holds every character, but the note no longer displays it.

Why it happens

Markdown allows raw HTML, and a tag name can be any identifier — it does not have to be a real element. So <name> is read as an opening HTML tag. The renderer passes it to the browser, the browser opens an element that is never closed, and each sibling after it is pulled into that element.

This is not a fault in Obsidian. It is Markdown following its specification, applied to text that was never meant to be markup.

What it does

On paste, it finds tag-shaped text and moves it where the renderer will not read it as markup. The characters are left unchanged.

SituationResult
A tag inside a sentenceWrapped in a code span: `<name>`
A tag on its own linePlaced in a code block
Several lines of HTMLThe whole run is placed in one code block
A full HTML documentOne code block, not one edit per tag

It leaves the following alone:

  • A bare < in prose, as in a < b.
  • Anything already inside a code span, code fence, or YAML frontmatter.
  • Autolinks such as <https://example.com> and <me@example.com>.
  • Inline and block math, and wikilinks.
  • Text already escaped by hand as \<name>.
  • Tags on the ignore list — br, img, hr, sup, sub, u, mark, kbd by default.

Where text is copied from

Where the text is copied from changes what lands on the clipboard.

Copying from a Markdown code block inside Obsidian puts the literal <name> on the clipboard as plain text, so it is caught before it is inserted. Copying from a rendered HTML preview — a web page, the reading view, a browser — does not. There, <name> renders as an empty element with no visible text, so the plain-text copy drops it and only the clipboard's HTML flavor still carries it. Obsidian then pastes its own conversion of that HTML, which puts <name> back into the note after the plugin has already looked at the paste.

The plugin handles this by recording the note at paste time and comparing against it once the paste settles, then protecting whatever was inserted. The check retries across a few frames, so it does not depend on the paste being synchronous.

Commands

CommandWhat it does
Protect raw HTML in current noteRepairs a note that is already broken
Protect raw HTML in selectionThe same, for a selection. Also in the right-click menu
Toggle protection on pasteTurns protection on or off. Also the status bar icon

Settings

Protect on paste is the master switch, on after install. Ask first turns silent protection into a confirmation prompt. Tell me what was fixed shows a short note after each change. Status bar shortcut adds the </> icon, which is struck through when protection is off.

How much to catch sets the scope. Anything that looks like a tag is the default. Only unknown or unclosed tags leaves correctly paired HTML such as <b>bold</b> as written. Tags to leave alone is your own ignore list. Check pastes from the web covers the HTML-preview case above.

Tags in a sentence chooses between a code span and a backslash escape. Where a line already contains an unpaired backtick, the plugin escapes regardless, because a code span would not survive there. Blocks of HTML puts standalone or multi-line HTML in a code block. Code block label is the language written after the opening fence.

Building from source

npm install
npm run build     # type-checks, then writes main.js
npm run lint      # the official Obsidian plugin review rules
npm test          # 44 unit tests, plus a runtime smoke test
npm run dev       # rebuild on change

src/protect.ts holds the protection logic and has no Obsidian dependency, so it is tested on its own. The plugin bundles no third-party code at runtime; it uses only the Obsidian API.

Acknowledgements

  • Build setup, release workflow, and manifest.json / versions.json conventions follow the Obsidian sample plugin (MIT).
  • Code is checked against eslint-plugin-obsidianmd, the official plugin review rules.
  • The tag grammar in src/protect.ts implements the raw-HTML rules from the CommonMark specification.
  • This plugin was built using Claude Opus 5.0 and Opus 4.8. The problem was first worked through with the help of DeepSeek and Poolside, whose design discussions are recorded in the project notes. The angle-bracket rendering behaviour it addresses was identified in day-to-day use.

License

MIT.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.