Instruction file imported from samrocketman/mkdocs-live-wysiwyg-plugin (
.cursor/rules/layout.mdc). Copyright stays with the author.
Layout
The Layout subsystem is the single authority for all positioning, dimension, animation, scroll, z-index stacking, dropdown dismissal, and DOM reparenting techniques across the WYSIWYG editor. Other subsystems define what needs to be positioned; Layout defines how. See docs/design/ui/DESIGN-layout.md for the full architecture, z-index registry, animation catalog, and scroll contracts.
Width Alignment (Unfocused Mode)
The WYSIWYG plugin injects CSS overrides (via <style id="live-wysiwyg-width-overrides">) so that editing surfaces match the readonly HTML content width. The overrides are injected when the editor is enabled and removed when disabled.
The content-editable area (.md-editable-area) and the markdown textarea (.live-edit-source) should be approximately the same width as the readonly HTML rendered by MkDocs. Without overrides, accumulated horizontal padding and margins from nested containers make the editing area significantly narrower.
The upstream live-edit-plugin sets padding: 5px 10px on div.live-edit-controls. Rather than modifying this padding (which would shift all child elements), the editing surfaces use negative horizontal margins (margin-left: -10px; margin-right: -10px) to extend through the padding and reach the controls border edge.
Affected selectors:
.live-edit-controls.live-edit-editing .live-edit-source— negative horizontal margin + 1px horizontal padding.live-edit-controls.live-edit-editing .live-edit-wysiwyg-wrapper— negative horizontal margin.live-edit-wysiwyg-wrapperinline style usesmargin: 5px 0(no horizontal margin)
Rules
Width Alignment Rules (1–6)
-
Buttons must not move. The Save, Cancel, Edit, Rename, Delete, New buttons, and the "Live Edit:" label must remain in their original positions when the editor is enabled or disabled. This is why the controls padding is left unchanged and only the editing surfaces use negative margins.
-
Negative margins must match controls padding. The
-10pxvalues correspond to the10pxhorizontal padding ondiv.live-edit-controls(defined inmkdocs-live-edit-plugin/live/live-edit.css). If the upstream plugin changes this padding, the negative margins must be updated to match. -
Overrides are dynamic. The
<style id="live-wysiwyg-width-overrides">element is injected inreplaceTextareaWithWysiwygand removed in the destroy/cleanup path. Do not make the overrides static ineditor.css— they must only apply when the WYSIWYG editor is active. -
!importantis required. The overrides use!importantbecause the upstream.live-edit-sourcestyles have equal specificity and the injected<style>may appear before the upstream CSS in document order. -
Do not remove controls padding. Removing or reducing
paddingon.live-edit-controlswould shift all buttons and labels. Always use the negative-margin approach on editing surfaces instead. -
Wrapper inline style. The
.live-edit-wysiwyg-wrappercontainer usesmargin: 5px 0(set inline in JS). The horizontal0is intentional — the CSS override provides the negative margin to extend through the controls padding. Do not revert this tomargin: 5px.
Layout Authority Rules (7–17)
-
Layout subsystem is the single authority for all positioning techniques. Other subsystems define what needs to be positioned; Layout defines how. All positioning, dimension, animation, scroll, and reparenting techniques must be declared in
DESIGN-layout.md. -
Z-index values must be registered in
DESIGN-layout.md. No new z-index value may be introduced without adding it to the z-index registry. The registry is the single source of truth for stacking order. -
All dropdowns and popups must use the universal positioning pattern.
getBoundingClientRect()on anchor,position: fixed, flip above/below if near viewport edge. New dropdown code must follow this contract. -
All dropdown openers must call
_dismissAllDropdowns(). Every function that opens a dropdown, popup, or autocomplete must dismiss all other active dropdowns first via the centralized function. No ad-hoc dismiss calls. New dropdown types must add their dismiss function to_dismissAllDropdowns(). -
All dialogs must support auto-expansion of text fields. Use
_attachContainerExpandBehavioror equivalent. Input fields in dropdowns must not clip or overflow — they must grow to accommodate the content. -
Animation durations and easing are Layout contracts. Toolbar drawer:
0.3s ease-in-out. Sidebar collapse:0.3s ease-in-out. All focus overlay slide transitions use0.3s ease-in-out. Mode-switch animations use shorter durations for responsiveness: toolbar wrap slide150ms ease-in-out, content crossfade80ms ease-in+80ms ease-out. New animations must use existing durations where possible. Introducing new timing requires documenting it inDESIGN-layout.md. -
Scroll containers are mode-dependent. Normal mode:
editableArea. Focus mode:.live-wysiwyg-focus-main. All scroll operations must target the correct container.scrollToCenterCursorauto-detects focus mode. TOC active heading tracking uses.live-wysiwyg-focus-main, notwindoworeditableArea. -
DOM reparenting order is a Layout invariant. Enter focus: toolbar first, then editor wrapper. Exit: toolbar first (back into editor wrapper), then editor wrapper (back into original container). Violating the order breaks cursor preservation. Toolbar is always the same DOM element — never cloned or recreated.
_captureEditorSelectionmust run before reparenting;_restoreEditorSelectionmust run after. -
JS-only positioning must be declared in
DESIGN-layout.md. When CSS alone cannot achieve positioning (e.g.,getBoundingClientRect-based dropdown placement,_alignNavControls, image resize), the technique must be documented in the Layout design doc. -
Responsive breakpoints are Layout contracts.
< 76.25em: nav sidebar hidden.< 60em: TOC hidden. New responsive behavior must use these breakpoints or declare new ones inDESIGN-layout.md. -
Nav DOM is never modified directly by Layout. If a layout operation requires changing nav sidebar DOM (adding/removing elements, reordering items, toggling classes that represent persistent state), it must go through the Nav Renderer subsystem contract: mutate
liveWysiwygNavData, then call_commitNavSnapshot(). Layout may only read nav DOM for measurement (e.g.,getBoundingClientRect,offsetTop) and scroll the nav container. Seenav-rendering-authority.mdc. -
Browser-specific layout normalization is governed by the Browser Compatibility subsystem. When layout rendering differs across browser engines (e.g., Gecko handling of
visibility: collapseon grid items, subpixel margin differences), the fix is implemented as engine-conditional CSS in the relevant_get*CSS()function, gated by_compat.engine. The Browser Compatibility subsystem (browser-compatibility.mdc,browser-compat.js) provides the engine detection. Layout owns the CSS rules; Browser Compatibility owns the detection mechanism. -
Sidebar extension variables use
clamp()for safe overflow. Both--_nav-extendand--_toc-extenduseclamp(0px, (100vw - 61rem) / 2 - 2em, 10rem)to extend sidebars beyond the grid'smax-widthwithout exceeding the viewport. The pattern is: wider width viacalc(base + var(--_*-extend, 0px))and negative margincalc(-1 * var(--_*-extend, 0px)). -
Focus mode scroll isolation is mandatory.
overflow: hiddenon bothdocument.bodyanddocument.documentElementwhen focus mode is active.overscroll-behavior: containon.live-wysiwyg-focus-main. Both overflow values are saved before entry and restored on exit. Seemodes-of-operation.mdcfor the suppression contract. -
Markdown mode vertical fill is scoped via
.focus-mode-markdown. All overrides that eliminate vertical gaps in markdown mode (gridmargin-top: 0, contentpadding-top: 0, markdown containerflex: 1 1 0) are scoped to.focus-mode-markdownand must not affect WYSIWYG mode layout. -
Toolbar drawer animation is governed by Layout. The drawer's
max-heighttransition (0.3s ease-in-out, usingvar(--_toolbar-h, 260px)) and the grid's synchronizedmin-heighttransition (0.3s ease-in-out) are Layout contracts.0.3s ease-in-outis the standard duration for all slide transitions in the focus overlay. Seetoolbars.mdcfor what the drawer contains; this subsystem defines how it animates. -
Mode-switch animations use the Web Animations API. Toolbar wrap slide (
_slideToolbarWrap,150ms) and content crossfade (_crossfadeContentSwitch,80ms+80ms) are JS-driven viaelement.animate(). They run on the compositor and start immediately on click, before the main-threadswitchToMode()content conversion. These shorter durations are exceptions to the standard0.3s— justified by the need for instant visual feedback on user-initiated mode toggles.