Added specific selectors:
- #woonoow-admin-app header
- #woonoow-admin-app nav
- #woonoow-admin-app [data-submenubar]
- #woonoow-admin-app [data-bottomnav]
These target the exact WooNooW app elements that need hiding.
Invoice Page (/orders/:id/invoice):
- A4-ready layout (210mm x 297mm)
- Store header, invoice number, QR code
- Billing/shipping address sections
- Styled items table with alternating rows
- Totals summary with conditional display
- Thank you footer
- Back to Order and Print buttons
Label Page (/orders/:id/label):
- 4x6 inch thermal label layout
- Ship To address with phone
- Items list (physical products only)
- Shipping method
- QR code for scanning
- Back to Order and Print buttons
Order Detail:
- Removed print-mode logic
- Removed print-only layouts
- Invoice/Label buttons now link to dedicated pages
- Label button still hidden for virtual-only orders
- Added license_duration_days field to ProductVariant type
- Added License Duration input to each variation card
- Backend: ProductsController saves/loads variation-level _license_duration_days meta
- Allows different license periods per variation (e.g., 1-year, 2-year, lifetime)
Copy Cart/Checkout Links:
- Added to GeneralTab for simple products (same pattern as variations)
- Link generation with add-to-cart and redirect params
Licensing Settings:
- 'Enable licensing for this product' checkbox in Additional Options
- License settings panel: activation limit, duration (days)
- State management in ProductFormTabbed
- Backend: ProductsController saves/loads licensing meta fields
Backend:
- _licensing_enabled, _license_activation_limit, _license_duration_days post meta
- LicensingSettings.php with key format, activation limits, expiry settings
- LicenseManager.php with key generation, activation/deactivation, validation
- LicensingModule.php with WooCommerce product meta integration
- LicensesController.php with admin, customer, and public API endpoints
- Database tables: woonoow_licenses, woonoow_license_activations
- has_settings enabled in ModuleRegistry
- Fetch avatar settings in AccountLayout on mount
- Display custom avatar or gravatar in sidebar
- Dispatch woonoow:avatar-updated event on upload/remove
- Listen for event in AccountLayout for real-time sync
1. Admin Store Link - Add to WP admin bar (Menu.php) with proper option check
2. Activity Log - Fix Loading text to show correct state after data loads
3. Avatar Upload - Use correct option key woonoow_allow_custom_avatar
4. Downloadable Files - Connect to WooCommerce native:
- Add downloads array to format_product_full
- Add downloads/download_limit/download_expiry handling in update_product
- Add downloads handling in create_product
Customer Avatar Upload:
- Add /account/avatar endpoint for upload/delete
- Add /account/avatar-settings endpoint for settings
- Update AccountDetails.tsx with avatar upload UI
- Support base64 image upload with validation
Product Downloadable Files:
- Create DownloadsTab component for file management
- Add downloads state to ProductFormTabbed
- Show Downloads tab when 'downloadable' is checked
- Support file name, URL, download limit, and expiry
- Add Store link to admin header (visible when customer SPA is enabled)
- Add storeUrl and customerSpaEnabled to WNW_CONFIG in Assets.php and StandaloneAdmin.php
- Update window.d.ts with new WNW_CONFIG properties
- Create ActivityLog.tsx component with search, filters, and pagination
- Add /notifications/logs API endpoint to NotificationsController
- Update Notifications.tsx to link to activity log page
- Add ActivityLog route to App.tsx
1. Add allow_custom_avatar toggle to Customer Settings
2. Implement coupon apply/remove in Cart and Checkout pages
3. Update Cart interface with coupons array and discount_total
4. Implement Downloads page to fetch from /account/downloads API
- Created Newsletter/index.tsx as tabs container
- Extracted Newsletter/Subscribers.tsx (from old Newsletter.tsx)
- Moved Campaigns to Newsletter/Campaigns.tsx
- Updated App.tsx routes (campaigns now under newsletter)
- Removed separate Campaigns card from Marketing index
- Follows Customer Notifications tab pattern for consistency
- Sticky not possible when page is inside overflow-auto container
- Using standard flexbox layout where sidebar and content scroll together
- Separate mobile (fixed overlay) and desktop (inline) sidebars
- Clean, simple layout matching typical documentation patterns
- Fixed sidebar to not use inset-y-0 (was overriding top offset)
- Mobile: fixed positioning with sidebarTopClass
- Desktop: lg:sticky for proper sticky behavior
- Added Help item to NavigationRegistry::get_base_tree
- Empty children array means no submenu bar displayed
- Incremented NAV_VERSION to 1.0.9 to trigger cache rebuild
- Help icon: help-circle
- Added X-WP-Nonce header to docs API fetch calls in Help page
- Fixed build-production.sh to include docs/ folder (changed --exclude='*.md' to --exclude='/*.md')
- This allows root-level docs like README.md to be excluded while keeping docs/ folder
Phase 1: Core Documentation
- Created docs/ folder with 8 markdown documentation files
- Getting Started, Installation, Troubleshooting, FAQ
- Configuration docs (Appearance, SPA Mode)
- Feature docs (Shop, Checkout)
- PHP registry with filter hook for addon extensibility
Phase 2: Documentation Viewer
- DocsController.php with REST API endpoints
- GET /woonoow/v1/docs - List all docs (with addon hook)
- GET /woonoow/v1/docs/{slug} - Get document content
- Admin SPA /help route with sidebar navigation
- Markdown rendering with react-markdown
- Added Help & Docs to More page for mobile access
Filter Hook: woonoow_docs_registry
Addons can register their own documentation sections.
- Removed shortcode replacement for Cart, Checkout, My Account pages
- WooCommerce pages now keep their original [woocommerce_*] shortcodes
- Plugin only creates dedicated SPA page (/store) with [woonoow_spa]
- Auto-sets spa_page in appearance settings
This aligns with template override approach - WC pages render normally
when SPA is disabled, and redirect to SPA when mode is 'full'.
- Fixed redirect_wc_pages_to_spa: added spa_mode check (only redirect when 'full')
- Fixed PHP fatal error: use get_queried_object() instead of global $product
- Removed all error_log debug statements from codebase
- Fixed broken syntax in PaymentGatewaysProvider.php after error_log removal
Phase 4: Dynamic Meta Tags
- Added react-helmet-async dependency
- Created SEOHead component with Open Graph and Twitter Card support
- Added HelmetProvider wrapper to App.tsx
- Integrated SEOHead in Product page (title, description, image, product info)
- Integrated SEOHead in Shop page (basic meta tags)
Phase 5: Auto-Flush Permalinks
- Enhanced settings change handler to only flush when spa_mode,
spa_page, or use_browser_router changes
- Plugin already flushes on activation (Installer.php)
This enables proper link previews when sharing product URLs
on Facebook, Twitter, Slack, etc.
Changed from /my-account to /store page URL:
- Now reads spa_page from woonoow_appearance_settings
- Uses get_permalink() on the configured SPA page ID
- Fallback to home_url if SPA not configured
- Reset URL format: /store/#/reset-password?key=...&login=...
- Removed 🔘 emoji prefix from button text
- Button now shows text with subtle purple background pill
- Added padding and border-radius to differentiate from regular links
- Hover tooltip still shows 'Button: text → url' for clarity
- build:admin: builds admin-spa
- build:customer: builds customer-spa
- build: builds both admin and customer SPAs
- dev:customer: added dev server for customer-spa
Button Styling:
- Buttons now render as simple links with 🔘 prefix in editor
- No more styled button appearance in TipTap (was inconsistent)
- Actual button styling still happens in email (EmailRenderer.php)
Click-to-Edit:
- Click any button in the editor to open edit dialog
- Edit button text, link URL, and style (solid/outline)
- Delete button option in edit mode
- Updates button in-place instead of requiring recreation
Dialog improvements:
- Shows 'Edit Button' title in edit mode
- Shows 'Update Button' vs 'Insert Button' based on mode
- Delete button (red) appears only in edit mode
Changed reset link URL from admin SPA to customer-spa:
- Old: /wp-admin/admin.php?page=woonoow#/reset-password?key=...
- New: /my-account#/reset-password?key=...
This fixes the login redirect issue - the customer-spa is publicly
accessible so users can reset their password without logging in first.
Added:
- customer-spa/src/pages/ResetPassword/index.tsx
- Route /reset-password in customer-spa App.tsx
EmailManager.php now:
- Uses wc_get_page_id('myaccount') to get my-account page URL
- Falls back to home_url if my-account page not found
- Created ResetPassword.tsx with:
- Password reset form with strength indicator
- Key validation on load
- Show/hide password toggle
- Success/error states
- Redirect to login on success
- Updated EmailManager.php:
- Changed reset_link from wp-login.php to SPA route
- Format: /wp-admin/admin.php?page=woonoow#/reset-password?key=KEY&login=LOGIN
- Added AuthController API methods:
- validate_reset_key: Validates reset key before showing form
- reset_password: Performs actual password reset
- Registered new REST routes in Routes.php:
- POST /auth/validate-reset-key
- POST /auth/reset-password
Password reset emails now link to the SPA instead of native WordPress.
OLD BEHAVIOR (broken):
parse_cards processed ALL [card:type] syntax FIRST, then [card type=...]
This caused cards to render out of order when syntaxes were mixed.
NEW BEHAVIOR (fixed):
Using a unified regex that matches BOTH syntaxes simultaneously:
/\[card(?::(\w+)|([^\]]*)?)\](.*?)\[\/card\]/s
Each match includes:
- Group 1: Card type from new syntax [card:type]
- Group 2: Attributes from old syntax [card type='...']
- Group 3: Card content
Cards now render in exact document order regardless of syntax used.
parseCardsForPreview was forcing text-align: center on all buttons
regardless of user alignment choice. Removed the hardcoded style
so buttons follow natural document flow alignment.
Added getAttrs functions to parseHTML in tiptap-button-extension.ts.
Now properly extracts text/href/style from DOM elements:
- data-button: extracts from data-text, data-href, data-style
- a.button: extracts text/href, defaults to solid style
- a.button-outline: extracts text/href, defaults to outline style
This fixes the issue where buttons appeared unstyled (outline
instead of solid) when editing a card that contained buttons.
Added data-button attribute selector to TipTap button parseHTML.
This ensures buttons are properly detected when text alignment is
applied, as alignment may affect CSS class detection.
Priority order:
1. a[data-button] - most reliable
2. a.button
3. a.button-outline
ROOT CAUSE:
When saving card edit in EmailBuilder, htmlToMarkdown() was called.
The old code at line 26 converted ALL <a> tags to markdown links:
<a href="url">text</a> → [text](url)
This lost TipTap button data-button attributes, converting buttons
to plain text instead of [button:style](url)Text[/button] shortcode.
FIX:
Added TipTap button detection BEFORE generic link conversion in
html-to-markdown.ts:
- Detects <a data-button...> elements
- Extracts style from data-style or class attribute
- Extracts URL from data-href or href attribute
- Converts to [button:style](url)Text[/button] format
FLOW NOW WORKS:
1. User adds button via TipTap toolbar
2. TipTap renders <a data-button data-style="solid"...>
3. User clicks Save Changes
4. htmlToMarkdown detects data-button → [button:solid](url)Text[/button]
5. Card content saved with proper button shortcode
6. On re-edit, button shortcode converted back to TipTap button
ROOT CAUSE:
Frontend blocksToMarkdown outputs NEW syntax:
- [card:type]...[/card]
- [button:style](url)Text[/button]
But backend EmailRenderer.php only had regex for OLD syntax:
- [card type="..."]...[/card]
- [button url="..."]Text[/button]
FIXES:
1. parse_cards() now handles BOTH syntaxes:
- NEW [card:type] regex first (extracts type from :type)
- OLD [card type="..."] regex for backward compatibility
2. render_card() now handles BOTH button syntaxes:
- NEW [button:style](url)Text[/button] regex
- OLD [button url="..."] regex for backward compatibility
3. Card types properly styled with inline CSS:
- hero: gradient background
- success: green background + border
- info: blue background + border
- warning: yellow background + orange border
4. Buttons rendered with full inline styles + table wrapper
for Gmail/email client compatibility
Button modals in both RichTextEditor and EmailBuilder filtered
for _url variables only, excluding reset_link. Updated filter to
include both _url and _link patterns.
Files changed:
- rich-text-editor.tsx line 415
- EmailBuilder.tsx line 359
ROOT CAUSE (from screenshot DevTools):
href="<span style=...>[login_url]</span>" - HTML span inside href attribute!
Flow causing the bug:
1. parseCardsForPreview converts [button url="{login_url}"] to <a href="{login_url}">
2. sampleData replacement runs but login_url NOT in sampleData
3. Variable highlighting injects <span>[login_url]</span> INTO href="..."
4. HTML is completely broken
FIXES APPLIED:
1. Added missing URL variables to sampleData:
- login_url, reset_link, reset_key
- user_login, user_email, user_temp_password
- customer_first_name, customer_last_name
2. Changed variable highlighting from HTML spans to plain text [variable]
- Prevents breaking HTML attributes if variable is inside href, src, etc.
ROOT CAUSE: Complete flow trace revealed syntax mismatch:
- blocksToMarkdown outputs NEW syntax: [card:type], [button:style](url)Text[/button]
- markdownToBlocks ONLY parsed OLD syntax: [card type="..."], [button url="..."]
This caused buttons/cards to be lost when:
1. User adds button in Visual mode
2. blocksToMarkdown converts to [button:solid]({url})Text[/button]
3. handleBlocksChange stores this in markdownContent
4. When switching tabs/previewing, markdownToBlocks runs
5. It FAILED to parse new syntax, buttons disappear!
FIX: Added handlers for NEW syntax in markdownToBlocks (converter.ts):
- [card:type]...[/card] pattern (before old syntax)
- [button:style](url)Text[/button] pattern (before old syntax)
Now both syntaxes work correctly in round-trip conversion.
Root cause: parseCardsForPreview was called TWICE in generatePreviewHTML:
1. Line 179 - correctly parses markdown to HTML including buttons
2. Line 283 - redundantly called AGAIN after variable highlighting
After first call, variable highlighting (lines 275-280) replaced unknown
variables like {login_url} with <span>[login_url]</span>. When the second
parseCardsForPreview ran, the [login_url] text was misinterpreted as
shortcode syntax, corrupting button HTML output.
Fix: Remove the redundant second call to parseCardsForPreview at line 283.
The function is already called at line 179 before any variable replacement.
- Added multiple htmlToMarkdown patterns for TipTap button output:
1. data-button with data-href/data-style attributes
2. Alternate attribute order (data-style before data-href)
3. Simple data-button fallback with href and class
4. Buttons wrapped in p tags (from preview HTML)
5. Direct button links without p wrapper
- Button shortcodes now correctly roundtrip:
RichEditor -> HTML -> [button url=... style=...] -> Preview/Email
- All patterns now explicitly include style=solid for consistency