diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd75c74f..25e35a24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,13 @@ refactors, dependency updates, CI changes, and code cleanup do not belong here.
---
+## [1.2.8] - 2026-08-15
+
+### Changed
+
+- Added consistent spacing between adjacent rendered code cards in Markdown live preview.
+- Kept document location, status, live-preview, and line-wrapping controls pinned to the top of the editor while scrolling.
+
## [1.2.7] - 2026-08-14
### Changed
diff --git a/apps/desktop/native-backend/Cargo.toml b/apps/desktop/native-backend/Cargo.toml
index 68e7d283..6478585e 100644
--- a/apps/desktop/native-backend/Cargo.toml
+++ b/apps/desktop/native-backend/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "neverwrite-native-backend"
-version = "1.2.7"
+version = "1.2.8"
edition = "2021"
[dependencies]
diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json
index 3e15ece7..a1c43e9f 100644
--- a/apps/desktop/package-lock.json
+++ b/apps/desktop/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "bifrostwrite-desktop",
- "version": "1.2.7",
+ "version": "1.2.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bifrostwrite-desktop",
- "version": "1.2.7",
+ "version": "1.2.8",
"hasInstallScript": true,
"dependencies": {
"@codemirror/commands": "^6.10.3",
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 38d44612..c2e0d981 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "bifrostwrite-desktop",
"private": true,
- "version": "1.2.7",
+ "version": "1.2.8",
"homepage": "https://github.com/bifrost-proxy/BifrostWrite",
"type": "module",
"engines": {
diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml
index 20f59f6e..b670a543 100644
--- a/apps/desktop/src-tauri/Cargo.toml
+++ b/apps/desktop/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "bifrostwrite-desktop"
-version = "1.2.7"
+version = "1.2.8"
edition = "2021"
description = "Tauri desktop shell for BifrostWrite"
license = "Apache-2.0"
diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json
index 53d8d734..566da2c1 100644
--- a/apps/desktop/src-tauri/tauri.conf.json
+++ b/apps/desktop/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "BifrostWrite",
- "version": "1.2.7",
+ "version": "1.2.8",
"identifier": "com.bifrostwrite.desktop",
"build": {
"beforeDevCommand": "npm run renderer:dev",
diff --git a/apps/desktop/src/features/editor/Editor.test.tsx b/apps/desktop/src/features/editor/Editor.test.tsx
index 0c678a47..32b92105 100644
--- a/apps/desktop/src/features/editor/Editor.test.tsx
+++ b/apps/desktop/src/features/editor/Editor.test.tsx
@@ -2593,9 +2593,15 @@ describe("Editor", () => {
renderComponent();
const firstView = getEditorView();
- expect(firstView.scrollDOM.firstElementChild).toHaveClass(
- "cm-lp-scroll-header",
- );
+ const firstScrollHeader = firstView.scrollDOM
+ .firstElementChild as HTMLElement;
+ expect(firstScrollHeader).toHaveClass("cm-lp-scroll-header");
+ expect(getComputedStyle(firstScrollHeader).display).toBe("contents");
+ expect(
+ firstScrollHeader.querySelector(
+ '[data-editor-note-toolbar="true"]',
+ ),
+ ).toHaveStyle({ position: "sticky", top: "0px" });
await act(async () => {
useEditorStore.getState().switchTab("tab-2");
diff --git a/apps/desktop/src/features/editor/MarkdownNoteHeader.test.tsx b/apps/desktop/src/features/editor/MarkdownNoteHeader.test.tsx
index 1fab3393..95ec888c 100644
--- a/apps/desktop/src/features/editor/MarkdownNoteHeader.test.tsx
+++ b/apps/desktop/src/features/editor/MarkdownNoteHeader.test.tsx
@@ -45,6 +45,15 @@ function renderHeader(lineWrapping: boolean) {
);
return {
+ spacer: document.querySelector(
+ '[data-editor-note-toolbar-spacer="true"]',
+ ) as HTMLElement | null,
+ toolbar: document.querySelector(
+ '[data-editor-note-toolbar="true"]',
+ ) as HTMLElement | null,
+ toolbarInner: document.querySelector(
+ '[data-editor-note-toolbar-inner="true"]',
+ ) as HTMLElement | null,
outer: document.querySelector(
'[data-editor-note-header="true"]',
) as HTMLElement | null,
@@ -64,13 +73,29 @@ describe("MarkdownNoteHeader", () => {
});
it("keeps the centered reading layout when line wrapping is enabled", () => {
- const { outer, inner } = renderHeader(true);
+ const { spacer, toolbar, toolbarInner, outer, inner } =
+ renderHeader(true);
+ expect(spacer).toHaveStyle({ height: "32px", flex: "0 0 100%" });
+ expect(toolbar).toHaveAttribute("data-line-wrapping", "true");
+ expect(toolbar).toHaveStyle({
+ position: "sticky",
+ top: "0px",
+ width: "100%",
+ flex: "0 0 100%",
+ padding: "6px var(--editor-horizontal-inset)",
+ });
+ expect(toolbarInner).toHaveStyle({
+ width: "min(100%, var(--editor-content-width))",
+ maxWidth: "var(--editor-content-width)",
+ margin: "0 auto",
+ });
expect(outer).not.toBeNull();
expect(inner).not.toBeNull();
expect(outer).toHaveAttribute("data-line-wrapping", "true");
expect(outer).toHaveStyle({
width: "100%",
- padding: "40px var(--editor-horizontal-inset) 0",
+ padding: "14px var(--editor-horizontal-inset) 0",
+ flex: "0 0 100%",
});
expect(inner).toHaveStyle({
width: "min(100%, var(--editor-content-width))",
@@ -82,13 +107,18 @@ describe("MarkdownNoteHeader", () => {
});
it("switches to a left-aligned layout when line wrapping is disabled", () => {
- const { outer, inner } = renderHeader(false);
+ const { toolbarInner, outer, inner } = renderHeader(false);
expect(outer).not.toBeNull();
expect(inner).not.toBeNull();
expect(outer).toHaveAttribute("data-line-wrapping", "false");
expect(outer).toHaveStyle({
width: "100%",
- padding: "40px var(--editor-horizontal-inset) 0",
+ padding: "14px var(--editor-horizontal-inset) 0",
+ });
+ expect(toolbarInner).toHaveStyle({
+ width: "100%",
+ maxWidth: "none",
+ margin: "0px",
});
expect(inner).toHaveStyle({
width: "100%",
diff --git a/apps/desktop/src/features/editor/MarkdownNoteHeader.tsx b/apps/desktop/src/features/editor/MarkdownNoteHeader.tsx
index 26cb44ec..584f084a 100644
--- a/apps/desktop/src/features/editor/MarkdownNoteHeader.tsx
+++ b/apps/desktop/src/features/editor/MarkdownNoteHeader.tsx
@@ -182,38 +182,56 @@ export function MarkdownNoteHeader({
const bannerCopy = status ? BANNER_COPY[status] : undefined;
+ const readingWidth = lineWrapping
+ ? "min(100%, var(--editor-content-width))"
+ : "100%";
+ const readingMaxWidth = lineWrapping
+ ? "var(--editor-content-width)"
+ : "none";
+ const readingMargin = lineWrapping ? "0 auto" : "0";
+
return (
-
+ <>
+
- {/* Location breadcrumb + OKF status / type badges */}
{locationSegments.length > 0 && (
@@ -264,61 +282,83 @@ export function MarkdownNoteHeader({
{
if (!propertiesExpanded) onToggleProperties();
}}
/>
)}
-
- {/* Title row */}
-
-
- {/* Toolbar: Properties toggle + Search */}
+
+
- }
- active={propertiesExpanded}
- onClick={onToggleProperties}
- />
- }
- onClick={onSearchClick}
+ {/* Title row */}
+
-
- {/* Trust banner (draft / in_review / deprecated / archived) */}
- {status && bannerCopy && (
-
- )}
-
- {/* Properties body (expanded below toolbar) */}
- {propertiesExpanded && (
-
-
+ }
+ active={propertiesExpanded}
+ onClick={onToggleProperties}
+ />
+ }
+ onClick={onSearchClick}
/>
- )}
+
+ {/* Trust banner (draft / in_review / deprecated / archived) */}
+ {status && bannerCopy && (
+
+ )}
+
+ {/* Properties body (expanded below toolbar) */}
+ {propertiesExpanded && (
+
+
+
+ )}
+
{statusMenu && (
@@ -340,7 +380,7 @@ export function MarkdownNoteHeader({
]}
/>
)}
-
+ >
);
}
diff --git a/apps/desktop/src/features/editor/editorExtensions.ts b/apps/desktop/src/features/editor/editorExtensions.ts
index 4b6c6fba..9e16bd2d 100644
--- a/apps/desktop/src/features/editor/editorExtensions.ts
+++ b/apps/desktop/src/features/editor/editorExtensions.ts
@@ -75,8 +75,10 @@ export const baseTheme = EditorView.theme({
overflowY: "auto",
},
".cm-lp-scroll-header": {
- flex: "0 0 100%",
- boxSizing: "border-box",
+ // Let the spacer, sticky action bar, and document header participate
+ // directly in the scroller's flex layout. A sticky descendant would
+ // otherwise stop sticking at the bottom of this short wrapper.
+ display: "contents",
},
".cm-content": {
flex: "1 1 0%",
diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css
index 1ce350d6..d0d1086b 100644
--- a/apps/desktop/src/index.css
+++ b/apps/desktop/src/index.css
@@ -758,6 +758,7 @@ html[data-desktop-platform="windows"].dark {
--code-fence-radius: 6px;
--code-fence-inline-size: min(100%, 68ch);
--editor-code-fence-inline-size: min(100%, 88ch);
+ --editor-code-card-gap: 0.75rem;
}
.chat-code-frame {
@@ -796,7 +797,7 @@ html[data-desktop-platform="windows"].dark {
}
.cm-code-block-header-shell {
- padding-top: 0.25rem;
+ padding-top: var(--editor-code-card-gap);
font-family: var(--font-mono), ui-monospace, monospace;
font-size: 0.93em;
}
@@ -820,14 +821,14 @@ html[data-desktop-platform="windows"].dark {
.cm-code-block-header-shell-unlabeled {
position: relative;
z-index: 10;
- height: 0;
+ height: var(--editor-code-card-gap);
padding-top: 0;
pointer-events: none;
}
.cm-code-block-header-unlabeled {
position: absolute;
- top: 0.55em;
+ top: calc(100% + 0.55em);
right: 0.55em;
min-height: 0;
padding: 0;
@@ -841,7 +842,7 @@ html[data-desktop-platform="windows"].dark {
.cm-code-block-header-shell-unlabeled-empty {
height: auto;
- padding-top: 0.25rem;
+ padding-top: var(--editor-code-card-gap);
}
.cm-code-block-header-unlabeled.cm-code-block-header-only {