diff --git a/docs/backend.md b/docs/backend.md
index b043d66..1cb3995 100644
--- a/docs/backend.md
+++ b/docs/backend.md
@@ -20,7 +20,7 @@ msgspec Structs for the kanta database. See `docs/content-model.md` for the full
markdown-it-py renderer (html passthrough + attrs, footnote, deflist, tasklists, admon, gfm_autolink, sub/superscript plugins; typographer + breaks on). In bodies with at least three top-level h1/h2 headings (nested ones, e.g. inside `::: aside`, never participate), each gets a slug id (`python-slugify`, mirroring the editor's `slugify.js` — unicode folds to ASCII, separators become single hyphens) unless the author set `{#id}`, and their text is wrapped in a self-link (`a.anchor`) so section links are copyable; anchored headings also carry `data-line` with their markdown source line (the page editor's section pens and piecewise scroll sync key off it); the first in-body h1 is the article title — when the markdown has no h1, `render(title=...)` injects it as `# {title}` so implicit and explicit titles take the same path — it gets no id and doesn't count toward the three, its self-link is `href=""` (scroll to top); shorter articles stay anchor-free, h3+ is never navigable, and duplicates get `-2`/`-3` suffixes. Custom image rule: relative srcs resolve against the page path; an image standing alone in its paragraph becomes a figure (captioned when titled), while inline-with-text images and raw `` HTML stay plain. A `{dates}` line expands to the article's published/updated dateline (`p.dateline`, from `Node.created`/`modified`; left literal in previews of unsaved pages). Code fences take pandoc-style brace attributes on the info line (` ```{.python .wide #id key=val} ` — the first class is the language when no bare language word precedes the braces) as well as a trailing `{...}` line; both land on the `
`, the `` keeps only the language class.
-`render()` returns a `Rendered(html, multicol)`: the article content segmented for the column layout (there is no wrapper div — segments and bare blocks are direct `` children) — h1/h2 headings, `.wide` blocks and margin-breakout blocks (`.margin`, `::: aside`) stand bare, the runs between them become `
` (plus `.cols` on segments with enough text in at least two paragraphs or one long enough to split across columns, `::: nocols` opting out; in column segments, paragraphs past `BREAKABLE_TEXT` visible characters are marked `.breakable` so they may split across columns), and `multicol` flags bodies long enough to columnize (visible-text thresholds, code excluded). `views.py` puts the class on the article; pagerite.css takes it from there (at most two columns, the left-margin breakout, all viewport adaptation).
+`render()` returns a `Rendered(html, multicol)`: the article content segmented for the column layout (there is no wrapper div — segments and bare blocks are direct `` children) — h1/h2 headings and `.wide` blocks stand bare, the runs between them become `
` (margin-breakout boxes — `.margin`, `::: aside` — stay inside the segment at their anchor point; the CSS positions them out of flow into the side zone) (plus `.cols` on segments with enough text in at least two paragraphs or one long enough to split across columns, `::: nocols` opting out; in column segments, paragraphs past `BREAKABLE_TEXT` visible characters are marked `.breakable` so they may split across columns), and `multicol` flags bodies long enough to columnize (visible-text thresholds, code excluded). `views.py` puts the class on the article; pagerite.css takes it from there (at most two columns, the left-margin breakout, all viewport adaptation).
## `views.py`
diff --git a/docs/design-principles.md b/docs/design-principles.md
index 1b99e8b..4caeb8e 100644
--- a/docs/design-principles.md
+++ b/docs/design-principles.md
@@ -19,8 +19,8 @@ Pagerite is a single-user CMS/blog. This document records the initial high-level
- Content is written in **Markdown** with powerful extensions (tables, footnotes, code highlighting, etc.).
- **Embedded HTML is passed through unfiltered**, including inline scripts and other dynamic content the author wants to post. This is safe by the single-trusted-author assumption above.
-- Renderer: **markdown-it-py** with mdit-py-plugins (footnotes, definition lists, task lists, brace-attributes, admonitions and `::: name` containers — generic `
`` (tagged
+segmented for the column layout — h1/h2 headings and ``.wide`` blocks
+stand on their own, the runs between them are wrapped in
+``
`` (tagged
``.cols`` when the segment holds enough text — COLS_TEXT — in at least
COLS_PARAS paragraphs or one paragraph long enough to turn .breakable,
unless a ``::: nocols`` container opts it out;
in column segments, paragraphs past BREAKABLE_TEXT are marked
-``.breakable`` so they may split across columns). The result carries
+``.breakable`` so they may split across columns). Margin-breakout boxes
+(``.margin``, ``::: aside``) stay inside the segment at their anchor
+point; pagerite.css takes them out of flow (absolute, off the article's
+left border, into the side zone), so the columns flow through as if the
+box wasn't there. The result carries
``multicol`` when the whole body justifies columns (views.py puts the
class on the article); how many columns (never more than two), whether
the margin breakout applies and every other viewport adaptation is then
@@ -268,8 +272,8 @@ def _container_attrs(state) -> None:
The container plugin's default render is a plain renderToken, so the
name and brace attributes must live on the token itself — and being a
core rule (rather than a render rule) lets the segmentation in
- render() see the classes (::: aside's margin breakout, the ::: nocols
- opt-out, {.wide} containers).
+ render() see the classes (the ::: nocols opt-out, {.wide}
+ containers).
"""
for token in state.tokens:
if token.type != "container_block_open":
@@ -493,11 +497,12 @@ _PARA_OPEN_RE = re.compile(r"
]")
_PARA_RE = re.compile(r"
]*)?)>(.*?)
", re.S)
# Classes that take their block out of the column flow: .wide is a
-# full-width separator, .margin/.aside float in the side zone at the
-# article's left (they must be direct article children for that — the zone
-# rules key off it — never inside a column).
+# full-width separator that splits the column segments. Margin-breakout
+# boxes (.margin/.aside) are NOT boundaries: they stay inside the segment
+# at their anchor point, and CSS positions them absolutely out of the
+# article's left border (the zone rules anchor off the article), so the
+# column flow is unaffected.
_WIDE = "wide"
-_BREAKOUT = ("margin", "aside")
class Rendered(NamedTuple):
@@ -557,14 +562,10 @@ def _top_level_blocks(tokens: list) -> list[list]:
def _is_boundary(block: list) -> bool:
"""True for blocks that never go inside a column segment (see the
- _WIDE/_BREAKOUT comment above): h1/h2 headings, anything carrying
- .wide, and blocks whose own element carries .margin/.aside — for a
- lone-image paragraph (which renders as a ) the image's classes
- count as the block's own."""
+ _WIDE comment above): h1/h2 headings and anything carrying .wide."""
first = block[0]
if first.type == "heading_open" and first.tag in ("h1", "h2"):
return True
- own = _classes(first)
for token in block:
if _WIDE in _classes(token):
return True
@@ -572,9 +573,7 @@ def _is_boundary(block: list) -> bool:
children = token.children or []
if any(_WIDE in _classes(c) for c in children):
return True
- if len(children) == 1 and children[0].type == "image":
- own |= _classes(children[0])
- return bool(own & set(_BREAKOUT))
+ return False
def render(
@@ -591,8 +590,8 @@ def render(
same pipeline as an explicit one (first-h1 anchor treatment included).
The top-level blocks are grouped into column segments: boundary blocks
- (h1/h2 headings, .wide, margin-breakout blocks — see _is_boundary) are
- rendered bare, the runs between them wrapped in
.
+ (h1/h2 headings, .wide — see _is_boundary) are rendered bare, the runs
+ between them wrapped in
.
A segment is tagged .cols when it holds enough text (COLS_TEXT) in at
least two paragraphs (COLS_PARAS) or one breakable-length paragraph,
and no ::: nocols container; its long paragraphs are marked .breakable;