diff --git a/content/guides/editor.md b/content/guides/editor.md
index 54ed9046..bf09822a 100644
--- a/content/guides/editor.md
+++ b/content/guides/editor.md
@@ -226,6 +226,25 @@ does not store secrets or run a deployer.
---
+## Density modes and the writing surface
+
+The shell opens in **Author** mode: Source and Project only, with the
+authoring hints folded into one disclosure under the editor. **Review** mode
+restores the full chrome — Problems, Preview, Watch, Graph, and Publication.
+The choice is a per-browser preference, not project state; switching modes
+never touches your buffer or unsaved changes.
+
+Source is the hero in both modes. Around the native textarea the editor draws
+presentation chrome: a line gutter measured against the real text geometry
+(numbers for the visible window sit where their lines actually are, so
+wrapped lines cannot drift them, and the current line is highlighted), a
+current-line band, and a seam under a recognized leading frontmatter fence. The seam reads fence *shape* only — it
+does not parse YAML or keys, and **Build diagnostics** and Boris remain the
+authority on frontmatter.
+
+A section-nav link for a pane that lives in Review switches modes and then
+jumps to that pane, so no link pretends a hidden pane is on screen.
+
## Compiler-backed commands and problems
The Problems pane runs a fixed allowlist of Boris invocations against saved
@@ -256,6 +275,14 @@ Boris exit codes stay distinct: **1** content/graph failure, **2**
usage/configuration failure, **3** I/O/system failure. The editor surfaces the
class plus the raw exit code.
+Commands are laid out by how often an author runs them: **Validate project**
+and **Build diagnostics** lead in a primary group, **Build HTML** sits with
+them, and **Check graph**, **Verify proof**, and **Run impact** recede into
+the analysis row — all still named, visible, and reachable from the command
+palette. The command in flight shows an in-button progress affordance and
+`aria-busy`, not only the status sentence, and a proof report longer than a
+screenful starts collapsed behind **Show proof verify report**.
+
Diagnostics are grouped by content-relative source, severity, and Boris code.
Each problem card offers:
diff --git a/docs/changelog.d/992-editor-writing-surface.md b/docs/changelog.d/992-editor-writing-surface.md
new file mode 100644
index 00000000..5e2e6f78
--- /dev/null
+++ b/docs/changelog.d/992-editor-writing-surface.md
@@ -0,0 +1,3 @@
+### Changed
+
+- The editor's everyday surface now reads as a writing tool first: a default **Author** density mode (Source + Project; review panes unmounted, authoring hints folded, and section-nav links to Review panes switching modes before they land) beside the full **Review** chrome, Source as a measured hero surface with a measured line gutter, current-line band, and presentation-only frontmatter seam, and a primary/secondary action hierarchy in Problems with per-command `aria-busy` progress and large reports collapsed behind an explicit disclosure; a mode-gated landing that clamps at max scroll keeps its active nav marker while it covers the reading line. Links: [the editor guide](/content/guides/editor.md#density-modes-and-the-writing-surface), [#988](https://github.com/drawmeanelephant/boris/issues/988), [#989](https://github.com/drawmeanelephant/boris/issues/989), [#990](https://github.com/drawmeanelephant/boris/issues/990), [#991](https://github.com/drawmeanelephant/boris/issues/991).
diff --git a/editor/README.md b/editor/README.md
index 217d13c6..b1d9448c 100644
--- a/editor/README.md
+++ b/editor/README.md
@@ -744,6 +744,49 @@ labels.
Presentation only: no endpoint, no Boris surface, and no pipeline change.
+## Density modes and the writing surface
+
+The shell has two density modes (#990), persisted per browser under
+`boris-editor-density` and validated on load like the other editor
+preferences. The mode is disposable UI state, never project truth:
+
+- **Author** (the cold-open default): Source + Project. The review panes
+ (Problems, Preview, Watch, Graph, Publication) are not mounted, and the
+ authoring hints fold into a single disclosure under the writing surface
+ instead of a second card. A section-nav link whose pane lives in Review
+ switches modes and then lands on the real pane — it stays enabled, muted,
+ and titled, so the nav never claims a hidden pane is present. A landing
+ that clamps at max scroll keeps the target's `aria-current` while the
+ target's box still covers the reading line; scrolling off it releases the
+ marker.
+- **Review**: the full diagnostics chrome, unchanged.
+
+Source is the hero in both modes (#989): the writing column outweighs the
+file and rail columns, and the editing surface is a bordered, elevated shell
+whose chrome is presentation only:
+
+- a measured **line gutter** — numbers for the visible window are placed at
+ each line's measured position and the current line is highlighted, so
+ wrapped lines cannot drift them (a fixed-rhythm number column would lie on
+ wrapped prose);
+- a **current-line band** behind the text;
+- a **frontmatter/body seam** drawn when the buffer opens with a recognized
+ `---` … `---` fence pair. It reads fence shape only and validates nothing;
+ Boris remains the frontmatter authority.
+
+The geometry comes from a hidden mirror that holds the buffer verbatim with
+the textarea's exact font, padding, and wrapping metrics, the same technique
+Focus writing mode uses for its paragraph bands. The native textarea remains
+the editing authority; typing, undo/redo, save, and recovery are the shell's
+existing machinery.
+
+Problems now carries an action hierarchy (#991): a primary **Build and
+validate** group (Validate project, Build diagnostics, Build HTML) and a
+secondary **Analysis** group (Check graph, Verify proof), with Run impact in
+its named row. The in-flight command carries `aria-busy` plus an in-button
+progress affordance, and a proof report over 24 lines starts collapsed behind
+a `Show …` disclosure. The allowlist and exit-class reporting are unchanged.
+
## Project file tree
The Project pane renders an indented directory tree over the host's file list.
diff --git a/editor/scripts/preview-frame-check.cjs b/editor/scripts/preview-frame-check.cjs
index 65aa5b3e..8f19c6c0 100644
--- a/editor/scripts/preview-frame-check.cjs
+++ b/editor/scripts/preview-frame-check.cjs
@@ -21,6 +21,12 @@ const { chromium } = require('playwright');
});
try {
await page.goto(appUrl, { waitUntil: 'domcontentloaded' });
+ // The Preview pane lives in Review density (#990); a cold open is the
+ // calm Author view. Switch modes the way an author would, then rebuild.
+ await page.getByRole('group', { name: 'Editor density' })
+ .getByRole('button', { name: 'Review', exact: true })
+ .click();
+ await page.locator('#preview').waitFor({ timeout: 30000 });
await page.getByRole('button', { name: 'Rebuild preview' }).click();
await page.locator('p.preview-state').filter({ hasText: 'success' }).waitFor({ timeout: 120000 });
const frameBody = page.frameLocator('iframe[title="Boris site preview"]').locator('body');
diff --git a/editor/ui/src/App.svelte b/editor/ui/src/App.svelte
index c1788129..bb8d32d8 100644
--- a/editor/ui/src/App.svelte
+++ b/editor/ui/src/App.svelte
@@ -1,5 +1,7 @@
@@ -890,11 +917,16 @@
-
+
-
+
-
-
- rebuildPreview('manual')} />
-
-
+ {#if density.mode === 'review'}
+
+
+ rebuildPreview('manual')} />
+
+
+ {/if}
+ import type { Snippet } from 'svelte';
+
+ // One action row of the editor's shared hierarchy language (#991): a
+ // labelled group whose tone carries primary vs secondary weight. Buttons
+ // keep their own accessible names; the group label is only a landmark for
+ // assistive tech and pointer grouping.
+ let {
+ label,
+ tone = 'plain',
+ children
+ }: {
+ label: string;
+ tone?: 'plain' | 'primary' | 'secondary';
+ children: Snippet;
+ } = $props();
+
+
+
+ {@render children()}
+
diff --git a/editor/ui/src/components/AuthoringTools.svelte b/editor/ui/src/components/AuthoringTools.svelte
index fa1c1c1a..8549b8d1 100644
--- a/editor/ui/src/components/AuthoringTools.svelte
+++ b/editor/ui/src/components/AuthoringTools.svelte
@@ -4,6 +4,11 @@
import { authoring, suggestions, changeCompletionKind, refreshAuthoring } from '../lib/state/authoring.svelte';
import { buffer, insertSuggestion } from '../lib/state/buffer.svelte';
+ // Author mode (#990) keeps the hints present but folded away: the combobox
+ // and schema bounds are one disclosure, not a permanent slab under the
+ // writing surface. Review mode renders the same content expanded.
+ let { collapsed = false }: { collapsed?: boolean } = $props();
+
// The completion combobox owns its keyboard behavior: Esc closes the list,
// the arrows move the active suggestion, Enter inserts it. Focus and input
// always reopen the list after an Esc close.
@@ -28,7 +33,7 @@
}
-