Skip to content

Conversation

@iftakharul-islam
Copy link
Member

@iftakharul-islam iftakharul-islam commented Jan 9, 2026

This pull request updates the build artifacts for the WordPress weDocs block, specifically the compiled JavaScript and asset manifest files. The changes are the result of a new build and do not include any direct source code modifications.

  • Build artifacts update:
    • assets/build/block.js: Rebuilt the block JavaScript bundle, reflecting recent changes in the source code or dependencies.
    • assets/build/block.asset.php: Updated the asset manifest, likely to reflect a new version hash and/or dependency changes due to the new build.

Summary by CodeRabbit

  • New Features

    • Added many new Gutenberg blocks (AI Summary with generate/load/save, Contributors, Doc Actions, Helpful Feedback, Contact Modal, Reading Progress, Table of Contents, Last Updated, Social Share, Font Size Switcher, enhanced Search & Docs Grid) and shared editor styling/controls for richer customization.
    • Frontend behaviors for new blocks (collapsible summaries, modals, voting, progress bar, TOC, social/share, font-size switching).
  • Chores

    • Updated build tooling versions and cleaned up generated build assets.
    • Added REST endpoints to manage AI-generated summaries.

✏️ Tip: You can customize this high-level summary in your review settings.

Introduces a new 'Helpful Feedback' block with customizable voting buttons, styling options, and server-side rendering for docs articles. Updates Contributors block to restrict rendering to docs post type and registers the new block in the main index.
Replaced all instances of the 'block-responsive' text domain with 'wedocs' in ColorsControls.js for consistency with the plugin's localization. Also fixed a variable reference in ListItems.js to use attributes.containerBackgroundColor directly.
Introduces a new AI-Powered Summary block with collapsible functionality, custom icons, and extensive style controls. Adds a new Doc Actions block (initial files). Refactors Contributors block to use shared style panels and improves InspectorControls organization. Updates block.json files for better support of color, spacing, and typography options. Adds a common controls utility for Inspector panels.
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

Walkthrough

This PR removes built assets under assets/build, updates tooling and localization, deletes legacy built bundles, and adds many new Gutenberg blocks and shared editor controls (src/blocks/*), plus new REST endpoints for AI summaries in includes/API/API.php.

Changes

Cohort / File(s) Summary
Build assets cleanup
\.gitignore, assets/build/*
Adds assets/build/ to .gitignore and deletes built assets and manifests (assets/build/block.js, store.js, *.asset.php, frontend.css, print.css, style-block.css, etc.). Review impact: asset enqueueing and runtime reliance on removed manifests.
Includes / localization
includes/Assets.php, includes/Frontend.php
Adds assetsUrl to localized script data, isPro flag to admin locale; comments out block style registration and template loader filter. Verify localized data consumers.
Dev tooling / docs
package.json, readme.md
Updates @wordpress/scripts devDependency and adds a commented Mozart config snippet in README. Check build/tooling compatibility.
AI REST endpoints
includes/API/API.php
Adds endpoints to GET/POST/DELETE /docs/{id}/ai-summary and POST /docs/{id}/ai-summary/generate with generation, persistence, and permissions.
New blocks — AISummary
src/blocks/AISummary/*
Adds AI Summary block (block.json, edit/save, view, styles, editor styles) with REST integration.
New blocks — AdvanceContributors / Contributors
src/blocks/AdvanceContributors/*, src/blocks/Contributors/*
Adds Contributor blocks with complex editor UI, server render callbacks, styles, and responsive controls.
New blocks — DocActions
src/blocks/DocActions/*
Adds documentation action buttons (copy markdown, open ChatGPT/Claude) with editor, save, view, styles, and frontend behaviors.
New blocks — HelpfulFeedback / HelpfulModal
src/blocks/HelpfulFeedback/*, src/blocks/HelpfulModal/*
Adds voting widget and contact modal blocks with editor, SSR renderers, front-end view scripts, and styles.
New blocks — LastUpdated / ReadingProgress / FontSizeSwitcher
src/blocks/LastUpdated/*, src/blocks/ReadingProgress/*, src/blocks/FontSizeSwitcher/*
Adds utility blocks (last-updated display, reading progress bar, font-size switcher) with editor, save, view, and styles.
New blocks — DocGrid / Search / SocialShare / TableOfContents
src/blocks/DocsGrid/*, src/blocks/Search/*, src/blocks/SocialShare/*, src/blocks/TableOfContents/*
DocsGrid: expanded supports/attributes and render refactor to named functions; Search: metadata-driven rewrite of edit/save; SocialShare & TOC: new manifests, editor/save/view, SSR renderers, front-end behaviors and styles.
Common controls / UI primitives
src/blocks/commonControls/*, src/blocks/TableOfContents/controls/*
Adds many reusable control components (ColorsControls, AlignmentControls, BackgroundImageControls, BorderAndShadowControls, DimensionsControls, DisplayControls, TypographyControls, CommonControls, ListItems, Inspector, etc.).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • weDevsOfficial/wedocs-pro#168 — TOC implementation: PR adds a full Table of Contents block (editor, render, view) matching the issue's scope.

Possibly related PRs

Suggested labels

Needs Review

Suggested reviewers

  • arifulhoque7

Poem

🐰 I hopped through src and cleared the floor,
New blocks sprung up, and old bundles no more.
TOC, modals, shares, controls all in bloom,
A rabbit’s small cheer across the code room.
Merge when ready — I’ll nibble a carrot in tune.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Feat/more blocks" is vague and does not clearly summarize the main changes. It uses a generic term that could apply to many types of changes and doesn't convey meaningful information about what is actually being added or modified. Provide a more descriptive title that clearly explains the primary change, such as 'Add AI Summary, Last Updated, Reading Progress, and other new blocks' or 'Implement new Gutenberg blocks for docs plugin'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/blocks/DocsGrid/edit.js (2)

534-536: Spinner component is not imported.

The Spinner component is used on line 535 but is not imported from @wordpress/components. This will cause a runtime error when loading is true.

Proposed fix

Add Spinner to the imports from @wordpress/components:

 import {
     PanelBody,
     SelectControl,
     ToggleControl,
     PanelRow,
-    FormTokenField
+    FormTokenField,
+    Spinner
 } from '@wordpress/components';

157-162: Use camelCase for SVG attributes in JSX.

React/JSX requires camelCase for DOM attributes. stroke-width and stroke-linecap should be strokeWidth and strokeLinecap.

Proposed fix
-                        <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
+                        <svg fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
                             <path
-                                stroke-linecap="round"
-                                stroke-linejoin="round"
+                                strokeLinecap="round"
+                                strokeLinejoin="round"
                                 d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
                             ></path>
                         </svg>
package.json (1)

43-64: Remove trailing whitespace / formatting drift to avoid lint-pkg-json failures.
Line 43 looks like a whitespace-only change; wp-scripts lint-pkg-json can fail on formatting/trailing spaces depending on configuration.

Proposed fix
   "devDependencies": {
     "@wordpress/scripts": "^30.15.0",
     "@wordpress/components": "^28.10.0",
     "@wordpress/icons": "^10.10.0",
     "@wordpress/block-editor": "^14.5.0",
     "archiver": "^5.3.1",
     "autoprefixer": "^10.4.13",
     "css-loader": "^6.7.3",
     "postcss": "^8.4.21",
     "replace-in-file": "^6.3.5",
     "style-loader": "^3.3.1",
     "tailwindcss": "^3.2.4",
     "tailwindcss-scoped-preflight": "^3.4.12"
   },
-  
+
   "dependencies": {
     "@dnd-kit/core": "^6.0.7",
     "@dnd-kit/sortable": "^7.0.2",
     "@dnd-kit/utilities": "^3.2.1",
     "@headlessui/react": "^1.7.7",
     "@heroicons/react": "^2.0.14",
     "@tailwindcss/forms": "^0.5.3",
     "@wordpress/api-fetch": "^6.21.0",
     "@wordpress/core-data": "^6.0.0",
     "@wordpress/data": "^8.3.0",
     "@wordpress/element": "^5.0.0",
     "@wordpress/i18n": "^4.23.0",
     "daisyui": "^2.49.0",
     "he": "^1.2.0",
     "less-loader": "^11.1.0",
     "react-responsive-carousel": "^3.2.23",
     "react-router-dom": "^6.6.2",
     "sweetalert2": "^11.7.1"
   }
 }
🤖 Fix all issues with AI agents
In @includes/Assets.php:
- Around line 85-89: The code attempts to wp_enqueue_style('wedocs-block-style')
but the wp_register_style call for 'wedocs-block-style' is commented out,
causing enqueue errors; either restore/uncomment the wp_register_style call that
registers 'wedocs-block-style' (ensure correct handle, URL $assets_url .
'/build/style-block.css' and dependency/version args match $block_dependencies)
or remove the wp_enqueue_style('wedocs-block-style') calls so you don't enqueue
an unregistered handle; update the occurrences where 'wedocs-block-style' is
enqueued to match the chosen fix.

In @readme.md:
- Around line 26-39: Remove the commented Mozart configuration block that
references "WPMUDEV\\PluginTest\\Vendor\\", "WPMUDEV_PluginTest_", and the
unrelated package "google/apiclient" from readme.md; replace it only if you
intend to document real configuration, in which case update the namespace and
prefix to the actual plugin values (e.g., "WeDevs\\WeDocs\\" per composer.json)
and list only real dependencies, or simply delete the entire commented block to
avoid misleading/template content.

In @src/blocks/AdvanceContributors/render.php:
- Line 503: The top-level call to
render_wedocs_advance_contributors_block($attributes, $content, $block) is
invalid because $attributes, $content and $block are undefined in this scope;
remove that invocation so the file only defines the function and lets WordPress
invoke it via the block.json "render": "file:./render.php" callback; keep the
render_wedocs_advance_contributors_block function definition intact and ensure
no other top-level calls pass undefined variables.
- Around line 116-289: The nested declaration of generate_responsive_css inside
render_wedocs_advance_contributors_block will cause a "Cannot redeclare
generate_responsive_css()" fatal error on multiple renders; fix by either moving
generate_responsive_css out of render_wedocs_advance_contributors_block into
file scope (so it’s declared once) or wrap its declaration in if
(!function_exists('generate_responsive_css')) { ... } or convert it to a local
closure ($generate_responsive_css = function(...) { ... }) and update all calls
inside render_wedocs_advance_contributors_block to use the closure variable.

In @src/blocks/commonControls/ColorsControls.js:
- Around line 17-20: ColorsControls currently reads device and colorsControls
incorrectly: ensure you get device from props/attributes (e.g., read device from
attributes or add a device prop to the component) and default colorsControls to
an object before indexing; update the ColorsControls signature/usage so device
is defined (or default to a known key like "desktop") and compute currentColors
as (attributes.colorsControls || {})[device || 'desktop'] to avoid undefined
errors and preserve the original props contract with setAttributes remaining
unchanged.

In @src/blocks/Contributors/render.php:
- Around line 124-155: The 'all' case treats $author_id inconsistently as an
array and scalar; normalize it to an array first by retrieving the raw value
from get_post_field('wedocs_contributors', $post->ID), ensuring if it's
empty/null you use an empty array, and if it's a scalar convert it to a
single-element array; then iterate that normalized $authors array to call
get_userdata() and populate $contributors; remove the redundant
get_userdata($author_id) scalar check, and ensure $contributor_ids is
initialized as an array (e.g., from the normalized $authors) before
comparing/adding revision authors via wp_get_post_revisions to avoid warnings
and duplicate entries.

In @src/blocks/HelpfulFeedback/render.php:
- Around line 37-52: Cookie data in $_COOKIE['wedocs_response'] is used directly
to build $previous via explode which is unsafe; validate that the cookie exists
and is a string, sanitize it (e.g. run sanitize_text_field or similar), split it
into parts, then normalize each part to integers or expected safe values (use
array_map('intval') and array_filter to remove non-numeric/zero entries) before
assigning to $previous and using in_array($post_id, $previous); also ensure
$voted_option is initialized and only set from trusted sources ($user_vote after
sanitizing get_post_meta output) so you don't display based on unsanitized
cookie contents.
- Line 206: Replace the hardcoded enqueue in
src/blocks/HelpfulFeedback/render.php (the wp_enqueue_script call that
references '/assets/js/helpful-feedback.js') by removing that manual enqueue
entirely and instead add a viewScript entry in the block’s block.json so
WordPress will register the compiled src/blocks/HelpfulFeedback/view.js as the
front-end view script; update block.json’s viewScript to point to the built
asset name (the same name used by your build/webpack) and rebuild assets so the
viewScript is registered automatically, matching how other blocks in the plugin
are structured.

In @src/blocks/HelpfulFeedback/view.js:
- Around line 118-163: The showThankYou function inserts unescaped
user-controlled values (thankYouMessage, data.yes_votes, data.no_votes) into
HTML via template literals, creating an XSS vector; fix by escaping or treating
those values as plain text instead of raw HTML: stop interpolating
thankYouMessage and the vote counts into the thankYouContent string, build the
thank-you DOM using safe methods (e.g., create elements and set textContent or
use jQuery .text() for those fields) and only insert sanitized/static HTML for
icons/structure; ensure any $container.append(now-built-node) or similar uses
the safe node rather than a string containing user data.

In @src/blocks/HelpfulModal/block.json:
- Around line 10-51: You have two duplicate "supports" objects in block.json;
remove the redundant one and merge their properties into a single "supports"
declaration so nothing is overwritten: ensure the final supports object includes
anchor: true, align: ["wide","full"], spacing.blockGap: true plus all color,
typography, spacing, and __experimentalBorder entries from both blocks so no
settings are lost.

In @src/blocks/HelpfulModal/save.js:
- Around line 1-3: The file is missing the required export for a save function
used by server-side rendered blocks; add and export a save function named save
that returns null (i.e., export function save() { return null; }) so the block
registration has the expected save implementation and won't fail at runtime.

In @src/blocks/LastUpdated/render.php:
- Around line 30-50: The function wedocs_format_last_updated_date is being
(re)declared inside the block render callback causing a fatal "Cannot redeclare"
error when the block renders multiple times; fix it by moving the
wedocs_format_last_updated_date definition out of the render callback into a
top-level/shared helper loaded once (or wrap the definition with if
(!function_exists('wedocs_format_last_updated_date')) { ... } so it only defines
once), and update the render code to call the now-top-level function instead of
declaring it inline.

In @src/blocks/Search/edit.js:
- Around line 51-56: searchWrapperStyles uses an invalid CSS value 'relative'
for flexDirection; update the ternary in searchWrapperStyles (and any matching
logic as in save.js) so that when buttonPosition !== 'outside' it returns a
valid flexDirection (e.g., 'column') instead of 'relative', keeping the existing
gap logic intact.

In @src/blocks/Search/save.js:
- Around line 36-41: The style object searchWrapperStyles sets flexDirection to
an invalid value 'relative' in src/blocks/Search/save.js; change the conditional
to a valid flexDirection (e.g., buttonPosition === 'outside' ? 'row' : 'column')
and, if the intent was to use CSS positioning, add a separate position:
'relative' property instead of misusing flexDirection; update the const
searchWrapperStyles accordingly.
🟠 Major comments (34)
src/blocks/AdvanceContributors/render.php-337-348 (1)

337-348: Display control hide logic appears inverted.

The current logic adds hide classes if any display control exists for a device, rather than checking for a specific "hide" value. This will incorrectly hide the block when any display property (like display: flex) is set.

🐛 Suggested fix
 // Handle display controls for hiding on devices
 $display_classes = [];
-if (isset($display_controls['desktop']) && $display_controls['desktop']) {
+if (isset($display_controls['desktop']['display']) && $display_controls['desktop']['display'] === 'none') {
     $display_classes[] = 'wedocs-hide-desktop';
 }
-if (isset($display_controls['tablet']) && $display_controls['tablet']) {
+if (isset($display_controls['tablet']['display']) && $display_controls['tablet']['display'] === 'none') {
     $display_classes[] = 'wedocs-hide-tablet';
 }
-if (isset($display_controls['mobile']) && $display_controls['mobile']) {
+if (isset($display_controls['mobile']['display']) && $display_controls['mobile']['display'] === 'none') {
     $display_classes[] = 'wedocs-hide-mobile';
 }
src/blocks/Search/save.js-49-63 (1)

49-63: Hover styles via CSS custom properties won't work without accompanying CSS.

The --hover-bg and --hover-color custom properties are set but there's no CSS rule that uses them for :hover states. The button hover effect won't work on the frontend.

Consider adding a stylesheet rule or using wp_add_inline_style in PHP to handle the hover states:

.wedocs-search-button:hover {
    background-color: var(--hover-bg) !important;
    color: var(--hover-color) !important;
}
src/blocks/Search/save.js-87-92 (1)

87-92: Potential SSR hydration mismatch with window.location.origin.

Using window in the save function can cause hydration mismatches since the server doesn't have access to window. For a search form, consider using an empty string or a relative path, as the form will submit to the current page by default.

Proposed fix
                     <form
                         className="wedocs-search-form"
                         role="search"
                         method="get"
-                        action={typeof window !== 'undefined' ? window.location.origin : ''}
+                        action=""
                         style={searchWrapperStyles}
                     >
src/blocks/Search/edit.js-14-18 (1)

14-18: Update WordPress component imports to use stable exports.

BoxControl was stabilized in WordPress 6.8 (April 2025) and should be imported directly instead of using the __experimentalBoxControl alias. UnitControl remains problematic—it's flagged for deprecation warnings in WordPress 6.8 and requires review to address these warnings or find a replacement.

Update the import statement to use BoxControl from stable exports and evaluate UnitControl usage for deprecation compliance.

src/blocks/HelpfulModal/edit.js-25-29 (1)

25-29: Migrate BoxControl and BorderControl to stable exports.

Lines 27–28 import __experimentalBoxControl and __experimentalBorderControl, which were stabilized in WordPress 6.7/6.8. Update to:

BoxControl,
BorderControl,

ToolsPanel, ToolsPanelItem, and Divider remain experimental with no stable replacements; document or track for future updates once they stabilize.

src/blocks/HelpfulFeedback/edit.js.backup-1-513 (1)

1-513: Remove backup file from repository.

The file src/blocks/HelpfulFeedback/edit.js.backup is tracked in git and contains an older version (different from the current edit.js). Backup files should not be committed to version control—use Git history to track previous versions instead. The active code imports only ./edit, making the .backup file redundant.

src/blocks/HelpfulFeedback/render.php-26-26 (1)

26-26: Sanitize IP address and handle proxy headers.

Direct access to $_SERVER['REMOTE_ADDR'] without sanitization is a security concern. Additionally, this approach doesn't account for proxy headers, which can result in inaccurate IP tracking for users behind proxies or load balancers.

🔒 Proposed fix
-$user_ip = $_SERVER['REMOTE_ADDR'] ?? '';
+// Get the real user IP, accounting for proxies
+$user_ip = '';
+if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+    $ip_list = explode(',', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])));
+    $user_ip = trim($ip_list[0]);
+} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
+    $user_ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
+}
+// Validate IP format
+if (!filter_var($user_ip, FILTER_VALIDATE_IP)) {
+    $user_ip = '';
+}
package.json-29-34 (1)

29-34: Verify Node.js compatibility and test build with @wordpress/scripts v30.15.0 before merging.

Upgrading @wordpress/scripts from v25 (Node ≥14) to v30.15.0 (Node ≥18.12.0) introduces breaking changes in CLI flags, webpack/babel configuration, and block.json entry-point detection. Confirm that your CI environment and local development setup have Node.js ≥18.12.0, run a full build and test suite locally to catch any webpack/babel/entry-point regressions, and ensure the lockfile is updated (package-lock.json or equivalent) if the repo uses one. The @wordpress/block-editor v14.5.0 has no peer-dependency conflict with wp-scripts v30, but compatibility depends on the WordPress/Gutenberg version where your blocks run—verify that during QA.

src/blocks/ReadingProgress/index.js-1-10 (1)

1-10: Import editor.scss so editorStyle: file:./index.css is actually generated.

Right now only style.scss is imported; if nothing imports editor.scss, the build may not emit index.css, breaking the block’s declared editorStyle.

Proposed fix
 import { registerBlockType } from '@wordpress/blocks';
 import './style.scss';
+import './editor.scss';
 import Edit from './edit';
 import save from './save';
 import metadata from './block.json';
 
 registerBlockType(metadata.name, {
 	edit: Edit,
 	save,
 });
src/blocks/ReadingProgress/block.json-1-14 (1)

1-14: Fix CSS asset filename in block.json: style-index.css won't be generated by webpack.

Block.json declares style: "file:./style-index.css", but only style.scss is imported in index.js, which generates index.css. The non-standard filename style-index.css doesn't match MiniCSSExtractPlugin's output naming convention ([name].css). Either rename the declared style to "file:./index.css" or create a separate entry point. Also note that editor.scss exists but is never imported; if editor-specific styles are needed, import it in index.js.

src/blocks/ReadingProgress/style.scss-80-83 (1)

80-83: Critical: Global smooth-scroll affects entire site.

Setting scroll-behavior: smooth on the html element applies site-wide, not just to the Reading Progress block. This can:

  • Override existing site styles or user preferences
  • Conflict with accessibility settings (users with vestibular disorders may prefer reduced motion)
  • Create unexpected behavior for other scrolling functionality
♻️ Recommended fix: Remove or scope the smooth scroll behavior

Option 1: Remove the global rule entirely (recommended):

-// Smooth scrolling enhancement
-html {
-	scroll-behavior: smooth;
-}
-

Option 2: If smooth scrolling is needed for the block's functionality, scope it properly or add a prefers-reduced-motion check:

-html {
+html:not([data-user-prefers-reduced-motion]) {
 	scroll-behavior: smooth;
 }
+
+@media (prefers-reduced-motion: reduce) {
+	html {
+		scroll-behavior: auto;
+	}
+}
src/blocks/Contributors/index.js-1-42 (1)

1-42: Remove function wrapper from icon.src — not a supported API pattern. The icon configuration in registerBlockType does not support functions. Per WordPress block icon documentation, valid forms are: Dashicon string, JSX element, component reference, or object { src, foreground?, background? } where src is a JSX element or component—not a function.

Also, ./save.js exists and should be imported and used instead of save: () => null, consistent with other blocks in the codebase.

Refactor
import { registerBlockType } from "@wordpress/blocks";
import Edit from "./edit";
import metadata from "./block.json";
import "./style.scss";
import { Icon } from '@wordpress/components';
import { starFilled } from '@wordpress/icons';
+import Save from "./save";

+const ContributorsProIcon = (
+	<span style={{ position: 'relative', display: 'inline-block' }}>
+		<Icon icon={ starFilled } />
+		<span
+			style={{
+				position: 'absolute',
+				top: '22px',
+				right: '-2px',
+				background: '#4f47e6',
+				color: 'white',
+				fontSize: '9px',
+				padding: '2px 3px',
+				borderRadius: '3px',
+				fontWeight: 'bold',
+				lineHeight: 1
+			}}
+		>
+			PRO
+		</span>
+	</span>
+);

 registerBlockType(metadata.name, {
 	edit: Edit,
-	save: () => null,
+	save: Save,
-    icon: {
-        src:() => (
-            <span style={{ position: 'relative', display: 'inline-block' }}>
-                <Icon icon={ starFilled } />
-                <span
-                    style={{
-                        position: 'absolute',
-                        top: '22px',
-                        right: '-2px',
-                        background: '#4f47e6',
-                        color: 'white',
-                        fontSize: '9px',
-                        padding: '2px 3px',
-                        borderRadius: '3px',
-                        fontWeight: 'bold',
-                        lineHeight: 1
-                    }}
-                >
-                    PRO
-                </span>
-            </span>
-        ),
-    }
+	icon: ContributorsProIcon
 });
src/blocks/AISummary/style.scss-57-76 (1)

57-76: Hardcoded max-height will clip long content.

The collapsible implementation uses max-height: 1000px (line 67) as the open state. Content exceeding this height will be clipped and inaccessible to users. Additionally, the !important declarations on lines 62 and 69 reduce style flexibility.

Consider using JavaScript to calculate actual content height or using a sufficiently large value with a note documenting the limitation.

💡 Alternative approaches

Option 1: Increase max-height with documentation

 		&.open {
 			.ai-summary-content {
-				max-height: 1000px;
+				max-height: 9999px; /* Large enough for most content */
 				opacity: 1;
 				margin-top: 15px !important;
 			}

Option 2: Calculate height dynamically in view.js

In src/blocks/AISummary/view.js, measure and set the actual content height:

const content = block.querySelector('.ai-summary-content');
if (content) {
    const height = content.scrollHeight;
    content.style.maxHeight = height + 'px';
}
src/blocks/AISummary/view.js-12-40 (1)

12-40: State restoration logic may override intended default behavior.

The initialization flow sets the initial state based on isOpenByDefault (lines 13-15), but then immediately restores from localStorage (lines 31-40). This means:

  1. If a user previously closed the block, localStorage will have 'closed'
  2. The block will remain closed even if isOpenByDefault is true
  3. Users can never experience the "open by default" behavior after their first interaction

This may be intentional (user preference takes precedence), but consider whether the isOpenByDefault attribute should have priority or if there should be a way to reset to defaults.

💡 Possible fix: Respect isOpenByDefault when no saved state exists
-		// Set initial state
-		if (isOpenByDefault) {
-			block.classList.add('open');
-		}
-
 		// Toggle on click
 		if (header) {
 			header.addEventListener('click', () => {
 				block.classList.toggle('open');
 
 				// Save state to localStorage
 				const blockId = block.getAttribute('data-block-id');
 				if (blockId) {
 					const isOpen = block.classList.contains('open');
 					localStorage.setItem(`ai-summary-${blockId}`, isOpen ? 'open' : 'closed');
 				}
 			});
 		}
 
 		// Restore state from localStorage
 		const blockId = block.getAttribute('data-block-id');
 		if (blockId) {
 			const savedState = localStorage.getItem(`ai-summary-${blockId}`);
 			if (savedState === 'open') {
 				block.classList.add('open');
 			} else if (savedState === 'closed') {
 				block.classList.remove('open');
+			} else {
+				// No saved state, use default
+				if (isOpenByDefault) {
+					block.classList.add('open');
+				}
 			}
+		} else if (isOpenByDefault) {
+			// No blockId, use default
+			block.classList.add('open');
 		}
 	});
 });
src/blocks/Contributors/render.php-160-169 (1)

160-169: Remove mock contributor fallback from production code.

Injecting a fake "Demo Author" masks real data issues and confuses users. Consider returning an empty container or a "No contributors found" message instead.

Proposed fix
     if (empty($contributors)) {
-        // For demo purposes, add mock contributors if none found
-        $contributors = [
-            (object) [
-                'ID' => 1,
-                'display_name' => 'Demo Author',
-                'user_email' => 'demo@example.com'
-            ]
-        ];
+        // Return empty if no contributors found
+        return '';
     }

Or display a message:

if (empty($contributors)) {
    return '<div class="wedocs-contributors"><p>' . esc_html__('No contributors found.', 'wedocs-plugin') . '</p></div>';
}
src/blocks/DocActions/edit.js-199-209 (1)

199-209: SpacingPanel prop mismatch.

Based on CommonControls.js, SpacingPanel expects padding/margin and onPaddingChange/onMarginChange props, not values/onChange. The current usage won't work correctly.

🐛 Proposed fix for SpacingPanel usage
 <SpacingPanel
   title={__('Padding', 'wedocs-plugin')}
-  values={padding}
-  onChange={(value) => setAttributes({ padding: value })}
+  padding={padding}
+  onPaddingChange={(value) => setAttributes({ padding: value })}
+  showMargin={false}
 />

 <SpacingPanel
   title={__('Margin', 'wedocs-plugin')}
-  values={margin}
-  onChange={(value) => setAttributes({ margin: value })}
+  margin={margin}
+  onMarginChange={(value) => setAttributes({ margin: value })}
+  showPadding={false}
 />
src/blocks/LastUpdated/edit.js-39-50 (1)

39-50: Potential null reference when accessing padding/margin properties.

Same issue as in DocActions - if padding or margin are undefined, accessing properties like .top will throw.

🐛 Proposed fix with defensive defaults
 const blockProps = useBlockProps({
   className: 'wp-block-wedocs-last-updated',
   'data-block-id': blockId,
   style: {
     color: textColor,
     fontSize,
     fontWeight,
     textAlign,
-    padding: `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}`,
-    margin: `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}`
+    padding: padding ? `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}` : undefined,
+    margin: margin ? `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` : undefined
   }
 });
src/blocks/LastUpdated/edit.js-167-177 (1)

167-177: SpacingPanel prop mismatch.

Same issue as DocActions - SpacingPanel expects padding/margin and onPaddingChange/onMarginChange, not values/onChange.

🐛 Proposed fix for SpacingPanel usage
 <SpacingPanel
   title={__('Padding', 'wedocs-plugin')}
-  values={padding}
-  onChange={(value) => setAttributes({ padding: value })}
+  padding={padding}
+  onPaddingChange={(value) => setAttributes({ padding: value })}
+  showMargin={false}
 />

 <SpacingPanel
   title={__('Margin', 'wedocs-plugin')}
-  values={margin}
-  onChange={(value) => setAttributes({ margin: value })}
+  margin={margin}
+  onMarginChange={(value) => setAttributes({ margin: value })}
+  showPadding={false}
 />
src/blocks/Contributors/edit.js-287-295 (1)

287-295: Incorrect Gravatar hash implementation.

btoa() is Base64 encoding, not MD5 hashing. Gravatar requires an MD5 hash of the lowercase, trimmed email. This code will produce invalid avatar URLs.

🐛 Proposed fix

Since computing MD5 in the browser requires a library (or SubtleCrypto for SHA, not MD5), consider removing this fallback or using a different approach:

-// If no avatar URL and we have an email, use Gravatar
-if (!avatarUrl && contributor.email) {
-  try {
-    const emailHash = btoa(contributor.email.toLowerCase().trim());
-    avatarUrl = `https://www.gravatar.com/avatar/${emailHash}?s=48&d=identicon`;
-  } catch (e) {
-    avatarUrl = avatar1;
-  }
-}
+// Fallback to demo avatar if no avatar URL available
+// Note: Gravatar requires MD5 hash which needs additional library
+if (!avatarUrl) {
+  avatarUrl = avatar1;
+}

Alternatively, if Gravatar support is required, use a library like crypto-js or fetch the avatar URL from the server.

src/blocks/DocActions/edit.js-46-53 (1)

46-53: Potential null reference when accessing padding/margin properties.

If padding or margin attributes are undefined or null, accessing .top, .right, etc. will throw a runtime error. Add defensive checks or ensure defaults are always provided.

🐛 Proposed fix with defensive defaults
 const blockProps = useBlockProps({
   className: 'wp-block-wedocs-doc-actions',
   'data-block-id': blockId,
   style: {
-    padding: `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}`,
-    margin: `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}`
+    padding: padding ? `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}` : undefined,
+    margin: margin ? `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` : undefined
   }
 });
src/blocks/Contributors/edit.js-39-55 (1)

39-55: Potential null reference when accessing padding/margin/borderWidth properties.

Multiple object properties are accessed without null checks. If any of these attributes are undefined, the component will crash.

🐛 Proposed fix with optional chaining
 const blockProps = useBlockProps({
   className: `wedocs-contributors ${attributes.additionalCssClass}`,
   style: {
     backgroundColor: attributes.backgroundType === 'classic' ? attributes.backgroundColor : undefined,
     backgroundImage: attributes.backgroundType === 'gradient' ? attributes.backgroundGradient :
       (attributes.backgroundImage?.url ? `url(${attributes.backgroundImage.url})` : undefined),
-    padding: `${attributes.padding.top} ${attributes.padding.right} ${attributes.padding.bottom} ${attributes.padding.left}`,
-    margin: `${attributes.margin.top} ${attributes.margin.right} ${attributes.margin.bottom} ${attributes.margin.left}`,
+    padding: attributes.padding ? `${attributes.padding.top} ${attributes.padding.right} ${attributes.padding.bottom} ${attributes.padding.left}` : undefined,
+    margin: attributes.margin ? `${attributes.margin.top} ${attributes.margin.right} ${attributes.margin.bottom} ${attributes.margin.left}` : undefined,
     borderStyle: attributes.borderStyle !== 'none' ? attributes.borderStyle : undefined,
     borderWidth: attributes.borderStyle !== 'none' ?
-      `${attributes.borderWidth.top} ${attributes.borderWidth.right} ${attributes.borderWidth.bottom} ${attributes.borderWidth.left}` : undefined,
+      (attributes.borderWidth ? `${attributes.borderWidth.top} ${attributes.borderWidth.right} ${attributes.borderWidth.bottom} ${attributes.borderWidth.left}` : undefined) : undefined,
     // ...
   }
 });

Committable suggestion skipped: line range outside the PR's diff.

src/blocks/commonControls/BorderAndShadowControls.js-21-25 (1)

21-25: Guard missing borderAndShadowControls to avoid indexing crashes.

Same pattern: if attributes.borderAndShadowControls isn’t initialized, borderAndShadowControls[device] throws.

src/blocks/commonControls/AlignmentControls.js-26-29 (1)

26-29: Guard against missing attributes.alignmentControls to avoid editor crashes.

If alignmentControls isn’t initialized in block attributes, alignmentControls[device] will throw.

Proposed fix
 const AlignmentControls = ({ attributes, setAttributes, device }) => {
-	const { alignmentControls } = attributes;
-	const currentAlignment = alignmentControls[device] || {};
+	const alignmentControls = attributes?.alignmentControls ?? {};
+	const currentAlignment = alignmentControls?.[device] ?? {};
src/blocks/commonControls/ColorsControls.js-30-320 (1)

30-320: Use the isTextColor/isBackgroundColor/isLinkColor flags (or remove them).

Right now the component always renders all controls, ignoring the flags, which makes the public API misleading.

Example pattern (wrap sections)
-			<div className="wedocs-colors-controls">
-				{/* Text Color Control */}
-				<Dropdown ... />
+			<div className="wedocs-colors-controls">
+				{isTextColor && (
+					<>
+						{/* Text Color Control */}
+						<Dropdown /* ... */ />
+					</>
+				)}
 ...
-				{/* Background Color Control */}
-				<Dropdown ... />
+				{isBackgroundColor && (
+					<>
+						{/* Background Color Control */}
+						<Dropdown /* ... */ />
+					</>
+				)}
 ...
-				{/* Link Color Control */}
-				<Dropdown ... />
+				{isLinkColor && (
+					<>
+						{/* Link Color Control */}
+						<Dropdown /* ... */ />
+					</>
+				)}
 			</div>
src/blocks/commonControls/BackgroundImageControls.js-14-17 (1)

14-17: Fix FocalPointPicker value and add guard for missing backgroundImageControls.

value={currentBackgroundImage.bgFocalPoint || {}} passes an invalid shape to FocalPointPicker, which requires an object with numeric x and y properties (0–1 range). Also, backgroundImageControls[device] will throw if the attribute isn't defined.

Proposed fix
 const BackgroundImageControls = ({ attributes, setAttributes, device }) => {
-	const { backgroundImageControls } = attributes;
-	const currentBackgroundImage = backgroundImageControls[device] || {};
+	const backgroundImageControls = attributes?.backgroundImageControls ?? {};
+	const currentBackgroundImage = backgroundImageControls[device] ?? {};
 ...
 					<FocalPointPicker
 ...
-						value={currentBackgroundImage.bgFocalPoint || {}}
+						value={currentBackgroundImage.bgFocalPoint ?? { x: 0.5, y: 0.5 }}

Also applies to: 110–119

src/blocks/TableOfContents/ColorsControls.js-21-28 (1)

21-28: Add fallback empty arrays for undefined theme colors/gradients.

getSettings() may not return colors or gradients properties (or they may be undefined), which will cause runtime errors when passed to ColorPalette and GradientPicker. Apply a fallback to empty arrays:

const { themeColors, themeGradients } = useSelect((select) => {
	const editorSettings = select('core/block-editor').getSettings();
	return {
		themeColors: editorSettings.colors || [],
		themeGradients: editorSettings.gradients || [],
	};
});

The grouped palette format colors={[{ colors: themeColors, name: '...' }]} is correct and intentional—it's the proper way to render palette groups in ColorPalette.

Also applies to: 62-82, 125-151, 163-191, 248-310, and ./src/blocks/commonControls/ColorsControls.js (lines 21-28, 62-82, 125-151, 163-191, 248-310)

src/blocks/TableOfContents/Inspector.js-126-126 (1)

126-126: Duplicate containerBackgroundColor control.

The containerBackgroundColor control appears twice in the Styles panel:

  1. Line 126: Inside the ListItems component
  2. Lines 131-135: Directly in the Container Style panel

This creates duplicate UI controls for the same attribute. Based on the context, it appears the ListItems component was intended to handle this control, so the duplicate at lines 131-135 should be removed.

🔧 Proposed fix
             <InspectorControls group='styles'>
                 <ListItems attributes={attributes} setAttributes={setAttributes} />
                 <PanelBody title={__('Container Style', 'wedocs')} initialOpen={false}>
                     <h4>{__('TOC Container', 'wedocs')}</h4>

-                    {/* <div style={{ marginBottom: '20px' }}> */}
-                        <label>{__('Background Color', 'wedocs')}</label>
-                        <ColorPicker
-                            color={containerBackgroundColor}
-                            onChange={(value) => setAttributes({ containerBackgroundColor: value })}
-                        />
-                    {/* </div> */}
-
                     <UnitControl
                         label={__('Width', 'wedocs')}

Also applies to: 131-135

src/blocks/TableOfContents/controls/ListItems.js-12-18 (1)

12-18: Duplicate containerBackgroundColor control.

The containerBackgroundColor control is also present in Inspector.js (lines 131-135) within the Container Style panel. This creates two separate UI controls for the same attribute, which can confuse users and lead to maintenance issues.

Consider removing this duplicate control from either ListItems.js or Inspector.js to maintain a single source of truth for this setting.

src/blocks/TableOfContents/view.js-63-73 (1)

63-73: Race condition: TOC generated before heading IDs are assigned.

The TOC HTML is generated and inserted (lines 64-66) before heading IDs are assigned (lines 69-73). This means the TOC links reference the heading.id which may be undefined at generation time, causing broken links.

Move the ID assignment before TOC generation.

🐛 Proposed fix
     function generateTOC(tocBlock) {
         const supportedHeadings = getSupportedHeadings(tocBlock);
         const showHierarchy = tocBlock.dataset.showHierarchy !== 'false';
         const showNumbering = tocBlock.dataset.showNumbering === 'true';

         // Find all headings in the page content
         const contentArea = document.querySelector('.entry-content, .post-content, main, .content') || document.body;
         const headingSelector = supportedHeadings.map(h => h.toLowerCase()).join(', ');
         const headings = contentArea.querySelectorAll(headingSelector);

         if (headings.length === 0) {
             const tocContent = tocBlock.querySelector('.toc-content');
             tocContent.innerHTML = '<div class="toc-empty-state"><p>No headings found in the content.</p></div>';
             return;
         }

+        // Add IDs to headings if they don't have them BEFORE generating TOC
+        headings.forEach(function(heading, index) {
+            if (!heading.id) {
+                heading.id = 'toc-heading-' + index;
+            }
+        });
+
         // Generate TOC HTML
         const tocHTML = generateTOCHTML(headings, supportedHeadings, showHierarchy, showNumbering);
         const tocContent = tocBlock.querySelector('.toc-content');
         tocContent.innerHTML = tocHTML;
-
-        // Add IDs to headings if they don't have them
-        headings.forEach(function(heading, index) {
-            if (!heading.id) {
-                heading.id = 'toc-heading-' + index;
-            }
-        });
     }
src/blocks/SocialShare/render.php-24-55 (1)

24-55: Fix potential PHP warnings from undefined array keys in object attributes.
Direct indexing like $enabled_platforms['facebook'] (Line 152) and $container_padding['top'] (Line 92) will warn if the attribute object is missing those keys (common with partial updates / old content). Normalize with defaults via wp_parse_args() and use ?? when reading keys.

Proposed normalization pattern
-$enabled_platforms = $attributes['enabledPlatforms'] ?? [
-    'facebook' => true,
-    'twitter' => true,
-    'linkedin' => true,
-    'pinterest' => true
-];
+$enabled_platforms = wp_parse_args(
+    is_array($attributes['enabledPlatforms'] ?? null) ? $attributes['enabledPlatforms'] : [],
+    [
+        'facebook'  => true,
+        'twitter'   => true,
+        'linkedin'  => true,
+        'pinterest' => true,
+    ]
+);

-$container_padding = $attributes['containerPadding'] ?? ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px'];
-$container_margin = $attributes['containerMargin'] ?? ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px'];
+$container_padding = wp_parse_args(
+    is_array($attributes['containerPadding'] ?? null) ? $attributes['containerPadding'] : [],
+    ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px']
+);
+$container_margin = wp_parse_args(
+    is_array($attributes['containerMargin'] ?? null) ? $attributes['containerMargin'] : [],
+    ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px']
+);

Also applies to: 92-97, 152-230

src/blocks/SocialShare/render.php-155-229 (1)

155-229: Add aria-label for icon-only share links (title isn’t sufficient).
When buttonStyle is icon-only, the <a> contains only an SVG. Add an aria-label="Share on Facebook" etc. to make the control accessible to screen readers.

Example change (apply to each platform link)
 <a href="<?php echo esc_url($facebook_url); ?>"
    target="_blank"
    rel="noopener noreferrer"
+   aria-label="<?php echo esc_attr(sprintf(__('Share on %s', 'wedocs'), $platform_names['facebook'])); ?>"
    class="wedocs-social-share-btn wedocs-social-share-facebook <?php echo esc_attr($size_class . ' ' . $shape_class); ?>"
src/blocks/SocialShare/view.js-39-54 (1)

39-54: Add consent check before emitting analytics events.

The code only verifies typeof gtag !== 'undefined' (line 45) but does not check user consent. This sends tracking events regardless of whether the user has opted in, which violates GDPR/CCPA requirements. Even if GA is loaded after consent is obtained, this implementation lacks a compliance gate.

The same pattern exists in src/blocks/HelpfulModal/view.js (line 42).

Wrap both gtag calls with a consent check—e.g., window.gtag_is_restricted or your site's consent object—before emitting events.

src/blocks/SocialShare/render.php-14-16 (1)

14-16: Use block context for post ID; rely less on global post state.

get_post_type() (line 14) and get_the_ID() (line 58) depend on the global post, which may be unavailable or incorrect in editor preview, REST API, or non-standard rendering contexts. Add "usesContext": ["postId"] to block.json, then update render.php to prefer $block->context['postId'] with a fallback to get_the_ID():

Required changes

Update block.json:

 {
   "name": "wedocs/social-share",
+  "usesContext": ["postId"],
   "render": "file:./render.php",
   "attributes": {

Update render.php (lines 13–58):

-// Check if this is a docs post type
-if (get_post_type() !== 'docs') {
-    return '';
-}
-
-// Start output buffering
-ob_start();
-
-// Extract attributes with defaults
+// Resolve post ID from block context or global state
+$post_id = 0;
+if (isset($block) && is_object($block) && !empty($block->context['postId'])) {
+    $post_id = (int) $block->context['postId'];
+} else {
+    $post_id = (int) get_the_ID();
+}
+
+// Validate post exists and is docs type
+if (!$post_id || get_post_type($post_id) !== 'docs') {
+    return '';
+}
+
+// Start output buffering
+ob_start();
+
+// Extract attributes with defaults
 $label_text = $attributes['labelText'] ?? 'Share This Article:';
-
-// Get current post data
-$post_id = get_the_ID();
 $post_title = get_the_title($post_id);

Also applies to: HelpfulFeedback/render.php (lines 13–22)

src/blocks/SocialShare/edit.js-489-547 (1)

489-547: Fix 'text-only' button style rendering and add i18n for platform names.

Two issues in the button preview rendering:

  1. When buttonStyle is 'text-only', the icon SVG is still rendered (lines 498, 513, 528, 543). For text-only style, icons should be hidden.
  2. Platform names ('Facebook', 'Twitter', 'LinkedIn', 'Pinterest') are hardcoded and not internationalized.
🐛 Proposed fixes
                         {enabledPlatforms.facebook && (
                             <button
                                 className={`wedocs-social-share-btn wedocs-social-share-facebook ${getButtonSizeClass()} ${getButtonShapeClass()}`}
                                 style={{
                                     backgroundColor: getButtonColor('facebook'),
                                     color: buttonIconColor
                                 }}
                                 disabled
                             >
-                                {socialIcons.facebook}
-                                {buttonStyle === 'icon-text' && <span>Facebook</span>}
-                                {buttonStyle === 'text-only' && <span>Facebook</span>}
+                                {buttonStyle !== 'text-only' && socialIcons.facebook}
+                                {buttonStyle !== 'icon-only' && <span>{__('Facebook', 'wedocs')}</span>}
                             </button>
                         )}

                         {enabledPlatforms.twitter && (
                             <button
                                 className={`wedocs-social-share-btn wedocs-social-share-twitter ${getButtonSizeClass()} ${getButtonShapeClass()}`}
                                 style={{
                                     backgroundColor: getButtonColor('twitter'),
                                     color: buttonIconColor
                                 }}
                                 disabled
                             >
-                                {socialIcons.twitter}
-                                {buttonStyle === 'icon-text' && <span>Twitter</span>}
-                                {buttonStyle === 'text-only' && <span>Twitter</span>}
+                                {buttonStyle !== 'text-only' && socialIcons.twitter}
+                                {buttonStyle !== 'icon-only' && <span>{__('Twitter', 'wedocs')}</span>}
                             </button>
                         )}

                         {enabledPlatforms.linkedin && (
                             <button
                                 className={`wedocs-social-share-btn wedocs-social-share-linkedin ${getButtonSizeClass()} ${getButtonShapeClass()}`}
                                 style={{
                                     backgroundColor: getButtonColor('linkedin'),
                                     color: buttonIconColor
                                 }}
                                 disabled
                             >
-                                {socialIcons.linkedin}
-                                {buttonStyle === 'icon-text' && <span>LinkedIn</span>}
-                                {buttonStyle === 'text-only' && <span>LinkedIn</span>}
+                                {buttonStyle !== 'text-only' && socialIcons.linkedin}
+                                {buttonStyle !== 'icon-only' && <span>{__('LinkedIn', 'wedocs')}</span>}
                             </button>
                         )}

                         {enabledPlatforms.pinterest && (
                             <button
                                 className={`wedocs-social-share-btn wedocs-social-share-pinterest ${getButtonSizeClass()} ${getButtonShapeClass()}`}
                                 style={{
                                     backgroundColor: getButtonColor('pinterest'),
                                     color: buttonIconColor
                                 }}
                                 disabled
                             >
-                                {socialIcons.pinterest}
-                                {buttonStyle === 'icon-text' && <span>Pinterest</span>}
-                                {buttonStyle === 'text-only' && <span>Pinterest</span>}
+                                {buttonStyle !== 'text-only' && socialIcons.pinterest}
+                                {buttonStyle !== 'icon-only' && <span>{__('Pinterest', 'wedocs')}</span>}
                             </button>
                         )}

Comment on lines +85 to +89
// wp_register_style(
// 'wedocs-block-style',
// $assets_url . '/build/style-block.css',
// $block_dependencies['version']
// );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Attempting to enqueue unregistered stylesheet.

The wedocs-block-style registration is commented out here, but lines 129 and 155 still attempt to enqueue this style. WordPress will trigger errors when trying to enqueue an unregistered stylesheet, and the style won't load.

🐛 Proposed fix

Either uncomment the registration if the style is needed:

-            // wp_register_style(
-            //     'wedocs-block-style',
-            //     $assets_url . '/build/style-block.css',
-            //     $block_dependencies['version']
-            // );
+            wp_register_style(
+                'wedocs-block-style',
+                $assets_url . '/build/style-block.css',
+                array(),
+                $block_dependencies['version']
+            );

Or remove the enqueue calls if the style is no longer needed:

-        wp_enqueue_style( 'wedocs-block-style' );

(at line 129)

-        wp_enqueue_style( 'wedocs-block-style' );

(at line 155)

📝 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
// wp_register_style(
// 'wedocs-block-style',
// $assets_url . '/build/style-block.css',
// $block_dependencies['version']
// );
wp_register_style(
'wedocs-block-style',
$assets_url . '/build/style-block.css',
array(),
$block_dependencies['version']
);
🤖 Prompt for AI Agents
In @includes/Assets.php around lines 85 - 89, The code attempts to
wp_enqueue_style('wedocs-block-style') but the wp_register_style call for
'wedocs-block-style' is commented out, causing enqueue errors; either
restore/uncomment the wp_register_style call that registers 'wedocs-block-style'
(ensure correct handle, URL $assets_url . '/build/style-block.css' and
dependency/version args match $block_dependencies) or remove the
wp_enqueue_style('wedocs-block-style') calls so you don't enqueue an
unregistered handle; update the occurrences where 'wedocs-block-style' is
enqueued to match the chosen fix.

Comment on lines +26 to +39
// {
// "extra": {
// "mozart": {
// "dep_namespace": "WPMUDEV\\PluginTest\\Vendor\\",
// "dep_directory": "/vendor-prefixed/",
// "classmap_directory": "/classes/",
// "classmap_prefix": "WPMUDEV_PluginTest_",
// "packages": [
// "google/apiclient"
// ],
// "delete_vendor_directories": false
// }
// }
// }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 519


🏁 Script executed:

cat readme.md

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1969


🏁 Script executed:

find . -name "composer.json" -o -name "composer.lock" | head -5

Repository: weDevsOfficial/wedocs-plugin

Length of output: 103


🏁 Script executed:

rg "PluginTest|google/apiclient|Mozart" --max-count=20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 241


🏁 Script executed:

cat composer.json

Repository: weDevsOfficial/wedocs-plugin

Length of output: 634


🏁 Script executed:

rg "wedocs|weDocs|namespace|WPMUDEV" --type php -A 2 | head -50

Repository: weDevsOfficial/wedocs-plugin

Length of output: 2076


🏁 Script executed:

rg '"google/apiclient"' .

Repository: weDevsOfficial/wedocs-plugin

Length of output: 113


Remove the Mozart configuration block from readme.md—it contains incorrect namespace and unrelated dependency references.

The commented Mozart configuration in lines 26-39 references "WPMUDEV\PluginTest\Vendor\" and "WPMUDEV_PluginTest_", which do not match the actual plugin context. The plugin uses the namespace "WeDevs\WeDocs\" (defined in composer.json), not PluginTest. Additionally, google/apiclient is not a dependency of this plugin and does not appear anywhere in composer.json.

This appears to be placeholder or template code that was inadvertently left in the readme. Either remove this configuration block or, if intentional documentation is needed, clarify its purpose and ensure values match the actual plugin configuration.

🤖 Prompt for AI Agents
In @readme.md around lines 26 - 39, Remove the commented Mozart configuration
block that references "WPMUDEV\\PluginTest\\Vendor\\", "WPMUDEV_PluginTest_",
and the unrelated package "google/apiclient" from readme.md; replace it only if
you intend to document real configuration, in which case update the namespace
and prefix to the actual plugin values (e.g., "WeDevs\\WeDocs\\" per
composer.json) and list only real dependencies, or simply delete the entire
commented block to avoid misleading/template content.

Comment on lines +116 to +289
function generate_responsive_css($block_id, $controls, $device_suffix = '') {
$css = '';

if (empty($controls)) {
return $css;
}

$selector = '.' . $block_id . $device_suffix;
$rules = [];

// Typography
if (isset($controls['fontFamily']) && $controls['fontFamily']) {
$rules[] = 'font-family: ' . esc_attr($controls['fontFamily']);
}
if (isset($controls['fontSize']) && $controls['fontSize']) {
$rules[] = 'font-size: ' . esc_attr($controls['fontSize']);
}
if (isset($controls['fontAppearance']['fontWeight']) && $controls['fontAppearance']['fontWeight']) {
$rules[] = 'font-weight: ' . esc_attr($controls['fontAppearance']['fontWeight']);
}
if (isset($controls['fontAppearance']['fontStyle']) && $controls['fontAppearance']['fontStyle']) {
$rules[] = 'font-style: ' . esc_attr($controls['fontAppearance']['fontStyle']);
}
if (isset($controls['lineHeight']) && $controls['lineHeight']) {
$rules[] = 'line-height: ' . esc_attr($controls['lineHeight']);
}
if (isset($controls['letterSpacing']) && $controls['letterSpacing']) {
$rules[] = 'letter-spacing: ' . esc_attr($controls['letterSpacing']);
}
if (isset($controls['textDecoration']) && $controls['textDecoration']) {
$rules[] = 'text-decoration: ' . esc_attr($controls['textDecoration']);
}
if (isset($controls['textTransform']) && $controls['textTransform']) {
$rules[] = 'text-transform: ' . esc_attr($controls['textTransform']);
}

// Colors
if (isset($controls['textColor']) && $controls['textColor']) {
$rules[] = 'color: ' . esc_attr($controls['textColor']);
}
if (isset($controls['backgroundColor']) && $controls['backgroundColor']) {
$rules[] = 'background-color: ' . esc_attr($controls['backgroundColor']);
}

// Dimensions
if (isset($controls['width']) && $controls['width']) {
$rules[] = 'width: ' . esc_attr($controls['width']);
}
if (isset($controls['minWidth']) && $controls['minWidth']) {
$rules[] = 'min-width: ' . esc_attr($controls['minWidth']);
}
if (isset($controls['maxWidth']) && $controls['maxWidth']) {
$rules[] = 'max-width: ' . esc_attr($controls['maxWidth']);
}
if (isset($controls['height']) && $controls['height']) {
$rules[] = 'height: ' . esc_attr($controls['height']);
}
if (isset($controls['minHeight']) && $controls['minHeight']) {
$rules[] = 'min-height: ' . esc_attr($controls['minHeight']);
}
if (isset($controls['maxHeight']) && $controls['maxHeight']) {
$rules[] = 'max-height: ' . esc_attr($controls['maxHeight']);
}

// Padding
if (isset($controls['padding']) && is_array($controls['padding'])) {
$padding_values = [];
$padding_values[] = $controls['padding']['top'] ?? '0';
$padding_values[] = $controls['padding']['right'] ?? '0';
$padding_values[] = $controls['padding']['bottom'] ?? '0';
$padding_values[] = $controls['padding']['left'] ?? '0';
$rules[] = 'padding: ' . implode(' ', $padding_values);
}

// Margin
if (isset($controls['margin']) && is_array($controls['margin'])) {
$margin_values = [];
$margin_values[] = $controls['margin']['top'] ?? '0';
$margin_values[] = $controls['margin']['right'] ?? '0';
$margin_values[] = $controls['margin']['bottom'] ?? '0';
$margin_values[] = $controls['margin']['left'] ?? '0';
$rules[] = 'margin: ' . implode(' ', $margin_values);
}

// Alignment
if (isset($controls['textAlign']) && $controls['textAlign']) {
$rules[] = 'text-align: ' . esc_attr($controls['textAlign']);
}
if (isset($controls['alignItems']) && $controls['alignItems']) {
$rules[] = 'align-items: ' . esc_attr($controls['alignItems']);
}
if (isset($controls['justifyContent']) && $controls['justifyContent']) {
$rules[] = 'justify-content: ' . esc_attr($controls['justifyContent']);
}
if (isset($controls['direction']) && $controls['direction']) {
$rules[] = 'flex-direction: ' . esc_attr($controls['direction']);
}

// Border
if (isset($controls['border']) && is_array($controls['border'])) {
$border = $controls['border'];
if (isset($border['width']) && isset($border['style']) && isset($border['color'])) {
$rules[] = 'border: ' . esc_attr($border['width']) . ' ' . esc_attr($border['style']) . ' ' . esc_attr($border['color']);
}
}

// Border Radius
if (isset($controls['borderRadius']) && is_array($controls['borderRadius'])) {
$radius = $controls['borderRadius'];
$radius_values = [];
$radius_values[] = $radius['topLeft'] ?? '0';
$radius_values[] = $radius['topRight'] ?? '0';
$radius_values[] = $radius['bottomRight'] ?? '0';
$radius_values[] = $radius['bottomLeft'] ?? '0';
$rules[] = 'border-radius: ' . implode(' ', $radius_values);
}

// Box Shadow
if (isset($controls['boxShadow']) && is_array($controls['boxShadow']) && isset($controls['boxShadow']['x'])) {
$shadow = $controls['boxShadow'];
$shadow_value = '';
if (isset($shadow['inset']) && $shadow['inset']) {
$shadow_value .= 'inset ';
}
$shadow_value .= esc_attr($shadow['x']) . ' ';
$shadow_value .= esc_attr($shadow['y']) . ' ';
$shadow_value .= esc_attr($shadow['blur']) . ' ';
$shadow_value .= esc_attr($shadow['spread']) . ' ';
$shadow_value .= esc_attr($shadow['color']);
$rules[] = 'box-shadow: ' . $shadow_value;
}

// Background Image
if (isset($controls['bgUrl']) && $controls['bgUrl']) {
$rules[] = 'background-image: url(' . esc_url($controls['bgUrl']) . ')';

if (isset($controls['bgSize']) && $controls['bgSize']) {
$rules[] = 'background-size: ' . esc_attr($controls['bgSize']);
}

if (isset($controls['bgFocalPoint']) && is_array($controls['bgFocalPoint'])) {
$focal = $controls['bgFocalPoint'];
$rules[] = 'background-position: ' . ($focal['x'] * 100) . '% ' . ($focal['y'] * 100) . '%';
}

if (isset($controls['bgRepeat'])) {
$rules[] = 'background-repeat: ' . ($controls['bgRepeat'] ? 'repeat' : 'no-repeat');
}

if (isset($controls['bgFixed']) && $controls['bgFixed']) {
$rules[] = 'background-attachment: fixed';
}
}

// Display
if (isset($controls['display']) && $controls['display']) {
$rules[] = 'display: ' . esc_attr($controls['display']);
}
if (isset($controls['visibility']) && $controls['visibility']) {
$rules[] = 'visibility: ' . esc_attr($controls['visibility']);
}
if (isset($controls['overflow']) && $controls['overflow']) {
$rules[] = 'overflow: ' . esc_attr($controls['overflow']);
}
if (isset($controls['zIndex']) && $controls['zIndex']) {
$rules[] = 'z-index: ' . esc_attr($controls['zIndex']);
}

if (!empty($rules)) {
$css .= $selector . ' { ' . implode('; ', $rules) . '; }';
}

return $css;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Function redefinition will cause fatal error when block is rendered multiple times.

The generate_responsive_css() function is defined inside render_wedocs_advance_contributors_block(). On the second render of this block on the same page, PHP will throw a fatal error: "Cannot redeclare generate_responsive_css()".

🐛 Proposed fix - use conditional function declaration or static closure
+// Define helper function outside the render function, or use a static check
+if (!function_exists('generate_wedocs_responsive_css')) {
+    function generate_wedocs_responsive_css($block_id, $controls, $device_suffix = '') {
+        // ... existing implementation ...
+    }
+}

 function render_wedocs_advance_contributors_block($attributes, $content, $block) {
     // ... existing code ...

-    // Helper function to generate responsive CSS
-    function generate_responsive_css($block_id, $controls, $device_suffix = '') {
-        // ... implementation ...
-    }

     // Generate CSS for all devices
     // ... use generate_wedocs_responsive_css instead of generate_responsive_css ...

Alternatively, use a closure:

$generate_responsive_css = function($block_id, $controls, $device_suffix = '') {
    // ... implementation ...
};

// Then call it as:
$responsive_css .= $generate_responsive_css($block_id, $device_controls);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/blocks/AdvanceContributors/render.php around lines 116 - 289, The nested
declaration of generate_responsive_css inside
render_wedocs_advance_contributors_block will cause a "Cannot redeclare
generate_responsive_css()" fatal error on multiple renders; fix by either moving
generate_responsive_css out of render_wedocs_advance_contributors_block into
file scope (so it’s declared once) or wrap its declaration in if
(!function_exists('generate_responsive_css')) { ... } or convert it to a local
closure ($generate_responsive_css = function(...) { ... }) and update all calls
inside render_wedocs_advance_contributors_block to use the closure variable.


return $output;
}
render_wedocs_advance_contributors_block( $attributes, $content, $block );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect function invocation outside render context.

This line calls render_wedocs_advance_contributors_block() at the file's top level, but $attributes, $content, and $block are not defined in this scope. This will cause undefined variable warnings/errors.

For WordPress block rendering, the function should only be registered as a callback in block.json (which is already done via "render": "file:./render.php"), and WordPress will invoke it with the proper parameters.

🐛 Remove the erroneous invocation
     return $output;
 }
-render_wedocs_advance_contributors_block( $attributes, $content, $block );
📝 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
render_wedocs_advance_contributors_block( $attributes, $content, $block );
🤖 Prompt for AI Agents
In @src/blocks/AdvanceContributors/render.php at line 503, The top-level call to
render_wedocs_advance_contributors_block($attributes, $content, $block) is
invalid because $attributes, $content and $block are undefined in this scope;
remove that invocation so the file only defines the function and lets WordPress
invoke it via the block.json "render": "file:./render.php" callback; keep the
render_wedocs_advance_contributors_block function definition intact and ensure
no other top-level calls pass undefined variables.

Comment on lines +17 to +20
const ColorsControls = ({ attributes, setAttributes, isTextColor = false, isBackgroundColor = false, isLinkColor = false }) => {
const { colorsControls } = attributes;
const currentColors = colorsControls[device] || {};

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix broken props contract (device is undefined) and default colorsControls.

As written, this component can’t work: it indexes colorsControls[device] but device is not provided, and colorsControls may be undefined.

Proposed fix
-const ColorsControls = ({ attributes, setAttributes, isTextColor = false, isBackgroundColor = false, isLinkColor = false }) => {
-	const { colorsControls } = attributes;
-	const currentColors = colorsControls[device] || {};
+const ColorsControls = ({
+	attributes,
+	setAttributes,
+	device,
+	isTextColor = true,
+	isBackgroundColor = true,
+	isLinkColor = true,
+}) => {
+	const colorsControls = attributes?.colorsControls ?? {};
+	const currentColors = colorsControls?.[device] ?? {};
🤖 Prompt for AI Agents
In @src/blocks/commonControls/ColorsControls.js around lines 17 - 20,
ColorsControls currently reads device and colorsControls incorrectly: ensure you
get device from props/attributes (e.g., read device from attributes or add a
device prop to the component) and default colorsControls to an object before
indexing; update the ColorsControls signature/usage so device is defined (or
default to a known key like "desktop") and compute currentColors as
(attributes.colorsControls || {})[device || 'desktop'] to avoid undefined errors
and preserve the original props contract with setAttributes remaining unchanged.

Comment on lines +10 to +51
"supports": {
"html": false,
"anchor": true,
"align": ["wide", "full"],
"color": {
"background": true,
"text": true,
"link": true,
"__experimentalDefaultControls": {
"background": true,
"text": true
}
},
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalFontFamily": true,
"__experimentalFontWeight": true,
"__experimentalDefaultControls": {
"fontSize": true
}
},
"spacing": {
"margin": true,
"padding": true,
"blockGap": true,
"__experimentalDefaultControls": {
"padding": true,
"margin": true
}
},
"__experimentalBorder": {
"color": true,
"radius": true,
"style": true,
"width": true,
"__experimentalDefaultControls": {
"color": true,
"radius": true
}
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Duplicate supports key will cause configuration loss.

The supports object is defined twice in this JSON file (lines 10-51 and lines 282-332). JSON does not allow duplicate keys—the second declaration overwrites the first. This causes the following settings to be lost:

  • anchor: true
  • align: ["wide", "full"]
  • spacing.blockGap: true

Merge both supports blocks into a single declaration to retain all configuration.

🔧 Suggested fix: Merge supports blocks

Remove the first supports block (lines 10-51) and update the second block (starting at line 282) to include the missing properties:

 	"supports": {
 		"html": false,
+		"anchor": true,
+		"align": ["wide", "full"],
 		"color": {
 			"gradients": true,
 			"link": true,
 			"__experimentalDefaultControls": {
 				"background": true,
 				"text": true,
 				"link": true
 			}
 		},
 		...
 		"spacing": {
 			"padding": true,
 			"margin": true,
+			"blockGap": true,
 			"__experimentalDefaultControls": {
 				"padding": true,
 				"margin": true
 			}
 		},
 		...
 	},

Also applies to: 282-332

🧰 Tools
🪛 Biome (2.1.2)

[error] 10-10: The key supports was already declared.

This where a duplicated key was declared again.

If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

(lint/suspicious/noDuplicateObjectKeys)

🤖 Prompt for AI Agents
In @src/blocks/HelpfulModal/block.json around lines 10 - 51, You have two
duplicate "supports" objects in block.json; remove the redundant one and merge
their properties into a single "supports" declaration so nothing is overwritten:
ensure the final supports object includes anchor: true, align: ["wide","full"],
spacing.blockGap: true plus all color, typography, spacing, and
__experimentalBorder entries from both blocks so no settings are lost.

Comment on lines +1 to +3



No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing save function implementation.

This file is empty but should export a save function that returns null for server-side rendered blocks. Without this, the block registration will fail or behave unexpectedly.

🔧 Required implementation
+/**
+ * Save function for the Need More Help - Contact Modal block.
+ * Returns null because this block uses server-side rendering via render.php.
+ *
+ * @return {null} Null - rendering handled server-side.
+ */
+export default function Save() {
+	return null;
+}
📝 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
/**
* Save function for the Need More Help - Contact Modal block.
* Returns null because this block uses server-side rendering via render.php.
*
* @return {null} Null - rendering handled server-side.
*/
export default function Save() {
return null;
}
🤖 Prompt for AI Agents
In @src/blocks/HelpfulModal/save.js around lines 1 - 3, The file is missing the
required export for a save function used by server-side rendered blocks; add and
export a save function named save that returns null (i.e., export function
save() { return null; }) so the block registration has the expected save
implementation and won't fail at runtime.

Comment on lines +30 to +50
function wedocs_format_last_updated_date($timestamp, $format) {
$date = new DateTime();
$date->setTimestamp($timestamp);

switch ($format) {
case 'MM/DD/YYYY':
return $date->format('m/d/Y');
case 'DD/MM/YYYY':
return $date->format('d/m/Y');
case 'YYYY-MM-DD':
return $date->format('Y-m-d');
case 'Month DD, YYYY':
return $date->format('F j, Y');
case 'DD Month YYYY':
return $date->format('j F Y');
case 'relative':
return human_time_diff($timestamp, current_time('timestamp')) . ' ago';
default:
return $date->format('m/d/Y');
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fatal error: Function redefinition on multiple block renders.

The wedocs_format_last_updated_date function is defined inside the render callback without a function_exists guard. If this block is used multiple times on a page, PHP will throw a fatal "Cannot redeclare" error.

🐛 Proposed fix: Move function outside or add existence check
+if (!function_exists('wedocs_format_last_updated_date')) {
 // Format date based on selected format
 function wedocs_format_last_updated_date($timestamp, $format) {
     $date = new DateTime();
     $date->setTimestamp($timestamp);

     switch ($format) {
         case 'MM/DD/YYYY':
             return $date->format('m/d/Y');
         case 'DD/MM/YYYY':
             return $date->format('d/m/Y');
         case 'YYYY-MM-DD':
             return $date->format('Y-m-d');
         case 'Month DD, YYYY':
             return $date->format('F j, Y');
         case 'DD Month YYYY':
             return $date->format('j F Y');
         case 'relative':
             return human_time_diff($timestamp, current_time('timestamp')) . ' ago';
         default:
             return $date->format('m/d/Y');
     }
 }
+}

Alternatively, move this function to a shared helper file that's loaded once.

📝 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
function wedocs_format_last_updated_date($timestamp, $format) {
$date = new DateTime();
$date->setTimestamp($timestamp);
switch ($format) {
case 'MM/DD/YYYY':
return $date->format('m/d/Y');
case 'DD/MM/YYYY':
return $date->format('d/m/Y');
case 'YYYY-MM-DD':
return $date->format('Y-m-d');
case 'Month DD, YYYY':
return $date->format('F j, Y');
case 'DD Month YYYY':
return $date->format('j F Y');
case 'relative':
return human_time_diff($timestamp, current_time('timestamp')) . ' ago';
default:
return $date->format('m/d/Y');
}
}
if (!function_exists('wedocs_format_last_updated_date')) {
function wedocs_format_last_updated_date($timestamp, $format) {
$date = new DateTime();
$date->setTimestamp($timestamp);
switch ($format) {
case 'MM/DD/YYYY':
return $date->format('m/d/Y');
case 'DD/MM/YYYY':
return $date->format('d/m/Y');
case 'YYYY-MM-DD':
return $date->format('Y-m-d');
case 'Month DD, YYYY':
return $date->format('F j, Y');
case 'DD Month YYYY':
return $date->format('j F Y');
case 'relative':
return human_time_diff($timestamp, current_time('timestamp')) . ' ago';
default:
return $date->format('m/d/Y');
}
}
}
🤖 Prompt for AI Agents
In @src/blocks/LastUpdated/render.php around lines 30 - 50, The function
wedocs_format_last_updated_date is being (re)declared inside the block render
callback causing a fatal "Cannot redeclare" error when the block renders
multiple times; fix it by moving the wedocs_format_last_updated_date definition
out of the render callback into a top-level/shared helper loaded once (or wrap
the definition with if (!function_exists('wedocs_format_last_updated_date')) {
... } so it only defines once), and update the render code to call the
now-top-level function instead of declaring it inline.

Comment on lines +51 to +56
const searchWrapperStyles = {
display: 'flex',
flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Invalid CSS value: flexDirection: 'relative' - same issue as save.js.

This matches the bug in save.js. flexDirection doesn't accept 'relative' as a value.

Proposed fix
     const searchWrapperStyles = {
         display: 'flex',
-        flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
+        flexDirection: 'row',
+        position: buttonPosition === 'inside' ? 'relative' : 'static',
         width: searchWidth,
         gap: buttonPosition === 'outside' ? '8px' : '0',
     };
📝 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 searchWrapperStyles = {
display: 'flex',
flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
const searchWrapperStyles = {
display: 'flex',
flexDirection: 'row',
position: buttonPosition === 'inside' ? 'relative' : 'static',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
🤖 Prompt for AI Agents
In @src/blocks/Search/edit.js around lines 51 - 56, searchWrapperStyles uses an
invalid CSS value 'relative' for flexDirection; update the ternary in
searchWrapperStyles (and any matching logic as in save.js) so that when
buttonPosition !== 'outside' it returns a valid flexDirection (e.g., 'column')
instead of 'relative', keeping the existing gap logic intact.

Comment on lines +36 to 41
const searchWrapperStyles = {
display: 'flex',
justifyContent: alignment,
flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Invalid CSS value: flexDirection: 'relative' is not valid.

flexDirection accepts row, row-reverse, column, or column-reverse. The value 'relative' appears to be a copy-paste error from position: relative.

Proposed fix
     const searchWrapperStyles = {
         display: 'flex',
-        flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
+        flexDirection: 'row',
+        position: buttonPosition === 'inside' ? 'relative' : 'static',
         width: searchWidth,
         gap: buttonPosition === 'outside' ? '8px' : '0',
     };
📝 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 searchWrapperStyles = {
display: 'flex',
justifyContent: alignment,
flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
const searchWrapperStyles = {
display: 'flex',
flexDirection: 'row',
position: buttonPosition === 'inside' ? 'relative' : 'static',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};
🤖 Prompt for AI Agents
In @src/blocks/Search/save.js around lines 36 - 41, The style object
searchWrapperStyles sets flexDirection to an invalid value 'relative' in
src/blocks/Search/save.js; change the conditional to a valid flexDirection
(e.g., buttonPosition === 'outside' ? 'row' : 'column') and, if the intent was
to use CSS positioning, add a separate position: 'relative' property instead of
misusing flexDirection; update the const searchWrapperStyles accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In @src/blocks/DocActions/block.json:
- Around line 57-79: The default colors risk making filled buttons invisible
because backgroundColor is empty while textColor is "#ffffff"; update the
block.json defaults so backgroundColor is a sensible dark color (e.g., "#333333"
or "#000000") to match textColor, and ensure hoverBackgroundColor, borderColor
and borderRadius defaults remain consistent with the filled style; modify the
"backgroundColor" default value in src/blocks/DocActions/block.json and test the
"buttonStyle" = "filled" rendering to confirm legibility.

In @src/blocks/DocActions/view.js:
- Around line 165-185: The switch block in view.js declares "const level =
parseInt(tag[1])" directly inside case labels (e.g., case 'h1' ... case 'h6')
which causes block-scoping issues; fix by introducing an explicit block for the
header cases (for example wrap the header cases with { ... } and move the const
level declaration inside that block) or alternatively declare level with let
before the switch and assign inside each header case; update the cases handling
'h1'..'h6' accordingly in the same switch in the view() function so the const is
properly scoped.
- Around line 71-95: The ChatGPT opener uses the wrong domain and query param;
in function handleOpenChatGPT replace the URL construction
`https://chatgpt.com/?prompt=${encodedPrompt}` with
`https://chat.openai.com/?q=${encodedPrompt}` so the encodedPrompt is passed
with the documented `q` parameter and opens the correct ChatGPT UI (keep
encodeURIComponent and window.open as-is).

In @src/blocks/FontSizeSwitcher/block.json:
- Around line 48-57: The fontSizeOptions attribute in block.json is declared but
never used—replace the hardcoded size arrays in save.js, edit.js, and view.js
with a reference to the block attribute (fontSizeOptions) so the components read
the configured options instead of fixed values; locate where sizes are currently
defined in the Edit, Save and View components (the arrays around lines mentioned
in the review) and destructure or access attributes.fontSizeOptions (or
props.attributes.fontSizeOptions) and fall back to the block.json default if
undefined, ensuring all three files use the same single source of truth for font
sizes.
- Line 3: The block metadata uses "apiVersion": 3 which requires WP 6.3+, but
the plugin's minimum WP version is 5.6; either change each block's apiVersion
from 3 to 2 (e.g., update src/blocks/FontSizeSwitcher/block.json and the other
12 block.json files to "apiVersion": 2) to maintain compatibility with WP
5.6–6.2, or update the plugin's minimum WordPress version in the plugin
header/readme (Requires at least) to 6.3 to match apiVersion 3—pick one approach
and apply it consistently across all blocks and the plugin metadata.

In @src/blocks/FontSizeSwitcher/edit.js:
- Around line 113-131: The JSX uses a selected attribute on an <option> which is
invalid; update the renderDropdown function to set the default selection via the
<select>'s defaultValue prop (e.g., defaultValue="Default") and remove the
selected attribute from the <option>s; also add explicit value attributes to
each <option> (e.g., value="Small", value="Default", etc.) to ensure the
selected value matches correctly.

In @src/blocks/FontSizeSwitcher/save.js:
- Around line 60-122: The save.js file duplicates rendering logic from edit.js:
extract shared UI renderers into a reusable module and import them in both
files. Create a new module (e.g. RenderControls.js) exporting renderButtons,
renderSlider, and renderDropdown that accept configurable inputs (e.g.
fontSizeOptions, buttonBaseStyle, defaultSize) and move the JSX implementations
there; then replace the local renderButtons, renderSlider, renderDropdown in
both save.js and edit.js with imports from that module (referencing the same
function names) so both files call the shared helpers instead of duplicating
code.
- Around line 60-81: The renderButtons function currently uses a hardcoded sizes
array; replace it to iterate over the fontSizeOptions block attribute instead
(use the fontSizeOptions prop/attribute exposed to save.js) and map each option
to the button elements (preserve data-size, data-label, title and label
rendering). Update the logic in save.js renderButtons to read fontSizeOptions
(fallback to a sensible default if undefined) so the UI reflects block.json
configuration; mirror the same change in edit.js and ensure block.json's schema
fields (label/title/value) match the property names you use.
- Around line 103-122: The renderDropdown function currently hardcodes option
elements and incorrectly uses the selected attribute; change it to set the
default selection via the select's defaultValue prop (remove selected on the
option), and generate the option list by mapping over the fontSizeOptions
attribute (same source used by renderButtons) to create <option> elements with
appropriate value and label; ensure defaultValue falls back to the current
fontSize attribute (or a sensible default like "16px") so the dropdown reflects
the saved setting.

In @src/blocks/FontSizeSwitcher/view.js:
- Around line 33-39: Wrap all localStorage access in try-catch: when reading the
stored font size (the localStorage.getItem call that sets storedSize/currentSize
before calling applyFontSize) catch any exception and fall back to defaultSize
before calling applyFontSize(currentSize); likewise wrap every
localStorage.setItem call (the places that persist font size changes) in
try-catch and silently handle/log the error so the UI still updates
(applyFontSize) even if storage is unavailable; reference the
storageKey/storedSize/currentSize variables and the applyFontSize function when
making these changes.
- Around line 159-186: applyFontSize is compounding because it reads
getComputedStyle which returns the last adjusted size; instead, on first run
capture and persist each element's original font size (e.g., store in a
data-original-font-size attribute or a WeakMap keyed by element) and on every
subsequent call use that stored original value multiplied by the new scaleFactor
(derived from defaultSize and incoming size) to compute and set
el.style.fontSize; ensure you only set the original value when absent and
continue to add the class/data-adjusted-size to contentElement as before.
🧹 Nitpick comments (7)
src/blocks/DocActions/save.js (2)

25-46: Consider extracting icon definitions to a shared module.

The getIconSVG function is duplicated in both save.js and edit.js. Extract it to a shared utility file to follow DRY principles and ensure consistency.

♻️ Suggested refactor

Create a new file src/blocks/DocActions/icons.js:

export const getIconSVG = (type) => {
	const icons = {
		copy: (
			<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
				<rect x="6" y="6" width="10" height="10" rx="1" stroke="currentColor" strokeWidth="1.5"/>
				<path d="M4 14H3C2.44772 14 2 13.5523 2 13V3C2 2.44772 2.44772 2 3 2H13C13.5523 2 14 2.44772 14 3V4" stroke="currentColor" strokeWidth="1.5"/>
			</svg>
		),
		chatgpt: (
			<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
				<path d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2Z" stroke="currentColor" strokeWidth="1.5"/>
				<circle cx="10" cy="10" r="3" fill="currentColor"/>
			</svg>
		),
		claude: (
			<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
				<path d="M10 2L13 8L19 9L14.5 13L16 19L10 16L4 19L5.5 13L1 9L7 8L10 2Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
			</svg>
		)
	};
	return icons[type];
};

Then import in both files:

+import { getIconSVG } from './icons';

61-66: Consider extracting container style logic to a shared utility.

The containerStyle logic (lines 61-66) is duplicated in edit.js (lines 97-102). Extract this to a shared helper function to maintain consistency and follow DRY principles.

src/blocks/DocActions/view.js (1)

191-194: Remove unused function.

The getPageContent function is defined but never called. The same logic is duplicated inline at line 48 in handleCopyMarkdown. Either remove this function or refactor handleCopyMarkdown to use it.

♻️ Suggested refactor

Option 1: Remove the unused function:

-	function getPageContent() {
-		const content = document.querySelector('.entry-content, .post-content, main');
-		return content ? content.textContent.trim() : document.body.textContent.trim();
-	}
-

Option 2: Use the function in handleCopyMarkdown:

 	async function handleCopyMarkdown(button) {
 		try {
 			// Get page content
-			const content = document.querySelector('.entry-content, .post-content, main');
+			const content = getPageContent();
 			if (!content) {
 				showNotification(button, 'Content not found', 'error');
 				return;
 			}

Note: If using Option 2, update getPageContent to return the element, not the text content.

src/blocks/DocActions/edit.js (1)

64-72: Remove inappropriate gap property from button styles.

Line 71 adds gap: spacing to individual button styles, but gap is a flexbox container property. The buttons themselves are not flex containers in this context (the container at line 228 is). This property will have no effect and should be removed to avoid confusion.

If the intent is to control spacing between the icon and text within each button, that should be handled via CSS in editor.scss or style.scss rather than inline styles.

♻️ Proposed fix
 	const getButtonStyles = () => ({
 		backgroundColor: buttonStyle === 'filled' ? backgroundColor : 'transparent',
 		color: textColor,
 		borderColor,
 		borderWidth,
 		borderRadius,
-		fontSize,
-		gap: spacing
+		fontSize
 	});
src/blocks/FontSizeSwitcher/style.scss (1)

115-140: Recommended: Reduce !important usage in responsive styles.

Lines 118, 122, 123, 127, 130, and 136 use !important to override styles. This suggests specificity issues and makes styles harder to maintain or override in themes. Consider refactoring to use more specific selectors or restructuring the cascade to avoid needing !important.

For example, increase specificity naturally:

@media (max-width: 768px) {
	.wp-block-wedocs-font-size-switcher {
		.font-size-switcher-container {
			gap: 6px;
		}
		
		.font-size-option {
			padding: 6px 10px;
			font-size: 13px;
		}
		// ... etc
	}
}

If inline styles are the culprit, document that the responsive overrides are intentional for mobile UX.

src/blocks/FontSizeSwitcher/view.js (1)

91-91: Consider specifying radix for parseInt calls.

While modern browsers default to base 10 for decimal strings, explicitly providing the radix (e.g., parseInt(currentSize, 10)) is a defensive best practice. This applies to lines 91, 108, and 122.

src/blocks/FontSizeSwitcher/edit.js (1)

86-95: Consider using size.value as key instead of index.

While using index is acceptable for this static array, using the unique size.value would be slightly more idiomatic and future-proof if the array were ever filtered or reordered.

-        <button
-            key={index}
+        <button
+            key={size.value}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c337e0 and de0d67b.

📒 Files selected for processing (13)
  • src/blocks/DocActions/block.json
  • src/blocks/DocActions/edit.js
  • src/blocks/DocActions/save.js
  • src/blocks/DocActions/view.js
  • src/blocks/FontSizeSwitcher/block.json
  • src/blocks/FontSizeSwitcher/edit.js
  • src/blocks/FontSizeSwitcher/editor.scss
  • src/blocks/FontSizeSwitcher/index.js
  • src/blocks/FontSizeSwitcher/save.js
  • src/blocks/FontSizeSwitcher/style.scss
  • src/blocks/FontSizeSwitcher/view.js
  • src/blocks/index.js
  • wedocs.php
🧰 Additional context used
🧬 Code graph analysis (6)
src/blocks/FontSizeSwitcher/save.js (2)
src/blocks/FontSizeSwitcher/edit.js (7)
  • attributes (15-33)
  • blockProps (42-49)
  • containerStyle (51-57)
  • buttonBaseStyle (59-68)
  • renderButtons (77-96)
  • renderSlider (98-111)
  • renderDropdown (113-131)
src/blocks/FontSizeSwitcher/view.js (7)
  • displayStyle (12-12)
  • buttonStyle (17-17)
  • contentSelector (10-10)
  • defaultSize (11-11)
  • activeColor (13-13)
  • textColor (14-14)
  • borderColor (16-16)
src/blocks/DocActions/save.js (2)
src/blocks/DocActions/edit.js (1)
  • attributes (20-39)
src/blocks/DocActions/view.js (1)
  • promptTemplate (11-11)
src/blocks/FontSizeSwitcher/view.js (1)
src/blocks/TableOfContents/view.js (1)
  • computedStyle (284-284)
src/blocks/FontSizeSwitcher/edit.js (2)
src/blocks/FontSizeSwitcher/save.js (1)
  • blockProps (24-39)
src/blocks/FontSizeSwitcher/view.js (7)
  • buttonStyle (17-17)
  • textColor (14-14)
  • borderColor (16-16)
  • activeColor (13-13)
  • defaultSize (11-11)
  • displayStyle (12-12)
  • contentSelector (10-10)
src/blocks/DocActions/edit.js (2)
src/blocks/DocActions/save.js (2)
  • attributes (4-23)
  • blockProps (48-59)
src/blocks/DocActions/view.js (1)
  • promptTemplate (11-11)
src/blocks/DocActions/view.js (2)
src/blocks/FontSizeSwitcher/view.js (1)
  • bgColor (15-15)
src/blocks/TableOfContents/view.js (4)
  • content (159-159)
  • text (105-105)
  • title (158-158)
  • level (104-104)
🪛 Biome (2.1.2)
src/blocks/DocActions/view.js

[error] 172-172: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (18)
src/blocks/DocActions/save.js (1)

78-113: LGTM!

The conditional rendering and button structure are correct. The buttons properly use the computed styles and data attributes for frontend behavior.

src/blocks/DocActions/block.json (1)

1-23: LGTM!

Block metadata is properly configured with appropriate supports and schema reference.

src/blocks/DocActions/view.js (2)

5-43: LGTM!

The event listener setup properly initializes hover effects and click handlers for each action button.


45-69: Verify content selector compatibility across themes.

The content selector at line 48 uses common WordPress theme patterns (.entry-content, .post-content, main), but may not work on all themes. Consider verifying this works with popular WordPress themes or documenting theme requirements.

src/blocks/DocActions/edit.js (3)

1-46: LGTM!

Imports and component initialization are properly structured. The useEffect correctly sets a unique blockId on first render.


113-225: LGTM!

Inspector controls are well-organized with clear sections and proper internationalization. The use of shared ColorSettingsPanel, BorderPanel, and SpacingPanel components promotes consistency across blocks.


227-248: LGTM!

The editor preview correctly mirrors the save output with conditional button rendering and proper styling.

src/blocks/FontSizeSwitcher/index.js (1)

1-10: LGTM!

Standard block registration following WordPress Gutenberg best practices.

src/blocks/FontSizeSwitcher/editor.scss (1)

5-27: Good editor UX design.

Disabling pointer events and showing a preview notice prevents confusion about non-functional controls in the editor while clearly communicating frontend interactivity.

src/blocks/FontSizeSwitcher/save.js (1)

24-39: Good data attribute strategy for frontend hydration.

Storing configuration in data attributes enables the frontend view script to read block settings without additional API calls.

src/blocks/FontSizeSwitcher/style.scss (1)

143-167: Excellent accessibility support.

The focus-visible outlines, high-contrast mode adjustments, and reduced-motion support demonstrate strong attention to WCAG compliance and inclusive design.

src/blocks/FontSizeSwitcher/view.js (4)

6-31: LGTM!

The initialization logic correctly reads configuration from data attributes with sensible fallbacks, and gracefully handles missing content elements with a console warning.


135-157: LGTM!

The dropdown control initialization follows the same clean pattern as other controls with proper event handling and display updates.


188-217: LGTM!

The utility functions are clean. The updateDisplay sizeMap provides friendly labels for common sizes and gracefully falls back to the raw value for slider positions not in the map.


57-79: The data-label attribute is correctly set in the button markup at save.js line 74 as data-label={size.title}. Each size object in the sizes array includes a title property (e.g., 'Large', 'Extra Large'), so the attribute will never be null and users will see the proper label display in view.js line 77.

Likely an incorrect or invalid review comment.

src/blocks/FontSizeSwitcher/edit.js (3)

35-49: LGTM!

The useEffect correctly initializes a unique blockId on first render, and block props are properly configured with styling.


142-244: LGTM!

The InspectorControls implementation follows WordPress block patterns correctly with proper i18n, logical panel organization, and conditional rendering for dependent controls.


246-270: LGTM!

The block render output is well-structured with proper use of blockProps, clean conditional rendering, and appropriate inline styling.

Comment on lines +57 to +79
"backgroundColor": {
"type": "string",
"default": ""
},
"textColor": {
"type": "string",
"default": "#ffffff"
},
"borderColor": {
"type": "string",
"default": "#444444"
},
"borderWidth": {
"type": "string",
"default": "1px"
},
"borderRadius": {
"type": "string",
"default": "6px"
},
"hoverBackgroundColor": {
"type": "string",
"default": "#333333"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential visibility issue with default color configuration.

The default textColor is white (#ffffff) while backgroundColor is empty. When a user switches buttonStyle to "filled" without configuring a background color, the white text on a transparent/empty background may be invisible against light-colored page backgrounds, impacting usability.

Consider setting a sensible default backgroundColor value (e.g., "#333333" or "#000000") to ensure the filled button style is immediately usable.

🤖 Prompt for AI Agents
In @src/blocks/DocActions/block.json around lines 57 - 79, The default colors
risk making filled buttons invisible because backgroundColor is empty while
textColor is "#ffffff"; update the block.json defaults so backgroundColor is a
sensible dark color (e.g., "#333333" or "#000000") to match textColor, and
ensure hoverBackgroundColor, borderColor and borderRadius defaults remain
consistent with the filled style; modify the "backgroundColor" default value in
src/blocks/DocActions/block.json and test the "buttonStyle" = "filled" rendering
to confirm legibility.

Comment on lines +71 to +95
function handleOpenChatGPT(promptTemplate) {
const pageTitle = document.title;
const pageUrl = window.location.href;
const prompt = promptTemplate
.replace(/{title}/g, pageTitle)
.replace(/{url}/g, pageUrl);

const encodedPrompt = encodeURIComponent(prompt);
const url = `https://chatgpt.com/?prompt=${encodedPrompt}`;

window.open(url, '_blank');
}

function handleOpenClaude(promptTemplate) {
const pageTitle = document.title;
const pageUrl = window.location.href;
const prompt = promptTemplate
.replace(/{title}/g, pageTitle)
.replace(/{url}/g, pageUrl);

const encodedPrompt = encodeURIComponent(prompt);
const url = `https://claude.ai/new?q=${encodedPrompt}`;

window.open(url, '_blank');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

ChatGPT pre-filled prompt URL format query parameter

💡 Result:

You can pre-fill ChatGPT by adding a URL query parameter. Common formats found in practice:

Always URL-encode the prompt (spaces → %20, special characters escaped). [1][2]

Sources: [1] GenAI Secret Sauce (example ?q=...), [2] GitHub extension README (examples, ?q & model), [3] ChatGPTToolkit/DeepWiki README (fragment prompt & autoSubmit examples), [4] LinkedIn post noting model and q/prompt usage.


🌐 Web query:

Claude AI pre-filled prompt URL format how to open

💡 Result:

Short answer: To prefill the user input in Claude's web UI use a URL like

https://claude.ai/new?q=URL-ENCODED_PROMPT

(e.g. https://claude.ai/new?q=teach%20me%20how%20to%20make%20a%20PBJ). This fills the chat box but does not auto-send — sending still requires Enter/click. For programmatic prefilling of Claude's response (the Assistant message) use the API messages parameter with an "assistant" role (see Anthropic docs). [1][2][3]

Sources:

  • Claude web prefill example. [1]
  • Anthropic docs: prefilling Assistant via API (messages with role "assistant"). [2]
  • Community notes about prefilled URL behavior (no auto-submit). [3]

Fix ChatGPT URL domain and parameter.

The ChatGPT URL on line 79 uses an incorrect domain and parameter. Change https://chatgpt.com/?prompt=${encodedPrompt} to https://chat.openai.com/?q=${encodedPrompt} to match the current ChatGPT web interface. The domain chatgpt.com does not host the ChatGPT UI, and the ?q= parameter is the documented format for pre-filling prompts.

The Claude URL on line 92 is correct and requires no changes.

🤖 Prompt for AI Agents
In @src/blocks/DocActions/view.js around lines 71 - 95, The ChatGPT opener uses
the wrong domain and query param; in function handleOpenChatGPT replace the URL
construction `https://chatgpt.com/?prompt=${encodedPrompt}` with
`https://chat.openai.com/?q=${encodedPrompt}` so the encodedPrompt is passed
with the documented `q` parameter and opens the correct ChatGPT UI (keep
encodeURIComponent and window.open as-is).

Comment on lines +165 to +185
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
const level = parseInt(tag[1]);
markdown += '\n' + '#'.repeat(level) + ' ';
break;
case 'p':
markdown += '\n\n';
break;
case 'li':
markdown += '\n- ';
break;
case 'code':
markdown += '`';
break;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix switch statement variable scoping issue.

The const level declaration at line 172 is directly in a switch case without a block, which can cause scoping issues and is flagged by static analysis. Wrap the case logic in a block.

As per static analysis hints from Biome.

🔧 Proposed fix
 				switch (tag) {
 					case 'h1':
 					case 'h2':
 					case 'h3':
 					case 'h4':
 					case 'h5':
-					case 'h6':
+					case 'h6': {
 						const level = parseInt(tag[1]);
 						markdown += '\n' + '#'.repeat(level) + ' ';
 						break;
+					}
 					case 'p':
 						markdown += '\n\n';
 						break;
 					case 'li':
 						markdown += '\n- ';
 						break;
 					case 'code':
 						markdown += '`';
 						break;
 				}
📝 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
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
const level = parseInt(tag[1]);
markdown += '\n' + '#'.repeat(level) + ' ';
break;
case 'p':
markdown += '\n\n';
break;
case 'li':
markdown += '\n- ';
break;
case 'code':
markdown += '`';
break;
}
}
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6': {
const level = parseInt(tag[1]);
markdown += '\n' + '#'.repeat(level) + ' ';
break;
}
case 'p':
markdown += '\n\n';
break;
case 'li':
markdown += '\n- ';
break;
case 'code':
markdown += '`';
break;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 172-172: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In @src/blocks/DocActions/view.js around lines 165 - 185, The switch block in
view.js declares "const level = parseInt(tag[1])" directly inside case labels
(e.g., case 'h1' ... case 'h6') which causes block-scoping issues; fix by
introducing an explicit block for the header cases (for example wrap the header
cases with { ... } and move the const level declaration inside that block) or
alternatively declare level with let before the switch and assign inside each
header case; update the cases handling 'h1'..'h6' accordingly in the same switch
in the view() function so the const is properly scoped.

@@ -0,0 +1,110 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

WordPress Block API version 3 minimum required WordPress version

💡 Result:

Block API version 3 requires WordPress 6.3 or newer. [1][2]

Sources:
[1] WordPress Block Editor Handbook — API Versions (Version 3 (>= WordPress 6.3)).
[2] WordPress.org support discussion referencing API version 3 introduced in WordPress 6.3.


🏁 Script executed:

# Search for WordPress version constraints in the repository
git ls-files | head -20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 519


🏁 Script executed:

# Check composer.json for WordPress version constraints
if [ -f composer.json ]; then
  cat composer.json | jq '.require | select(. != null)'
fi

# Check for readme.txt in .wordpress-org directory
if [ -f .wordpress-org/readme.txt ]; then
  head -40 .wordpress-org/readme.txt
fi

# Check for main plugin file (likely wedocs.php)
fd "wedocs\.php" -t f | head -5

Repository: weDevsOfficial/wedocs-plugin

Length of output: 132


🏁 Script executed:

# Read the wedocs.php file to find WordPress version requirement in plugin header
head -50 wedocs.php | grep -i "requires\|WordPress"

Repository: weDevsOfficial/wedocs-plugin

Length of output: 181


🏁 Script executed:

# Read more of the plugin header to find the Requires at least field
head -30 wedocs.php

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1068


🏁 Script executed:

# Check for readme.txt files which typically declare WordPress minimum version
fd "readme\.txt" -t f | head -10

# Also check package.json for any version constraints
if [ -f package.json ]; then
  cat package.json | jq '.engines // empty'
fi

Repository: weDevsOfficial/wedocs-plugin

Length of output: 82


🏁 Script executed:

# Read readme.txt to find WordPress minimum version
head -40 readme.txt

Repository: weDevsOfficial/wedocs-plugin

Length of output: 2180


🏁 Script executed:

# Find all block.json files and check their apiVersion values
find . -name "block.json" -type f | head -20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 555


🏁 Script executed:

# Once we have block.json files, let's check their apiVersion values
fd "block\.json" -t f -x cat {} \; | grep -A 1 -B 1 "apiVersion" | head -50

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1482


Block API version 3 is incompatible with the plugin's minimum WordPress version.

The plugin declares support for WordPress 5.6 (readme.txt: "Requires at least: 5.6"), but Block API version 3 requires WordPress 6.3 or newer. All 13 blocks in the codebase use apiVersion: 3, which means none of them will function on WordPress 5.6 through 6.2. Either downgrade blocks to apiVersion: 2 for compatibility with WordPress 5.6+, or update the minimum WordPress requirement to 6.3.

🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/block.json at line 3, The block metadata uses
"apiVersion": 3 which requires WP 6.3+, but the plugin's minimum WP version is
5.6; either change each block's apiVersion from 3 to 2 (e.g., update
src/blocks/FontSizeSwitcher/block.json and the other 12 block.json files to
"apiVersion": 2) to maintain compatibility with WP 5.6–6.2, or update the
plugin's minimum WordPress version in the plugin header/readme (Requires at
least) to 6.3 to match apiVersion 3—pick one approach and apply it consistently
across all blocks and the plugin metadata.

Comment on lines +48 to +57
"fontSizeOptions": {
"type": "array",
"default": [
{ "label": "Small", "value": "14px" },
{ "label": "Default", "value": "16px" },
{ "label": "Medium", "value": "18px" },
{ "label": "Large", "value": "20px" },
{ "label": "Extra Large", "value": "24px" }
]
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all references to fontSizeOptions
rg -n "fontSizeOptions" -C 2

Repository: weDevsOfficial/wedocs-plugin

Length of output: 406


🏁 Script executed:

#!/bin/bash
# First, find the block directory and examine the save.js file
find . -name "save.js" | grep -i fontsizeswitcher

Repository: weDevsOfficial/wedocs-plugin

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Also check the complete structure of the FontSizeSwitcher block
find . -path "*FontSizeSwitcher*" -type f | head -20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 348


🏁 Script executed:

#!/bin/bash
# Read save.js to check the implementation
cat -n src/blocks/FontSizeSwitcher/save.js

Repository: weDevsOfficial/wedocs-plugin

Length of output: 5171


🏁 Script executed:

#!/bin/bash
# Read edit.js to check if fontSizeOptions is used
cat -n src/blocks/FontSizeSwitcher/edit.js

Repository: weDevsOfficial/wedocs-plugin

Length of output: 9836


🏁 Script executed:

#!/bin/bash
# Read view.js to check for usage
cat -n src/blocks/FontSizeSwitcher/view.js

Repository: weDevsOfficial/wedocs-plugin

Length of output: 8458


🏁 Script executed:

#!/bin/bash
# Read index.js 
cat -n src/blocks/FontSizeSwitcher/index.js

Repository: weDevsOfficial/wedocs-plugin

Length of output: 370


fontSizeOptions attribute is defined but completely unused—hardcoded sizes in save.js (lines 60-67), edit.js (lines 77-84), and view.js will always override it.

The fontSizeOptions attribute in block.json is declared with customizable defaults but never destructured or referenced in any component. Instead, the size options are hardcoded in three separate locations (save.js, edit.js, and view.js), making the attribute inert. Any developer attempting to customize font sizes via this block attribute will find their changes ignored. Either use the attribute across all files or remove it from block.json to avoid confusion.

🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/block.json around lines 48 - 57, The
fontSizeOptions attribute in block.json is declared but never used—replace the
hardcoded size arrays in save.js, edit.js, and view.js with a reference to the
block attribute (fontSizeOptions) so the components read the configured options
instead of fixed values; locate where sizes are currently defined in the Edit,
Save and View components (the arrays around lines mentioned in the review) and
destructure or access attributes.fontSizeOptions (or
props.attributes.fontSizeOptions) and fall back to the block.json default if
undefined, ensuring all three files use the same single source of truth for font
sizes.

Comment on lines +60 to +122
const renderButtons = () => {
const sizes = [
{ label: 'S', value: '14px', title: 'Small' },
{ label: 'M', value: '16px', title: 'Default' },
{ label: 'L', value: '18px', title: 'Medium' },
{ label: 'XL', value: '20px', title: 'Large' },
{ label: '2XL', value: '24px', title: 'Extra Large' }
];

return sizes.map((size, index) => (
<button
key={index}
className="font-size-option"
data-size={size.value}
data-label={size.title}
style={buttonBaseStyle}
title={size.title}
>
{size.label}
</button>
));
};

const renderSlider = () => (
<div className="font-size-slider-controls" style={{ display: 'flex', alignItems: 'center', gap: '10px', flex: 1, maxWidth: '300px' }}>
<button className="font-size-decrease" style={{ ...buttonBaseStyle, padding: '6px 10px' }} title="Decrease font size">
A-
</button>
<input
type="range"
className="font-size-slider"
min="12"
max="28"
step="2"
defaultValue="16"
style={{ flex: 1 }}
/>
<button className="font-size-increase" style={{ ...buttonBaseStyle, padding: '6px 10px' }} title="Increase font size">
A+
</button>
</div>
);

const renderDropdown = () => (
<select
className="font-size-dropdown"
style={{
...buttonBaseStyle,
padding: '8px 32px 8px 12px',
appearance: 'none',
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'right 10px center',
minWidth: '120px'
}}
>
<option value="14px">Small</option>
<option value="16px" selected>Default</option>
<option value="18px">Medium</option>
<option value="20px">Large</option>
<option value="24px">Extra Large</option>
</select>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Major: Code duplication with edit.js.

The renderButtons(), renderSlider(), and renderDropdown() functions are nearly identical to those in edit.js (per relevant snippets). Consider extracting shared rendering logic into a common utility module to follow DRY principles.

Example approach:

// src/blocks/FontSizeSwitcher/components/RenderControls.js
export const renderButtons = (fontSizeOptions, buttonBaseStyle) => { ... }
export const renderSlider = (buttonBaseStyle) => { ... }
export const renderDropdown = (fontSizeOptions, buttonBaseStyle, defaultSize) => { ... }

Then import and use in both edit.js and save.js.

🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 60 - 122, The save.js file
duplicates rendering logic from edit.js: extract shared UI renderers into a
reusable module and import them in both files. Create a new module (e.g.
RenderControls.js) exporting renderButtons, renderSlider, and renderDropdown
that accept configurable inputs (e.g. fontSizeOptions, buttonBaseStyle,
defaultSize) and move the JSX implementations there; then replace the local
renderButtons, renderSlider, renderDropdown in both save.js and edit.js with
imports from that module (referencing the same function names) so both files
call the shared helpers instead of duplicating code.

Comment on lines +60 to +81
const renderButtons = () => {
const sizes = [
{ label: 'S', value: '14px', title: 'Small' },
{ label: 'M', value: '16px', title: 'Default' },
{ label: 'L', value: '18px', title: 'Medium' },
{ label: 'XL', value: '20px', title: 'Large' },
{ label: '2XL', value: '24px', title: 'Extra Large' }
];

return sizes.map((size, index) => (
<button
key={index}
className="font-size-option"
data-size={size.value}
data-label={size.title}
style={buttonBaseStyle}
title={size.title}
>
{size.label}
</button>
));
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Hardcoded sizes ignore the fontSizeOptions attribute.

The sizes array hardcoded here (14px, 16px, 18px, 20px, 24px with specific labels) completely ignores the fontSizeOptions attribute defined in block.json (lines 48-57), which provides a different schema and allows customization. This makes the attribute unusable and creates a misleading API.

♻️ Proposed fix to use fontSizeOptions attribute
 const renderButtons = () => {
-	const sizes = [
-		{ label: 'S', value: '14px', title: 'Small' },
-		{ label: 'M', value: '16px', title: 'Default' },
-		{ label: 'L', value: '18px', title: 'Medium' },
-		{ label: 'XL', value: '20px', title: 'Large' },
-		{ label: '2XL', value: '24px', title: 'Extra Large' }
-	];
+	const { fontSizeOptions } = attributes;
 
-	return sizes.map((size, index) => (
+	return fontSizeOptions.map((size, index) => (
 		<button
 			key={index}
 			className="font-size-option"
 			data-size={size.value}
 			data-label={size.label}
 			style={buttonBaseStyle}
-			title={size.title}
+			title={size.label}
 		>
 			{size.label}
 		</button>
 	));
 };

Note: You'll also need to update edit.js similarly and adjust the block.json fontSizeOptions schema to match the required fields.

📝 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 renderButtons = () => {
const sizes = [
{ label: 'S', value: '14px', title: 'Small' },
{ label: 'M', value: '16px', title: 'Default' },
{ label: 'L', value: '18px', title: 'Medium' },
{ label: 'XL', value: '20px', title: 'Large' },
{ label: '2XL', value: '24px', title: 'Extra Large' }
];
return sizes.map((size, index) => (
<button
key={index}
className="font-size-option"
data-size={size.value}
data-label={size.title}
style={buttonBaseStyle}
title={size.title}
>
{size.label}
</button>
));
};
const renderButtons = () => {
const { fontSizeOptions } = attributes;
return fontSizeOptions.map((size, index) => (
<button
key={index}
className="font-size-option"
data-size={size.value}
data-label={size.label}
style={buttonBaseStyle}
title={size.label}
>
{size.label}
</button>
));
};
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 60 - 81, The renderButtons
function currently uses a hardcoded sizes array; replace it to iterate over the
fontSizeOptions block attribute instead (use the fontSizeOptions prop/attribute
exposed to save.js) and map each option to the button elements (preserve
data-size, data-label, title and label rendering). Update the logic in save.js
renderButtons to read fontSizeOptions (fallback to a sensible default if
undefined) so the UI reflects block.json configuration; mirror the same change
in edit.js and ensure block.json's schema fields (label/title/value) match the
property names you use.

Comment on lines +103 to +122
const renderDropdown = () => (
<select
className="font-size-dropdown"
style={{
...buttonBaseStyle,
padding: '8px 32px 8px 12px',
appearance: 'none',
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'right 10px center',
minWidth: '120px'
}}
>
<option value="14px">Small</option>
<option value="16px" selected>Default</option>
<option value="18px">Medium</option>
<option value="20px">Large</option>
<option value="24px">Extra Large</option>
</select>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Invalid selected attribute and hardcoded options.

Two issues:

  1. HTML5 violation: Line 117 uses the selected attribute on an <option>. In React/JSX, use defaultValue on the <select> element instead.
  2. Ignores fontSizeOptions: The dropdown options are hardcoded and don't respect the fontSizeOptions attribute, same as renderButtons().
🐛 Proposed fix
 const renderDropdown = () => (
 	<select
 		className="font-size-dropdown"
+		defaultValue={defaultSize}
 		style={{
 			...buttonBaseStyle,
 			padding: '8px 32px 8px 12px',
 			appearance: 'none',
 			backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`,
 			backgroundRepeat: 'no-repeat',
 			backgroundPosition: 'right 10px center',
 			minWidth: '120px'
 		}}
 	>
-		<option value="14px">Small</option>
-		<option value="16px" selected>Default</option>
-		<option value="18px">Medium</option>
-		<option value="20px">Large</option>
-		<option value="24px">Extra Large</option>
+		{fontSizeOptions.map((size, index) => (
+			<option key={index} value={size.value}>
+				{size.label}
+			</option>
+		))}
 	</select>
 );

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 103 - 122, The
renderDropdown function currently hardcodes option elements and incorrectly uses
the selected attribute; change it to set the default selection via the select's
defaultValue prop (remove selected on the option), and generate the option list
by mapping over the fontSizeOptions attribute (same source used by
renderButtons) to create <option> elements with appropriate value and label;
ensure defaultValue falls back to the current fontSize attribute (or a sensible
default like "16px") so the dropdown reflects the saved setting.

Comment on lines 33 to 39
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
const storedSize = localStorage.getItem(storageKey);
let currentSize = storedSize || defaultSize;

// Apply initial font size
applyFontSize(currentSize);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wrap localStorage access in try-catch for resilience.

localStorage can throw SecurityError in private browsing mode (Safari), when storage is disabled, or when quota is exceeded. This could break the block initialization in restricted environments.

Suggested fix
 // Get stored font size from localStorage
 const storageKey = 'wedocs-font-size';
-const storedSize = localStorage.getItem(storageKey);
+let storedSize = null;
+try {
+    storedSize = localStorage.getItem(storageKey);
+} catch (e) {
+    // localStorage unavailable (private mode, disabled cookies, etc.)
+}
 let currentSize = storedSize || defaultSize;

Also wrap localStorage.setItem calls (lines 69, 102, 113, 127, 149) in try-catch blocks.

📝 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
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
const storedSize = localStorage.getItem(storageKey);
let currentSize = storedSize || defaultSize;
// Apply initial font size
applyFontSize(currentSize);
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
let storedSize = null;
try {
storedSize = localStorage.getItem(storageKey);
} catch (e) {
// localStorage unavailable (private mode, disabled cookies, etc.)
}
let currentSize = storedSize || defaultSize;
// Apply initial font size
applyFontSize(currentSize);
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 33 - 39, Wrap all
localStorage access in try-catch: when reading the stored font size (the
localStorage.getItem call that sets storedSize/currentSize before calling
applyFontSize) catch any exception and fall back to defaultSize before calling
applyFontSize(currentSize); likewise wrap every localStorage.setItem call (the
places that persist font size changes) in try-catch and silently handle/log the
error so the UI still updates (applyFontSize) even if storage is unavailable;
reference the storageKey/storedSize/currentSize variables and the applyFontSize
function when making these changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In @includes/API/API.php:
- Around line 301-303: The validate_callback closures (anonymous functions used
for 'validate_callback') declare unused $request and $key parameters which
static analysis flags; remove the unused parameters from each closure so they
accept only $param (apply to the four occurrences shown), or replace all four
with a single instance method like validate_numeric_id that takes one argument
and returns is_numeric($param) and reference it via ['validate_callback' => [
$this, 'validate_numeric_id' ]] in each endpoint to eliminate the extra
parameters and duplicate logic.
- Around line 338-351: The endpoint registered via register_rest_route for
'/ai-summary/generate' currently uses permission_callback => '__return_true',
allowing unauthenticated callers to invoke generate_ai_summary; change the
permission_callback to validate the request (e.g., verify REST nonce and
current_user_can or is_user_logged_in), or implement a custom permission
callback that checks wp_verify_nonce from the request headers and capability
checks (e.g., current_user_can('edit_posts')) and return WP_Error on failure;
additionally add optional rate-limiting/caching in generate_ai_summary (use
transients keyed by user ID or IP) to throttle repeated calls and prevent abuse.

In @src/blocks/AISummary/block.json:
- Around line 1-10: The PR includes a new block source manifest
"wedocs/ai-summary" (src/blocks/AISummary/block.json) but the PR
title/description claims only rebuilt assets under assets/build/*; update the PR
to accurately reflect that you are adding a new block source (or split this
commit into a separate PR that adds the new block and keep the current PR
limited to build artifacts), and ensure the review summary and changelog mention
the new block manifest ("AI-Powered Summary") so reviewers/users are not
surprised.

In @src/blocks/AISummary/edit.js:
- Around line 75-97: Remove or gate noisy console logging that may leak PII:
eliminate the console.log calls in loadSavedSummary (the "No saved summary
found..." log) and the other debug logs around generating AI summaries (lines
~110-131). Either delete those console.log statements or wrap them behind a
debug flag/ENV check (e.g., DEBUG or NODE_ENV/WORDPRESS_DEBUG) so they only run
in non-production/development builds; keep error handling and use processLogger
or setError for user-visible errors instead of unconditional console output.
- Around line 99-176: The generateSummary/clearSummary/saveCurrentSummary
handlers currently show a generic AI error; first use the editor data store
getCurrentPostType() (select('core/editor').getCurrentPostType()) to
early-return with setError('AI Summary is available only for Docs') and avoid
calling apiFetch when post type !== 'docs', and also use that same post-type
check to disable the UI buttons where they are rendered; additionally, in each
catch block (in generateSummary, clearSummary, saveCurrentSummary) parse the
apiFetch error (inspect err.code or err.status and err.message) and when a
404/invalid-post-type is detected replace the generic message with
setError(__('AI Summary is available only for Docs', 'wedocs-plugin')) otherwise
keep the existing AI-config error message.
- Around line 10-11: The import currently uses the deprecated
__experimentalBoxControl; replace that import with the stable BoxControl from
'@wordpress/components' (i.e., swap __experimentalBoxControl for BoxControl in
the import list), or better yet, if the component only manipulates
border-radius, import and use BorderRadiusControl instead of a generic box
control; update any usage of __experimentalBoxControl in the AISummary edit
component to the chosen stable symbol (BoxControl or BorderRadiusControl) and
adjust props names if needed to match the stable API.

In @src/blocks/AISummary/view.js:
- Around line 66-75: The click handler attached to generateBtn disables the
button and applies "Generating..." styling but only restores these UI states in
the error path, causing the button to remain disabled/styled after a successful
generation; update the async click handler (the anonymous function added via
generateBtn.addEventListener('click', async function() { ... })) to ensure the
generateBtn.disabled, generateBtn.textContent, generateBtn.style.opacity and
generateBtn.style.cursor are restored on success as well (or alternatively
hide/remove the button once content is inserted); perform the restoration in a
finally-like block or after the success branch so both success and error paths
reset the UI, and apply the same fix to the similar code block around lines
93-104 that manipulates the same generateBtn state.
- Around line 62-126: The code injects untrusted data.summary into the DOM via
contentDiv.innerHTML = `<div>${data.summary}</div>`, creating an XSS risk;
change both the generate flow handler (the generateBtn click listener) and the
load flow that use the same pattern to assign the summary as plain text (use
contentDiv.textContent or equivalent DOM text node insertion for data.summary)
or, if HTML is required, pass data.summary through a vetted sanitizer before
setting innerHTML; also ensure server-side sanitization of the summary output to
double-check malicious content.

In @src/blocks/FontSizeSwitcher/view.js:
- Around line 61-64: The parseInt calls in the FontSizeSwitcher view are missing
an explicit radix; update parseInt(size) and parseInt(defaultSize) to use radix
10 (e.g., parseInt(size, 10) and parseInt(defaultSize, 10)) so newSize and
defaultSizeNum are reliably decimal, and likewise change the other occurrences
where parseInt(currentSize) is used (the calls that feed currentSize at lines
referenced) to parseInt(currentSize, 10); keep the rest of the logic
(scaleFactor calculation, newSize/defaultSizeNum usage) the same.
- Around line 33-36: Wrap all localStorage accesses in try-catch blocks so
SecurityError or other exceptions don't break the font size switcher: when
reading the key (storageKey / localStorage.getItem used to set storedSize and
currentSize) catch errors and fall back to defaultSize, and for every
localStorage.setItem call (the calls around lines where setItem is used to
persist changes) catch and silently handle/log the error so the UI still updates
without throwing; keep using the same storageKey and currentSize logic but guard
every getItem/setItem with try-catch and a safe fallback.
🧹 Nitpick comments (12)
includes/API/API.php (2)

1493-1511: Extract duplicate post validation logic.

The same validation code appears in all four AI summary methods (lines 1496-1503, 1526-1533, 1555-1562, 1584-1591), checking that the post exists and has type 'docs'. This duplication violates the DRY principle and increases maintenance burden.

♻️ Proposed refactor: Create a validation helper method

Add a private helper method after the existing get_doc() method (around line 889):

/**
 * Validate that a post ID corresponds to a valid docs post.
 *
 * @since 2.0.0
 *
 * @param int $post_id Post ID to validate
 *
 * @return WP_Post|WP_Error Post object on success, WP_Error on failure
 */
private function validate_docs_post( $post_id ) {
    $post = get_post( $post_id );
    if ( ! $post || $post->post_type !== 'docs' ) {
        return new WP_Error(
            'wedocs_invalid_doc',
            __( 'Invalid documentation post.', 'wedocs' ),
            [ 'status' => 404 ]
        );
    }
    return $post;
}

Then simplify each method:

 public function get_ai_summary( $request ) {
     $post_id = (int) $request->get_param( 'id' );
-
-    $post = get_post( $post_id );
-    if ( ! $post || $post->post_type !== 'docs' ) {
-        return new WP_Error(
-            'wedocs_invalid_doc',
-            __( 'Invalid documentation post.', 'wedocs' ),
-            [ 'status' => 404 ]
-        );
-    }
+    
+    $post = $this->validate_docs_post( $post_id );
+    if ( is_wp_error( $post ) ) {
+        return $post;
+    }

     $summary = get_post_meta( $post_id, '_wedocs_ai_summary', true );

Apply the same pattern to save_ai_summary(), delete_ai_summary(), and generate_ai_summary().

Also applies to: 1522-1541, 1552-1570, 1581-1591


1665-1666: Consider making AI summary parameters configurable.

The max_tokens (500) and temperature (0.5) are hardcoded specifically for summary generation. While the inline comments explain the rationale, these values might benefit from being configurable or defined as constants for easier adjustment.

💡 Optional improvement: Define as class constants or settings

Option 1: Define as class constants near the top of the class:

/**
 * AI summary generation parameters
 */
const AI_SUMMARY_MAX_TOKENS = 500;
const AI_SUMMARY_TEMPERATURE = 0.5;

Then use:

     $response = $this->make_ai_api_call(
         $selected_provider,
         $provider_endpoint_config,
         $selected_model,
         $provider_config['api_key'],
         $prompt,
         $system_prompt,
-        500, // Shorter max tokens for summaries
-        0.5  // Lower temperature for more focused summaries
+        self::AI_SUMMARY_MAX_TOKENS,
+        self::AI_SUMMARY_TEMPERATURE
     );

Option 2: Add to AI settings configuration to allow admin customization.

src/blocks/FontSizeSwitcher/view.js (4)

42-54: Extract duplicate text element selector to a constant.

The selector 'p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code' appears at both line 46 and line 67. This violates the DRY principle and makes maintenance harder.

♻️ Proposed refactor

At the top of the block iteration (after line 17), add:

+		const TEXT_ELEMENTS_SELECTOR = 'p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code';
+
 		// Find the content element

Then use it in both places:

-		const textElements = contentElement.querySelectorAll('p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code');
+		const textElements = contentElement.querySelectorAll(TEXT_ELEMENTS_SELECTOR);

107-134: Consider tracking active button to avoid iteration.

Lines 125-126 reset all buttons by iterating through them on every click. For a small number of buttons this is fine, but tracking the currently active button would eliminate unnecessary iterations.

♻️ Optional performance optimization
 		// Button controls
 		function initButtonControls() {
 			const buttons = block.querySelectorAll('.font-size-option');
+			let activeButton = null;
 
 			buttons.forEach(button => {
 				const size = button.getAttribute('data-size');
 				const label = button.getAttribute('data-label');
 
 				// Highlight active button
 				if (size === currentSize) {
+					activeButton = button;
 					setActiveButton(button);
 				}
 
 				button.addEventListener('click', () => {
 					currentSize = size;
 					applyFontSize(size);
 					localStorage.setItem(storageKey, size);
 
 					// Update active state
-					buttons.forEach(btn => resetButtonStyle(btn));
+					if (activeButton) {
+						resetButtonStyle(activeButton);
+					}
+					activeButton = button;
 					setActiveButton(button);
 
 					// Update current display
 					if (currentDisplay) {
 						currentDisplay.textContent = `(${label})`;
 					}
 				});
 			});
 		}

213-225: Make size labels configurable instead of hard-coded.

The hard-coded sizeMap (lines 215-221) doesn't align with potentially custom font size options. If the block uses different sizes or labels, the display will show incorrect text.

Consider reading size labels from the block's configuration (data attributes) or deriving them from the fontSizeOptions defined in block.json. This would ensure labels match the actual configured options.

Based on the AI summary, block.json includes a fontSizeOptions attribute that likely contains label information.


227-241: Consider using CSS classes instead of inline styles.

The helper functions directly manipulate inline styles. Using CSS classes (e.g., .active, .filled) would be more maintainable and allow easier theming via stylesheets.

This is a minor code quality suggestion; the current implementation works correctly.

src/blocks/AISummary/block.json (2)

11-40: Avoid duplicating core supports with parallel custom attrs (style conflicts + poor Global Styles interop).

You enable supports.color/typography/spacing, but also store backgroundColor, textColor, titleFontSize, padding, margin, etc. as custom attributes. This can conflict with Gutenberg’s standard style/preset handling and makes theme/global-style integration harder. Consider relying on supports + style attribute (or rename custom attrs to avoid reserved meanings like backgroundColor/textColor).

Also applies to: 74-119


4-9: Consider restricting block usage to the docs post type (endpoints are /wp/v2/docs/...).

As-is, the block can be inserted anywhere, but the edit/view code targets /wp/v2/docs/${postId}/ai-summary. If the block is intended only for weDocs docs, consider adding postTypes to block.json (or make the editor UX gracefully handle non-doc contexts).

Also applies to: 46-49, 122-126

src/blocks/AISummary/edit.js (2)

49-72: Reduce duplicate setAttributes calls in the init effect (avoid extra renders).

You call setAttributes up to twice in the same effect; consider batching into a single object update when both blockId and postId need setting.


133-152: Prefer a WP modal/confirm component over window.confirm for editor accessibility/consistency.

confirm() works, but it’s a jarring UX in wp-admin and limits styling/accessibility patterns. Consider a small <Modal> confirmation flow.

src/blocks/AISummary/view.js (2)

41-59: Guard localStorage usage (can throw in some browsers/contexts).

localStorage.setItem/getItem can throw (blocked storage, quota, privacy modes). Wrap in try/catch so the block doesn’t break on click or init.


12-28: Normalize/validate postId before building REST paths.

You treat postId as a string and only loosely validate with isNaN. Consider const docId = Number.parseInt(postId, 10); if (!Number.isFinite(docId)) return; and use docId in URLs to avoid odd edge cases.

Also applies to: 63-64, 128-131

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de0d67b and d02e59e.

📒 Files selected for processing (6)
  • includes/API/API.php
  • src/blocks/AISummary/block.json
  • src/blocks/AISummary/edit.js
  • src/blocks/AISummary/save.js
  • src/blocks/AISummary/view.js
  • src/blocks/FontSizeSwitcher/view.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/blocks/AISummary/save.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/blocks/AISummary/edit.js (2)
src/blocks/AISummary/save.js (2)
  • attributes (4-22)
  • blockProps (55-69)
src/blocks/AISummary/view.js (6)
  • blockId (51-51)
  • postId (12-12)
  • response (77-84)
  • response (139-145)
  • isCollapsible (10-10)
  • isOpenByDefault (11-11)
includes/API/API.php (3)
assets/js/frontend.js (1)
  • param (61-61)
includes/functions.php (1)
  • wedocs_get_ai_provider_configs (617-668)
src/blocks/AISummary/view.js (2)
  • response (77-84)
  • response (139-145)
src/blocks/AISummary/view.js (2)
src/blocks/AISummary/edit.js (3)
  • postId (65-65)
  • error (45-45)
  • loadSavedSummary (75-97)
src/blocks/DocActions/view.js (1)
  • originalText (197-197)
🪛 PHPMD (2.15.0)
includes/API/API.php

345-345: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)


345-345: Avoid unused parameters such as '$key'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (4)
src/blocks/FontSizeSwitcher/view.js (4)

9-31: LGTM! Good graceful handling.

The configuration reading with fallback defaults and the early-return pattern when content elements are not found is well-implemented.


91-104: LGTM!

The initial font size application and display style routing logic is clean and well-structured.


188-210: LGTM!

The dropdown controls implementation is straightforward and correct.


243-246: LGTM!

The display initialization properly checks for stored values before updating.

Comment on lines +301 to +303
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param );
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused parameters from validate callbacks.

The validation closures declare $request and $key parameters but only use $param. While PHP allows this, it creates unnecessary noise flagged by static analysis tools.

♻️ Proposed fix: Remove unused parameters
             'id' => [
-                'validate_callback' => function( $param, $request, $key ) {
+                'validate_callback' => function( $param ) {
                     return is_numeric( $param );
                 }
             ],

Apply this change to all four occurrences (lines 301-303, 313-315, 330-332, 345-347).

Alternatively, extract to a reusable method:

private function validate_numeric_id( $param ) {
    return is_numeric( $param );
}

Then reference it as 'validate_callback' => [ $this, 'validate_numeric_id' ] in each endpoint.

Also applies to: 313-315, 330-332, 345-347

🤖 Prompt for AI Agents
In @includes/API/API.php around lines 301 - 303, The validate_callback closures
(anonymous functions used for 'validate_callback') declare unused $request and
$key parameters which static analysis flags; remove the unused parameters from
each closure so they accept only $param (apply to the four occurrences shown),
or replace all four with a single instance method like validate_numeric_id that
takes one argument and returns is_numeric($param) and reference it via
['validate_callback' => [ $this, 'validate_numeric_id' ]] in each endpoint to
eliminate the extra parameters and duplicate logic.

Comment on lines +338 to +351
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/ai-summary/generate', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'generate_ai_summary' ],
'permission_callback' => '__return_true', // Allow public access for frontend generation
'args' => [
'id' => [
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param );
}
],
],
],
] );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical security issue: Public AI generation endpoint enables abuse.

The /ai-summary/generate endpoint uses __return_true for permission checks, allowing unauthenticated users to trigger AI API calls. This creates multiple vulnerabilities:

  • Cost explosion: Attackers can make unlimited requests, consuming API quotas and incurring significant charges
  • DoS vector: Flooding this endpoint can exhaust rate limits and degrade service
  • No accountability: Anonymous requests cannot be traced or throttled per-user

The frontend code sends a nonce header, but the endpoint doesn't validate it due to __return_true.

🔒 Proposed fix: Require authentication and consider rate limiting

At minimum, change the permission callback to validate authentication:

 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/ai-summary/generate', [
     [
         'methods'             => WP_REST_Server::CREATABLE,
         'callback'            => [ $this, 'generate_ai_summary' ],
-        'permission_callback' => '__return_true', // Allow public access for frontend generation
+        'permission_callback' => [ $this, 'ai_generate_permissions_check' ],
         'args'                => [
             'id' => [
                 'validate_callback' => function( $param, $request, $key ) {
                     return is_numeric( $param );
                 }
             ],
         ],
     ],
 ] );

Additionally, consider implementing:

  • Rate limiting per user/IP (e.g., via transients keyed by user ID or IP address)
  • Caching to prevent regenerating summaries within a time window
  • Capability checks if this should be restricted to editors/admins only
🧰 Tools
🪛 PHPMD (2.15.0)

345-345: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)


345-345: Avoid unused parameters such as '$key'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In @includes/API/API.php around lines 338 - 351, The endpoint registered via
register_rest_route for '/ai-summary/generate' currently uses
permission_callback => '__return_true', allowing unauthenticated callers to
invoke generate_ai_summary; change the permission_callback to validate the
request (e.g., verify REST nonce and current_user_can or is_user_logged_in), or
implement a custom permission callback that checks wp_verify_nonce from the
request headers and capability checks (e.g., current_user_can('edit_posts')) and
return WP_Error on failure; additionally add optional rate-limiting/caching in
generate_ai_summary (use transients keyed by user ID or IP) to throttle repeated
calls and prevent abuse.

Comment on lines +1 to +10
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wedocs/ai-summary",
"version": "1.0.0",
"title": "AI-Powered Summary",
"category": "text",
"icon": "lightbulb",
"description": "Display an AI-generated summary of documentation content with collapsible functionality.",
"example": {},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PR scope mismatch: this adds new source (not “build artifacts only”).

PR objectives say only rebuilt assets/build/*, but this introduces a new block source manifest. Please reconcile the PR description/scope (or split PRs) so reviewers/users aren’t surprised.

🤖 Prompt for AI Agents
In @src/blocks/AISummary/block.json around lines 1 - 10, The PR includes a new
block source manifest "wedocs/ai-summary" (src/blocks/AISummary/block.json) but
the PR title/description claims only rebuilt assets under assets/build/*; update
the PR to accurately reflect that you are adding a new block source (or split
this commit into a separate PR that adds the new block and keep the current PR
limited to build artifacts), and ensure the review summary and changelog mention
the new block manifest ("AI-Powered Summary") so reviewers/users are not
surprised.

Comment on lines +23 to +39
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalFontFamily": true,
"__experimentalFontWeight": true,
"__experimentalDefaultControls": {
"fontSize": true
}
},
"spacing": {
"margin": true,
"padding": true,
"__experimentalDefaultControls": {
"padding": true,
"margin": true
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's explore the repository structure to find version requirements
find . -maxdepth 2 -type f \( -name "package.json" -o -name "README.md" -o -name "composer.json" -o -name "*.md" \) | head -20

Repository: weDevsOfficial/wedocs-plugin

Length of output: 114


🏁 Script executed:

# Check for WordPress version requirements in common files
rg "wordpress|requires|version" -t md -t json --max-count=10 | head -30

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1980


🏁 Script executed:

# Look at the block.json file to understand apiVersion and other context
cat -n src/blocks/AISummary/block.json | head -50

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1461


🌐 Web query:

Gutenberg block.json typography __experimentalFontFamily __experimentalFontWeight support stable WordPress version

💡 Result:

Short answer: The experimental keys (__experimentalFontFamily, __experimentalFontWeight) were mapped to stable supports (fontFamily, fontWeight) and landed in WordPress core as part of the stabilization work included in WordPress 6.5. [1][2]

Sources

  • WordPress Trac ticket describing the mapping of experimental typography supports to stable keys. [1]
  • WP developer docs / WP_Theme_JSON code showing the stabilized/theme-json schema changes (since 6.5.0) and typography support usage. [2][3]

References

  1. core.trac.wordpress.org ticket #61728 (stabilize experimental block supports). [1]
  2. WP_Theme_JSON class / developer handbook (theme.json typography and schema, since 6.5.0). [2][3]

🌐 Web query:

Gutenberg block supports spacing __experimentalDefaultControls stability WordPress 6.0+

💡 Result:

Short answer:

  • Spacing support (supports.spacing: margin, padding, blockGap) is a stable block supports API and available in WordPress 6.0+ (docs). [1][2]
  • The experimental flag __experimentalDefaultControls is planned to be stabilized/renamed to defaultControls (tracked in core Trac ticket #61728 and Gutenberg issues); the mapping/stabilization work is documented but required syncing from Gutenberg into core. Use the stable key defaultControls when your WP core version/export supports it; otherwise continue using the experimental name until your WP version includes the core change. [3][4]

Sources:

  1. Block supports — spacing (WordPress Block Editor Handbook). [1]
  2. Layout / spacing changes (WordPress core notes, layout refactor / block-level spacing). [2]
  3. Trac ticket #61728 — mapping __experimentalDefaultControls → defaultControls and stabilization plan. [3]
  4. Gutenberg discussions/PRs referenced from the ticket (stabilization tracking). [3]

🏁 Script executed:

# Check composer.json for WordPress version requirement
cat -n composer.json

Repository: weDevsOfficial/wedocs-plugin

Length of output: 816


🏁 Script executed:

# Check readme.md for WordPress compatibility requirements
head -100 readme.md | cat -n

Repository: weDevsOfficial/wedocs-plugin

Length of output: 2242


🏁 Script executed:

# Find the main plugin file
find . -maxdepth 1 -name "*.php" -type f

Repository: weDevsOfficial/wedocs-plugin

Length of output: 84


🏁 Script executed:

# Check the main plugin file header for WordPress version requirements
head -50 wedocs.php 2>/dev/null || head -50 index.php 2>/dev/null || head -50 weDocs.php 2>/dev/null || rg "Requires:\s*WordPress|minimum.*wordpress" -i -A 2 --max-count=5

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1728


🌐 Web query:

WordPress 6.5 typography fontFamily fontWeight block supports stable

💡 Result:

Short answer: Yes — in WordPress 6.5 the typography fontFamily support was stabilized (mapped from __experimentalFontFamily → fontFamily) and font-weight controls (fontWeight) are available via the theme.json / block supports system.

Sources:

  • Trac ticket documenting stabilization mapping (__experimentalFontFamily → fontFamily). [1]
  • Block supports / typography reference in the Block Editor Handbook (shows typography block support and how supports extend attributes). [2]
  • Theme/typography docs showing theme.json typography options including fontFamilies and fontWeight. [3]

References:
[1] https://core.trac.wordpress.org/ticket/61728
[2] https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/
[3] https://developer.wordpress.org/themes/global-settings-and-styles/settings/typography/


🌐 Web query:

Gutenberg __experimentalDefaultControls when will stabilize defaultControls WordPress 2025

💡 Result:

Short answer: It has not been stabilized in WordPress core as of January 9, 2026, and there is no public date published for promoting __experimentalDefaultControls to a stable API. See WordPress/Gutenberg release notes and discussions for stabilized APIs and reverts. [1][2][3]

Quick pointers to track progress:

  • Follow the Make WordPress Core and WordPress Developer blog posts (stabilizations are announced there). [1][2]
  • Watch the Gutenberg repository and PR/issue mentions of "__experimentalDefaultControls" on GitHub for any timeline or PR to remove the experimental prefix. (No such stabilization PR was published as of today.) [3]

Sources:

  • Make WordPress Core — Miscellaneous Block Editor changes (stabilizations summary). [1]
  • WordPress Developer Blog — developer updates about stabilized/reverted experimental block supports. [2]
  • WordPress.org support / examples showing __experimentalDefaultControls usage (still experimental). [3]

Address the one remaining experimental key and declare minimum WordPress version.

Two of the three experimental keys have been stabilized in WordPress 6.5 (__experimentalFontFamily and __experimentalFontWeight now use stable fontFamily and fontWeight). However, __experimentalDefaultControls in spacing remains experimental with no announced stabilization timeline as of January 2026.

More importantly, since the plugin declares no minimum WordPress version requirement, clarify which version(s) this block targets. The plugin is tested with WordPress 6.5, but if it supports older versions, the typography keys may not behave as expected in those releases. Either declare a minimum WordPress requirement or add version-specific handling for the unresolved spacing.__experimentalDefaultControls key.

Comment on lines +10 to +11
__experimentalBoxControl as BoxControl
} from '@wordpress/components';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and see the actual imports
fd -t f "edit.js" | grep AISummary

Repository: weDevsOfficial/wedocs-plugin

Length of output: 100


🏁 Script executed:

# Check the imports and usage around lines 10-11 and 389-395
if [ -f "src/blocks/AISummary/edit.js" ]; then
  echo "=== Lines 1-20 (context around line 10-11) ===" 
  head -20 src/blocks/AISummary/edit.js
  echo ""
  echo "=== Lines 385-400 (context around line 389-395) ===" 
  sed -n '385,400p' src/blocks/AISummary/edit.js
fi

Repository: weDevsOfficial/wedocs-plugin

Length of output: 1232


🏁 Script executed:

# Check package.json for @wordpress/components version
if [ -f "package.json" ]; then
  jq '.dependencies."@wordpress/components" // .devDependencies."@wordpress/components"' package.json
fi

Repository: weDevsOfficial/wedocs-plugin

Length of output: 82


🌐 Web query:

__experimentalBoxControl @wordpress/components API stability 2025

💡 Result:

Short answer: __experimentalBoxControl was stabilized in 2024–2025 and is deprecated — use the stable BoxControl import instead. The experimental export remains available for compatibility but is marked deprecated and may show warnings; migrate your code to import { BoxControl } from '@wordpress/components'. [1][2]

Sources:

  • Announcements noting BoxControl stabilization and deprecation of the __experimental export (WordPress Core posts, Oct 2024 & Mar 2025). [1][2]

🌐 Web query:

@wordpress/components BoxControl stable alternative border radius

💡 Result:

Use BorderRadiusControl (the dedicated border-radius control) instead of using BoxControl for radius. BorderRadiusControl has been available in Gutenberg/WordPress component releases and the components package updates note stabilizing/updating the border-related controls (BorderBoxControl / BorderControl / BorderRadiusControl). [1][2][3]

Sources:

  • @wordpress/components changelog (notes about BoxControl/BorderBoxControl/BorderControl updates). [1]
  • BorderControl docs (notes that radius is a separate concern / shape). [2]
  • Make WordPress Core posts listing stabilization and updates to border-related controls. [3]

🌐 Web query:

__experimentalBoxControl deprecated replacement WordPress Gutenberg

💡 Result:

Short answer: __experimentalBoxControl was stabilized — use BoxControl instead. Import it from @wordpress/components (or the stabilized package export shown in the release notes). [1][2]

Example:

  • Old (experimental): import { __experimentalBoxControl as BoxControl } from '@wordpress/components';
  • New (stable): import { BoxControl } from '@wordpress/components'. [1][2]

Sources:

  1. Make WordPress Core — Updates to user-interface components (notes about BoxControl stabilization). [1]
  2. Make WordPress Core / Editor components updates (BoxControl promoted to stable). [2]

Update to the stable BoxControl import; the __experimentalBoxControl API is now stable.

__experimentalBoxControl was stabilized in 2024–2025. Replace the import with import { BoxControl } from '@wordpress/components' to eliminate the deprecated experimental export. Alternatively, for border-radius specifically, consider using the dedicated BorderRadiusControl import, which is semantically more appropriate for this use case than a generic box control.

🤖 Prompt for AI Agents
In @src/blocks/AISummary/edit.js around lines 10 - 11, The import currently uses
the deprecated __experimentalBoxControl; replace that import with the stable
BoxControl from '@wordpress/components' (i.e., swap __experimentalBoxControl for
BoxControl in the import list), or better yet, if the component only manipulates
border-radius, import and use BorderRadiusControl instead of a generic box
control; update any usage of __experimentalBoxControl in the AISummary edit
component to the chosen stable symbol (BoxControl or BorderRadiusControl) and
adjust props names if needed to match the stable API.

Comment on lines +62 to +126
// Handle AI summary generation on frontend
if (!hasContent && postId) {
const generateBtn = block.querySelector('.ai-summary-generate-btn');
if (generateBtn) {
generateBtn.addEventListener('click', async function() {
// Prevent multiple clicks
if (generateBtn.disabled) return;

generateBtn.disabled = true;
const originalText = generateBtn.textContent;
generateBtn.textContent = generateBtn.getAttribute('data-generating-text') || 'Generating...';
generateBtn.style.opacity = '0.6';
generateBtn.style.cursor = 'not-allowed';

try {
const response = await fetch(`/wp-json/wp/v2/docs/${postId}/ai-summary/generate`, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': window.wpApiSettings?.nonce || ''
}
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || 'Failed to generate summary');
}

const data = await response.json();

if (data.success && data.summary) {
// Replace the placeholder with the generated summary
const contentDiv = block.querySelector('.ai-summary-content');
if (contentDiv) {
contentDiv.innerHTML = `<div>${data.summary}</div>`;
block.setAttribute('data-has-content', 'true');
block.classList.remove('no-summary');
block.classList.add('has-summary');
}
} else {
throw new Error('No summary was generated');
}
} catch (error) {
console.error('AI Summary generation error:', error);

// Show error message
const contentDiv = block.querySelector('.ai-summary-content');
if (contentDiv) {
const errorMsg = document.createElement('p');
errorMsg.style.color = '#d63638';
errorMsg.style.fontSize = '14px';
errorMsg.textContent = error.message || 'Failed to generate summary. Please try again later.';
contentDiv.appendChild(errorMsg);
}

// Re-enable button
generateBtn.disabled = false;
generateBtn.textContent = originalText;
generateBtn.style.opacity = '1';
generateBtn.style.cursor = 'pointer';
}
});
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: XSS risk from injecting data.summary via innerHTML.

Both the generate flow and load flow do contentDiv.innerHTML = \

${data.summary}
`;. If data.summary` contains HTML (or attacker-controlled content via compromised endpoint/meta), this becomes script injection on the frontend.

At minimum, render as text (textContent). If you truly need HTML, sanitize with a robust HTML sanitizer (and also sanitize server-side).

Proposed safer default (render as text)
- contentDiv.innerHTML = `<div>${data.summary}</div>`;
+ const wrapper = document.createElement('div');
+ wrapper.textContent = data.summary;
+ contentDiv.replaceChildren(wrapper);

Also applies to: 151-160

🤖 Prompt for AI Agents
In @src/blocks/AISummary/view.js around lines 62 - 126, The code injects
untrusted data.summary into the DOM via contentDiv.innerHTML =
`<div>${data.summary}</div>`, creating an XSS risk; change both the generate
flow handler (the generateBtn click listener) and the load flow that use the
same pattern to assign the summary as plain text (use contentDiv.textContent or
equivalent DOM text node insertion for data.summary) or, if HTML is required,
pass data.summary through a vetted sanitizer before setting innerHTML; also
ensure server-side sanitization of the summary output to double-check malicious
content.

Comment on lines +66 to +75
generateBtn.addEventListener('click', async function() {
// Prevent multiple clicks
if (generateBtn.disabled) return;

generateBtn.disabled = true;
const originalText = generateBtn.textContent;
generateBtn.textContent = generateBtn.getAttribute('data-generating-text') || 'Generating...';
generateBtn.style.opacity = '0.6';
generateBtn.style.cursor = 'not-allowed';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: generate button stays disabled / “Generating…” styling after success.

On success you never restore generateBtn.disabled, text, opacity, or cursor (you only restore on error). Either restore state or remove/hide the button once content is present.

One possible fix (restore on success)
 					if (data.success && data.summary) {
 						// Replace the placeholder with the generated summary
 						const contentDiv = block.querySelector('.ai-summary-content');
 						if (contentDiv) {
-							contentDiv.innerHTML = `<div>${data.summary}</div>`;
+							const wrapper = document.createElement('div');
+							wrapper.textContent = data.summary;
+							contentDiv.replaceChildren(wrapper);
 							block.setAttribute('data-has-content', 'true');
 							block.classList.remove('no-summary');
 							block.classList.add('has-summary');
 						}
+						// Restore button UI (or alternatively: remove the button)
+						generateBtn.disabled = false;
+						generateBtn.textContent = originalText;
+						generateBtn.style.opacity = '1';
+						generateBtn.style.cursor = 'pointer';
 					} else {

Also applies to: 93-104

🤖 Prompt for AI Agents
In @src/blocks/AISummary/view.js around lines 66 - 75, The click handler
attached to generateBtn disables the button and applies "Generating..." styling
but only restores these UI states in the error path, causing the button to
remain disabled/styled after a successful generation; update the async click
handler (the anonymous function added via generateBtn.addEventListener('click',
async function() { ... })) to ensure the generateBtn.disabled,
generateBtn.textContent, generateBtn.style.opacity and generateBtn.style.cursor
are restored on success as well (or alternatively hide/remove the button once
content is inserted); perform the restoration in a finally-like block or after
the success branch so both success and error paths reset the UI, and apply the
same fix to the similar code block around lines 93-104 that manipulates the same
generateBtn state.

Comment on lines +33 to +36
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
const storedSize = localStorage.getItem(storageKey);
let currentSize = storedSize || defaultSize;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add error handling for localStorage access.

Accessing localStorage can throw a SecurityError in private browsing mode or when storage is disabled. This will break the entire font size switcher functionality.

🛡️ Proposed fix to wrap localStorage access in try-catch
 		// Get stored font size from localStorage
 		const storageKey = 'wedocs-font-size';
-		const storedSize = localStorage.getItem(storageKey);
+		let storedSize = null;
+		try {
+			storedSize = localStorage.getItem(storageKey);
+		} catch (e) {
+			console.warn('Font Size Switcher: localStorage access denied', e);
+		}
 		let currentSize = storedSize || defaultSize;

Apply the same pattern to all localStorage.setItem() calls at lines 122, 155, 166, 180, and 202.

📝 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
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
const storedSize = localStorage.getItem(storageKey);
let currentSize = storedSize || defaultSize;
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
let storedSize = null;
try {
storedSize = localStorage.getItem(storageKey);
} catch (e) {
console.warn('Font Size Switcher: localStorage access denied', e);
}
let currentSize = storedSize || defaultSize;
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 33 - 36, Wrap all
localStorage accesses in try-catch blocks so SecurityError or other exceptions
don't break the font size switcher: when reading the key (storageKey /
localStorage.getItem used to set storedSize and currentSize) catch errors and
fall back to defaultSize, and for every localStorage.setItem call (the calls
around lines where setItem is used to persist changes) catch and silently
handle/log the error so the UI still updates without throwing; keep using the
same storageKey and currentSize logic but guard every getItem/setItem with
try-catch and a safe fallback.

Comment on lines +61 to +64
// Calculate the scale factor from default size
const newSize = parseInt(size);
const defaultSizeNum = parseInt(defaultSize);
const scaleFactor = newSize / defaultSizeNum;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Specify radix parameter for parseInt calls.

Lines 62 and 63 use parseInt() without specifying a radix. While unlikely with px values, omitting the radix can lead to unexpected octal interpretation of strings with leading zeros.

🔧 Proposed fix
 			// Calculate the scale factor from default size
-			const newSize = parseInt(size);
-			const defaultSizeNum = parseInt(defaultSize);
+			const newSize = parseInt(size, 10);
+			const defaultSizeNum = parseInt(defaultSize, 10);
 			const scaleFactor = newSize / defaultSizeNum;

Apply the same fix to lines 144, 161, and 175 where parseInt(currentSize) is used.

📝 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
// Calculate the scale factor from default size
const newSize = parseInt(size);
const defaultSizeNum = parseInt(defaultSize);
const scaleFactor = newSize / defaultSizeNum;
// Calculate the scale factor from default size
const newSize = parseInt(size, 10);
const defaultSizeNum = parseInt(defaultSize, 10);
const scaleFactor = newSize / defaultSizeNum;
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 61 - 64, The parseInt calls
in the FontSizeSwitcher view are missing an explicit radix; update
parseInt(size) and parseInt(defaultSize) to use radix 10 (e.g., parseInt(size,
10) and parseInt(defaultSize, 10)) so newSize and defaultSizeNum are reliably
decimal, and likewise change the other occurrences where parseInt(currentSize)
is used (the calls that feed currentSize at lines referenced) to
parseInt(currentSize, 10); keep the rest of the logic (scaleFactor calculation,
newSize/defaultSizeNum usage) the same.

Comment on lines +159 to +185
if (decreaseBtn) {
decreaseBtn.addEventListener('click', () => {
const current = parseInt(currentSize);
if (current > 12) {
const newSize = (current - 2) + 'px';
currentSize = newSize;
applyFontSize(newSize);
localStorage.setItem(storageKey, newSize);
if (slider) slider.value = current - 2;
updateDisplay(newSize);
}
});
}

if (increaseBtn) {
increaseBtn.addEventListener('click', () => {
const current = parseInt(currentSize);
if (current < 28) {
const newSize = (current + 2) + 'px';
currentSize = newSize;
applyFontSize(newSize);
localStorage.setItem(storageKey, newSize);
if (slider) slider.value = current + 2;
updateDisplay(newSize);
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Extract hard-coded font size limits and step to configurable attributes.

Lines 162, 176 hard-code minimum (12) and maximum (28) font sizes, and lines 163, 177 hard-code the step size (2). These should be configurable via data attributes to allow flexibility per block instance.

⚙️ Proposed fix to make limits configurable

After line 17, add:

+		const minSize = parseInt(block.getAttribute('data-min-size') || '12', 10);
+		const maxSize = parseInt(block.getAttribute('data-max-size') || '28', 10);
+		const stepSize = parseInt(block.getAttribute('data-step-size') || '2', 10);

Then update the decrease and increase handlers:

 			if (decreaseBtn) {
 				decreaseBtn.addEventListener('click', () => {
 					const current = parseInt(currentSize);
-					if (current > 12) {
-						const newSize = (current - 2) + 'px';
+					if (current > minSize) {
+						const newSize = (current - stepSize) + 'px';
 						currentSize = newSize;
 						applyFontSize(newSize);
 						localStorage.setItem(storageKey, newSize);
-						if (slider) slider.value = current - 2;
+						if (slider) slider.value = current - stepSize;
 						updateDisplay(newSize);
 					}
 				});
 			}
 
 			if (increaseBtn) {
 				increaseBtn.addEventListener('click', () => {
 					const current = parseInt(currentSize);
-					if (current < 28) {
-						const newSize = (current + 2) + 'px';
+					if (current < maxSize) {
+						const newSize = (current + stepSize) + 'px';
 						currentSize = newSize;
 						applyFontSize(newSize);
 						localStorage.setItem(storageKey, newSize);
-						if (slider) slider.value = current + 2;
+						if (slider) slider.value = current + stepSize;
 						updateDisplay(newSize);
 					}
 				});
 			}

Also update block.json to include these new attributes.

Committable suggestion skipped: line range outside the PR's diff.

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.

2 participants