Skip to content

fix(editor): Safari preview performance, unified block CSS, and editor scale#3726

Open
bfintal wants to merge 7 commits into
developfrom
fix/safari-editor-preview-performance
Open

fix(editor): Safari preview performance, unified block CSS, and editor scale#3726
bfintal wants to merge 7 commits into
developfrom
fix/safari-editor-preview-performance

Conversation

@bfintal

@bfintal bfintal commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2269 — Safari (and other browsers) freezing or spiking CPU when switching Gutenberg responsive preview (Desktop → Tablet/Mobile) on pages with many Stackable blocks.

This PR is a layered editor performance pass: reduce work on preview remount, avoid redundant CSS regeneration, and cut per-block observer/DOM overhead at scale.

Preview switch & editor DOM

  • onClassChange utility — single pooled MutationObserver per DOM node, rAF-batched callbacks, observer disconnected during callback execution (prevents class-mutation feedback loops).
  • Consolidated duplicate onClassChange implementations across device preview class, theme block style inheritance, color schemes, and design-system loaders.
  • get-editor-domuseLayoutEffect, rAF polling + iframe.onload + scoped MutationObserver (replaces 200ms intervals); sync dispatch to store.
  • editor-device-preview-class — device/hover/theme classes applied in useLayoutEffect (before paint).
  • useDevicePreviewOptimization — restored root-block mount stagger after preview remount (was 300ms on develop). Now 50ms base + random 0–99ms spread (~50–149ms per unselected root block) so mounts spread across frames instead of bunching in one commit. Selected root blocks and all nested blocks still mount immediately. Safe with unified CSS because block styles persist in stackable/editor-block-css and are re-adopted on preview switch.

Unified editor block CSS (stackable/editor-block-css)

  • New store + loader plugin: blocks register CSS via useBlockCssGenerator; no per-block <style> in the React tree.
  • Per-block CSSStyleSheet via adoptedStyleSheets on the main document (classic editor).
  • Per-block <style id="stk-block-css-*"> fallback inside the editor-canvas iframe (FSE / tablet / mobile preview) — constructable sheets cannot be shared across documents.
  • CSS persisted in store across iframe remounts; removeBlockCss only when the block is deleted from the editor.
  • Incremental updates (rAF-batched SET); synchronous readoptAll on preview switch.

CSS regeneration at scale

  • Style dependency fingerprintuseBlockCssGenerator only regenerates CSS when style-relevant attributes change (not on text/content-only edits).
  • Fingerprint includes: static style deps, string renderCondition attrs, valuePreCallback attrs, enabledCallback attrs (parsed from callback source), and dynamic conditional styles (addBlockStyleConditionally dependency list — e.g. Columns columnArrangement).

Text editing

  • Removed 300ms text debounce default in Typography (canvas + inspector). Instant setAttributes on keystroke; fingerprint prevents CSS regen on text-only edits.
  • enableDebounce={ true } remains available for edge cases.

Image block

  • Removed per-image ResizeObserver in the editor; container queries hide the resizer tooltip when the image is narrow (<140px).
  • Stylelint updated for container-type / @container.

Related issues

Related: #2225, #3394


How to test whether this PR is good

Use a stress page (50–100+ Stackable blocks, nested columns, images, styled cards/headings) in Safari first, then Chrome. Open Activity Monitor / Task Manager and watch CPU while running the checks below.

For React Profiler: compare commit count and longest single commit — staggered root mounts should avoid one huge spike; also check time-to-interactive (can you select a block right after switch?).

1. Preview switch (primary fix — #2269)

  • Safari: Desktop → Tablet → Mobile → Desktop repeatedly (10+ times). Tab should stay responsive; CPU should spike briefly then settle (not peg at 100% for 4–5s).
  • Chrome / Firefox: same smoke test.
  • After switch: block styles, colors, spacing, and typography still look correct in each preview (including briefly delayed root blocks — styles should come from persisted CSS store, not flash unstyled).
  • FSE site editor and classic post editor both tested if possible.
  • Pages with many root-level blocks: preview switch should feel smoother than mounting all roots at once (subtle stagger OK; no long “empty” sections).

2. Block CSS still works

  • Change color, padding, font size on a Card/Heading — styles update live in the editor.
  • Toggle Normal / Hover in the Style tab — hover-specific styles still apply.
  • Columns: change column arrangement / order — column order CSS updates correctly (fingerprint + dynamic deps).
  • Button: toggle Full width — width styles show/hide correctly (enabledCallback fingerprint).
  • Image / Card horizontal layout: change block style variation — image width rules still respond (className-gated callbacks).

3. Text editing (no debounce regression)

  • Type in Heading / Text / Card title on canvas — characters appear immediately (no 300ms lag).
  • Inspector Content field stays in sync with canvas.
  • On the stress page, typing in one block should not cause visible lag or CSS flicker on other blocks.

4. Global settings & classes

  • Global typography / color schemes / design system classes (stk-preview-device-*, hover state classes) still apply after preview switch.
  • Theme block style inheritance still works.

5. Image resizer tooltip

  • Wide image: resizer size tooltip visible while resizing.
  • Very narrow image (<140px): tooltip hidden (container query).
  • Image Box / Card nested images: resize handles still work.

6. Regression smoke

  • Query Loop block with dynamic content in a styled field — preview updates when post context changes.
  • Duplicate page / save / reload — frontend styles unchanged.
  • Widgets screen (if Stackable blocks used there) — no console errors.

Pass criteria

Area Good Bad (needs follow-up)
Safari preview switch Responsive within ~1s, CPU recovers Multi-second freeze or sustained 100% CPU
Style sliders Instant visual update Styles stuck until blur/save
Text typing Instant, no snap-back Delayed chars or reverted text
Columns order CSS matches arrangement Stale column order in editor
Large page typing Smooth on active block Input lag across whole page
Root block stagger Styles correct during brief delay; no long unstyled gaps Blocks stay broken/unstyled after ~200ms

Known follow-ups (out of scope)

  • Dead { blockCss && <style> } in ~46 block edit.js files (harmless; plugin injects CSS).
  • Store SET without equality check — may still dispatch on remount at very large block counts.
  • Iframe mode uses N <style> tags (chunking could help 200+ blocks further).

Summary by CodeRabbit

  • New Features

    • Image resizer tooltips now adapt to available space and automatically hide in very narrow containers.
    • Typography editing now updates content more responsively by default.
  • Bug Fixes

    • Improved reliability of editor previews, styling synchronization, and device-specific class updates.
    • Reduced visual delays when displaying root blocks in device previews.
  • Performance

    • Improved editor style updates to reduce unnecessary recalculation and provide smoother rendering across editor layouts.

Pool editor DOM class observers into a shared onClassChange utility with
rAF batching to avoid MutationObserver feedback loops when switching
device preview. Replace per-image ResizeObservers with CSS container
queries for narrow resizer tooltip hiding.

Fixes #2269

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change centralizes class observation and generated block CSS, improves editor DOM and responsive preview handling, replaces image resizer width state with a container query, updates typography editing behavior, and adjusts style dependency tracking and Stylelint rules.

Changes

Shared class-change observation

Layer / File(s) Summary
Shared observer utility
src/util/on-class-change.js, src/util/index.js
Adds shared per-node MutationObserver state with callback coalescing, subscription management, and cleanup.
Editor listener migration
src/plugins/editor-device-preview-class/index.js, src/plugins/global-settings/..., src/plugins/theme-block-style-inheritance/index.js
Updates editor synchronization effects to use the shared utility and unsubscribe during cleanup.

Container-based image tooltip

Layer / File(s) Summary
Container query tooltip behavior
.stylelintrc.js, src/block-components/image/editor.scss, src/block-components/image/image.js
Allows container CSS features, hides the tooltip based on container width, and removes JavaScript width tracking and the old CSS class.

Centralized editor block CSS

Layer / File(s) Summary
Block CSS state and producer
src/plugins/editor-block-css/store.js, src/components/block-css/*
Adds a block CSS store, dependency extraction and fingerprints, and store-backed CSS generation.
Editor stylesheet operations
src/plugins/editor-block-css/block-style-sheets.js
Adds constructable stylesheet and fallback <style> management across editor documents.
Batched editor CSS synchronization
src/plugins/editor-block-css/index.js, src/plugins/index.js
Batches store changes, re-adopts styles after editor iframe changes, restores fallback elements, and loads the plugin.

Editor preview readiness

Layer / File(s) Summary
Synchronous editor DOM resolution
src/plugins/get-editor-dom/index.js
Resolves and publishes the editor DOM through layout effects with load, mutation, and animation-frame retries.
Responsive preview display timing
src/higher-order/with-block-wrapper/index.js
Replaces the fixed root-block reveal delay with randomized timing.

Typography editing updates

Layer / File(s) Summary
Typography input update behavior
src/block-components/typography/*, src/block/icon-list-item/edit.js
Makes direct typography editing the default, preserves optional debouncing, and removes the explicit non-debounced icon-list setting.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BlockStyleGenerator
  participant EditorBlockCssStore
  participant EditorBlockCssLoader
  participant BlockStyleSheets
  participant EditorDocument
  BlockStyleGenerator->>EditorBlockCssStore: Set generated block CSS
  EditorBlockCssStore->>EditorBlockCssLoader: Publish SET change
  EditorBlockCssLoader->>BlockStyleSheets: Batch stylesheet update
  BlockStyleSheets->>EditorDocument: Adopt stylesheet or sync style element
  EditorBlockCssStore->>EditorBlockCssLoader: Publish REMOVE change
  EditorBlockCssLoader->>BlockStyleSheets: Remove stylesheet
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses #2269 by reducing preview-switching work and adding pooled observers, faster DOM detection, and batched block CSS handling.
Out of Scope Changes check ✅ Passed The typography, image, and style-regeneration changes align with the stated preview and CSS-performance goals, with no clear unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main themes: Safari preview performance, unified block CSS, and editor responsiveness changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/safari-editor-preview-performance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

🤖 Pull request artifacts

file commit
pr3726-stackable-3726-merge.zip 41b7bb1

github-actions Bot added a commit that referenced this pull request Jul 11, 2026
@bfintal bfintal self-assigned this Jul 11, 2026
@bfintal bfintal requested a review from Arukuen July 11, 2026 05:21
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

Size Change: +2.07 kB (+0.08%)

Total Size: 2.63 MB

📦 View Changed
Filename Size Change
build/stackable/dist/admin_welcome.js 145 kB +511 B (+0.35%)
build/stackable/dist/chunks/470.3a197c8a8ff404096af1.js 12.7 kB +12.7 kB (new file) 🆕
build/stackable/dist/chunks/470.efe64a24dd56a919b61f.js 0 B -12.7 kB (removed) 🏆
build/stackable/dist/chunks/cimo-download-notice.5f8ba7b00bde0a09df9e.js 0 B -1.71 kB (removed) 🏆
build/stackable/dist/chunks/cimo-download-notice.bfc72e70c42f6880c414.js 1.71 kB +1.71 kB (new file) 🆕
build/stackable/dist/chunks/design-library.1a69448b7582400b5915.js 9.12 kB +9.12 kB (new file) 🆕
build/stackable/dist/chunks/design-library.545fd477b5950cca0fc6.js 0 B -9.12 kB (removed) 🏆
build/stackable/dist/chunks/style-guide.5d8655a5aa57c0fb4c01.js 68.7 kB +68.7 kB (new file) 🆕
build/stackable/dist/chunks/style-guide.5d8655a5aa57c0fb4c01.js.LICENSE.txt 129 B +129 B (new file) 🆕
build/stackable/dist/chunks/style-guide.9436c4f9c24b5ed6edeb.js 0 B -68.8 kB (removed) 🏆
build/stackable/dist/chunks/style-guide.9436c4f9c24b5ed6edeb.js.LICENSE.txt 0 B -129 B (removed) 🏆
build/stackable/dist/deprecated/frontend_blocks_deprecated_v2.css 10.5 kB +3 B (+0.03%)
build/stackable/dist/editor_blocks.css 32.9 kB +21 B (+0.06%)
build/stackable/dist/editor_blocks.js 186 kB +1.1 kB (+0.59%)
build/stackable/dist/stk.js 200 kB +595 B (+0.3%)
build/stackable/dist/translation-strings.js 9.62 kB -3 B (-0.03%)
ℹ️ View Unchanged
Filename Size
build/stackable/dist/admin_welcome.css 6.56 kB
build/stackable/dist/admin_welcome.js.LICENSE.txt 303 B
build/stackable/dist/chunks/865.33e395e7b3cae1c5dc60.js 4.55 kB
build/stackable/dist/chunks/google-fonts.f834b4c5631c13dffad2.js 18.4 kB
build/stackable/dist/chunks/html-to-image.ed052e3a1c03afea65db.js 5.31 kB
build/stackable/dist/chunks/index.php 96 B
build/stackable/dist/chunks/modal-tour.c043905302c8630fefd7.js 12.7 kB
build/stackable/dist/deprecated/editor_blocks_deprecated_v2.css 5.63 kB
build/stackable/dist/deprecated/editor_blocks_deprecated_v2.js 171 kB
build/stackable/dist/deprecated/editor_blocks_deprecated_v2.js.LICENSE.txt 307 B
build/stackable/dist/deprecated/frontend_blocks_deprecated_v2.js 11.1 kB
build/stackable/dist/deprecated/frontend_blocks_deprecated_v2.js.LICENSE.txt 233 B
build/stackable/dist/deprecated/index.php 96 B
build/stackable/dist/editor_blocks.js.LICENSE.txt 303 B
build/stackable/dist/frontend_block_accordion_polyfill.js 967 B
build/stackable/dist/frontend_block_accordion.js 1.49 kB
build/stackable/dist/frontend_block_carousel.js 3.72 kB
build/stackable/dist/frontend_block_count_up.js 1.18 kB
build/stackable/dist/frontend_block_countdown.js 1.24 kB
build/stackable/dist/frontend_block_expand.js 619 B
build/stackable/dist/frontend_block_horizontal_scroller.js 714 B
build/stackable/dist/frontend_block_map.js 730 B
build/stackable/dist/frontend_block_notification.js 508 B
build/stackable/dist/frontend_block_progress_bar.js 454 B
build/stackable/dist/frontend_block_progress_circle.js 456 B
build/stackable/dist/frontend_block_tabs.js 1.22 kB
build/stackable/dist/frontend_block_video_popup.js 4.8 kB
build/stackable/dist/frontend_blocks_responsive.css 1.94 kB
build/stackable/dist/frontend_blocks.css 12.9 kB
build/stackable/dist/frontend_blocks.js 317 B
build/stackable/dist/frontend_image_lightbox.css 2.27 kB
build/stackable/dist/frontend_image_lightbox.js 16.5 kB
build/stackable/dist/frontend_image_optimizer_polyfill.js 591 B
build/stackable/dist/images/block-accordion-basic.68f5b01.png 1.34 kB
build/stackable/dist/images/block-accordion-colored-hover.8f72f13.png 1.78 kB
build/stackable/dist/images/block-accordion-colored.b9ef3d8.png 1.06 kB
build/stackable/dist/images/block-accordion-line-colored-hover.4610270.png 1.29 kB
build/stackable/dist/images/block-accordion-line-colored.c1836c4.png 1.03 kB
build/stackable/dist/images/block-accordion-plain.007aaa8.png 1.14 kB
build/stackable/dist/images/block-blockquote-basic.e72b033.png 2.12 kB
build/stackable/dist/images/block-blockquote-centered-quote.26d25c7.png 1.44 kB
build/stackable/dist/images/block-blockquote-highlight.55187fa.png 1.47 kB
build/stackable/dist/images/block-blockquote-huge.8356ba3.png 1.2 kB
build/stackable/dist/images/block-blockquote-plain.19ab34f.png 1.29 kB
build/stackable/dist/images/block-blog-posts-basic.680a570.png 1.12 kB
build/stackable/dist/images/block-blog-posts-horizontal-card.ba75baa.png 1.2 kB
build/stackable/dist/images/block-blog-posts-image-card.77c6eb9.png 1.47 kB
build/stackable/dist/images/block-blog-posts-list.b951ba2.png 1.18 kB
build/stackable/dist/images/block-blog-posts-portfolio.4e75beb.png 1.13 kB
build/stackable/dist/images/block-blog-posts-portfolio2.3b4e83e.png 1.12 kB
build/stackable/dist/images/block-blog-posts-vertical-card.9b351f9.png 1.18 kB
build/stackable/dist/images/block-blog-posts-vertical-card2.94c4317.png 1.64 kB
build/stackable/dist/images/block-button-basic.51ee84c.png 1.27 kB
build/stackable/dist/images/block-button-fullwidth.289248b.png 970 B
build/stackable/dist/images/block-button-grouped-1.d5b8cea.png 1.08 kB
build/stackable/dist/images/block-button-grouped-2.2997468.png 1.12 kB
build/stackable/dist/images/block-button-spread.90a905b.png 1.08 kB
build/stackable/dist/images/block-call-to-action-basic.d807764.png 2.38 kB
build/stackable/dist/images/block-call-to-action-horizontal-2.789cf52.png 2.13 kB
build/stackable/dist/images/block-call-to-action-horizontal-3.5e7b64d.png 1.11 kB
build/stackable/dist/images/block-call-to-action-horizontal.ca8c70b.png 1.07 kB
build/stackable/dist/images/block-call-to-action-plain.1329525.png 1 kB
build/stackable/dist/images/block-call-to-action-split-centered.32f650b.png 1.69 kB
build/stackable/dist/images/block-card-basic.3dab7e1.png 1.91 kB
build/stackable/dist/images/block-card-faded.d90df58.png 1.06 kB
build/stackable/dist/images/block-card-full.5628067.png 1.04 kB
build/stackable/dist/images/block-card-horizontal.d3bbeff.png 1.11 kB
build/stackable/dist/images/block-card-plain.da837ce.png 1.03 kB
build/stackable/dist/images/block-column-basic.b01f03b.png 432 B
build/stackable/dist/images/block-column-plain.754d303.png 242 B
build/stackable/dist/images/block-columns-grid.1ff8b85.png 351 B
build/stackable/dist/images/block-columns-plain.652f4f5.png 245 B
build/stackable/dist/images/block-columns-tiled.3667138.png 458 B
build/stackable/dist/images/block-columns-uneven-2.9e10f3f.png 383 B
build/stackable/dist/images/block-columns-uneven.8d5964c.png 246 B
build/stackable/dist/images/block-components-separator-curve-1.254c17a.png 1.18 kB
build/stackable/dist/images/block-components-separator-curve-2.d665227.png 1.25 kB
build/stackable/dist/images/block-components-separator-curve-3.4244deb.png 779 B
build/stackable/dist/images/block-components-separator-rounded-1.13da362.png 1.18 kB
build/stackable/dist/images/block-components-separator-rounded-2.ff50047.png 931 B
build/stackable/dist/images/block-components-separator-rounded-3.e9fc958.png 667 B
build/stackable/dist/images/block-components-separator-slant-1.5247f2f.png 1.18 kB
build/stackable/dist/images/block-components-separator-slant-2.7fb32a4.png 1.16 kB
build/stackable/dist/images/block-components-separator-straight-1.8900892.png 863 B
build/stackable/dist/images/block-components-separator-wave-1.f17479d.png 1.28 kB
build/stackable/dist/images/block-components-separator-wave-2.eb49cf9.png 1.19 kB
build/stackable/dist/images/block-components-separator-wave-3.baef658.png 1.29 kB
build/stackable/dist/images/block-components-separator-wave-4.28b0f44.png 510 B
build/stackable/dist/images/block-container-basic.dd84828.png 1.01 kB
build/stackable/dist/images/block-container-image.4c81c13.png 1.01 kB
build/stackable/dist/images/block-container-image2.752422b.png 1.16 kB
build/stackable/dist/images/block-container-image3.f995a65.png 1.86 kB
build/stackable/dist/images/block-container-plain.34d3d43.png 966 B
build/stackable/dist/images/block-count-up-abstract.67823c9.png 1.34 kB
build/stackable/dist/images/block-count-up-boxed.985f72c.png 2.18 kB
build/stackable/dist/images/block-count-up-plain-2.9e90150.png 1.41 kB
build/stackable/dist/images/block-count-up-plain.0a13db2.png 1.4 kB
build/stackable/dist/images/block-count-up-side.ec8c2dd.png 1.32 kB
build/stackable/dist/images/block-design-library-preview.f1220a2.jpg 18.1 kB
build/stackable/dist/images/block-divider-asterisks.019c81a.png 1.17 kB
build/stackable/dist/images/block-divider-bar.4f8f364.png 947 B
build/stackable/dist/images/block-divider-basic.ea69f9d.png 873 B
build/stackable/dist/images/block-divider-dots.8407764.png 1.03 kB
build/stackable/dist/images/block-feature-basic.d5c6217.png 2.31 kB
build/stackable/dist/images/block-feature-grid-basic.c2975a9.png 1.62 kB
build/stackable/dist/images/block-feature-grid-horizontal.d6e2e24.png 1.95 kB
build/stackable/dist/images/block-feature-grid-large-mid.e0e8e0a.png 1.82 kB
build/stackable/dist/images/block-feature-grid-plain.c733275.png 1.24 kB
build/stackable/dist/images/block-feature-grid-zigzag.306f879.png 1.87 kB
build/stackable/dist/images/block-feature-half.7d450d0.png 1.08 kB
build/stackable/dist/images/block-feature-overlap-shape.7dec2f3.png 1.94 kB
build/stackable/dist/images/block-feature-overlap-shape2.44a52d2.png 2.04 kB
build/stackable/dist/images/block-feature-overlap-shape3.cd36866.png 2.55 kB
build/stackable/dist/images/block-feature-overlap-shape4.a6a8039.png 1.82 kB
build/stackable/dist/images/block-feature-overlap-shape5.911477a.png 1.85 kB
build/stackable/dist/images/block-feature-overlap.eecca61.png 1.16 kB
build/stackable/dist/images/block-feature-overlap2.d656224.png 1.73 kB
build/stackable/dist/images/block-feature-overlap3.fac5ae2.png 1.24 kB
build/stackable/dist/images/block-feature-overlap4.fb5f612.png 1.32 kB
build/stackable/dist/images/block-feature-overlap5.64c2bc0.png 1.21 kB
build/stackable/dist/images/block-feature-plain.8af805e.png 1.12 kB
build/stackable/dist/images/block-header-basic.3ade30c.png 1.07 kB
build/stackable/dist/images/block-header-center-overlay.e78fa5b.png 1.63 kB
build/stackable/dist/images/block-header-half-overlay.6e52b0b.png 1.08 kB
build/stackable/dist/images/block-header-half.55c4824.png 1.06 kB
build/stackable/dist/images/block-header-huge.a2ab3b3.png 1.81 kB
build/stackable/dist/images/block-header-plain.b02346e.png 1.03 kB
build/stackable/dist/images/block-header-side-overlay.38f09f6.png 1.14 kB
build/stackable/dist/images/block-image-box-basic.f0c950a.png 1.79 kB
build/stackable/dist/images/block-image-box-box.941daff.png 1.32 kB
build/stackable/dist/images/block-image-box-captioned.d153775.png 1.14 kB
build/stackable/dist/images/block-image-box-fade.e40838a.png 1.76 kB
build/stackable/dist/images/block-image-box-line.3dfcfff.png 2.6 kB
build/stackable/dist/images/block-image-box-plain.17f7c1e.png 1.6 kB
build/stackable/dist/images/block-map-aubergine.a0d1ec8.png 5.03 kB
build/stackable/dist/images/block-map-dark.d22258f.png 3.74 kB
build/stackable/dist/images/block-map-default.f277d0b.png 6.74 kB
build/stackable/dist/images/block-map-night.b489d95.png 9.43 kB
build/stackable/dist/images/block-map-retro.6e9e18c.png 5.43 kB
build/stackable/dist/images/block-map-silver.28c1a66.png 4.93 kB
build/stackable/dist/images/block-notification-basic.c622a4c.png 1.45 kB
build/stackable/dist/images/block-notification-bordered.9ce3a4b.png 1.06 kB
build/stackable/dist/images/block-notification-large-icon.222b1ce.png 2.01 kB
build/stackable/dist/images/block-notification-outlined.f5db8f6.png 1.05 kB
build/stackable/dist/images/block-notification-plain.ffbc0a7.png 1.02 kB
build/stackable/dist/images/block-number-box-background.5d2e597.png 2.52 kB
build/stackable/dist/images/block-number-box-basic.48056c0.png 2.77 kB
build/stackable/dist/images/block-number-box-faded.36e0ec4.png 1.52 kB
build/stackable/dist/images/block-number-box-heading.ebb5529.png 2.01 kB
build/stackable/dist/images/block-number-box-heading2.e3a9fdd.png 1.96 kB
build/stackable/dist/images/block-number-box-plain.7879145.png 1.55 kB
build/stackable/dist/images/block-pricing-box-basic.274f7b9.png 2.16 kB
build/stackable/dist/images/block-pricing-box-colored.b4279c7.png 1.78 kB
build/stackable/dist/images/block-pricing-box-compact.c67635e.png 2.27 kB
build/stackable/dist/images/block-pricing-box-plain.b5adaa7.png 1.37 kB
build/stackable/dist/images/block-pricing-box-sectioned.f376238.png 2.02 kB
build/stackable/dist/images/block-team-member-basic.cd0961b.png 2.2 kB
build/stackable/dist/images/block-team-member-half.bd51006.png 1.74 kB
build/stackable/dist/images/block-team-member-horizontal.09870f5.png 2.01 kB
build/stackable/dist/images/block-team-member-overlay.2bcdb9d.png 2.4 kB
build/stackable/dist/images/block-team-member-plain.3203ea6.png 1.29 kB
build/stackable/dist/images/block-testimonial-background.5d6565d.png 2.21 kB
build/stackable/dist/images/block-testimonial-basic.cc376c2.png 3.15 kB
build/stackable/dist/images/block-testimonial-basic2.2f9f6ac.png 2.19 kB
build/stackable/dist/images/block-testimonial-bubble.2008575.png 1.77 kB
build/stackable/dist/images/block-testimonial-plain.478ddbc.png 1.27 kB
build/stackable/dist/images/block-testimonial-vertical-inverse.a8a8da4.png 2.42 kB
build/stackable/dist/images/block-testimonial-vertical.ef92405.png 2.28 kB
build/stackable/dist/images/block-text-plain.c150f04.png 342 B
build/stackable/dist/images/block-text-side-title-1.f3774a5.png 361 B
build/stackable/dist/images/block-text-side-title-2.b87a180.png 331 B
build/stackable/dist/images/components-button-controls-basic.ab5e9f1.png 1.11 kB
build/stackable/dist/images/components-button-controls-ghost.d3d7b9a.png 1.18 kB
build/stackable/dist/images/components-button-controls-link.dba9a28.png 939 B
build/stackable/dist/images/components-button-controls-plain.c779ddf.png 963 B
build/stackable/dist/images/components-columns-width-control-2-1-grid.afd9a92.png 350 B
build/stackable/dist/images/components-columns-width-control-2-1-uneven.6cd9789.png 415 B
build/stackable/dist/images/components-columns-width-control-2-1.acc91e5.png 408 B
build/stackable/dist/images/components-columns-width-control-2-2-grid.fb0db0a.png 320 B
build/stackable/dist/images/components-columns-width-control-2-2-uneven.343b32f.png 352 B
build/stackable/dist/images/components-columns-width-control-2-2.07b68cc.png 282 B
build/stackable/dist/images/components-columns-width-control-2-3-grid.49f9601.png 304 B
build/stackable/dist/images/components-columns-width-control-2-3-uneven.2f83bb9.png 311 B
build/stackable/dist/images/components-columns-width-control-2-3.014c061.png 270 B
build/stackable/dist/images/components-columns-width-control-3-1-grid.74f65ec.png 319 B
build/stackable/dist/images/components-columns-width-control-3-1-uneven.e06e928.png 313 B
build/stackable/dist/images/components-columns-width-control-3-1.40b83bd.png 288 B
build/stackable/dist/images/components-columns-width-control-3-2-grid.3a3415f.png 395 B
build/stackable/dist/images/components-columns-width-control-3-2-uneven.16d95c7.png 390 B
build/stackable/dist/images/components-columns-width-control-3-2.d6c2e1a.png 280 B
build/stackable/dist/images/components-columns-width-control-3-3-grid.dc0bc99.png 371 B
build/stackable/dist/images/components-columns-width-control-3-3-uneven.1563189.png 383 B
build/stackable/dist/images/components-columns-width-control-3-3.5463833.png 353 B
build/stackable/dist/images/components-columns-width-control-3-4-grid.a51c65c.png 344 B
build/stackable/dist/images/components-columns-width-control-3-4-uneven.c30113e.png 361 B
build/stackable/dist/images/components-columns-width-control-3-4.f094a43.png 350 B
build/stackable/dist/images/components-columns-width-control-4-1-grid.8273e1a.png 387 B
build/stackable/dist/images/components-columns-width-control-4-1-uneven.6ac045a.png 384 B
build/stackable/dist/images/components-columns-width-control-4-1.cd295e6.png 484 B
build/stackable/dist/images/components-columns-width-control-4-2-grid.b0b7a8e.png 325 B
build/stackable/dist/images/components-columns-width-control-4-2-uneven.24ee9ad.png 309 B
build/stackable/dist/images/components-columns-width-control-4-2.38a83ef.png 312 B
build/stackable/dist/images/components-columns-width-control-4-3-grid.92b740b.png 377 B
build/stackable/dist/images/components-columns-width-control-4-3-uneven.1e31bb8.png 353 B
build/stackable/dist/images/components-columns-width-control-4-3.dc1bfc0.png 435 B
build/stackable/dist/images/components-design-separator-control-curve-1.254c17a.png 1.18 kB
build/stackable/dist/images/components-design-separator-control-curve-2.d665227.png 1.25 kB
build/stackable/dist/images/components-design-separator-control-curve-3.4244deb.png 779 B
build/stackable/dist/images/components-design-separator-control-rounded-1.13da362.png 1.18 kB
build/stackable/dist/images/components-design-separator-control-rounded-2.ff50047.png 931 B
build/stackable/dist/images/components-design-separator-control-rounded-3.e9fc958.png 667 B
build/stackable/dist/images/components-design-separator-control-slant-1.5247f2f.png 1.18 kB
build/stackable/dist/images/components-design-separator-control-slant-2.7fb32a4.png 1.16 kB
build/stackable/dist/images/components-design-separator-control-straight-1.8900892.png 863 B
build/stackable/dist/images/components-design-separator-control-wave-1.f17479d.png 1.28 kB
build/stackable/dist/images/components-design-separator-control-wave-2.eb49cf9.png 1.19 kB
build/stackable/dist/images/components-design-separator-control-wave-3.baef658.png 1.29 kB
build/stackable/dist/images/components-design-separator-control-wave-4.28b0f44.png 510 B
build/stackable/dist/images/components-icon-controls-outlined.d293bf5.png 1.26 kB
build/stackable/dist/images/components-icon-controls-plain.d0d9e7b.png 473 B
build/stackable/dist/images/components-icon-controls-shaped.3f56d80.png 1.03 kB
build/stackable/dist/images/components-image-shape-control-blob1.52fa507.png 1.66 kB
build/stackable/dist/images/components-image-shape-control-circle.355575c.png 1.6 kB
build/stackable/dist/images/components-image-shape-control-default.a1e033f.png 853 B
build/stackable/dist/images/components-image-shape-control-square.b0df757.png 887 B
build/stackable/dist/images/components-pro-control-pro-icon.114d653.png 7.13 kB
build/stackable/dist/images/higher-order-with-design-layout-selector-basic.3ade30c.png 1.07 kB
build/stackable/dist/images/index.php 96 B
build/stackable/dist/images/lazy-components-design-library-maps.0f15487.webp 34.2 kB
build/stackable/dist/images/lazy-components-style-guide-hero-bg.c55e339.webp 80.4 kB
build/stackable/dist/images/lazy-components-style-guide-media-text.c06b506.webp 32.6 kB
build/stackable/dist/images/lazy-components-style-guide-profile.5f2c46d.webp 25.5 kB
build/stackable/dist/images/src-welcome-cimo-icon.b7e3f36.png 1.28 kB
build/stackable/dist/images/src-welcome-interactions-icon.cc1be03.png 613 B
build/stackable/dist/index.php 96 B
build/stackable/dist/stk.js.LICENSE.txt 303 B
build/stackable/freemius.php 1.3 kB
build/stackable/freemius/assets/css/admin/account.css 1.25 kB
build/stackable/freemius/assets/css/admin/add-ons.css 2.08 kB
build/stackable/freemius/assets/css/admin/affiliation.css 513 B
build/stackable/freemius/assets/css/admin/checkout.css 420 B
build/stackable/freemius/assets/css/admin/clone-resolution.css 523 B
build/stackable/freemius/assets/css/admin/common.css 1.52 kB
build/stackable/freemius/assets/css/admin/connect.css 2.41 kB
build/stackable/freemius/assets/css/admin/debug.css 474 B
build/stackable/freemius/assets/css/admin/dialog-boxes.css 2.4 kB
build/stackable/freemius/assets/css/admin/gdpr-optin-notice.css 158 B
build/stackable/freemius/assets/css/admin/index.php 96 B
build/stackable/freemius/assets/css/admin/optout.css 1.03 kB
build/stackable/freemius/assets/css/admin/plugins.css 249 B
build/stackable/freemius/assets/css/customizer.css 788 B
build/stackable/freemius/assets/css/index.php 96 B
build/stackable/freemius/assets/img/index.php 96 B
build/stackable/freemius/assets/img/plugin-icon.png 7.56 kB
build/stackable/freemius/assets/img/stackable-ultimate-gutenberg-blocks.png 3.67 kB
build/stackable/freemius/assets/img/theme-icon.png 8.91 kB
build/stackable/freemius/assets/index.php 96 B
build/stackable/freemius/assets/js/index.php 96 B
build/stackable/freemius/assets/js/jquery.form.js 327 B
build/stackable/freemius/assets/js/nojquery.ba-postmessage.js 1.18 kB
build/stackable/freemius/assets/js/postmessage.js 764 B
build/stackable/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg 8.5 kB
build/stackable/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png 5.37 kB
build/stackable/freemius/assets/js/pricing/45da596e2b512ffc3bb638baaf0fdc4e.png 3.39 kB
build/stackable/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png 5.05 kB
build/stackable/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png 7.56 kB
build/stackable/freemius/assets/js/pricing/a34e046aee1702a5690679750a7f4d0f.svg 10.1 kB
build/stackable/freemius/assets/js/pricing/b09d0b38b627c2fa564d050f79f2f064.svg 2.33 kB
build/stackable/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png 5.2 kB
build/stackable/freemius/assets/js/pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png 8.33 kB
build/stackable/freemius/assets/js/pricing/d65812c447b4523b42d59018e1c0bb53.png 29.3 kB
build/stackable/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png 5.08 kB
build/stackable/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png 8.91 kB
build/stackable/freemius/assets/js/pricing/freemius-pricing.js 83.3 kB
build/stackable/freemius/assets/js/pricing/freemius-pricing.js.LICENSE.txt 395 B
build/stackable/freemius/assets/js/pricing/index.php 96 B
build/stackable/freemius/composer.json 562 B
build/stackable/freemius/config.php 3.28 kB
build/stackable/freemius/includes/class-freemius-abstract.php 2.33 kB
build/stackable/freemius/includes/class-freemius.php 167 kB
build/stackable/freemius/includes/class-fs-admin-notices.php 2.12 kB
build/stackable/freemius/includes/class-fs-api.php 5.13 kB
build/stackable/freemius/includes/class-fs-garbage-collector.php 3.56 kB
build/stackable/freemius/includes/class-fs-hook-snapshot.php 517 B
build/stackable/freemius/includes/class-fs-lock.php 840 B
build/stackable/freemius/includes/class-fs-logger.php 4.92 kB
build/stackable/freemius/includes/class-fs-options.php 2.81 kB
build/stackable/freemius/includes/class-fs-plugin-updater.php 13 kB
build/stackable/freemius/includes/class-fs-security.php 803 B
build/stackable/freemius/includes/class-fs-storage.php 3.68 kB
build/stackable/freemius/includes/class-fs-user-lock.php 722 B
build/stackable/freemius/includes/customizer/class-fs-customizer-support-section.php 1 kB
build/stackable/freemius/includes/customizer/class-fs-customizer-upsell-control.php 1.74 kB
build/stackable/freemius/includes/customizer/index.php 96 B
build/stackable/freemius/includes/debug/class-fs-debug-bar-panel.php 702 B
build/stackable/freemius/includes/debug/debug-bar-start.php 590 B
build/stackable/freemius/includes/debug/index.php 96 B
build/stackable/freemius/includes/entities/class-fs-affiliate-terms.php 1.27 kB
build/stackable/freemius/includes/entities/class-fs-affiliate.php 523 B
build/stackable/freemius/includes/entities/class-fs-billing.php 587 B
build/stackable/freemius/includes/entities/class-fs-entity.php 1.05 kB
build/stackable/freemius/includes/entities/class-fs-payment.php 1.21 kB
build/stackable/freemius/includes/entities/class-fs-plugin-info.php 400 B
build/stackable/freemius/includes/entities/class-fs-plugin-license.php 1.82 kB
build/stackable/freemius/includes/entities/class-fs-plugin-plan.php 1.02 kB
build/stackable/freemius/includes/entities/class-fs-plugin-tag.php 550 B
build/stackable/freemius/includes/entities/class-fs-plugin.php 1.07 kB
build/stackable/freemius/includes/entities/class-fs-pricing.php 895 B
build/stackable/freemius/includes/entities/class-fs-scope-entity.php 339 B
build/stackable/freemius/includes/entities/class-fs-site.php 1.89 kB
build/stackable/freemius/includes/entities/class-fs-subscription.php 933 B
build/stackable/freemius/includes/entities/class-fs-user.php 812 B
build/stackable/freemius/includes/entities/index.php 96 B
build/stackable/freemius/includes/fs-core-functions.php 8.03 kB
build/stackable/freemius/includes/fs-essential-functions.php 4.22 kB
build/stackable/freemius/includes/fs-html-escaping-functions.php 915 B
build/stackable/freemius/includes/fs-plugin-info-dialog.php 13.9 kB
build/stackable/freemius/includes/index.php 96 B
build/stackable/freemius/includes/l10n.php 510 B
build/stackable/freemius/includes/managers/class-fs-admin-menu-manager.php 5.53 kB
build/stackable/freemius/includes/managers/class-fs-admin-notice-manager.php 3.72 kB
build/stackable/freemius/includes/managers/class-fs-cache-manager.php 1.55 kB
build/stackable/freemius/includes/managers/class-fs-checkout-manager.php 2.37 kB
build/stackable/freemius/includes/managers/class-fs-clone-manager.php 11.1 kB
build/stackable/freemius/includes/managers/class-fs-contact-form-manager.php 871 B
build/stackable/freemius/includes/managers/class-fs-debug-manager.php 3.39 kB
build/stackable/freemius/includes/managers/class-fs-gdpr-manager.php 1.38 kB
build/stackable/freemius/includes/managers/class-fs-key-value-storage.php 2.49 kB
build/stackable/freemius/includes/managers/class-fs-license-manager.php 883 B
build/stackable/freemius/includes/managers/class-fs-option-manager.php 2.47 kB
build/stackable/freemius/includes/managers/class-fs-permission-manager.php 3.88 kB
build/stackable/freemius/includes/managers/class-fs-plan-manager.php 1.01 kB
build/stackable/freemius/includes/managers/class-fs-plugin-manager.php 1.74 kB
build/stackable/freemius/includes/managers/index.php 96 B
build/stackable/freemius/includes/sdk/Exceptions/ArgumentNotExistException.php 168 B
build/stackable/freemius/includes/sdk/Exceptions/EmptyArgumentException.php 164 B
build/stackable/freemius/includes/sdk/Exceptions/Exception.php 656 B
build/stackable/freemius/includes/sdk/Exceptions/index.php 96 B
build/stackable/freemius/includes/sdk/Exceptions/InvalidArgumentException.php 162 B
build/stackable/freemius/includes/sdk/Exceptions/OAuthException.php 196 B
build/stackable/freemius/includes/sdk/FreemiusBase.php 2.21 kB
build/stackable/freemius/includes/sdk/FreemiusWordPress.php 6.16 kB
build/stackable/freemius/includes/sdk/index.php 96 B
build/stackable/freemius/includes/sdk/LICENSE.txt 6.8 kB
build/stackable/freemius/includes/supplements/fs-essential-functions-1.1.7.1.php 652 B
build/stackable/freemius/includes/supplements/fs-essential-functions-2.2.1.php 515 B
build/stackable/freemius/includes/supplements/fs-migration-2.5.1.php 463 B
build/stackable/freemius/includes/supplements/index.php 96 B
build/stackable/freemius/index.php 96 B
build/stackable/freemius/languages/freemius-cs_CZ.mo 14.8 kB
build/stackable/freemius/languages/freemius-da_DK.mo 12.6 kB
build/stackable/freemius/languages/freemius-de_DE.mo 24.4 kB
build/stackable/freemius/languages/freemius-es_ES.mo 19.3 kB
build/stackable/freemius/languages/freemius-fr_FR.mo 17.2 kB
build/stackable/freemius/languages/freemius-he_IL.mo 13.2 kB
build/stackable/freemius/languages/freemius-hu_HU.mo 10.4 kB
build/stackable/freemius/languages/freemius-it_IT.mo 21 kB
build/stackable/freemius/languages/freemius-ja.mo 16.7 kB
build/stackable/freemius/languages/freemius-nl_NL.mo 17.2 kB
build/stackable/freemius/languages/freemius-ru_RU.mo 16.3 kB
build/stackable/freemius/languages/freemius-ta.mo 19.2 kB
build/stackable/freemius/languages/freemius-zh_CN.mo 24.1 kB
build/stackable/freemius/languages/freemius.pot 16.5 kB
build/stackable/freemius/languages/index.php 96 B
build/stackable/freemius/LICENSE.txt 12.1 kB
build/stackable/freemius/README.md 4.01 kB
build/stackable/freemius/require.php 682 B
build/stackable/freemius/start.php 5.84 kB
build/stackable/freemius/templates/account.php 12.1 kB
build/stackable/freemius/templates/account/billing.php 4.15 kB
build/stackable/freemius/templates/account/index.php 96 B
build/stackable/freemius/templates/account/partials/activate-license-button.php 863 B
build/stackable/freemius/templates/account/partials/addon.php 4.35 kB
build/stackable/freemius/templates/account/partials/deactivate-license-button.php 614 B
build/stackable/freemius/templates/account/partials/disconnect-button.php 1.66 kB
build/stackable/freemius/templates/account/partials/index.php 96 B
build/stackable/freemius/templates/account/partials/site.php 3.85 kB
build/stackable/freemius/templates/account/payments.php 754 B
build/stackable/freemius/templates/add-ons.php 5.01 kB
build/stackable/freemius/templates/add-trial-to-pricing.php 500 B
build/stackable/freemius/templates/admin-notice.php 961 B
build/stackable/freemius/templates/ajax-loader.php 184 B
build/stackable/freemius/templates/api-connectivity-message-js.php 498 B
build/stackable/freemius/templates/auto-installation.php 2.53 kB
build/stackable/freemius/templates/checkout.php 375 B
build/stackable/freemius/templates/checkout/frame.php 2.53 kB
build/stackable/freemius/templates/checkout/index.php 96 B
build/stackable/freemius/templates/checkout/process-redirect.php 1.53 kB
build/stackable/freemius/templates/checkout/redirect.php 1.31 kB
build/stackable/freemius/templates/clone-resolution-js.php 1.24 kB
build/stackable/freemius/templates/connect.php 10.3 kB
build/stackable/freemius/templates/connect/index.php 96 B
build/stackable/freemius/templates/connect/permission.php 679 B
build/stackable/freemius/templates/connect/permissions-group.php 873 B
build/stackable/freemius/templates/contact.php 1.83 kB
build/stackable/freemius/templates/debug.php 8.43 kB
build/stackable/freemius/templates/debug/api-calls.php 1.81 kB
build/stackable/freemius/templates/debug/index.php 96 B
build/stackable/freemius/templates/debug/logger.php 950 B
build/stackable/freemius/templates/debug/plugins-themes-sync.php 854 B
build/stackable/freemius/templates/debug/scheduled-crons.php 1.41 kB
build/stackable/freemius/templates/email.php 545 B
build/stackable/freemius/templates/forms/affiliation.php 5.4 kB
build/stackable/freemius/templates/forms/data-debug-mode.php 2.33 kB
build/stackable/freemius/templates/forms/deactivation/contact.php 458 B
build/stackable/freemius/templates/forms/deactivation/form.php 6.07 kB
build/stackable/freemius/templates/forms/deactivation/index.php 96 B
build/stackable/freemius/templates/forms/deactivation/retry-skip.php 601 B
build/stackable/freemius/templates/forms/email-address-update.php 3.02 kB
build/stackable/freemius/templates/forms/index.php 96 B
build/stackable/freemius/templates/forms/license-activation.php 7.56 kB
build/stackable/freemius/templates/forms/optout.php 2.65 kB
build/stackable/freemius/templates/forms/premium-versions-upgrade-handler.php 2.36 kB
build/stackable/freemius/templates/forms/premium-versions-upgrade-metadata.php 682 B
build/stackable/freemius/templates/forms/resend-key.php 2.39 kB
build/stackable/freemius/templates/forms/subscription-cancellation.php 3.17 kB
build/stackable/freemius/templates/forms/trial-start.php 2.01 kB
build/stackable/freemius/templates/forms/user-change.php 2.77 kB
build/stackable/freemius/templates/gdpr-optin-js.php 912 B
build/stackable/freemius/templates/index.php 96 B
build/stackable/freemius/templates/js/index.php 96 B
build/stackable/freemius/templates/js/jquery.content-change.php 649 B
build/stackable/freemius/templates/js/open-license-activation.php 537 B
build/stackable/freemius/templates/js/permissions.php 3.7 kB
build/stackable/freemius/templates/js/style-premium-theme.php 731 B
build/stackable/freemius/templates/partials/index.php 46 B
build/stackable/freemius/templates/partials/network-activation.php 1.14 kB
build/stackable/freemius/templates/plugin-icon.php 361 B
build/stackable/freemius/templates/plugin-info/description.php 869 B
build/stackable/freemius/templates/plugin-info/features.php 1.14 kB
build/stackable/freemius/templates/plugin-info/index.php 96 B
build/stackable/freemius/templates/plugin-info/screenshots.php 450 B
build/stackable/freemius/templates/pricing.php 1.52 kB
build/stackable/freemius/templates/secure-https-header.php 616 B
build/stackable/freemius/templates/sticky-admin-notice-js.php 694 B
build/stackable/freemius/templates/tabs-capture-js.php 860 B
build/stackable/freemius/templates/tabs.php 2.14 kB
build/stackable/index.php 96 B
build/stackable/languages/index.php 96 B
build/stackable/languages/stackable-ultimate-gutenberg-blocks.pot 54 kB
build/stackable/plugin.php 3.7 kB
build/stackable/readme.txt 14.5 kB
build/stackable/src/admin.php 657 B
build/stackable/src/block-components/alignment/index.php 1.11 kB
build/stackable/src/block-components/index.php 96 B
build/stackable/src/block/accordion/block.json 383 B
build/stackable/src/block/accordion/index.php 1.65 kB
build/stackable/src/block/blockquote/block.json 324 B
build/stackable/src/block/blockquote/index.php 96 B
build/stackable/src/block/button-group/block.json 447 B
build/stackable/src/block/button-group/index.php 96 B
build/stackable/src/block/button/block.json 339 B
build/stackable/src/block/button/index.php 96 B
build/stackable/src/block/call-to-action/block.json 423 B
build/stackable/src/block/call-to-action/index.php 96 B
build/stackable/src/block/card/block.json 405 B
build/stackable/src/block/card/index.php 96 B
build/stackable/src/block/carousel/block.json 300 B
build/stackable/src/block/carousel/index.php 290 B
build/stackable/src/block/column/block.json 357 B
build/stackable/src/block/column/index.php 96 B
build/stackable/src/block/columns/block.json 392 B
build/stackable/src/block/columns/index.php 464 B
build/stackable/src/block/count-up/block.json 357 B
build/stackable/src/block/count-up/index.php 293 B
build/stackable/src/block/countdown/block.json 315 B
build/stackable/src/block/countdown/index.php 292 B
build/stackable/src/block/design-library/block.json 350 B
build/stackable/src/block/design-library/index.php 96 B
build/stackable/src/block/divider/block.json 250 B
build/stackable/src/block/divider/index.php 96 B
build/stackable/src/block/expand/block.json 397 B
build/stackable/src/block/expand/index.php 288 B
build/stackable/src/block/feature-grid/block.json 413 B
build/stackable/src/block/feature-grid/index.php 96 B
build/stackable/src/block/feature/block.json 397 B
build/stackable/src/block/feature/index.php 96 B
build/stackable/src/block/heading/block.json 327 B
build/stackable/src/block/heading/index.php 96 B
build/stackable/src/block/hero/block.json 383 B
build/stackable/src/block/hero/index.php 96 B
build/stackable/src/block/horizontal-scroller/block.json 354 B
build/stackable/src/block/horizontal-scroller/index.php 305 B
build/stackable/src/block/icon-box/block.json 359 B
build/stackable/src/block/icon-box/index.php 96 B
build/stackable/src/block/icon-button/block.json 346 B
build/stackable/src/block/icon-button/index.php 96 B
build/stackable/src/block/icon-label/block.json 342 B
build/stackable/src/block/icon-label/deprecated.php 546 B
build/stackable/src/block/icon-label/index.php 96 B
build/stackable/src/block/icon-list-item/block.json 345 B
build/stackable/src/block/icon-list-item/index.php 96 B
build/stackable/src/block/icon-list/block.json 401 B
build/stackable/src/block/icon-list/index.php 96 B
build/stackable/src/block/icon/block.json 327 B
build/stackable/src/block/icon/index.php 96 B
build/stackable/src/block/image-box/block.json 403 B
build/stackable/src/block/image-box/index.php 96 B
build/stackable/src/block/image/block.json 315 B
build/stackable/src/block/image/index.php 96 B
build/stackable/src/block/index.php 96 B
build/stackable/src/block/map/block.json 326 B
build/stackable/src/block/map/index.php 459 B
build/stackable/src/block/notification/block.json 424 B
build/stackable/src/block/notification/index.php 289 B
build/stackable/src/block/number-box/block.json 337 B
build/stackable/src/block/number-box/index.php 96 B
build/stackable/src/block/posts/block.json 475 B
build/stackable/src/block/posts/index.php 5.46 kB
build/stackable/src/block/price/block.json 373 B
build/stackable/src/block/price/index.php 96 B
build/stackable/src/block/pricing-box/block.json 435 B
build/stackable/src/block/pricing-box/index.php 96 B
build/stackable/src/block/progress-bar/block.json 330 B
build/stackable/src/block/progress-bar/index.php 518 B
build/stackable/src/block/progress-circle/block.json 333 B
build/stackable/src/block/progress-circle/index.php 518 B
build/stackable/src/block/separator/block.json 323 B
build/stackable/src/block/separator/index.php 96 B
build/stackable/src/block/spacer/block.json 222 B
build/stackable/src/block/spacer/index.php 96 B
build/stackable/src/block/subtitle/block.json 316 B
build/stackable/src/block/subtitle/index.php 96 B
build/stackable/src/block/tab-content/block.json 269 B
build/stackable/src/block/tab-content/index.php 96 B
build/stackable/src/block/tab-labels/block.json 270 B
build/stackable/src/block/tab-labels/index.php 96 B
build/stackable/src/block/table-of-contents/block.json 343 B
build/stackable/src/block/table-of-contents/index.php 265 B
build/stackable/src/block/tabs/block.json 385 B
build/stackable/src/block/tabs/index.php 287 B
build/stackable/src/block/team-member/block.json 410 B
build/stackable/src/block/team-member/index.php 96 B
build/stackable/src/block/testimonial/block.json 393 B
build/stackable/src/block/testimonial/index.php 96 B
build/stackable/src/block/text/block.json 329 B
build/stackable/src/block/text/index.php 96 B
build/stackable/src/block/timeline/block.json 322 B
build/stackable/src/block/timeline/index.php 551 B
build/stackable/src/block/video-popup/block.json 414 B
build/stackable/src/block/video-popup/index.php 1.33 kB
build/stackable/src/blocks.php 1.04 kB
build/stackable/src/compatibility/blocksy/index.php 1.99 kB
build/stackable/src/compatibility/ewww.php 580 B
build/stackable/src/compatibility/index.php 179 B
build/stackable/src/compatibility/neve/index.php 416 B
build/stackable/src/compatibility/woocommerce.php 721 B
build/stackable/src/components/__experimental-multi-post-picker-control/index.php 762 B
build/stackable/src/components/index.php 96 B
build/stackable/src/css-optimize.php 4.98 kB
build/stackable/src/deprecated/block-defaults.php 794 B
build/stackable/src/deprecated/block-defaults/custom-block-styles.php 3.43 kB
build/stackable/src/deprecated/block-defaults/index.php 96 B
build/stackable/src/deprecated/editor-settings.php 441 B
build/stackable/src/deprecated/font-awesome-version.php 433 B
build/stackable/src/deprecated/global-color-schemes.php 617 B
build/stackable/src/deprecated/index.php 96 B
build/stackable/src/deprecated/native-global-colors.php 1.18 kB
build/stackable/src/deprecated/navigation-panel-pre-enabled.php 449 B
build/stackable/src/deprecated/v2/block/accordion/block.json 187 B
build/stackable/src/deprecated/v2/block/accordion/index.php 96 B
build/stackable/src/deprecated/v2/block/blockquote/block.json 143 B
build/stackable/src/deprecated/v2/block/blockquote/index.php 96 B
build/stackable/src/deprecated/v2/block/blog-posts/attributes.php 2.9 kB
build/stackable/src/deprecated/v2/block/blog-posts/block.json 222 B
build/stackable/src/deprecated/v2/block/blog-posts/deprecated.php 1.82 kB
build/stackable/src/deprecated/v2/block/blog-posts/index.php 5.68 kB
build/stackable/src/deprecated/v2/block/blog-posts/util.php 663 B
build/stackable/src/deprecated/v2/block/button/block.json 163 B
build/stackable/src/deprecated/v2/block/button/index.php 96 B
build/stackable/src/deprecated/v2/block/call-to-action/block.json 216 B
build/stackable/src/deprecated/v2/block/call-to-action/index.php 96 B
build/stackable/src/deprecated/v2/block/card/block.json 208 B
build/stackable/src/deprecated/v2/block/card/index.php 96 B
build/stackable/src/deprecated/v2/block/column/block.json 222 B
build/stackable/src/deprecated/v2/block/column/index.php 96 B
build/stackable/src/deprecated/v2/block/columns/block.json 228 B
build/stackable/src/deprecated/v2/block/columns/index.php 96 B
build/stackable/src/deprecated/v2/block/container/block.json 207 B
build/stackable/src/deprecated/v2/block/container/index.php 96 B
build/stackable/src/deprecated/v2/block/count-up/block.json 217 B
build/stackable/src/deprecated/v2/block/count-up/index.php 96 B
build/stackable/src/deprecated/v2/block/divider/block.json 150 B
build/stackable/src/deprecated/v2/block/divider/index.php 96 B
build/stackable/src/deprecated/v2/block/expand/block.json 220 B
build/stackable/src/deprecated/v2/block/expand/index.php 96 B
build/stackable/src/deprecated/v2/block/feature-grid/block.json 188 B
build/stackable/src/deprecated/v2/block/feature-grid/index.php 96 B
build/stackable/src/deprecated/v2/block/feature/block.json 161 B
build/stackable/src/deprecated/v2/block/feature/index.php 96 B
build/stackable/src/deprecated/v2/block/header/block.json 191 B
build/stackable/src/deprecated/v2/block/header/index.php 96 B
build/stackable/src/deprecated/v2/block/heading/block.json 184 B
build/stackable/src/deprecated/v2/block/heading/index.php 96 B
build/stackable/src/deprecated/v2/block/icon-list/block.json 214 B
build/stackable/src/deprecated/v2/block/icon-list/index.php 96 B
build/stackable/src/deprecated/v2/block/icon/block.json 186 B
build/stackable/src/deprecated/v2/block/icon/index.php 96 B
build/stackable/src/deprecated/v2/block/image-box/block.json 199 B
build/stackable/src/deprecated/v2/block/image-box/index.php 96 B
build/stackable/src/deprecated/v2/block/index.php 96 B
build/stackable/src/deprecated/v2/block/notification/block.json 209 B
build/stackable/src/deprecated/v2/block/notification/index.php 96 B
build/stackable/src/deprecated/v2/block/number-box/block.json 196 B
build/stackable/src/deprecated/v2/block/number-box/index.php 96 B
build/stackable/src/deprecated/v2/block/pricing-box/block.json 167 B
build/stackable/src/deprecated/v2/block/pricing-box/index.php 96 B
build/stackable/src/deprecated/v2/block/separator/block.json 184 B
build/stackable/src/deprecated/v2/block/separator/index.php 96 B
build/stackable/src/deprecated/v2/block/spacer/block.json 147 B
build/stackable/src/deprecated/v2/block/spacer/index.php 96 B
build/stackable/src/deprecated/v2/block/team-member/block.json 186 B
build/stackable/src/deprecated/v2/block/team-member/index.php 96 B
build/stackable/src/deprecated/v2/block/testimonial/block.json 167 B
build/stackable/src/deprecated/v2/block/testimonial/index.php 96 B
build/stackable/src/deprecated/v2/block/text/block.json 190 B
build/stackable/src/deprecated/v2/block/text/index.php 96 B
build/stackable/src/deprecated/v2/block/video-popup/block.json 250 B
build/stackable/src/deprecated/v2/block/video-popup/index.php 96 B
build/stackable/src/deprecated/v2/blocks.php 1.95 kB
build/stackable/src/deprecated/v2/design-library/index.php 96 B
build/stackable/src/deprecated/v2/design-library/init.php 1.33 kB
build/stackable/src/deprecated/v2/disabled-blocks.php 831 B
build/stackable/src/deprecated/v2/global-settings.php 308 B
build/stackable/src/deprecated/v2/index.php 96 B
build/stackable/src/deprecated/v2/init.php 3.28 kB
build/stackable/src/deprecated/v2/optimization-settings.php 1.06 kB
build/stackable/src/design-library/index.php 96 B
build/stackable/src/design-library/init.php 2.7 kB
build/stackable/src/dynamic-breakpoints.php 4.08 kB
build/stackable/src/editor-settings.php 1.82 kB
build/stackable/src/fonts.php 1.92 kB
build/stackable/src/global-settings.php 7.73 kB
build/stackable/src/icons.php 602 B
build/stackable/src/index.php 96 B
build/stackable/src/init.php 5.76 kB
build/stackable/src/jetpack.php 781 B
build/stackable/src/kses.php 1.41 kB
build/stackable/src/lightbox/index.php 427 B
build/stackable/src/multisite.php 1.31 kB
build/stackable/src/plugins/global-settings/block-styles/index.php 927 B
build/stackable/src/plugins/global-settings/buttons-and-icons/index.php 1.49 kB
build/stackable/src/plugins/global-settings/color-schemes/deprecated/index.php 306 B
build/stackable/src/plugins/global-settings/color-schemes/index.php 5.12 kB
build/stackable/src/plugins/global-settings/index.php 96 B
build/stackable/src/plugins/global-settings/preset-controls/index.php 2.5 kB
build/stackable/src/plugins/global-settings/spacing-and-borders/index.php 1.48 kB
build/stackable/src/plugins/index.php 96 B
build/stackable/src/plugins/theme-block-style-inheritance/index.php 4.79 kB
build/stackable/src/pro.php 1.61 kB
build/stackable/src/stk-block-types.php 3.97 kB
build/stackable/src/styles/block-design-system.php 870 B
build/stackable/src/styles/index.php 96 B
build/stackable/src/unique-id.php 567 B
build/stackable/src/welcome/freemius.php 478 B
build/stackable/src/welcome/getting-started.php 717 B
build/stackable/src/welcome/images/align-left.svg 180 B
build/stackable/src/welcome/images/arrow-left.svg 422 B
build/stackable/src/welcome/images/arrow-right.svg 478 B
build/stackable/src/welcome/images/arrow-up-right.svg 330 B
build/stackable/src/welcome/images/banner-bg.svg 3.2 kB
build/stackable/src/welcome/images/book-open.svg 202 B
build/stackable/src/welcome/images/check.svg 383 B
build/stackable/src/welcome/images/cimo-icon.png 3.92 kB
build/stackable/src/welcome/images/divider.svg 912 B
build/stackable/src/welcome/images/docs.svg 308 B
build/stackable/src/welcome/images/gambit-logo-small.png 3.08 kB
build/stackable/src/welcome/images/headphones.svg 228 B
build/stackable/src/welcome/images/index.php 96 B
build/stackable/src/welcome/images/info.svg 216 B
build/stackable/src/welcome/images/interactions-icon.png 816 B
build/stackable/src/welcome/images/pro-icon.svg 381 B
build/stackable/src/welcome/images/quick-buttons-arrow.svg 496 B
build/stackable/src/welcome/images/settings-icon-essential.svg 277 B
build/stackable/src/welcome/images/settings-icon-section.svg 258 B
build/stackable/src/welcome/images/settings-icon-special.svg 260 B
build/stackable/src/welcome/images/smile.svg 233 B
build/stackable/src/welcome/images/stackable-icon.png 4.55 kB
build/stackable/src/welcome/images/stackable-icon.svg 336 B
build/stackable/src/welcome/images/stackable-logo.png 24.8 kB
build/stackable/src/welcome/images/stackable-logo.svg 1.43 kB
build/stackable/src/welcome/images/star.svg 216 B
build/stackable/src/welcome/images/tutorials.svg 320 B
build/stackable/src/welcome/images/user.svg 304 B
build/stackable/src/welcome/index.php 4.96 kB
build/stackable/src/welcome/news.php 1.3 kB
build/stackable/src/welcome/notification-rate.php 808 B
build/stackable/src/welcome/notification.php 1.82 kB
build/stackable/src/welcome/updates.php 646 B
build/stackable/src/welcome/useful-plugins.php 2.74 kB

compressed-size-action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/util/on-class-change.js (1)

52-64: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use attributeFilter: ['class'] to reduce unnecessary observer invocations

The observer currently fires for all attribute changes and filters in the callback. The editor element has many attributes that change during editing (data-type, style, etc.). Using attributeFilter prevents firing for non-class changes entirely, eliminating unnecessary mutation record creation and callback overhead — directly aligned with this PR's Safari performance objective.

⚡ Proposed fix

Apply at both observe() call sites (lines 37 and 64):

-		state.mutationObserver.observe( node, { attributes: true } )
+		state.mutationObserver.observe( node, { attributes: true, attributeFilter: [ 'class' ] } )
-		state.mutationObserver.observe( node, { attributes: true } )
+		state.mutationObserver.observe( node, { attributes: true, attributeFilter: [ 'class' ] } )

With attributeFilter in place, the item.attributeName === 'class' check in the MutationObserver callback (line 54) becomes redundant and can be simplified.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/on-class-change.js` around lines 52 - 64, Configure both
MutationObserver.observe call sites in the class-change handling logic to use {
attributes: true, attributeFilter: ['class'] }, preventing callbacks for
unrelated attribute updates. Update the MutationObserver callback around
scheduleCallbacks to remove the now-redundant item.attributeName === 'class'
check while preserving class-change detection and callback scheduling.
src/block-components/image/editor.scss (1)

87-97: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Container query approach looks solid.

The .stk-img-resizer@container.stk-resizer-tooltip chain is correct: the relevant code in src/components/resizer-tooltip/index.js confirms .stk-resizer-tooltip is a descendant of .stk-img-resizer. container-type: inline-size is safe here since the resizable box has explicit inline dimensions.

One minor note: the 139px threshold is a magic number. Consider adding a brief comment explaining why this specific value was chosen (e.g., "Below this width the tooltip overflows the resizer bounds").

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/block-components/image/editor.scss` around lines 87 - 97, Add a brief
comment next to the 139px container-query threshold explaining that widths below
it cause the resizer tooltip to overflow the resizer bounds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/util/on-class-change.js`:
- Around line 30-37: Wrap the callback loop in the class-change observer handler
with a try-finally block so state.mutationObserver.observe(node, { attributes:
true }) always executes even when a callback throws. Preserve callback error
propagation while guaranteeing reconnection after iterating state.callbacks.

---

Nitpick comments:
In `@src/block-components/image/editor.scss`:
- Around line 87-97: Add a brief comment next to the 139px container-query
threshold explaining that widths below it cause the resizer tooltip to overflow
the resizer bounds.

In `@src/util/on-class-change.js`:
- Around line 52-64: Configure both MutationObserver.observe call sites in the
class-change handling logic to use { attributes: true, attributeFilter:
['class'] }, preventing callbacks for unrelated attribute updates. Update the
MutationObserver callback around scheduleCallbacks to remove the now-redundant
item.attributeName === 'class' check while preserving class-change detection and
callback scheduling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3266dff-1e33-402c-948b-db3a4d9f37d2

📥 Commits

Reviewing files that changed from the base of the PR and between e467b0d and 089a88f.

📒 Files selected for processing (10)
  • .stylelintrc.js
  • src/block-components/image/editor.scss
  • src/block-components/image/image.js
  • src/plugins/editor-device-preview-class/index.js
  • src/plugins/global-settings/color-schemes/editor-loader.js
  • src/plugins/global-settings/utils/block-layout-utils.js
  • src/plugins/global-settings/utils/use-block-layout-editor-loader.js
  • src/plugins/theme-block-style-inheritance/index.js
  • src/util/index.js
  • src/util/on-class-change.js
💤 Files with no reviewable changes (2)
  • src/plugins/global-settings/utils/block-layout-utils.js
  • src/block-components/image/image.js

Comment on lines +30 to +37
state.mutationObserver.disconnect()

for ( const callback of state.callbacks ) {
callback()
}

state.lastClassString = node.classList.toString()
state.mutationObserver.observe( node, { attributes: true } )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wrap callback execution in try-finally to prevent permanent observer disconnection

If a callback throws, the observer is never re-connected (line 37 is unreachable), permanently breaking class-change detection for all subscribers on that node. Since multiple plugins share the same editorEl observer, one failing callback silences every consumer.

🔒 Proposed fix
 		state.mutationObserver.disconnect()

-		for ( const callback of state.callbacks ) {
-			callback()
-		}
+		try {
+			for ( const callback of state.callbacks ) {
+				callback()
+			}
+		} finally {
+			state.lastClassString = node.classList.toString()
+			state.mutationObserver.observe( node, { attributes: true } )
+		}

-		state.lastClassString = node.classList.toString()
-		state.mutationObserver.observe( node, { attributes: true } )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
state.mutationObserver.disconnect()
for ( const callback of state.callbacks ) {
callback()
}
state.lastClassString = node.classList.toString()
state.mutationObserver.observe( node, { attributes: true } )
state.mutationObserver.disconnect()
try {
for ( const callback of state.callbacks ) {
callback()
}
} finally {
state.lastClassString = node.classList.toString()
state.mutationObserver.observe( node, { attributes: true } )
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/util/on-class-change.js` around lines 30 - 37, Wrap the callback loop in
the class-change observer handler with a try-finally block so
state.mutationObserver.observe(node, { attributes: true }) always executes even
when a callback throws. Preserve callback error propagation while guaranteeing
reconnection after iterating state.callbacks.

github-actions Bot added a commit that referenced this pull request Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/plugins/editor-block-css/index.js (1)

73-83: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Hoist the constructable-support check out of the per-key loop.

shouldUseConstructableStyleSheets( editorDom ) is evaluated for every key in keysToUpdate (Line 74) and again at Line 81. Each call runs getEditorCanvasDocument(), which does two document.querySelector lookups. On pages with many Stackable blocks (the exact scenario this PR targets) this is O(N) DOM queries per animation frame. Compute it once per frame.

♻️ Proposed refactor
 		const keysToUpdate = [ ...pendingKeys ]
 		pendingKeys.clear()
 
+		const useConstructable = shouldUseConstructableStyleSheets( editorDom )
 		keysToUpdate.forEach( key => {
-			if ( shouldUseConstructableStyleSheets( editorDom ) ) {
+			if ( useConstructable ) {
 				replaceBlockSheetCss( key, blockStyles[ key ], editorDom )
 			} else {
 				syncBlockStyleSheet( key, blockStyles[ key ], editorDom )
 			}
 		} )
 
-		if ( shouldUseConstructableStyleSheets( editorDom ) && ( keysToUpdate.length || hadRemovals ) ) {
+		if ( useConstructable && ( keysToUpdate.length || hadRemovals ) ) {
 			adoptBlockStyleSheets( editorDom )
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/editor-block-css/index.js` around lines 73 - 83, Compute
shouldUseConstructableStyleSheets(editorDom) once before iterating over
keysToUpdate, store the result, and reuse it for both the per-key
replaceBlockSheetCss/syncBlockStyleSheet branch and the final
adoptBlockStyleSheets condition. This removes repeated getEditorCanvasDocument()
DOM lookups while preserving the existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/editor-block-css/block-style-sheets.js`:
- Around line 206-220: Update removeBlockStyleSheet and the stale-key cleanup in
readoptAllBlockStyleSheets to remove deleted constructable stylesheets from
doc.adoptedStyleSheets before deleting their documentStyleSheets entries or
calling mergeAdoptedStyleSheets. Preserve foreign sheets, then rebuild the
adopted sheet list so removed block sheets are not reclassified as foreign.

---

Nitpick comments:
In `@src/plugins/editor-block-css/index.js`:
- Around line 73-83: Compute shouldUseConstructableStyleSheets(editorDom) once
before iterating over keysToUpdate, store the result, and reuse it for both the
per-key replaceBlockSheetCss/syncBlockStyleSheet branch and the final
adoptBlockStyleSheets condition. This removes repeated getEditorCanvasDocument()
DOM lookups while preserving the existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e76ae1c-39c2-456a-a7df-2e94e41e0852

📥 Commits

Reviewing files that changed from the base of the PR and between 089a88f and 78d84ea.

📒 Files selected for processing (5)
  • src/components/block-css/use-block-style-generator.js
  • src/plugins/editor-block-css/block-style-sheets.js
  • src/plugins/editor-block-css/index.js
  • src/plugins/editor-block-css/store.js
  • src/plugins/index.js
✅ Files skipped from review due to trivial changes (2)
  • src/plugins/index.js
  • src/plugins/editor-block-css/store.js

Comment on lines +206 to +220
export const removeBlockStyleSheet = ( key, editorDom ) => {
const doc = getTargetEditorDocument( editorDom )

getDocumentStyleSheets( doc ).delete( key )

const fallbackEl = getDocumentFallbackStyles( doc ).get( key )
if ( fallbackEl ) {
fallbackEl.remove()
getDocumentFallbackStyles( doc ).delete( key )
}

if ( shouldUseConstructableStyleSheets( editorDom ) && doc?.adoptedStyleSheets ) {
mergeAdoptedStyleSheets( doc )
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate all call sites that mutate adoptedStyleSheets and the sheet lifecycle
rg -nP --type=js -C3 'adoptedStyleSheets|documentStyleSheets|getBlockStyleSheet' src/plugins/editor-block-css/block-style-sheets.js

Repository: gambitph/Stackable

Length of output: 3227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/plugins/editor-block-css/block-style-sheets.js"

echo "=== lines 120-240 ==="
sed -n '120,240p' "$file" | cat -n

echo
echo "=== symbol outline ==="
ast-grep outline "$file" --view expanded

Repository: gambitph/Stackable

Length of output: 4531


Drop deleted sheets from adoptedStyleSheets before merging.
mergeAdoptedStyleSheets() rebuilds the array from documentStyleSheets values, so deleting the map entry first leaves the old sheet in doc.adoptedStyleSheets and it gets treated as foreign. The same applies to the stale-key cleanup in readoptAllBlockStyleSheets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/editor-block-css/block-style-sheets.js` around lines 206 - 220,
Update removeBlockStyleSheet and the stale-key cleanup in
readoptAllBlockStyleSheets to remove deleted constructable stylesheets from
doc.adoptedStyleSheets before deleting their documentStyleSheets entries or
calling mergeAdoptedStyleSheets. Preserve foreign sheets, then rebuild the
adopted sheet list so removed block sheets are not reclassified as foreign.

github-actions Bot added a commit that referenced this pull request Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/plugins/editor-block-css/index.js`:
- Around line 88-96: In the lastChange handling logic, clear the removed block’s
key from pendingKeys before calling removeBlockStyleSheet in the REMOVE branch.
Update the code associated with the pendingKeys collection and
removeBlockStyleSheet so queued SET updates cannot recreate styles for deleted
blocks.

In `@src/plugins/get-editor-dom/index.js`:
- Around line 54-82: Update the editor-DOM resolution flow around
commitEditorDom, tryResolve, and the MutationObserver to disconnect the observer
immediately after the first successful resolution, regardless of whether it
succeeds via the initial check, iframe load, or mutation callback; retain
cleanup safety and the iframe reload behavior as described. Also deduplicate
commitEditorDom by comparing the resolved DOM element with the current store
value and dispatching stackable/editor-dom only when the value has changed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0fa948a2-bb33-4546-b8d9-84731b970317

📥 Commits

Reviewing files that changed from the base of the PR and between 78d84ea and 8820465.

📒 Files selected for processing (5)
  • src/components/block-css/use-block-style-generator.js
  • src/higher-order/with-block-wrapper/index.js
  • src/plugins/editor-block-css/index.js
  • src/plugins/editor-device-preview-class/index.js
  • src/plugins/get-editor-dom/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/block-css/use-block-style-generator.js

Comment on lines +88 to +96
if ( lastChange.type === 'REMOVE' ) {
const editorDom = select( 'stackable/editor-dom' ).getEditorDom()
removeBlockStyleSheet( lastChange.key, editorDom )
return
}

if ( lastChange.type === 'SET' ) {
pendingKeys.add( lastChange.key )
scheduleStyleUpdates()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP -C5 'export const replaceBlockSheetCss' src/plugins/editor-block-css/block-style-sheets.js

Repository: gambitph/Stackable

Length of output: 507


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== index.js outline ==\n'
ast-grep outline src/plugins/editor-block-css/index.js --view expanded || true

printf '\n== block-style-sheets.js outline ==\n'
ast-grep outline src/plugins/editor-block-css/block-style-sheets.js --view expanded || true

printf '\n== pendingKeys usages ==\n'
rg -n "pendingKeys|replaceSheetCss|syncBlockStyleSheet|removeBlockStyleSheet" src/plugins/editor-block-css

printf '\n== relevant slices ==\n'
sed -n '1,220p' src/plugins/editor-block-css/index.js
printf '\n--- block-style-sheets.js ---\n'
sed -n '1,260p' src/plugins/editor-block-css/block-style-sheets.js

Repository: gambitph/Stackable

Length of output: 12843


Clear pendingKeys on REMOVE.
A queued SET can leave its key behind after the block is removed, so the next frame recreates an empty stylesheet/style tag for a block that’s already gone. Drop the key before removeBlockStyleSheet(...).

Proposed fix
 		if ( lastChange.type === 'REMOVE' ) {
 			const editorDom = select( 'stackable/editor-dom' ).getEditorDom()
+			pendingKeys.delete( lastChange.key )
 			removeBlockStyleSheet( lastChange.key, editorDom )
 			return
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ( lastChange.type === 'REMOVE' ) {
const editorDom = select( 'stackable/editor-dom' ).getEditorDom()
removeBlockStyleSheet( lastChange.key, editorDom )
return
}
if ( lastChange.type === 'SET' ) {
pendingKeys.add( lastChange.key )
scheduleStyleUpdates()
if ( lastChange.type === 'REMOVE' ) {
const editorDom = select( 'stackable/editor-dom' ).getEditorDom()
pendingKeys.delete( lastChange.key )
removeBlockStyleSheet( lastChange.key, editorDom )
return
}
if ( lastChange.type === 'SET' ) {
pendingKeys.add( lastChange.key )
scheduleStyleUpdates()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/editor-block-css/index.js` around lines 88 - 96, In the
lastChange handling logic, clear the removed block’s key from pendingKeys before
calling removeBlockStyleSheet in the REMOVE branch. Update the code associated
with the pendingKeys collection and removeBlockStyleSheet so queued SET updates
cannot recreate styles for deleted blocks.

Comment on lines +54 to +82
const commitEditorDom = dom => {
if ( cancelledRef.current || ! dom ) {
return false
}

dispatch( 'stackable/editor-dom' ).updateEditorDom( dom )
return true
}

if ( deviceType === 'Desktop' ) {
// We have to wait for the editor area to load (e.g. FSE iframe takes a long time to load)
interval.current = setInterval( () => {
const editorEl = document.querySelector( `.editor-styles-wrapper, iframe[name="editor-canvas"]` )
if ( editorEl ) {
clearInterval( interval.current )
clearTimeout( timeout.current )
setIframeForceUpdate( v => v + 1 )
// There's a chance that the editor STILL isn't ready, try again.
setTimeout( () => {
setIframeForceUpdate( v => v + 1 )
}, 200 )
}
}, 200 )
} else { // Tablet or Mobile.
const iframeEl = document.querySelector( `iframe[name="editor-canvas"]` )
if ( iframeEl ) {
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
clearInterval( interval.current )
setIframeForceUpdate( v => v + 1 )
} else {
clearTimeout( timeout.current )
timeout.current = setTimeout( () => {
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
setIframeForceUpdate( v => v + 1 )
}
}, 200 )
iframeEl.onload = () => {
clearTimeout( timeout.current )
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
setIframeForceUpdate( v => v + 1 )
} else {
setTimeout( () => {
setIframeForceUpdate( v => v + 1 )
}, 200 )
}
}
}
if ( commitEditorDom( resolveEditorDom() ) ) {
return () => {
cancelledRef.current = true
}
}

return () => {
clearInterval( interval.current )
clearTimeout( timeout.current )
const tryResolve = () => {
return commitEditorDom( resolveEditorDom() )
}
}, [ deviceType, editorMode, editedSitePostId, currentPage ] )

useMemo( () => {
const iframeEl = document.querySelector( `iframe[name="editor-canvas"]` )
if ( iframeEl ) {
setEditorDom( iframeEl.contentDocument.body )
} else {
setEditorDom( document.querySelector( `.editor-styles-wrapper` ) )
const iframeEl = document.querySelector( 'iframe[name="editor-canvas"]' )
const onIframeLoad = () => {
tryResolve()
}
iframeEl?.addEventListener( 'load', onIframeLoad )

const observer = new MutationObserver( () => {
tryResolve()
} )
observer.observe( document.body, { childList: true, subtree: true } )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

MutationObserver remains active after successful resolution, causing per-mutation overhead

The MutationObserver on document.body with subtree: true is never disconnected after tryResolve() succeeds. Every subsequent DOM mutation triggers resolveEditorDom() (2–3 querySelector calls) and a store dispatch—even though the resolved DOM hasn't changed. In a Gutenberg editor with frequent DOM mutations (typing, selection, hover), this creates continuous unnecessary work that counteracts this PR's Safari performance goals.

Additionally, commitEditorDom dispatches unconditionally, even when the DOM element is identical to the current store value. The reducer always produces a new state object, so every dispatch triggers subscriber notifications.

Suggested fixes: disconnect the observer after first successful resolution (regardless of which callback triggered it), and skip the dispatch when the DOM hasn't changed.

⚡ Proposed fix: disconnect observer on success + deduplicate dispatch
 import { dispatch, useSelect } from '`@wordpress/data`'
+import { dispatch, select, useSelect } from '`@wordpress/data`'
 	const commitEditorDom = dom => {
 		if ( cancelledRef.current || ! dom ) {
 			return false
 		}
+		if ( select( 'stackable/editor-dom' ).getEditorDom() === dom ) {
+			return true
+		}
 		dispatch( 'stackable/editor-dom' ).updateEditorDom( dom )
 		return true
 	}
+	let observer
+
 	const tryResolve = () => {
-		return commitEditorDom( resolveEditorDom() )
+		const success = commitEditorDom( resolveEditorDom() )
+		if ( success && observer ) {
+			observer.disconnect()
+		}
+		return success
 	}
-	const observer = new MutationObserver( () => {
+	observer = new MutationObserver( () => {
 		tryResolve()
 	} )
 	observer.observe( document.body, { childList: true, subtree: true } )

The cleanup function's observer.disconnect() remains safe—calling disconnect on an already-disconnected observer is a no-op. The iframe load listener stays active to handle iframe reloads within the same session, and the effect dependencies (deviceType, editorMode, etc.) ensure the observer is re-created when the editor context changes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const commitEditorDom = dom => {
if ( cancelledRef.current || ! dom ) {
return false
}
dispatch( 'stackable/editor-dom' ).updateEditorDom( dom )
return true
}
if ( deviceType === 'Desktop' ) {
// We have to wait for the editor area to load (e.g. FSE iframe takes a long time to load)
interval.current = setInterval( () => {
const editorEl = document.querySelector( `.editor-styles-wrapper, iframe[name="editor-canvas"]` )
if ( editorEl ) {
clearInterval( interval.current )
clearTimeout( timeout.current )
setIframeForceUpdate( v => v + 1 )
// There's a chance that the editor STILL isn't ready, try again.
setTimeout( () => {
setIframeForceUpdate( v => v + 1 )
}, 200 )
}
}, 200 )
} else { // Tablet or Mobile.
const iframeEl = document.querySelector( `iframe[name="editor-canvas"]` )
if ( iframeEl ) {
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
clearInterval( interval.current )
setIframeForceUpdate( v => v + 1 )
} else {
clearTimeout( timeout.current )
timeout.current = setTimeout( () => {
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
setIframeForceUpdate( v => v + 1 )
}
}, 200 )
iframeEl.onload = () => {
clearTimeout( timeout.current )
const body = iframeEl.contentDocument.body
if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) {
setIframeForceUpdate( v => v + 1 )
} else {
setTimeout( () => {
setIframeForceUpdate( v => v + 1 )
}, 200 )
}
}
}
if ( commitEditorDom( resolveEditorDom() ) ) {
return () => {
cancelledRef.current = true
}
}
return () => {
clearInterval( interval.current )
clearTimeout( timeout.current )
const tryResolve = () => {
return commitEditorDom( resolveEditorDom() )
}
}, [ deviceType, editorMode, editedSitePostId, currentPage ] )
useMemo( () => {
const iframeEl = document.querySelector( `iframe[name="editor-canvas"]` )
if ( iframeEl ) {
setEditorDom( iframeEl.contentDocument.body )
} else {
setEditorDom( document.querySelector( `.editor-styles-wrapper` ) )
const iframeEl = document.querySelector( 'iframe[name="editor-canvas"]' )
const onIframeLoad = () => {
tryResolve()
}
iframeEl?.addEventListener( 'load', onIframeLoad )
const observer = new MutationObserver( () => {
tryResolve()
} )
observer.observe( document.body, { childList: true, subtree: true } )
const commitEditorDom = dom => {
if ( cancelledRef.current || ! dom ) {
return false
}
if ( select( 'stackable/editor-dom' ).getEditorDom() === dom ) {
return true
}
dispatch( 'stackable/editor-dom' ).updateEditorDom( dom )
return true
}
let observer
const tryResolve = () => {
const success = commitEditorDom( resolveEditorDom() )
if ( success && observer ) {
observer.disconnect()
}
return success
}
if ( commitEditorDom( resolveEditorDom() ) ) {
return () => {
cancelledRef.current = true
}
}
const iframeEl = document.querySelector( 'iframe[name="editor-canvas"]' )
const onIframeLoad = () => {
tryResolve()
}
iframeEl?.addEventListener( 'load', onIframeLoad )
observer = new MutationObserver( () => {
tryResolve()
} )
observer.observe( document.body, { childList: true, subtree: true } )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/get-editor-dom/index.js` around lines 54 - 82, Update the
editor-DOM resolution flow around commitEditorDom, tryResolve, and the
MutationObserver to disconnect the observer immediately after the first
successful resolution, regardless of whether it succeeds via the initial check,
iframe load, or mutation callback; retain cleanup safety and the iframe reload
behavior as described. Also deduplicate commitEditorDom by comparing the
resolved DOM element with the current store value and dispatching
stackable/editor-dom only when the value has changed.

github-actions Bot added a commit that referenced this pull request Jul 11, 2026
@bfintal bfintal changed the title fix(editor): improve Safari responsive preview performance fix(editor): Safari preview performance, unified block CSS, and editor scale Jul 11, 2026
github-actions Bot added a commit that referenced this pull request Jul 11, 2026
Resolve conflict in use-block-style-generator.js by combining PR #3716's
setAttributes fix with styleFingerprint-based save CSS generation.

Co-authored-by: Cursor <cursoragent@cursor.com>
github-actions Bot added a commit that referenced this pull request Jul 11, 2026
github-actions Bot added a commit that referenced this pull request Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Blocksy] Switching responsive preview in Safari freezes browser and shoots up to 100% CPU usage

1 participant