---
title: Pasting rich text (formatting survives the trip into chat)
---

# Paste Rich Text → Markdown

## What it is

When you paste from Google Docs, Word on the web, a web page, Notion, or any
source that puts HTML on the clipboard, Omniscio converts the formatting to Markdown
so headings, bullets, bold/italic, code, blockquotes, and tables survive the
trip into the chat textarea or the Add Project Docs Paste tab.

## Where to find it

### Where it works

- **Chat compose textarea** — the box where you type messages to the agent
- **Add Project Docs → Paste tab** — the textarea you paste content into when
  saving a doc to a project
- **Quick Launch composers** — the **New Session** and **Ask Omniscio** prompt boxes
  in the Ctrl+Space floating composer. (A >5,000-char paste there becomes a
  removable "pasted text" chip too; the chip is remove-only — click-to-edit is
  chat-composer only.)

Settings inputs, snippet bodies, away-mode bodies, and bookmarks are NOT in
scope — they receive whitespace-trim only (single-line short fields). The Quick
Launch **Search** tab is also out of scope (a single-line input).

## How it behaves

### How to paste plain text

Hold **Shift** while pasting. The clipboard's HTML representation is ignored
and the plain-text version lands in the textarea. This works on every paste,
no setting required.

For a permanent "always plain text" preference, turn off **Settings → Sessions
→ "Convert pasted rich text to Markdown"**. With the toggle off, every Ctrl+V
(or Cmd+V) behaves like Ctrl+Shift+V — structural HTML is ignored and the
plain-text representation always wins. Default: **on** (historical behavior).
The Google Sheets bypass (see below) runs regardless of this toggle — Sheets
HTML never goes through the Markdown converter in either mode. Holding Shift
or turning the toggle off DOES drop Sheets hyperlinks (the escape hatches
force pure plain text from every source).

### When conversion fires

- Clipboard contains a `text/html` representation
- Shift is NOT held
- "Convert pasted rich text to Markdown" is ON (the default)
- One of the following is true about the HTML:
  - It is from Google Sheets and contains a hyperlink (`<a href>`) — runs
    the narrow link-preserving transform described below, not turndown.
  - It contains structural tags (any of `h1`–`h6`, `ul`, `ol`, `li`,
    `pre`, `code`, `blockquote`, `table`, `strong`, `em`, `b`, `i`, `a`)
    AND is NOT from Google Sheets — runs the full Markdown converter.

If none of the above hold, Omniscio uses the plain-text representation (today's
default behavior).

### Google Sheets paste — plain-text TSV, hyperlinks preserved

Pastes from Google Sheets never run through turndown. Sheets clipboard HTML
carries two kinds of noise that don't convert cleanly:

1. A `<style>` block whose body is wrapped in an HTML comment to hide CSS
   from legacy non-CSS-aware parsers
   (`<!--td {border: 1px solid #cccccc;} br {mso-data-placement:same-cell;}-->`).
   Turndown's default text-emission for `<style>` dumps that literal
   comment string into the markdown output as garbage.
2. Bare-URL cells are emitted as `<a href="URL">URL</a>`, which the
   default markdown rule turns into the redundant `[URL](URL)`.

So Omniscio keeps the **plain-text TSV** as the canonical shape — the cell's
displayed value (single cell) or tab-separated rows (range), which is what
users actually want when pasting from a spreadsheet. Detection looks for
either the `<google-sheets-html-origin>` marker tag or the
`mso-data-placement:same-cell` CSS rule — both stable signatures Sheets has
emitted across years of captures.

### Hyperlinks are preserved (since 2026-05-21)

When a cell carries a `<a href="...">` hyperlink, the URL is reinjected
into the corresponding cell of the plain-text TSV. The plain-text rep alone
drops link targets (it only contains the cell's _displayed_ text), so this
narrow augmentation recovers them without ever running turndown on the
Sheets HTML.

Per-cell rules:

- **Labeled link** — cell displays "Vimeo upload" and links to
  `https://vimeo.com/123` → pastes as `[Vimeo upload](https://vimeo.com/123)`.
- **Bare URL cell** — cell displays the same URL it links to → pastes as
  the bare URL once. No `[URL](URL)` brackets (the original Sheets paste
  complaint that motivated this whole bypass).
- **No-link cell** — pasted verbatim from the plain-text rep, byte-identical
  to the pre-link-preserving bypass.

Multi-cell range pastes work the same way per cell — the TSV shape is
preserved; only cells with a `<a href>` get the URL reinjected.

### Escape hatches

Both Shift-paste and the "Convert pasted rich text to Markdown" toggle
defeat link preservation — they force pure plain text from every source,
including Sheets. A Shift-paste of a labeled Sheets hyperlink lands as the
cell's displayed text (the URL is dropped). Use this when you want exactly
what Sheets shows on screen with no markdown decoration.

### Safety: ambiguous pastes degrade to plain text

If the HTML and plain-text grids can't be safely aligned — row count
mismatch between `<tr>` and plain-text lines, cell count mismatch in a row,
no `<table>` wrapper with multiple anchors, or a DOMParser failure — the
transform bails to the plain-text rep exactly as the pre-link-preserving
bypass did. The link is dropped, but the cells never shift. There is no
path where garbage can land in your textarea.

### Interaction with other paste features

- **Pasting a bare Google Doc URL** still triggers the existing Drive-API
  import flow (off by default; opt in at Settings → Google → "Import Google
  Docs on paste"). The URL flow takes precedence — it pulls the full doc with
  better fidelity than HTML conversion. That path strips inline base64 `data:`
  image references from Google's markdown export the same way this clipboard
  path drops `<img src="data:…">` — so neither route dumps a multi-kilobyte
  base64 wall into the composer. See
  [google-integrations.md](google-integrations.md) step 6.
- **Pasting >5,000 chars** still creates an attachment "chip" rather than
  inlining into the textarea. Conversion happens FIRST, then the chip rule
  operates on the converted Markdown length.

### Google Docs inline formatting (bold / italic / code)

Google Docs doesn't put `<strong>`, `<em>`, or `<code>` tags in its
clipboard HTML — it serializes inline formatting as **styled `<span>`
elements** with CSS hints on the `style` attribute. Omniscio reads those CSS
hints and emits the right Markdown:

- **Bold** — any `<span>` with `font-weight: 700` (or `bold` / `bolder`,
  or a numeric value ≥ `600`) wraps its content in `**...**`. Lighter
  weights (`400` / `normal` / `500`) pass through unchanged.
- **Italic** — `<span style="font-style: italic">` wraps its content in
  `_..._`.
- **Inline code** — `<span>` whose `font-family` references a monospace
  font (Courier, Courier New, Consolas, Monaco, Menlo, Roboto Mono,
  Source Code Pro, Fira Code/Mono, JetBrains Mono, IBM Plex Mono, SF
  Mono, Liberation Mono, DejaVu Sans Mono, Inconsolata, Hack, Cascadia
  Code/Mono, or the literal `monospace` keyword) wraps its content in
  `` `...` ``. Proportional fonts (Arial, Cambria, Georgia, etc.) pass
  through unchanged.

Bold + italic compose as `**_text_**`. Code wins outermost — a span
flagged as both monospace and bold emits `` `text` ``, not ``**`text`**``
(inline code spans don't compose with other markers in Markdown).

Headings (`<h1>`–`<h6>`) work as expected: Google Docs auto-bolds the
heading text content with an inner styled `<span>`, but Omniscio recognizes
this and emits `# Title` instead of `# **Title**`. Italic and code
spans inside a heading still fire — those reflect the author's intent.

### What gets dropped

- Inline styles (`color`, `font-family` not in the monospace list,
  `font-size`, background-color, text-decoration) — Markdown has no
  syntax for these. Note: `font-family` IS read when it references a
  monospace font (see Google Docs inline formatting above).
- Google Docs's outer `<b id="docs-internal-guid-...">` wrapper that surrounds
  every clipboard payload. The `font-weight:normal` inline style on it is
  meant to tell CSS consumers that the whole document is NOT bold, but a
  naive HTML→Markdown converter would still treat the `<b>` as bold and emit
  `**...**` around the entire pasted block. Omniscio unwraps this so pasted
  content doesn't start or end with stray `**`. A plain `<b>bold</b>` from
  any other source still converts to `**bold**` as expected.
- Image references from web pages convert to `![](url)`. The image is NOT
  downloaded — only the URL.
- **Inline base64 images (Google Docs).** Google Docs embeds inline images
  as base64 `data:` URIs — either directly as
  `<img src="data:image/png;base64,...">`, or carried on a **link**
  (`<a href="data:...">` — e.g. a small `https` display image wrapped in a
  link to its full-res `data:` payload). Omniscio strips the `data:` payload in
  BOTH forms: the `<img>` is dropped entirely, and a `data:` link keeps its
  visible text but loses the payload href. So a single Doc paste doesn't dump
  a multi-kilobyte base64 wall into the textarea (real user pastes ballooned
  to ~265k and ~1.6M tokens before this). External image references
  (`<img src="https://...">`) and normal links still convert to `![](url)` /
  `[text](url)` — only the embedded `data:` form is dropped. If you want the
  image content itself, paste it as a separate attachment instead — see
  [chat-attachments.md](chat-attachments.md).

### Edge cases

- **Pasting code from VS Code or a syntax-highlighted source.** If the source
  uses `<pre><code class="language-X">`, conversion produces a clean fenced
  code block. If the source uses only coloured `<span>` tags without a `<pre>`
  wrapper, conversion may produce noisy output. Hold **Shift** to paste the
  plain version.
- **Conversion failure (rare).** If turndown can't parse the HTML, Omniscio silently
  falls back to plain text. You'll see the same content you'd have seen
  without this feature.

## Related

### See also

- [chat-attachments.md](chat-attachments.md) — how the >5,000-char chip rule
  works and where pasted text persists in the database

