-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Initial scaffold of Toolbar #432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Integrate toolbar demo with real WordPress instance using wp-env: - Add CORS headers via mu-plugin for localhost:3000 - Fetch real WordPress user via REST API - Fetch posts using WPGraphQL - Update demo UI with real data workflow - Document WordPress integration in README Updates: - Use query parameter format for REST API (/?rest_route=) - Use query parameter format for GraphQL (/?graphql) - Add lifecycle script for permalink setup - Update demo instructions for real WordPress workflow
…base styles for full developer control
…tion Switch vanilla example from hardcoded configs to auto-generated approach. Files like mu-plugin.php, .htaccess, and .wp-env.json are now generated from centralized templates by the setup-env.js script. - Add setup-env.js script that generates configs from templates - Remove mu-plugin.php, .htaccess, .wp-env.json from version control - Update .gitignore to exclude auto-generated example files
Add comprehensive Next.js App Router example demonstrating the toolbar with real WordPress integration. - Complete Next.js example with App Router - Hash-based port calculation for deterministic ports - Template-based configuration (mu-plugin, .htaccess, .wp-env.json) - One-command startup with concurrently - Real WordPress data integration (no mocks) - WPGraphQL for post fetching - Responsive toolbar with real user data
Enhance toolbar with configurable options and example improvements: Toolbar Package: - Add position config (top/bottom) with CSS positioning - Add theme config with custom CSS variables and className - Add getConfig/setConfig methods for runtime updates - Improve CSS with position-aware dropdown menus Vanilla Example: - Use hash-based port calculation from setup-env.js - Load ports from generated .env file in Vite config - Update scripts to use template-based configuration - Add pnpm workspace configuration - Remove hardcoded mu-plugin.php (now generated)
Introduces three reusable, single-responsibility plugins for headless WordPress development: - hwp-cors-local: Enables CORS headers for local development environments - hwp-frontend-links: Adds "View on Frontend" links to WordPress admin interface - hwp-wp-env-helpers: Fixes wp-env REST API routing quirks Each plugin is independently configurable via HEADLESS_FRONTEND_URL constant and can be used standalone or in combination based on project needs.
… plugins Updates toolbar demo examples to use production-ready configuration: - WP_HOME points to WordPress instance (not frontend) - HEADLESS_FRONTEND_URL configured separately for frontend application - Replaces mu-plugin template approach with modular plugin references - Updates PHP version to 8.3 - Removes mu-plugin mapping in favor of standard plugin loading This allows WordPress to remain fully functional while supporting headless architecture, and enables composable plugin usage across different projects.
wp-env cannot resolve relative plugin paths from subdirectories. Changed setup-env.js to generate absolute paths using path.resolve() and path.join(), ensuring plugins load correctly in both vanilla and Next.js examples. Also added comprehensive implementation breakdown documentation.
Updated example:start scripts in both toolbar demos to use concurrently properly. WordPress and frontend now start together using concurrently, with a 5 second delay on frontend to ensure WordPress completes startup. Changes: - Separated wp-env setup (npm install) into wp:ensure script - Modified wp:start to only run wp-env start (no blocking npm install) - Used concurrently to run both services in parallel - Added sleep 5 before frontend start to allow wp-env initialization
… DRY - Remove mu-plugin.php template and generation code - Use relative paths for plugin references (portable across machines) - Simplify npm scripts (inline npm install, remove wp:ensure) - Add .gitignore files to exclude generated files - Align both toolbar examples with consistent configuration
Refactored plugin to support both single and multiple frontend configurations via HEADLESS_FRONTEND_URL or HWP_FRONTEND_LINKS. Admin bar and post row actions now display a 'View in [Label]' link for each configured frontend, improving flexibility for sites with multiple environments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a comprehensive headless WordPress toolkit with a modular plugin architecture and production-ready toolbar package. It transforms the previous template-based approach into a maintainable, reusable system with dynamic configuration and comprehensive documentation.
- Creates three single-responsibility WordPress plugins for CORS, frontend links, and wp-env helpers
- Introduces the @wpengine/hwp-toolbar package with framework-agnostic design and React hooks
- Updates examples to use the new plugin architecture with dynamic port calculation
- Provides complete documentation and production-ready configuration patterns
Reviewed Changes
Copilot reviewed 44 out of 47 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
packages/toolbar/* | Core toolbar package with TypeScript, React hooks, and vanilla renderer |
plugins/* | Three modular WordPress plugins for headless development |
examples//toolbar-demo/ | Updated demo applications using new architecture |
scripts/get-ports.js | Dynamic port calculation system for multi-example setup |
IMPLEMENTATION_BREAKDOWN.md | Comprehensive implementation documentation |
Comments suppressed due to low confidence (1)
plugins/hwp-wp-env-helpers/hwp-wp-env-helpers.php:1
- Direct $_SERVER access should be validated. Consider using
sanitize_text_field()
or checking if the key exists before accessing.
<?php
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
@ahuseyn I'm working on resolving these last build issues but i'll mark it ready for review now :) |
Co-authored-by: Copilot <[email protected]>
📦 Plugin Artifacts Ready!Download from GitHub Actions run Available plugins:
See the "Artifacts" section at the bottom of the Actions run page |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 55 out of 58 changed files in this pull request and generated 5 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
const response = await fetch(`${WP_URL}/?rest_route=/wp/v2/users/1`); | ||
|
||
if (response.ok) { | ||
const user = await response.json(); |
Copilot
AI
Oct 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the WordPress connection fails, the error handling only checks response.ok but doesn't provide specific guidance about the likely cause. Consider adding more specific error messages for common scenarios like CORS issues or WordPress not running.
Copilot uses AI. Check for mistakes.
Update plugin initialization to activate when either WP_ENVIRONMENT_TYPE is 'local' OR WP_DEBUG is true, matching the documented behavior. Previously, the code only checked WP_ENVIRONMENT_TYPE, creating a mismatch with the README and IMPLEMENTATION_BREAKDOWN docs which stated both conditions were supported. Files modified: - plugins/hwp-cors-local/hwp-cors-local.php: Add WP_DEBUG check - plugins/hwp-cors-local/README.md: Clarify OR logic - IMPLEMENTATION_BREAKDOWN.md: Update feature description
Add complete set of CSS custom properties to match documented theming capabilities in README. Includes WordPress admin-inspired default values. CSS Variables Added: - --hwp-toolbar-bg: Background color (#23282d) - --hwp-toolbar-border: Border color (#32373c) - --hwp-toolbar-text: Text color (#ffffff) - --hwp-toolbar-text-hover: Hover text color (#72aee6) - --hwp-toolbar-primary: Primary action color (#0073aa) - --hwp-toolbar-primary-hover: Primary hover (#005177) - --hwp-toolbar-danger: Danger action color (#dc3232) - --hwp-toolbar-danger-hover: Danger hover (#a00) - --hwp-toolbar-divider: Divider color (#464b50) - --hwp-toolbar-shadow: Box shadow (rgba(0, 0, 0, 0.3)) - --hwp-toolbar-z-index: Stacking context (9999) - --hwp-toolbar-font-family: Font stack - --hwp-toolbar-font-size: Base font size (13px) Enhancements: - Applied variables to all toolbar elements - Added hover states with smooth transitions - Improved button, link, and dropdown styling - Added proper borders and shadows - Enhanced visual feedback for interactive elements
Replace unsafe type casting with proper getConfig() method to access toolbar configuration. This eliminates the TypeScript 'as any' escape hatch and provides proper type safety. Before: const config = (toolbar as any).config; After: const config = toolbar.getConfig(); The getConfig() method is already publicly exposed by the Toolbar class and returns a properly typed ToolbarConfig object.
Add documentation for HWP_FRONTEND_LINKS constant which enables configuring multiple frontend environments. This feature was already implemented in the code but not documented. Users can now easily see how to configure multiple frontend targets (production, staging, local) which will each appear as separate "View in [Label]" links in the WordPress admin. Example configuration added showing three frontends with custom labels.
Add detailed error handling to vanilla toolbar demo login function with specific guidance for common failure scenarios. Improvements: - 404 errors: Guide user to WordPress admin for initial setup - 500+ errors: Indicate server issues and suggest checking if WP is running - Network errors: Distinguish between connection failures and provide troubleshooting steps (WordPress not running, CORS issues, wrong URL) - Generic errors: Provide fallback error message with console reference This addresses Copilot feedback about providing more specific error messages instead of generic "response.ok" checks.
Add comprehensive documentation explaining the port calculation strategy and rationale behind PORT_RANGE and PORT_BASE constants. Documentation now explains: - Why PORT_RANGE is 900 (space for many examples, manageable range) - Why PORT_BASE is 100 (avoid common default ports like 3000, 8000) - The full port calculation algorithm (hash -> decimal -> modulo) - Expected port ranges (frontend: 3100-3999, WordPress: 8100-8999) - Key properties: deterministic and collision-resistant Addresses Copilot feedback about documenting "magic numbers."
Enhance dropdown menus with full keyboard navigation, ARIA attributes, and click-outside-to-close behavior for improved accessibility and UX. Accessibility Improvements: - ARIA attributes: aria-haspopup, aria-expanded, role="menu", role="menuitem" - Keyboard navigation: Enter/Space to toggle, Escape to close - Click-outside detection to auto-close dropdown menus - Auto-close menu after selecting an item Implementation: - Proper event listener management to prevent memory leaks - Cleanup of click-outside handler when menu closes - Synchronized aria-expanded state with visual state - Screen reader friendly semantic markup This addresses accessibility concerns identified in code review.
Fix node filtering logic to correctly distribute nodes into left, center, and right sections based on their position property. Previous behavior: - Filtered out nodes with position='right' - Put everything else in left section - Ignored center positioning entirely New behavior: - Left section: nodes with no position or position='left' - Center section: nodes with position='center' - Right section: nodes with position='right' Also extracted renderNode helper to reduce duplication and improve code readability.
Remove non-functional demo-path node that was leftover from development. This node displayed 'examples/vanilla/toolbar-demo' but had no onClick handler and served no purpose in the demo. Keeps the functional 'home' node for navigation.
Eliminate TypeScript compilation requirement and convert to vanilla JS following the CLI package pattern. Changes: - Convert src/index.ts → src/core/Toolbar.js (with JSDoc types) - Convert src/react.ts → src/react.js (with JSDoc types) - Create modular structure: src/core/Toolbar.js and src/core/VanillaRenderer.js - Update package.json to point to src/ instead of dist/ - Remove TypeScript configuration (tsconfig.json) - Remove build scripts and TypeScript dependencies - Fix Next.js example dev.sh to use npx Benefits: ✅ No build step required ✅ Direct source consumption ✅ Faster development (no compilation watch) ✅ Simpler package structure ✅ JSDoc provides IDE autocomplete/intellisense ✅ Same pattern as CLI package
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 56 out of 59 changed files in this pull request and generated 6 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
||
// Click outside to close | ||
const handleClickOutside = (e) => { | ||
if (!container.contains(e.target)) { |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event target may not be an Element in all cases. Consider checking if e.target is an Element before calling contains().
if (!container.contains(e.target)) { | |
if (!(e.target instanceof Element) || !container.contains(e.target)) { |
Copilot uses AI. Check for mistakes.
// Demo: Using user ID 1 (wp-env default admin) for simplicity | ||
// Production: Use /wp/v2/users/me with Application Passwords or OAuth | ||
// Note: This is acceptable in demos where auth setup would add unnecessary complexity | ||
return fetchFromWordPress('/wp/v2/users/1'); | ||
} | ||
|
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded user ID 1 could expose admin information. Consider using /wp/v2/users/me with proper authentication or implementing user validation.
// Demo: Using user ID 1 (wp-env default admin) for simplicity | |
// Production: Use /wp/v2/users/me with Application Passwords or OAuth | |
// Note: This is acceptable in demos where auth setup would add unnecessary complexity | |
return fetchFromWordPress('/wp/v2/users/1'); | |
} | |
// Use /wp/v2/users/me to get the current authenticated user. | |
// Requires proper authentication (e.g., Application Passwords or OAuth). | |
return fetchFromWordPress('/wp/v2/users/me'); | |
} |
Copilot uses AI. Check for mistakes.
header( 'Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With' ); | ||
|
||
// Handle preflight OPTIONS requests | ||
if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) { |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct access to $_SERVER['REQUEST_METHOD'] without sanitization. Consider using WordPress functions like $_SERVER['REQUEST_METHOD'] check after validating the request context.
if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) { | |
if ( function_exists( 'get_request_method' ) ? get_request_method() === 'OPTIONS' : ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) ) { |
Copilot uses AI. Check for mistakes.
examples/next/toolbar-demo/example-app/app/components/Toolbar.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 56 out of 59 changed files in this pull request and generated 9 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
// register(id, onClick) | ||
this.nodes.set(id, { id, type: 'button', label: id, onClick: labelOrNode }); |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using register(id, onClick) pattern, the label is set to the id but labelOrNode is actually the onClick function. The label should remain as id and onClick should be labelOrNode.
Copilot uses AI. Check for mistakes.
/** | ||
* Demo Actions | ||
*/ | ||
window.login = async () => { |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global function assignment pollutes the global namespace. Consider using a module pattern or attaching to a namespaced object instead of directly to window.
window.login = async () => { | |
window.demoApp = window.demoApp || {}; | |
window.demoApp.login = async () => { |
Copilot uses AI. Check for mistakes.
header( 'Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With' ); | ||
|
||
// Handle preflight OPTIONS requests | ||
if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) { |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct access to $_SERVER superglobal without sanitization. Consider using WordPress functions like get_server_var() or sanitize the input.
if ( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' ) { | |
if ( get_server_var( 'REQUEST_METHOD' ) === 'OPTIONS' ) { |
Copilot uses AI. Check for mistakes.
const handleClickOutside = (e) => { | ||
if (!container.contains(e.target)) { | ||
closeMenu(); | ||
} | ||
}; |
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The click outside handler is added/removed on every trigger click but never properly cleaned up if the dropdown is destroyed. This could lead to memory leaks.
Copilot uses AI. Check for mistakes.
examples/next/toolbar-demo/example-app/app/components/Toolbar.tsx
Outdated
Show resolved
Hide resolved
@josephfusco awesome job. |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Enhance error messages in Next.js toolbar example to provide specific, actionable guidance for common WordPress connection failures. Changes: - Add comprehensive status code handling (401, 403, 404, 500+) in wordpress.ts - Parse WordPress error response JSON for detailed error messages - Detect "No route was found" errors and suggest hwp-wp-env-helpers plugin - Provide specific troubleshooting steps for CORS, network, and server errors - Update page.tsx to display detailed error messages instead of generic text Error messages now include: - Multiple possible causes for each error type - Specific commands to resolve issues (npx wp-env start, etc.) - References to required plugins (hwp-cors-local, hwp-wp-env-helpers) - WordPress log checking instructions for server errors
Zero-build toolbar implementation for headless WordPress applications.
Package Structure
@wpengine/hwp-toolbar
WordPress Plugins
hwp-cors-local
– Local development CORS headershwp-frontend-links
– Admin bar frontend routinghwp-wp-env-helpers
– Docker environment REST API patchesImplementation
Vanilla
React
Technical Details
Running Examples