Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

Quill is a Tauri 2 + React 19 desktop app — a Markdown editor with track-changes and inline comments, similar to Google Docs suggesting mode. The frontend is React/TypeScript built with Vite; the backend is a thin Rust/Tauri layer that exposes file I/O and native dialogs to the frontend.

## Knowledge Stores

Before starting work in a documented area, and when finishing a fix worth remembering, consult and grow these:

- **`docs/solutions/`** — compounded learnings from solved problems (bug fixes and knowledge notes), each with YAML frontmatter for searchability. Check here first when debugging something that feels familiar; add an entry (via `/ce-compound`) after a non-trivial fix so the next occurrence is fast.
- **`CONCEPTS.md`** — project glossary of domain terms. Read it to ground in the vocabulary; extend it when a new term surfaces that a new engineer would need defined.

## Commands

```bash
Expand Down
26 changes: 26 additions & 0 deletions CONCEPTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Concepts

A glossary of project-specific terms. Each entry is a self-contained, one-sentence
definition — enough for a new engineer to ground in the vocabulary without reading
the code. Grows as learnings surface new domain nouns.

## Editor & styling

- **Continuous-scroll editor** — Quill's editor is a single, uninterrupted
scrolling text surface rather than a paginated one, so text reflows freely and
there are no fixed page boundaries in the layout.

- **Reflowing text surface** — an editable region whose line breaks and vertical
positions change whenever font size, zoom, window width, or content changes, so
nothing positioned by fixed pixel offset can be assumed to stay clear of the text.

- **ProseMirror surface** — the underlying rich-text editing area (`.ProseMirror`)
that Tiptap renders the document into and that carries the editor's text styling.

- **Suggesting mode** — an editing mode where changes are recorded as tracked
insertions and deletions (like Google Docs suggesting) instead of being applied
directly to the document.

- **Sidecar** — the companion file saved alongside a document that holds its
comments, suggestions, and linked-session metadata, keeping the Markdown file
itself plain.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: 'fix: Remove page-break guide lines that cross through text'
type: fix
date: 2026-07-07
depth: lightweight
status: ready
---

# fix: Remove page-break guide lines that cross through text

## Summary

Quill's editor paints a horizontal guide line every 912px down the page via a CSS `repeating-linear-gradient` on `.ProseMirror`. Because it's a background image behind the text (not between blocks), the line draws **through** any text that happens to sit at the 912px boundary, which looks broken. This plan removes the guide-line rendering entirely. The change is CSS-only in `src/App.css`; there is no ProseMirror node, extension, or Rust code backing the feature.

## Problem Frame

The "page break" attempt was implemented as a decorative background gradient rather than as real pagination (which a continuous ProseMirror editor can't do without a layout engine). The gradient has no awareness of text position, so at each 912px interval it strikes a line across whatever line of text is there. Users read it as a horizontal rule slashing through their words. The feature adds no real value — Quill is a continuous-scroll editor, not a paginated one — so the fix is removal, not repair.

## Requirements

- **R1.** The editor must never render a horizontal line across or between document text on screen. (Removes the visual defect.)
- **R2.** Editor layout (page card, top/inter-page spacing, min-height feel) must not visibly regress for a normal single- or multi-screen document.
- **R3.** The Export-to-PDF / print path must continue to produce a clean copy — no page-break artifacts, no new regressions. (The print block already sets `background-image: none !important`; removal must keep that path correct.)
- **R4.** No dead CSS or misleadingly-named variables left referencing the removed feature.

## Key Technical Decisions

- **Remove the gradient, keep the page surface.** The `.editor-page` card, its border, and the `--page-max-width` / padding system are the document's visual frame and are unrelated to the guide lines — they stay. Only the `repeating-linear-gradient` background and the now-pointless `min-height: 912px` tied to the "one page" metaphor are in question.
- **`min-height: 912px` — reduce, don't necessarily zero.** The 912px min-height gave an empty document a full-page feel. Dropping it entirely would make a blank editor collapse to a few lines. Keep a sensible min-height (retain 912px or a comparable value) purely as a layout minimum, decoupled from any page-break meaning, and update the comment so it no longer claims to mark page boundaries.
- **`--page-break-gap` variable — rename or repurpose, don't blindly delete.** It's consumed by `.editor-scroll-area` `padding` and `gap` (lines 626/629) as ordinary spacing. Deleting the variable would break that spacing. Rename it to a neutral spacing name (e.g. `--editor-scroll-gap`) so nothing references a removed feature, OR leave the value and just update naming/comments. Behavior must be unchanged.
- **Leave `.pending-comment`'s dashed border alone.** The dashed `border-bottom` at ~line 858 belongs to the comment-composer highlight, not page breaks. Out of scope — do not touch.

## Implementation Units

### U1. Remove the page-break gradient from `.ProseMirror`

**Goal:** Delete the `repeating-linear-gradient` background so no horizontal line is ever painted through text (R1).

**Files:** `src/App.css`

**Approach:** In the `.ProseMirror` rule (~lines 647–665), remove the `background-image: repeating-linear-gradient(...)` declaration and its `/* Page break lines: dashed rule every 912px */` comment. Re-evaluate `min-height: 912px`: keep it as a plain layout minimum (updating the adjacent comment so it no longer describes "one page per 912px"), since removing it would collapse an empty editor. Do not alter font, color, or line-height declarations in the same rule.

**Patterns to follow:** Match the surrounding CSS comment style in `src/App.css`.

**Test scenarios:** Test expectation: none — pure presentational CSS with no behavioral surface. Verification is visual (see U3) and via the existing `format:check`/lint gates. The round-trip and component test suites already cover editor behavior and must stay green.

**Verification:** Grep confirms no `repeating-linear-gradient` (or any page-break gradient) remains on `.ProseMirror`. In the running app, a document long enough to cross 912px shows no line through or between text.

---

### U2. Retire the `--page-break-gap` naming and stale print comment

**Goal:** Remove the last references to the deleted feature so no misleadingly-named token or comment survives (R4), with zero behavior change (R2).

**Files:** `src/App.css`

**Approach:** Rename `--page-break-gap` (declared ~line 169) to a neutral spacing name such as `--editor-scroll-gap`, updating both consumers in `.editor-scroll-area` (`padding` ~line 626, `gap` ~line 629) to the new name with the **same value** (40px). Update the print-media comment (~line 1895) that says "Drop … the dashed page-break guide lines" to reflect that there are no longer guide lines to drop (the `background-image: none !important` on `.ProseMirror` in the print block may stay as a harmless safety net, or be removed if it's now redundant — keep it if in doubt).

**Dependencies:** Independent of U1, but land together (same file, same theme).

**Test scenarios:** Test expectation: none — rename + comment edit with no behavioral change. Verification is that computed spacing is identical (same 40px) and no other rule references the old variable name.

**Verification:** `grep -rn "page-break-gap\|page-break\|page break" src/` returns no CSS matches (comments included). Editor top/inter-page spacing is visually unchanged from before.

---

### U3. Visual verification in the running app

**Goal:** Confirm on screen that text is never crossed and layout is intact (R1, R2), and that Export-to-PDF is still clean (R3).

**Files:** none (verification only)

**Approach:** Run the app (`npm run tauri dev`, or `npm run dev` for a browser-only visual check), open/create a document long enough to span past 912px, and confirm: no horizontal line through or between text; the page card, spacing, and empty-doc min-height look right; toggling suggesting mode and comments still renders normally. Trigger Export to PDF (Cmd/Ctrl+P) and confirm the output has no page-break artifacts.

**Test scenarios:** Manual visual check per the above — this is the acceptance gate for a presentational change (`/verify`-style end-to-end observation, not a unit test).

**Verification:** Screenshot or direct observation shows clean text with the guide lines gone and no layout/PDF regression.

---

## Scope Boundaries

**In scope:** Removing the CSS guide-line gradient and cleaning up its naming/comments in `src/App.css`.

**Out of scope / non-goals:**

- The `.pending-comment` dashed border (~line 858) — belongs to the comment composer, unrelated.
- The `.editor-page` card, border, radius, and `--page-max-width`/padding system — that's the document frame, staying.
- Real pagination / page-numbering — Quill is a continuous-scroll editor; not attempting it.

## Verification Strategy

CI gates (`typecheck`, `eslint`, `prettier --check`, `vitest`, and the Rust suite) must stay green — none should be affected by a CSS-only change, but running them confirms nothing regressed. The decisive check is visual (U3): the guide lines are gone, text is uncrossed, and the PDF export is clean.

## Compounding Note

After the fix lands, capture a short learning (via `ce-compound` / `docs/solutions/`) recording **why the page-break attempt failed**: a `repeating-linear-gradient` background on a continuous ProseMirror editor paints lines by pixel offset with no awareness of text position, so any "page break every N px" approach will strike through text at the boundary. The general lesson — _don't fake pagination with a fixed-interval background on a reflowing text surface_ — is the reusable takeaway.
158 changes: 158 additions & 0 deletions docs/solutions/ui-bugs/page-break-guide-lines-slash-through-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: 'Page-break guide lines drew through reflowing editor text'
category: ui-bugs
date: 2026-07-07
module: editor-styling
problem_type: ui_bug
component: frontend_stimulus
severity: low
symptoms:
- 'A faint horizontal rule painted every 912px slashed through any line of text sitting on the boundary'
- 'Words got cut by a horizontal line in the editor'
- 'Any change in font size, zoom, or window width reflowed text onto the 912px boundary, so the rule cut through different text'
root_cause: logic_error
resolution_type: code_fix
tags:
- css
- prosemirror
- editor
- pagination
- background-image
- reflow
- tiptap
---

# Page-break guide lines drew through reflowing editor text

## Problem

Quill's editor painted a faint horizontal "page break" rule every 912px down the
page using a CSS `repeating-linear-gradient` on `.ProseMirror`. Because the rule
was a full-bleed background image _behind_ the text (not a divider _between_
blocks), it had no awareness of where lines of text fell — so it drew straight
through any line that happened to sit on a 912px boundary. The fix was to remove
the feature entirely: Quill is a continuous-scroll editor, not a paginated one,
so the cue added no value.

## Symptoms

- A faint horizontal line appeared roughly every 912px down the editor.
- Where a line of text landed on that boundary, the rule cut straight through
the words — the text looked struck out or broken.
- The defect moved: any change in font size, zoom, or window width reflowed the
text, so a _different_ line ended up on the boundary and got sliced instead.
There was no stable "safe" layout.

## What Didn't Work

There was no repair path, and that was the key realization. The instinct is to
"nudge" a decorative background line so it lands in the gutter between lines
rather than on a line — but a `repeating-linear-gradient` is positioned by fixed
pixel offset from the top of the element. It has zero knowledge of line-box
positions, and those positions shift with every reflow (font, zoom, width,
content). No offset, interval, or thickness makes a fixed-interval background
consistently miss the text on a surface whose text reflows. Any "page break
every N px" drawn this way will strike through text at some layout. The only
correct move was removal, not repair. _(root cause corroborated by auto memory
[claude])_

## Solution

CSS-only change in `src/App.css`. Three edits, all in the same theme:

**1. Remove the gradient from `.ProseMirror`.** Delete the `background-image`
declaration and its comment; keep `min-height` as a plain layout minimum
(comment updated so it no longer claims to mark page boundaries).

```css
/* before */
.ProseMirror {
/* Page break lines: dashed rule every 912px */
background-image: repeating-linear-gradient(
to bottom,
transparent,
transparent calc(912px - 1px),
var(--color-border-light) calc(912px - 1px),
var(--color-border-light) 912px
);
/* 8.5×11 at 96dpi = 1056px total; minus 2×72px padding = 912px content per page */
min-height: 912px;
/* …font, color, line-height unchanged… */
}

/* after */
.ProseMirror {
/* Layout minimum so an empty document keeps a comfortable page-sized surface */
min-height: 912px;
/* …font, color, line-height unchanged… */
}
```

**2. Rename the now-misleading spacing variable.** `--page-break-gap` was
consumed by `.editor-scroll-area` as ordinary spacing (padding + gap), unrelated
to the gradient. Renaming keeps the value identical and removes a token that
names a feature that no longer exists.

```css
/* before */ /* after */
--page-break-gap: 40px;
--editor-scroll-gap: 40px;
```

Both consumers in `.editor-scroll-area` (`padding` and `gap`) update to the new
name with the same `40px` value — zero behavior change.

**3. Leave the print-block reset as a harmless net.** The print media block
already set `background-image: none !important` on `.ProseMirror`. With the
gradient gone this override now matches nothing; it was kept intentionally as a
cheap defensive net in case a background is ever reintroduced.

Left untouched (out of scope, unrelated to page breaks): the `.editor-page`
card, `--page-max-width` and the padding system, and `.pending-comment`'s dashed
border (that belongs to the comment composer).

## Why This Works

Removing the gradient removes the only thing that ever painted a line across the
text, so the defect cannot recur by that mechanism. The retained `min-height:
912px` is purely a layout floor — it gives an empty document a page-sized
surface and paints nothing — so keeping it does not reintroduce any rule. The
variable rename preserves the exact spacing (`40px`) it always applied; nothing
references the removed feature anymore.

The root cause is the reusable part: **a fixed-pixel-interval background on a
reflowing text surface has no awareness of where line boxes fall.** As soon as
font size, zoom, or window width shifts the text, some line lands on the
boundary and the rule cuts through it. Faking pagination this way is
fundamentally unsound on a continuous, reflowing surface. _(auto memory
[claude])_

## Prevention

- **Before adding any repeating, tiled, or fixed-offset decorative line to an
editable text surface, ask whether the surface reflows.** If it does, a
fixed-interval background is unsafe — it will eventually intersect a line of
text. This applies to `repeating-linear-gradient`, tiled background images,
and any absolutely-positioned rule placed by pixel math rather than by
document structure.
- **Don't fake pagination on a continuous editor.** Quill is continuous-scroll,
not paginated. If real pagination is ever wanted, it needs a layout approach —
page-shaped containers with margins and shadows between breaks (which _do_
track content flow) — not a full-bleed repeating background painted behind the
text.
- **Watch for orphaned tokens and dead resets when removing a feature.** A
removed CSS feature tends to leave behind a misleadingly-named variable
(`--page-break-gap`) still doing real work elsewhere, and a now-inert
`!important` reset. Rename the former to something neutral (don't delete it if
it's load-bearing) and either drop the latter or keep it as a documented net —
but never leave a token whose name lies about what it does.
- **This is a presentational change with no automated visual-regression suite.**
Verification is visual: open a document long enough to cross 912px and confirm
no line runs through or between text, then confirm Export-to-PDF is clean. Keep
that manual check in mind for any future editor-background work.

## Related Issues

- **PR #69** — `fix/remove-page-break-guide-lines`; the removal that resolved
this. No prior `docs/solutions/` entries and no related GitHub issues existed
at capture time — this is the first solution doc in the store.
20 changes: 6 additions & 14 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
--page-max-width: 816px;
--page-padding-x: 96px;
--page-padding-y: 72px;
--page-break-gap: 40px;
--editor-scroll-gap: 40px;

font-family: var(--font-sans);
font-size: var(--text-ui);
Expand Down Expand Up @@ -623,10 +623,10 @@ body,
display: flex;
flex-direction: column;
align-items: center;
padding: var(--page-break-gap) var(--space-xl);
padding: var(--editor-scroll-gap) var(--space-xl);
padding-right: calc(var(--comment-panel-width) + var(--space-xl));
background: var(--color-bg);
gap: var(--page-break-gap);
gap: var(--editor-scroll-gap);
}

/* ── Page surface ───────────────────────────────────────────────── */
Expand All @@ -646,16 +646,8 @@ body,

.ProseMirror {
outline: none;
/* 8.5×11 at 96dpi = 1056px total; minus 2×72px padding = 912px content per page */
/* Layout minimum so an empty document keeps a comfortable page-sized surface */
min-height: 912px;
/* Page break lines: dashed rule every 912px */
background-image: repeating-linear-gradient(
to bottom,
transparent,
transparent calc(912px - 1px),
var(--color-border-light) calc(912px - 1px),
var(--color-border-light) 912px
);
font-family: var(--font-sans);
font-size: var(--text-body);
font-weight: 400;
Expand Down Expand Up @@ -1892,8 +1884,8 @@ del.track-delete {
align-items: stretch !important;
}

/* Drop the screen zoom and the dashed page-break guide lines; the page is
the paper now. Full-width, no page card border or rounded corners. */
/* Drop the screen zoom; the page is the paper now.
Full-width, no page card border or rounded corners. */
.editor-page-zoom-wrapper {
zoom: 1 !important;
}
Expand Down
Loading