From 5b4571ddd7cea1781c8c2d344a430a8d4da2b178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 1 Sep 2026 20:09:25 +0100 Subject: [PATCH] guides on the page builder, and negative horizontal on a block The measure was drawn on Layout only, and this is the screen where things are placed against it. It also answers the question a negative margin asks: a block escaping the gutter has to be seen escaping it, against the line it crossed. Horizontal padding now offers negatives, built from the same scale so there is still one list. Co-Authored-By: Claude Opus 5 (1M context) --- web/sections/BlockSettings.tsx | 15 ++++++++++++++- web/sections/PageBuilderPanel.tsx | 18 ++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/web/sections/BlockSettings.tsx b/web/sections/BlockSettings.tsx index aa327d5..a919a08 100644 --- a/web/sections/BlockSettings.tsx +++ b/web/sections/BlockSettings.tsx @@ -134,7 +134,20 @@ export const BlockSettings = ({ - {steps.map((step) => ( + {/* + Horizontal offers NEGATIVES, vertical does not. + + A negative horizontal is how a block escapes the gutter its band applied — a video + that runs to the edge of the window inside an inset band. It becomes `-mx-*`, which + cancels the band's `px-*` exactly, so the value to pick is the band's own number. + + Vertically there is nothing to cancel: a band's vertical padding is the room around + the whole band, and a block pulling itself into the one above is a bug. + */} + {(axis === 'x' + ? [...steps.filter((step) => step > 0).map((step) => -step).reverse(), ...steps] + : steps + ).map((step) => ( diff --git a/web/sections/PageBuilderPanel.tsx b/web/sections/PageBuilderPanel.tsx index 89759f0..9bff87f 100644 --- a/web/sections/PageBuilderPanel.tsx +++ b/web/sections/PageBuilderPanel.tsx @@ -7,7 +7,7 @@ import { useClient } from 'hooks/useClient'; import { readableError } from '../errors'; import { SitePreview, useDevPort } from '../SitePreview'; import { usePreviewable, useViewMode } from '../ViewMode'; -import { paintSelection, type CanvasComponent, type CanvasSection } from '../canvas'; +import { paintMeasure, paintSelection, type CanvasComponent, type CanvasSection } from '../canvas'; /* What the Spacing dialog calls a block, by kind. @@ -226,7 +226,16 @@ export const PageBuilderPanel = () => { const onDocument = useCallback( (frameDocument: Document) => { frameDoc.current = frameDocument; - return paintSelection(frameDocument, { + + /* + The measure, here as well as on Layout. + + Where the content column ends is invisible until something is placed against it, and this is + the screen where things are placed. It also answers the question a negative margin asks: a + block escaping the gutter has to be seen escaping it, against the line it crossed. + */ + const measure = paintMeasure(frameDocument); + const selection = paintSelection(frameDocument, { sections, onAddSection: () => handlersRef.current.add(), onDeleteSection: (section) => handlersRef.current.remove(section.id), @@ -245,6 +254,11 @@ export const PageBuilderPanel = () => { onSpaceSection: (section) => handlersRef.current.spaceSection(section), onSpaceComponent: (component) => handlersRef.current.spaceComponent(component), }); + + return () => { + selection(); + measure(); + }; }, // `mode` is in here so switching to View gives SitePreview a NEW function, which runs the previous // paint's cleanup and then paints nothing. That is what removes the chrome without a reload.