From fd95922a67f30c2af16abc032bbeaca6809a925f Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 25 Sep 2026 18:02:29 +0545 Subject: [PATCH 1/4] Fix - Block editor ignores Customizer typography and colors Co-Authored-By: Claude Opus 5.5 --- inc/functions.php | 105 +++++++++++++++++++++++++++++++++++++++++ style-editor-block.css | 18 +++---- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 66741197..710402ee 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -17,10 +17,115 @@ */ function spacious_block_editor_styles() { wp_enqueue_style( 'spacious-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css' ); + + $editor_css = spacious_block_editor_dynamic_css(); + + if ( $editor_css ) { + wp_add_inline_style( 'spacious-block-editor-styles', $editor_css ); + } } add_action( 'enqueue_block_editor_assets', 'spacious_block_editor_styles', 1, 1 ); +/** + * Load the theme fonts inside the block editor canvas. + * + * Lato is bundled with the theme, like on the front end; other Customizer fonts + * come from the same loader the front end uses. + */ +function spacious_block_editor_fonts() { + if ( ! is_admin() ) { + return; + } + + $lato_faces = array( + 'Thin' => array( 100, 'normal' ), + 'ThinItalic' => array( 100, 'italic' ), + 'Light' => array( 300, 'normal' ), + 'LightItalic' => array( 300, 'italic' ), + 'Regular' => array( 400, 'normal' ), + 'Italic' => array( 400, 'italic' ), + 'Bold' => array( 700, 'normal' ), + 'BoldItalic' => array( 700, 'italic' ), + 'Black' => array( 900, 'normal' ), + 'BlackItalic' => array( 900, 'italic' ), + ); + $lato_css = ''; + + foreach ( $lato_faces as $file => $face ) { + $lato_css .= "@font-face{font-family:'Lato';font-style:" . $face[1] . ';font-weight:' . $face[0] . ';src:url(' . esc_url( get_template_directory_uri() . '/assets/fonts/Lato-' . $file . '.woff' ) . ") format('woff');}"; + } + + wp_register_style( 'spacious-editor-fonts', false, array(), SPACIOUS_THEME_VERSION ); + wp_enqueue_style( 'spacious-editor-fonts' ); + wp_add_inline_style( 'spacious-editor-fonts', $lato_css ); + + Spacious_Generate_Fonts::render_fonts(); +} + +add_action( 'enqueue_block_assets', 'spacious_block_editor_fonts' ); + +if ( ! function_exists( 'spacious_block_editor_dynamic_css' ) ) : + + /** + * Build block editor CSS from the Customizer typography and color settings. + * + * Mirrors the post content rules of Spacious_Dynamic_CSS::render_output(), scoped to + * the editor canvas. Like the front end, a setting outputs nothing while it is at its default. + * + * @return string Editor CSS. + */ + function spacious_block_editor_dynamic_css() { + $wrapper = '.editor-styles-wrapper'; + $css = ''; + + // The Dark skin stylesheet is front-end only, so its colors would be overridden here. + if ( 'dark' !== get_theme_mod( 'spacious_color_skin', 'light' ) ) { + $primary_color = get_theme_mod( 'spacious_primary_color', '#0FBE7C' ); + $primary_dark = spacious_darkcolor( $primary_color, -50 ); + + $css .= spacious_parse_css( + '#0FBE7C', + $primary_color, + array( + $wrapper . ' a, ' . $wrapper . ' .wp-block-file .wp-block-file__textlink' => array( + 'color' => esc_html( $primary_color ), + ), + $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"]' => array( + 'background-color' => esc_html( $primary_color ), + ), + $wrapper . ' blockquote:not(.wp-block-quote)' => array( + 'border-left-color' => esc_html( $primary_color ), + ), + $wrapper . ' input[type="reset"]:hover, ' . $wrapper . ' input[type="button"]:hover, ' . $wrapper . ' input[type="submit"]:hover' => array( + 'background' => esc_html( $primary_dark ), + ), + ) + ); + } + + $font_default = array( + 'font-family' => 'Lato', + 'font-weight' => 'regular', + ); + + $css .= spacious_parse_typography_css( + $font_default, + get_theme_mod( 'spacious_content_font_typography', $font_default ), + $wrapper . ', ' . $wrapper . ' > *, ' . $wrapper . ' p' + ); + + $css .= spacious_parse_typography_css( + $font_default, + get_theme_mod( 'spacious_titles_font_typography', $font_default ), + $wrapper . ' h1, ' . $wrapper . ' h2, ' . $wrapper . ' h3, ' . $wrapper . ' h4, ' . $wrapper . ' h5, ' . $wrapper . ' h6' + ); + + return $css; + } + +endif; + /* * Display the related posts. */ diff --git a/style-editor-block.css b/style-editor-block.css index 58af11af..bbad2201 100644 --- a/style-editor-block.css +++ b/style-editor-block.css @@ -42,32 +42,32 @@ font-family: 'Lato', sans-serif; } -.editor-styles-wrapper .wp-block h1 { +.editor-styles-wrapper h1 { font-size: 30px; line-height: 36px; } -.editor-styles-wrapper .wp-block h2 { +.editor-styles-wrapper h2 { font-size: 28px; line-height: 34px; } -.editor-styles-wrapper .wp-block h3 { +.editor-styles-wrapper h3 { font-size: 26px; - line-height: 30px; + line-height: 32px; } -.editor-styles-wrapper .wp-block h4 { +.editor-styles-wrapper h4 { font-size: 24px; - line-height: 28px; + line-height: 30px; } -.editor-styles-wrapper .wp-block h5 { +.editor-styles-wrapper h5 { font-size: 22px; - line-height: 26px; + line-height: 28px; } -.editor-styles-wrapper .wp-block h6 { +.editor-styles-wrapper h6 { font-size: 19px; line-height: 25px; } From 35a374dafec6b342be9e7d28e08c0ef57072ca5c Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 25 Sep 2026 18:19:19 +0545 Subject: [PATCH 2/4] Fix - Editor post title used the H1 size instead of the front-end title size Co-Authored-By: Claude Opus 5.5 --- style-editor-block.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/style-editor-block.css b/style-editor-block.css index bbad2201..c4979436 100644 --- a/style-editor-block.css +++ b/style-editor-block.css @@ -20,10 +20,7 @@ max-width: 1268px; } -.editor-styles-wrapper .wp-block.editor-post-title__block .editor-post-title__input { - font-weight: normal; - color: #222222; - font-family: 'Lato', sans-serif; +.editor-styles-wrapper .editor-post-title__input { font-size: 22px; padding-bottom: 0; line-height: 36px; From de5481b6725043508aed1b41508d081332ceeeec Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 25 Sep 2026 18:30:28 +0545 Subject: [PATCH 3/4] Add - E2E guard: block editor matches the front-end Customizer typography and colors Co-Authored-By: Claude Opus 5.5 --- ...k-editor-matches-customizer-styles.spec.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts diff --git a/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts b/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts new file mode 100644 index 00000000..e1ac1f6b --- /dev/null +++ b/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts @@ -0,0 +1,124 @@ +import { test, expect } from '../../fixtures/customizer'; +import type { FrameLocator, Locator, Page } from '@playwright/test'; + +const MODS: Record = { + spacious_content_font_typography: { 'font-family': 'Merriweather', 'font-weight': 'regular' }, + spacious_titles_font_typography: { 'font-family': 'Pacifico', 'font-weight': 'regular' }, + spacious_primary_color: '#d63384', +}; + +type Look = { family: string; size: string; color: string }; + +async function look(element: Locator): Promise { + return element.evaluate((el) => { + const cs = getComputedStyle(el); + return { + family: cs.fontFamily.split(',')[0].replace(/["']/g, '').trim(), + size: cs.fontSize, + color: cs.color, + }; + }); +} + +async function latestPost(page: Page): Promise<{ id: number; link: string }> { + const response = await page.request.get('/wp-json/wp/v2/posts?per_page=1&status=publish&orderby=date&order=desc'); + expect(response.ok(), 'could not list published posts over REST').toBeTruthy(); + const posts = (await response.json()) as Array<{ id: number; link: string }>; + expect(posts.length, 'this spec needs one published post with a paragraph').toBeGreaterThan(0); + return posts[0]; +} + +async function editorCanvas(page: Page, postId: number): Promise { + await page.goto(`/wp-admin/post.php?post=${postId}&action=edit`); + const canvas = page.frameLocator('iframe[name="editor-canvas"]'); + await canvas.locator('.editor-post-title__input').waitFor({ timeout: 30_000 }); + return canvas; +} + +/** + * @area customizer + * @tier fresh + * @source agent 2026-09-25 + * @why themegrill/spacious-pro#66 (free part): the block editor loaded only a + * static stylesheet and not even the bundled Lato, so it ignored the + * Customizer body font, titles font and primary color. The fix builds + * editor CSS from the same settings as the front end, so this guard + * compares the editor with the front end for the same post instead of + * hardcoding pixels. + * + * Covers the paragraph (body font, size, color), a link (primary color), the post + * title (titles font, front-end title size) and that the chosen fonts actually + * loaded inside the editor iframe rather than only being declared. + */ +test('block editor shows the same Customizer typography and colors as the front end @fresh @customizer', async ({ + page, + customizer, +}) => { + test.setTimeout(150_000); + + const post = await latestPost(page); + + await customizer.open(); + const original = await page.evaluate( + (ids) => Object.fromEntries(ids.map((id) => [id, (window as any).wp.customize(id).get()])), + Object.keys(MODS), + ); + + try { + for (const [id, value] of Object.entries(MODS)) { + await customizer.setControl(id, value); + } + await customizer.publish(); + + await page.goto(`${post.link}${post.link.includes('?') ? '&' : '?'}e2e-cache-bust=${Date.now()}`); + const front = { + paragraph: await look(page.locator('.entry-content p').first()), + title: await look(page.locator('.header-post-title-class').first()), + }; + + expect(front.paragraph.family, 'front end did not apply the published body font').toBe('Merriweather'); + expect(front.title.family, 'front end did not apply the published titles font').toBe('Pacifico'); + + const canvas = await editorCanvas(page, post.id); + const paragraph = canvas.locator('.is-root-container p').first(); + + await expect + .poll(async () => (await look(paragraph)).family, { timeout: 20_000 }) + .toBe('Merriweather'); + + expect(await look(paragraph), 'editor paragraph differs from the front end').toEqual(front.paragraph); + expect( + await look(canvas.locator('.editor-post-title__input')), + 'editor post title differs from the front end', + ).toEqual(front.title); + + const linkColor = await canvas.locator('body').evaluate((body) => { + const probe = body.ownerDocument.createElement('a'); + probe.href = '#e2e'; + body.querySelector('.is-root-container')?.appendChild(probe); + const color = getComputedStyle(probe).color; + probe.remove(); + return color; + }); + expect(linkColor, 'editor links do not use the primary color').toBe('rgb(214, 51, 132)'); + + const loaded = await canvas.locator('body').evaluate(async () => { + await document.fonts.ready; + return ['Merriweather', 'Pacifico'].map((family) => + [...document.fonts].some((face) => face.family.replace(/["']/g, '') === family && face.status === 'loaded'), + ); + }); + expect(loaded, 'Customizer fonts are declared but not loaded inside the editor iframe').toEqual([true, true]); + } finally { + // Courtesy revert for a reused context; the customizer fixture's teardown is the real safety net. + try { + await customizer.open(); + for (const [id, value] of Object.entries(original)) { + await customizer.setControl(id, value); + } + await customizer.publish(); + } catch (revertError) { + console.warn('Revert of the block editor typography controls did not complete cleanly:', revertError); + } + } +}); From aec332d5c26ff630061f671499ce3c61131fc788 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 25 Sep 2026 18:32:39 +0545 Subject: [PATCH 4/4] Remove - manually added E2E spec; specs come from the verify-fix flow Co-Authored-By: Claude Opus 5.5 --- ...k-editor-matches-customizer-styles.spec.ts | 124 ------------------ 1 file changed, 124 deletions(-) delete mode 100644 tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts diff --git a/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts b/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts deleted file mode 100644 index e1ac1f6b..00000000 --- a/tests/e2e/specs/customizer/block-editor-matches-customizer-styles.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { test, expect } from '../../fixtures/customizer'; -import type { FrameLocator, Locator, Page } from '@playwright/test'; - -const MODS: Record = { - spacious_content_font_typography: { 'font-family': 'Merriweather', 'font-weight': 'regular' }, - spacious_titles_font_typography: { 'font-family': 'Pacifico', 'font-weight': 'regular' }, - spacious_primary_color: '#d63384', -}; - -type Look = { family: string; size: string; color: string }; - -async function look(element: Locator): Promise { - return element.evaluate((el) => { - const cs = getComputedStyle(el); - return { - family: cs.fontFamily.split(',')[0].replace(/["']/g, '').trim(), - size: cs.fontSize, - color: cs.color, - }; - }); -} - -async function latestPost(page: Page): Promise<{ id: number; link: string }> { - const response = await page.request.get('/wp-json/wp/v2/posts?per_page=1&status=publish&orderby=date&order=desc'); - expect(response.ok(), 'could not list published posts over REST').toBeTruthy(); - const posts = (await response.json()) as Array<{ id: number; link: string }>; - expect(posts.length, 'this spec needs one published post with a paragraph').toBeGreaterThan(0); - return posts[0]; -} - -async function editorCanvas(page: Page, postId: number): Promise { - await page.goto(`/wp-admin/post.php?post=${postId}&action=edit`); - const canvas = page.frameLocator('iframe[name="editor-canvas"]'); - await canvas.locator('.editor-post-title__input').waitFor({ timeout: 30_000 }); - return canvas; -} - -/** - * @area customizer - * @tier fresh - * @source agent 2026-09-25 - * @why themegrill/spacious-pro#66 (free part): the block editor loaded only a - * static stylesheet and not even the bundled Lato, so it ignored the - * Customizer body font, titles font and primary color. The fix builds - * editor CSS from the same settings as the front end, so this guard - * compares the editor with the front end for the same post instead of - * hardcoding pixels. - * - * Covers the paragraph (body font, size, color), a link (primary color), the post - * title (titles font, front-end title size) and that the chosen fonts actually - * loaded inside the editor iframe rather than only being declared. - */ -test('block editor shows the same Customizer typography and colors as the front end @fresh @customizer', async ({ - page, - customizer, -}) => { - test.setTimeout(150_000); - - const post = await latestPost(page); - - await customizer.open(); - const original = await page.evaluate( - (ids) => Object.fromEntries(ids.map((id) => [id, (window as any).wp.customize(id).get()])), - Object.keys(MODS), - ); - - try { - for (const [id, value] of Object.entries(MODS)) { - await customizer.setControl(id, value); - } - await customizer.publish(); - - await page.goto(`${post.link}${post.link.includes('?') ? '&' : '?'}e2e-cache-bust=${Date.now()}`); - const front = { - paragraph: await look(page.locator('.entry-content p').first()), - title: await look(page.locator('.header-post-title-class').first()), - }; - - expect(front.paragraph.family, 'front end did not apply the published body font').toBe('Merriweather'); - expect(front.title.family, 'front end did not apply the published titles font').toBe('Pacifico'); - - const canvas = await editorCanvas(page, post.id); - const paragraph = canvas.locator('.is-root-container p').first(); - - await expect - .poll(async () => (await look(paragraph)).family, { timeout: 20_000 }) - .toBe('Merriweather'); - - expect(await look(paragraph), 'editor paragraph differs from the front end').toEqual(front.paragraph); - expect( - await look(canvas.locator('.editor-post-title__input')), - 'editor post title differs from the front end', - ).toEqual(front.title); - - const linkColor = await canvas.locator('body').evaluate((body) => { - const probe = body.ownerDocument.createElement('a'); - probe.href = '#e2e'; - body.querySelector('.is-root-container')?.appendChild(probe); - const color = getComputedStyle(probe).color; - probe.remove(); - return color; - }); - expect(linkColor, 'editor links do not use the primary color').toBe('rgb(214, 51, 132)'); - - const loaded = await canvas.locator('body').evaluate(async () => { - await document.fonts.ready; - return ['Merriweather', 'Pacifico'].map((family) => - [...document.fonts].some((face) => face.family.replace(/["']/g, '') === family && face.status === 'loaded'), - ); - }); - expect(loaded, 'Customizer fonts are declared but not loaded inside the editor iframe').toEqual([true, true]); - } finally { - // Courtesy revert for a reused context; the customizer fixture's teardown is the real safety net. - try { - await customizer.open(); - for (const [id, value] of Object.entries(original)) { - await customizer.setControl(id, value); - } - await customizer.publish(); - } catch (revertError) { - console.warn('Revert of the block editor typography controls did not complete cleanly:', revertError); - } - } -});