From 78eec6bf3401f59892d6733aefad0df6c9dd7fd2 Mon Sep 17 00:00:00 2001 From: Ethan Graf Date: Sat, 18 Jul 2026 23:52:19 -0400 Subject: [PATCH] Make code-block background seamless across lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cm-codeblock line decoration carried vertical margin, per-line padding, and border-radius, so each code line rendered as its own rounded box with the editor background showing through the gaps between lines. Drop the vertical margin and give the block horizontal padding only, then tag the first/last block lines (cm-codeblock-first / -last) to add the vertical padding and rounded corners — the run now reads as one continuous box. Co-Authored-By: Claude Opus 4.8 --- src/editors/automerge/automergeEditor.css | 21 +++++++++++++--- .../automerge/livePreview/codeDecoration.ts | 25 ++++++++++++++++++- .../automerge/livePreview/livePreview.test.ts | 4 +++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/editors/automerge/automergeEditor.css b/src/editors/automerge/automergeEditor.css index f6cb13a..c170aa9 100644 --- a/src/editors/automerge/automergeEditor.css +++ b/src/editors/automerge/automergeEditor.css @@ -88,16 +88,31 @@ font-size: 0.9em; } +/* + * Fenced code block. Applied as one line decoration per line, so the + * background must be seamless: no vertical margin (which would show the + * editor background through the gaps) and only horizontal padding here. + * The first/last lines add the vertical padding and rounded corners so the + * whole run reads as a single box. + */ .automerge-editor-container .cm-codeblock { background: rgba(175, 184, 193, 0.1); - border-radius: 6px; - padding: 0.8em 1em; - margin: 0.5em 0; + padding: 0 1em; font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; font-size: 0.9em; line-height: 1.5; } +.automerge-editor-container .cm-codeblock-first { + padding-top: 0.6em; + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} +.automerge-editor-container .cm-codeblock-last { + padding-bottom: 0.6em; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} /* Lists */ .automerge-editor-container .cm-list-1 { diff --git a/src/editors/automerge/livePreview/codeDecoration.ts b/src/editors/automerge/livePreview/codeDecoration.ts index d415bda..6279b22 100644 --- a/src/editors/automerge/livePreview/codeDecoration.ts +++ b/src/editors/automerge/livePreview/codeDecoration.ts @@ -27,7 +27,26 @@ import { import { inlineFormattingHiddenMark } from './hiddenClasses'; const inlineCodeMark = Decoration.mark({ class: 'cm-inline-code' }); +// Per-line background. The first/last lines additionally get rounded corners +// and vertical padding so the block reads as one continuous box (no vertical +// margin between lines, which would show the editor background through gaps). const codeBlockLine = Decoration.line({ class: 'cm-codeblock' }); +const codeBlockLineFirst = Decoration.line({ + class: 'cm-codeblock cm-codeblock-first', +}); +const codeBlockLineLast = Decoration.line({ + class: 'cm-codeblock cm-codeblock-last', +}); +const codeBlockLineOnly = Decoration.line({ + class: 'cm-codeblock cm-codeblock-first cm-codeblock-last', +}); + +function codeBlockLineDeco(line: number, first: number, last: number) { + if (line === first && line === last) return codeBlockLineOnly; + if (line === first) return codeBlockLineFirst; + if (line === last) return codeBlockLineLast; + return codeBlockLine; +} export function buildCodeDecorations( state: EditorState, @@ -74,7 +93,11 @@ export function buildCodeDecorations( } for (let l = startLine; l <= endLine; l++) { const line = state.doc.line(l); - out.push(line.from, line.from, codeBlockLine); + out.push( + line.from, + line.from, + codeBlockLineDeco(l, startLine, endLine), + ); } if (!isCursorOnBlockLine(state, node.from, node.to)) { diff --git a/src/editors/automerge/livePreview/livePreview.test.ts b/src/editors/automerge/livePreview/livePreview.test.ts index 83639e7..9ad066c 100644 --- a/src/editors/automerge/livePreview/livePreview.test.ts +++ b/src/editors/automerge/livePreview/livePreview.test.ts @@ -170,6 +170,10 @@ describe('codeDecoration', () => { const state = makeState('```js\ncode\n```\n\nafter', 18); // in "after" const list = decos(state, buildCodeDecorations(state)); expect(withClass(list, 'cm-codeblock').length).toBeGreaterThanOrEqual(3); + // First and last block lines are tagged so the box is seamless (one + // continuous background, rounded only at the ends). + expect(withClass(list, 'cm-codeblock-first')).toHaveLength(1); + expect(withClass(list, 'cm-codeblock-last')).toHaveLength(1); // Opening and closing fence rows hidden. expect( withClass(list, 'cm-inline-formatting-hidden').length, -- 2.51.2