diff --git a/.distignore b/.distignore new file mode 100644 index 0000000..58601de --- /dev/null +++ b/.distignore @@ -0,0 +1,28 @@ +# Files and folders excluded from the distributable plugin zip. +# Patterns are rsync --exclude-from compatible. + +.git/ +.github/ +.gitignore +.gitattributes +.distignore +.vscode/ +.editorconfig +.DS_Store + +node_modules/ +tests/ +coverage/ +docs/ + +phpcs.xml +phpunit.xml.dist +vitest.config.js +webpack.config.js +tsconfig.json +package-lock.json +composer.lock + +CHANGELOG.md +fix-step2.php +fix-tour2.php diff --git a/.github/workflows/manually-build-zip.yml b/.github/workflows/manually-build-zip.yml new file mode 100644 index 0000000..d5f6b71 --- /dev/null +++ b/.github/workflows/manually-build-zip.yml @@ -0,0 +1,61 @@ +name: Manually build plugin zip + +on: + workflow_dispatch: + inputs: + tag: + description: "Existing tag/release to attach the zip to (e.g. 0.5.0)" + required: true + +permissions: + contents: write + +env: + PLUGIN_SLUG: admin-coach-tours + PLUGIN_ZIP: admin-coach-tours.zip + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout tag + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag }} + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies and build assets + run: | + npm ci + npm run build + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: composer + + - name: Install production Composer dependencies + run: composer install --no-dev --optimize-autoloader --no-interaction + + - name: Verify updater dependency is bundled + run: | + test -d vendor/yahnis-elsts/plugin-update-checker \ + || { echo "Missing yahnis-elsts/plugin-update-checker in vendor/"; exit 1; } + + - name: Build plugin zip + run: | + mkdir -p "/tmp/${PLUGIN_SLUG}" + rsync -a --exclude-from=.distignore --exclude='.git' ./ "/tmp/${PLUGIN_SLUG}/" + ( cd /tmp && zip -rq "${GITHUB_WORKSPACE}/${PLUGIN_ZIP}" "${PLUGIN_SLUG}" ) + + - name: Attach zip to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.inputs.tag }} + files: ${{ env.PLUGIN_ZIP }} diff --git a/.github/workflows/on-release-add.zip.yml b/.github/workflows/on-release-add.zip.yml new file mode 100644 index 0000000..c071edc --- /dev/null +++ b/.github/workflows/on-release-add.zip.yml @@ -0,0 +1,55 @@ +name: Build and attach release zip + +on: + release: + types: [published] + +permissions: + contents: write + +env: + PLUGIN_SLUG: admin-coach-tours + PLUGIN_ZIP: admin-coach-tours.zip + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies and build assets + run: | + npm ci + npm run build + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: composer + + - name: Install production Composer dependencies + run: composer install --no-dev --optimize-autoloader --no-interaction + + - name: Verify updater dependency is bundled + run: | + test -d vendor/yahnis-elsts/plugin-update-checker \ + || { echo "Missing yahnis-elsts/plugin-update-checker in vendor/"; exit 1; } + + - name: Build plugin zip + run: | + mkdir -p "/tmp/${PLUGIN_SLUG}" + rsync -a --exclude-from=.distignore --exclude='.git' ./ "/tmp/${PLUGIN_SLUG}/" + ( cd /tmp && zip -rq "${GITHUB_WORKSPACE}/${PLUGIN_ZIP}" "${PLUGIN_SLUG}" ) + + - name: Attach zip to release + uses: softprops/action-gh-release@v2 + with: + files: ${{ env.PLUGIN_ZIP }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c19d73..1f112b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] - 2026-07-24 + +### Changed + +- Reworked the AI layer to use the WordPress 7 AI Connector (`wp_get_connectors` / `wp_ai_client_prompt`); removed the bundled OpenAI/Azure/Anthropic providers and API-key encryption. **Now requires WordPress 7.0+ and at least one configured AI provider connector.** +- Refactored the AI tour-generation flow into deep modules (`TourRequest`, `TourGenerator`, `TourSchema`); the REST controller is now a thin adapter. +- Slimmed the settings page to connector status, an enable toggle, and an optional provider/model override. +- Reordered task categories (Text first) and expanded the task list: added Paragraph, plus Code, Separator, Details, Audio, File, Group, Spacer, and Embed-from-URL. + +### Added + +- Self-updates from GitHub releases via the WordPress Plugin GitHub Updater, plus GitHub Actions workflows to build and attach the release zip. +- Coach panel shows the `/command` as a header above the instruction. +- Refocus the current block when a tour closes. +- Finish the tour by clicking the highlighted block on the last step. +- Hide the launcher when the editor is in code editor mode. +- Stop the tour with a clear message when a different block than expected is inserted. + +### Fixed + +- `generate-tour` REST route rejected `null` `editorContext`/`failureContext` (400 `rest_invalid_param`). +- `wpBlock: "selected"` now falls back to the last-selected block so multi-step tours don't fail on confirmation steps. +- Removed the arrow "Next" button that let users skip ahead before a block existed. +- Tour copy no longer uses positional words ("below"/"above"). + ## [0.4.1] - 2026-02-03 ### Added diff --git a/README.md b/README.md index a3f613c..6c113af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Admin Coach Tours [![Version](https://img.shields.io/badge/version-0.3.6-blue.svg)](https://github.com/soderlind/admin-coach-tours) -[![WordPress](https://img.shields.io/badge/WordPress-6.8%2B-blue.svg)](https://wordpress.org) +[![WordPress](https://img.shields.io/badge/WordPress-7.0%2B-blue.svg)](https://wordpress.org) [![PHP](https://img.shields.io/badge/PHP-8.3%2B-purple.svg)](https://php.net) [![License](https://img.shields.io/badge/license-GPL--2.0--or--later-green.svg)](https://www.gnu.org/licenses/gpl-2.0.html) @@ -16,7 +16,7 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera ## Features - **AI-Generated Tours** — On-demand tutorials created by AI based on your request -- **12 Predefined Tasks** — Common tasks like adding images, videos, headings, and more +- **Ready-made Task Library** — 20+ common tasks like adding images, videos, headings, paragraphs, and more - **Freeform Questions** — Ask anything about the block editor - **Interactive Overlay** — Visual highlighting guides you through each step - **Smart Block Targeting** — Accurately identifies and highlights the correct elements @@ -24,28 +24,43 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera ## Requirements -- WordPress 6.8+ +- WordPress 7.0+ - PHP 8.3+ -- sodium extension (for API key encryption) -- AI provider API key (OpenAI, Azure OpenAI, or Anthropic) +- At least one WordPress AI provider connector configured ## Installation -1. Upload `admin-coach-tours` to `/wp-content/plugins/` -2. Activate the plugin -3. Go to **Tools → Coach Tours** to configure AI +### From a release zip (recommended) + +1. Download `admin-coach-tours.zip` from the [latest release](https://github.com/soderlind/admin-coach-tours/releases/latest). +2. In wp-admin, go to **Plugins → Add New → Upload Plugin**, choose the zip, and click **Install Now**. +3. Activate the plugin. +4. Go to **Tools → Coach Tours** to enable AI. + +### From source + +```bash +git clone https://github.com/soderlind/admin-coach-tours.git +cd admin-coach-tours +composer install --no-dev +npm ci && npm run build +``` + +Copy the folder into `wp-content/plugins/` and activate it. + +### Updates + +The plugin updates itself from GitHub releases — new versions show up under **Plugins** and **Dashboard → Updates** like any other plugin (checked roughly every 6 hours). ## Setup ### Configure AI Provider -1. Navigate to **Tours → Settings** -2. Enable AI Features -3. Select your provider: - - **OpenAI** — Add your API key - - **Azure OpenAI** — Add your API key and endpoint URL - - **Anthropic** — Add your API key -4. Save settings +1. Configure at least one WordPress AI provider connector +2. Navigate to **Tools → Coach Tours** +3. Enable AI Features +4. Optionally choose a preferred provider and model override +5. Save settings ## Usage @@ -58,10 +73,10 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera **Common Tasks:** | Category | Tasks | |----------|-------| -| Media | Add image, Add video, Create gallery, Add cover | -| Text | Add heading, Create list, Add quote, Create table | -| Design | Add button, Create columns | -| Embed | Embed YouTube | +| Text | Add paragraph, heading, list, quote, table; format text; code, separator, details | +| Media | Add image, video, gallery, cover, audio, file | +| Design | Add button, columns, group, spacer | +| Embed | Embed YouTube, embed from URL | **Or Ask a Question:** Type any question about the block editor and press Enter. @@ -69,11 +84,6 @@ Type any question about the block editor and press Enter. 4. Follow the highlighted steps to complete the task 5. Each step auto-advances when you perform the action -### Tour Controls - -- **Previous/Next** — Navigate between steps -- **Skip** — Skip a step you already know -- **Stop** — Exit the tour at any time ## How It Works @@ -150,7 +160,7 @@ npm run lint # JavaScript (ESLint) ## Security -- API keys encrypted with libsodium +- API keys are owned by the WordPress AI connector (not stored by this plugin) - All endpoints require authentication - Capability checks on all operations - Input sanitization and output escaping @@ -162,5 +172,5 @@ GPL v2 or later — see [LICENSE](LICENSE) for details. ## Credits - Built with [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) and [@wordpress/data](https://www.npmjs.com/package/@wordpress/data) -- AI integration supports OpenAI, Azure OpenAI, and Anthropic +- AI generation via the WordPress 7 AI Connector (`wp_get_connectors` / `wp_ai_client_prompt`) - RAG knowledge base includes pedagogical content from [Learn WordPress](https://learn.wordpress.org/learning-pathway/user/) courses diff --git a/admin-coach-tours.php b/admin-coach-tours.php index 77d4755..4055139 100644 --- a/admin-coach-tours.php +++ b/admin-coach-tours.php @@ -11,8 +11,8 @@ * Plugin Name: Admin Coach Tours * Plugin URI: https://github.com/soderlind/admin-coach-tours * Description: Interactive guided tours for WordPress admin, enabling educators to create step-by-step tutorials and pupils to learn with guided overlays. - * Version: 0.4.1 - * Requires at least: 6.8 + * Version: 0.5.0 + * Requires at least: 7.0 * Requires PHP: 8.3 * Author: Per Soderlind * Author URI: https://github.com/soderlind @@ -32,7 +32,7 @@ /** * Plugin version. */ -const VERSION = '0.4.1'; +const VERSION = '0.5.0'; /** * Plugin slug. @@ -57,7 +57,7 @@ /** * Minimum WordPress version. */ -const MIN_WP_VERSION = '6.8'; +const MIN_WP_VERSION = '7.0'; /** * Minimum PHP version. @@ -100,11 +100,6 @@ function check_requirements(): bool { ); } - // Check sodium extension. - if ( ! function_exists( 'sodium_crypto_secretbox' ) ) { - $errors[] = __( 'Admin Coach Tours requires the PHP Sodium extension for secure API key storage.', 'admin-coach-tours' ); - } - if ( ! empty( $errors ) ) { add_action( 'admin_notices', @@ -141,6 +136,18 @@ function init(): void { dirname( PLUGIN_BASENAME ) . '/languages' ); + // Self-updates from GitHub releases. + if ( class_exists( \Soderlind\WordPress\GitHubUpdater::class ) ) { + \Soderlind\WordPress\GitHubUpdater::init( + github_url: 'https://github.com/soderlind/admin-coach-tours', + plugin_file: __FILE__, + plugin_slug: 'admin-coach-tours', + name_regex: '/admin-coach-tours\.zip/', + branch: 'main', + check_period: 6, + ); + } + // Initialize components. $initializers = [ Cpt\ToursCpt::class, @@ -160,6 +167,52 @@ function init(): void { // Enqueue admin assets (for settings page). add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\enqueue_admin_assets' ); + + // One-time cleanup of legacy AI options after upgrading to the WP AI connector. + add_action( 'admin_init', __NAMESPACE__ . '\\maybe_upgrade' ); +} + +/** + * Run one-time upgrade routines when the stored version differs from the current one. + * + * Removes legacy provider API-key options that are now managed by the + * WordPress AI connector (wp_get_connectors) instead of this plugin. + * + * @return void + */ +function maybe_upgrade(): void { + $stored = get_option( 'act_version', '' ); + + if ( VERSION === $stored ) { + return; + } + + // Legacy options that stored encrypted API keys and per-provider config. + $legacy_options = [ + 'act_ai_provider', + 'act_ai_openai_api_key', + 'act_ai_openai_model', + 'act_ai_azure_api_key', + 'act_ai_azure_endpoint', + 'act_ai_azure_deployment', + 'act_ai_azure_model', + 'act_ai_anthropic_api_key', + 'act_ai_anthropic_model', + 'act_ai_api_key', + 'act_ai_endpoint', + 'act_encryption_key', + ]; + + // Preserve act_ai_provider only if it now holds a connector ID (repurposed). + // A fresh install has no legacy encrypted keys, so removing the others is safe. + foreach ( $legacy_options as $option ) { + if ( 'act_ai_provider' === $option ) { + continue; + } + delete_option( $option ); + } + + update_option( 'act_version', VERSION ); } /** diff --git a/assets/js/pupil/CoachPanel.jsx b/assets/js/pupil/CoachPanel.jsx index 5f1a5a1..b31c15c 100644 --- a/assets/js/pupil/CoachPanel.jsx +++ b/assets/js/pupil/CoachPanel.jsx @@ -7,7 +7,7 @@ * @since 0.1.0 */ -import { useState, useEffect, useCallback, useRef } from '@wordpress/element'; +import { useState, useEffect, useCallback, useRef, useMemo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { Button, @@ -16,7 +16,7 @@ import { FlexItem, FlexBlock, } from '@wordpress/components'; -import { arrowRight, close } from '@wordpress/icons'; +import { close } from '@wordpress/icons'; /** * @typedef {import('../types/step.js').Step} Step @@ -66,6 +66,17 @@ export default function CoachPanel( { const isLastStep = stepIndex === totalSteps - 1; const isManualCompletion = step?.completion?.type === 'manual'; + // Detect a "/command" in the instruction so it can be surfaced as a header. + const slashCommand = useMemo( () => { + const text = ( step?.content || '' ).replace( /<[^>]*>/g, ' ' ); + const match = text.match( /(?:^|\s)(\/[a-z][a-z0-9-]*)/i ); + return match ? match[ 1 ] : null; + }, [ step?.content ] ); + + // Only steps the user must confirm show a footer control; auto-advancing + // steps have none (removing the old "next" arrow that skipped ahead). + const hasControl = isManualCompletion || isLastStep; + /** * Position panel relative to target element. */ @@ -284,6 +295,43 @@ export default function CoachPanel( { return ( <> + { slashCommand && ( +
+ + { __( 'Type', 'admin-coach-tours' ) } + + + { slashCommand } + +
+ ) } { step?.content && (
- { isManualCompletion || isLastStep ? ( - - ) : ( -
@@ -414,17 +452,19 @@ export default function CoachPanel( { { /* Footer */ } -
- { renderControls() } -
+ { hasControl && ( +
+ { renderControls() } +
+ ) } ); } diff --git a/assets/js/pupil/PupilLauncher.jsx b/assets/js/pupil/PupilLauncher.jsx index a4ceb48..3bd10a2 100644 --- a/assets/js/pupil/PupilLauncher.jsx +++ b/assets/js/pupil/PupilLauncher.jsx @@ -11,7 +11,6 @@ import { useState, useEffect, useCallback, useRef, createPortal } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; import { Dashicon } from '@wordpress/components'; const STORE_NAME = 'admin-coach-tours'; @@ -20,13 +19,18 @@ const STORE_NAME = 'admin-coach-tours'; * Icons for task categories. */ const CATEGORY_ICONS = { + text: '📝', media: '🖼️', - content: '📝', - layout: '📐', - formatting: '✨', + design: '📐', + embed: '🔗', default: '📚', }; +/** + * Display order for task categories (Text first). + */ +const CATEGORY_ORDER = [ 'text', 'media', 'design', 'embed' ]; + /** * Pupil Launcher component. * @@ -51,6 +55,7 @@ export default function PupilLauncher() { isPlaying, aiAvailable, lastFailureContext, + editorMode, } = useSelect( ( select ) => { const store = select( STORE_NAME ); return { @@ -61,6 +66,8 @@ export default function PupilLauncher() { aiAvailable: window.adminCoachTours?.aiAvailable ?? false, // Failure context for contextual retry. lastFailureContext: store.getLastFailureContext?.() ?? null, + // 'visual' or 'text' (code editor). + editorMode: select( 'core/edit-post' )?.getEditorMode?.() ?? 'visual', }; }, [] ); @@ -83,7 +90,7 @@ export default function PupilLauncher() { }, [ aiTourError, isPlaying, isOpen ] ); // Get dispatch actions. - const { requestAiTour, clearEphemeralTour, setAiTourError, setLastFailureContext } = useDispatch( STORE_NAME ); + const { requestAiTour, clearEphemeralTour, setAiTourError, setLastFailureContext, fetchAiTasks } = useDispatch( STORE_NAME ); /** * Fetch available tasks when launcher opens. @@ -93,7 +100,7 @@ export default function PupilLauncher() { setIsTasksLoading( true ); setTasksError( null ); - apiFetch( { path: '/admin-coach-tours/v1/ai/tasks' } ) + fetchAiTasks() .then( ( response ) => { if ( response.available && response.tasks ) { setTasks( response.tasks ); @@ -215,6 +222,11 @@ export default function PupilLauncher() { setIsOpen( false ); }, [] ); + // Hide the launcher entirely when the editor is in code (text) mode. + if ( editorMode === 'text' ) { + return null; + } + // Show configuration prompt if AI is not available. if ( ! aiAvailable ) { return ( @@ -268,6 +280,15 @@ export default function PupilLauncher() { return acc; }, {} ); + // Order categories by CATEGORY_ORDER; unknown categories go last. + const orderedCategories = Object.entries( tasksByCategory ).sort( + ( [ a ], [ b ] ) => { + const ia = CATEGORY_ORDER.indexOf( a ); + const ib = CATEGORY_ORDER.indexOf( b ); + return ( ia === -1 ? 999 : ia ) - ( ib === -1 ? 999 : ib ); + } + ); + // Render loading overlay via portal (always available, even when tour is playing). // This must be outside the isPlaying guard to persist during the transition. const loadingOverlay = isAiTourLoading && createPortal( @@ -414,7 +435,7 @@ export default function PupilLauncher() { ) } - { ! isTasksLoading && Object.entries( tasksByCategory ).map( ( [ category, categoryTasks ] ) => ( + { ! isTasksLoading && orderedCategories.map( ( [ category, categoryTasks ] ) => (

diff --git a/assets/js/pupil/TourRunner.jsx b/assets/js/pupil/TourRunner.jsx index aaa19b0..2d38f83 100644 --- a/assets/js/pupil/TourRunner.jsx +++ b/assets/js/pupil/TourRunner.jsx @@ -15,7 +15,7 @@ import { __, sprintf } from '@wordpress/i18n'; import CoachPanel from './CoachPanel.jsx'; import Highlighter from './Highlighter.js'; import { resolveTarget, resolveTargetWithRecovery } from '../runtime/resolveTarget.js'; -import { applyPreconditions, onLeaveStep, onEnterStep, clearInsertedBlocks, setCurrentStepIndex } from '../runtime/applyPreconditions.js'; +import { applyPreconditions, onLeaveStep, onEnterStep, clearInsertedBlocks, setCurrentStepIndex, focusCurrentBlock } from '../runtime/applyPreconditions.js'; import { watchCompletion } from '../runtime/watchCompletion.js'; import { waitForNextStepBlock } from '../runtime/waitForNextStepBlock.js'; @@ -57,6 +57,79 @@ function getExpectedBlockType( locators ) { return null; } +/** + * Extract the raw expected block name (e.g. "core/image") from step locators. + * + * @param {Array} locators Array of locators from the step. + * @return {string|null} Raw block name or null. + */ +function getExpectedBlockName( locators ) { + if ( ! Array.isArray( locators ) ) { + return null; + } + + for ( const locator of locators ) { + if ( typeof locator.value !== 'string' ) { + continue; + } + if ( locator.type === 'css' ) { + const match = locator.value.match( /data-type=["']?(core\/[\w-]+)["']?/i ); + if ( match ) { + return match[ 1 ]; + } + } + if ( locator.type === 'wpBlock' ) { + const match = locator.value.match( /type:(core\/[\w-]+)/i ); + if ( match ) { + return match[ 1 ]; + } + } + } + + return null; +} + +/** + * Detect whether the user diverged from the tour by adding a different block + * than the step expects (e.g. asked for an Image but inserted a Gallery). + * + * @param {string|null} expectedName Raw expected block name (e.g. "core/image"). + * @return {boolean} True if a different, non-empty block is present and the + * expected block is absent. + */ +function userAddedDifferentBlock( expectedName ) { + if ( ! expectedName ) { + return false; + } + + const blockEditor = wpSelect( 'core/block-editor' ); + if ( ! blockEditor?.getBlocks ) { + return false; + } + + const blocks = blockEditor.getBlocks() || []; + + const hasExpected = + ( blockEditor.getBlocksByName?.( expectedName )?.length || 0 ) > 0 || + blocks.some( ( block ) => block.name === expectedName ); + + if ( hasExpected ) { + return false; + } + + // Any non-paragraph block, or a paragraph the user has typed into, counts + // as the user having built something other than the expected block. + return blocks.some( ( block ) => { + if ( block.name === expectedName ) { + return false; + } + if ( block.name === 'core/paragraph' ) { + return ( block.attributes?.content || '' ).toString().trim().length > 0; + } + return true; + } ); +} + /** * Scroll an element into view, handling cross-frame scenarios. * When element is inside an iframe, we need to scroll both the iframe content @@ -136,6 +209,7 @@ export default function TourRunner() { const highlighterRef = useRef( null ); const previousStepIndexRef = useRef( null ); const completionWatcherRef = useRef( null ); // Use ref to avoid stale closure in cleanup. + const lastStepClickCleanupRef = useRef( null ); // Removes the last-step click-to-finish listener. // Get playback state from store. const { @@ -309,6 +383,36 @@ export default function TourRunner() { }, 350 ); // Allow time for smooth scroll to finish } else { resolvedElement = null; + + // If the user diverged (added a different block than this step + // expects), retrying can't succeed. Stop the tour cleanly with a + // clear message instead of the confusing "Try Again" loop. + const expectedName = getExpectedBlockName( currentStep.target?.locators ); + if ( userAddedDifferentBlock( expectedName ) ) { + const expectedLabel = getExpectedBlockType( currentStep.target?.locators ); + console.log( '[ACT TourRunner] User added a different block than expected; aborting tour.' ); + + if ( highlighterRef.current ) { + highlighterRef.current.clear(); + } + + clearInsertedBlocks(); + previousStepIndexRef.current = null; + stopTour(); + + setAiTourError( + expectedLabel + ? sprintf( + /* translators: %s: expected block type name. */ + __( 'This tour was for adding a %s block, but a different block was added. The tour has stopped — start a new tour for the block you added.', 'admin-coach-tours' ), + expectedLabel + ) + : __( 'A different block than expected was added, so the tour has stopped.', 'admin-coach-tours' ) + ); + + return; + } + setTargetElement( null ); setResolutionError( result.error ); setExpectedBlockType( getExpectedBlockType( currentStep.target?.locators ) ); @@ -373,6 +477,48 @@ export default function TourRunner() { ); completionWatcherRef.current = watcher; // Store in ref for reliable cleanup. + // On the last step, clicking the highlighted block also finishes + // the tour (no need to press "Finish"). A short grace period avoids + // the click that reached this step from closing it immediately. + if ( stepIndex === totalSteps - 1 && resolvedElement ) { + const finishOnClick = () => { + if ( ! isMounted ) { + return; + } + console.log( '[ACT TourRunner] Last step: block clicked, finishing tour' ); + clearInsertedBlocks(); + focusCurrentBlock(); + nextStep(); + }; + + const graceTimer = setTimeout( () => { + if ( ! isMounted || ! resolvedElement.isConnected ) { + return; + } + resolvedElement.addEventListener( 'click', finishOnClick, { + once: true, + capture: true, + } ); + lastStepClickCleanupRef.current = () => { + resolvedElement.removeEventListener( 'click', finishOnClick, { + capture: true, + } ); + }; + }, 400 ); + + // Ensure the pending timer is cleared on cleanup too. + const priorCleanup = lastStepClickCleanupRef.current; + lastStepClickCleanupRef.current = () => { + clearTimeout( graceTimer ); + if ( priorCleanup ) { + priorCleanup(); + } + resolvedElement.removeEventListener( 'click', finishOnClick, { + capture: true, + } ); + }; + } + // Wait for completion. watcher.promise.then( async ( completionResult ) => { if ( ! isMounted ) { @@ -404,6 +550,8 @@ export default function TourRunner() { // Last step - end the tour. console.log( '[ACT TourRunner] Last step completed, ending tour' ); clearInsertedBlocks(); + // Return the caret to the current block before the panel closes. + focusCurrentBlock(); nextStep(); // This will end the tour since there's no next step. } } @@ -421,6 +569,11 @@ export default function TourRunner() { completionWatcherRef.current.cancel(); completionWatcherRef.current = null; } + // Remove any last-step click-to-finish listener/timer. + if ( lastStepClickCleanupRef.current ) { + lastStepClickCleanupRef.current(); + lastStepClickCleanupRef.current = null; + } }; }, [ isPlaying, currentStep, stepIndex, repeatCounter, stopTour, setAiTourError ] ); @@ -473,6 +626,8 @@ export default function TourRunner() { clearInsertedBlocks(); previousStepIndexRef.current = null; stopTour(); + // Return the caret to the block the user was on. + focusCurrentBlock(); }, [ stopTour ] ); // Don't render if not playing. diff --git a/assets/js/runtime/applyPreconditions.js b/assets/js/runtime/applyPreconditions.js index 49e6be9..2f82705 100644 --- a/assets/js/runtime/applyPreconditions.js +++ b/assets/js/runtime/applyPreconditions.js @@ -461,6 +461,10 @@ export async function onLeaveStep( leavingStepIndex ) { // Deselect current block when leaving. const selectedClientId = blockEditorSelect.getSelectedBlockClientId?.(); if ( selectedClientId ) { + // Remember it so a later step's `wpBlock: "selected"` target can fall + // back to this block after we clear the selection here. + window.__actLastSelectedBlockClientId = selectedClientId; + try { await blockEditorDispatch.clearSelectedBlock?.(); console.log( '[ACT onLeaveStep] Deselected block:', selectedClientId ); @@ -491,6 +495,7 @@ export function clearInsertedBlocks() { currentStepIndex = 0; // Also clear the last appeared block tracking. delete window.__actLastAppearedBlockClientId; + delete window.__actLastSelectedBlockClientId; console.log( '[ACT clearInsertedBlocks] Cleared all tracking' ); } @@ -761,6 +766,24 @@ async function focusBlockElement( clientId ) { return true; } +/** + * Refocus the editor on the currently selected block. + * + * Used when the tour closes so the caret returns to the block the user was on. + * + * @return {Promise} True if a block was focused. + */ +export async function focusCurrentBlock() { + const blockEditorSelect = select( 'core/block-editor' ); + const clientId = blockEditorSelect?.getSelectedBlockClientId?.(); + + if ( ! clientId ) { + return false; + } + + return focusBlockElement( clientId ); +} + /** * Close any open modals/popovers. * diff --git a/assets/js/runtime/resolveTarget.js b/assets/js/runtime/resolveTarget.js index 981b647..76e3c7f 100644 --- a/assets/js/runtime/resolveTarget.js +++ b/assets/js/runtime/resolveTarget.js @@ -437,6 +437,21 @@ function findByWpBlock( value, doc = document ) { targetClientId = blocks[ blocks.length - 1 ]?.clientId; } else if ( value === 'selected' ) { targetClientId = blockEditor.getSelectedBlockClientId(); + + // The tour deselects blocks between steps (onLeaveStep), so a later step + // targeting "selected" can find nothing even though the user is still on + // that block. Fall back to the block the tour last worked with, then to + // the only block present. + if ( ! targetClientId ) { + targetClientId = + window.__actLastSelectedBlockClientId || + window.__actLastAppearedBlockClientId || + ( blocks.length === 1 ? blocks[ 0 ]?.clientId : null ); + + if ( targetClientId ) { + console.log( '[ACT findByWpBlock] "selected" fell back to:', targetClientId ); + } + } } else if ( value.startsWith( 'type:' ) ) { // Find block by type, optionally with index. const parts = value.substring( 5 ).split( ':' ); diff --git a/assets/js/store/actions.js b/assets/js/store/actions.js index 8c7a02c..7898edc 100644 --- a/assets/js/store/actions.js +++ b/assets/js/store/actions.js @@ -781,6 +781,9 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) yield setAiTourLoading( true ); yield setAiTourError( null ); + // Resolve the response language once, at the composition root. + const locale = window.adminCoachTours?.locale || ''; + try { // Gather editor context to help AI generate accurate selectors. const editorContext = yield { @@ -794,6 +797,7 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) postType, editorContext, failureContext, + locale, }; console.log( '[ACT AI Response] Full result:', result ); @@ -824,6 +828,20 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) } } +/** + * Fetch the available AI tasks through the store. + * + * Keeps all AI REST access behind the store's control seam so components + * don't call apiFetch directly or duplicate the endpoint path. + * + * @return {Generator} Generator resolving to the tasks response. + */ +export function* fetchAiTasks() { + return yield { + type: 'FETCH_AI_TASKS', + }; +} + /** * Start an ephemeral tour directly (for pre-loaded tours). * @@ -831,7 +849,6 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) * @return {Generator} Generator that sets up and starts the tour. */ export function* startEphemeralTour( tour ) { - // Add ID if not present. const tourWithId = { id: 'ephemeral', ...tour, diff --git a/assets/js/store/controls.js b/assets/js/store/controls.js index 1384134..a5dafe5 100644 --- a/assets/js/store/controls.js +++ b/assets/js/store/controls.js @@ -156,7 +156,7 @@ const controls = { postType: action.postType, editorContext: action.editorContext || null, failureContext: action.failureContext || null, - locale: window.adminCoachTours?.locale || '', + locale: action.locale || '', }, } ); }, diff --git a/build/educator/index.asset.php b/build/educator/index.asset.php index d68c6a4..577c325 100644 --- a/build/educator/index.asset.php +++ b/build/educator/index.asset.php @@ -1 +1 @@ - array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives'), 'version' => 'fdeb44c02a995dfbf518'); + array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives'), 'version' => '6777c96f0bf797901935'); diff --git a/build/educator/index.js b/build/educator/index.js index 952b21d..b643cdd 100644 --- a/build/educator/index.js +++ b/build/educator/index.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={exports:{}};return n[e](i,i.exports,o),i.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);o.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,o.d(i,a),i},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};o.r(i),o.d(i,{activatePicker:()=>F,addStep:()=>W,clearAiDraft:()=>Q,clearEphemeralTour:()=>ae,clearResolvedTarget:()=>B,createTour:()=>T,deactivatePicker:()=>q,deleteStep:()=>K,endTour:()=>C,fetchTour:()=>E,fetchTours:()=>b,incrementResolutionAttempts:()=>j,markStepComplete:()=>N,nextStep:()=>R,previousStep:()=>A,receiveEphemeralTour:()=>ie,receiveTour:()=>m,receiveTours:()=>h,reorderSteps:()=>X,repeatStep:()=>k,requestAiDraft:()=>ee,requestAiTour:()=>se,resetCompletion:()=>L,saveTour:()=>y,selectStep:()=>$,setAiDraftError:()=>Z,setAiDraftLoading:()=>Y,setAiDraftResult:()=>J,setAiTourError:()=>re,setAiTourLoading:()=>ne,setCompletionSatisfied:()=>O,setCurrentStep:()=>_,setCurrentTour:()=>v,setLastError:()=>U,setLastFailureContext:()=>oe,setMode:()=>D,setPendingChanges:()=>H,setRecovering:()=>M,setResolvedTarget:()=>P,setSidebarOpen:()=>te,setToursError:()=>g,setToursLoading:()=>f,skipStep:()=>I,startEphemeralTour:()=>le,startPicking:()=>V,startTour:()=>x,stopPicking:()=>G,stopTour:()=>w,updateStep:()=>z,updateTour:()=>S});var a={};o.r(a),o.d(a,{getAiDraft:()=>$e,getAiDraftError:()=>qe,getAiDraftResult:()=>Ge,getAiTourError:()=>We,getCurrentStep:()=>Ee,getCurrentStepIndex:()=>be,getCurrentTour:()=>ve,getCurrentTourId:()=>me,getEphemeralTour:()=>Xe,getLastError:()=>Ne,getLastFailureContext:()=>Ke,getMode:()=>Ce,getPickingStepId:()=>Be,getProgress:()=>xe,getResolutionAttempts:()=>Le,getResolvedTarget:()=>De,getSelectedStep:()=>je,getSelectedStepId:()=>Me,getSkippedSteps:()=>Ie,getTotalSteps:()=>ye,getTour:()=>de,getTours:()=>ue,getToursByEditor:()=>he,getToursById:()=>ce,getToursByPostType:()=>ge,getToursError:()=>fe,hasNextStep:()=>Te,hasPendingChanges:()=>Ue,hasPreviousStep:()=>Se,isAiDraftLoading:()=>Fe,isAiDrafting:()=>Ve,isAiTourLoading:()=>ze,isCompletionSatisfied:()=>Ae,isEducatorMode:()=>we,isEphemeralTourActive:()=>Ye,isPickerActive:()=>Pe,isPupilMode:()=>_e,isRecovering:()=>Oe,isSidebarOpen:()=>He,isTourActive:()=>Re,isToursLoading:()=>pe,wasStepSkipped:()=>ke});var s={};o.r(s),o.d(s,{getTour:()=>et,getTours:()=>Qe,getToursByPostType:()=>tt});const l=window.wp.plugins,c=window.wp.data,u={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},d={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},p=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function f(e){return{type:d.SET_TOURS_LOADING,isLoading:e}}function g(e){return{type:d.SET_TOURS_ERROR,error:e}}function h(e){return{type:d.RECEIVE_TOURS,tours:e}}function m(e){return{type:d.RECEIVE_TOUR,tour:e}}function*v(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield m(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield m({id:e,title:"",steps:[],status:"draft"})}yield{type:d.SET_CURRENT_TOUR,tourId:e}}else yield{type:d.SET_CURRENT_TOUR,tourId:null}}function b(e={}){return{type:"FETCH_TOURS",args:e}}function E(e){return{type:"FETCH_TOUR",tourId:e}}function*y(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function*T(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield m(t)),t}catch(e){throw e}}function*S(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function x(e,t="pupil"){return{type:d.START_TOUR,tourId:e,mode:t}}function C(){return{type:d.END_TOUR}}function w(){return C()}function _(e){return{type:d.SET_CURRENT_STEP,stepIndex:e}}function R(){return{type:d.NEXT_STEP}}function A(){return{type:d.PREVIOUS_STEP}}function I(){return{type:d.SKIP_STEP}}function k(){return{type:d.REPEAT_STEP}}function D(e){return{type:d.SET_MODE,mode:e}}function O(e){return{type:d.SET_COMPLETION_SATISFIED,satisfied:e}}function L(){return{type:d.RESET_COMPLETION}}function N(){return R()}function P(e){return{type:d.SET_RESOLVED_TARGET,target:e}}function B(){return{type:d.CLEAR_RESOLVED_TARGET}}function M(e){return{type:d.SET_RECOVERING,isRecovering:e}}function j(){return{type:d.INCREMENT_RESOLUTION_ATTEMPTS}}function U(e){return{type:d.SET_LAST_ERROR,error:e}}function F(e=null){return{type:d.ACTIVATE_PICKER,stepId:e}}function V(e=null){return F(e)}function q(){return{type:d.DEACTIVATE_PICKER}}function G(){return q()}function $(e){return{type:d.SELECT_STEP,stepId:e}}function H(e){return{type:d.SET_PENDING_CHANGES,pending:e}}function*z(e,t,n){yield{type:d.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*W(e,t={},n=null){const r={id:p(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:d.ADD_STEP,tourId:e,step:r,index:n}}function*K(e,t){yield{type:d.DELETE_STEP,tourId:e,stepId:t}}function*X(e,t){yield{type:d.REORDER_STEPS,tourId:e,stepIds:t}}function Y(e){return{type:d.SET_AI_DRAFT_LOADING,isLoading:e}}function Z(e){return{type:d.SET_AI_DRAFT_ERROR,error:e}}function J(e){return{type:d.SET_AI_DRAFT_RESULT,result:e}}function Q(){return{type:d.CLEAR_AI_DRAFT}}function*ee(e,t){yield Y(!0),yield Z(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield J(n),n}catch(e){throw yield Z(e.message||"Failed to generate AI draft"),e}finally{yield Y(!1)}}function te(e){return{type:d.SET_SIDEBAR_OPEN,isOpen:e}}function ne(e){return{type:d.SET_AI_TOUR_LOADING,isLoading:e}}function re(e){return{type:d.SET_AI_TOUR_ERROR,error:e}}function oe(e){return{type:d.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function ie(e){return{type:d.RECEIVE_EPHEMERAL_TOUR,tour:e}}function ae(){return{type:d.CLEAR_EPHEMERAL_TOUR}}function*se(e,t,n,r=null){yield ne(!0),yield re(null);try{const o=yield{type:"GATHER_EDITOR_CONTEXT"},i=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:o,failureContext:r};console.log("[ACT AI Response] Full result:",i),console.log("[ACT AI Response] Tour:",JSON.stringify(i.tour,null,2));const a={id:"ephemeral",...i.tour};return yield ie(a),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil"),a}catch(e){throw yield re(e.message||"Failed to generate tour"),e}finally{yield ne(!1)}}function*le(e){const t={id:"ephemeral",...e};yield ie(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil")}function ce(e){return e.tours}const ue=(0,c.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function de(e,t){return e.tours[t]||null}function pe(e){return e.toursLoading}function fe(e){return e.toursError}const ge=(0,c.createSelector)((e,t)=>ue(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),he=(0,c.createSelector)((e,t)=>ue(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function me(e){return e.currentTourId}function ve(e){return e.currentTourId?e.tours[e.currentTourId]:null}function be(e){return e.currentStepIndex}const Ee=(0,c.createSelector)(e=>{const t=ve(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function ye(e){const t=ve(e);return t?.steps?.length||0}function Te(e){return e.currentStepIndex0}function xe(e){const t=ye(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function Ce(e){return e.mode}function we(e){return"educator"===e.mode}function _e(e){return"pupil"===e.mode}function Re(e){return null!==e.currentTourId&&null!==e.mode}function Ae(e){return e.completionSatisfied}function Ie(e){return e.skippedSteps}function ke(e,t){return e.skippedSteps.includes(t)}function De(e){return e.resolvedTarget}function Oe(e){return e.isRecovering}function Le(e){return e.resolutionAttempts}function Ne(e){return e.lastError}function Pe(e){return e.isPickerActive}function Be(e){return e.pickingStepId||null}function Me(e){return e.selectedStepId}const je=(0,c.createSelector)(e=>{const t=ve(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function Ue(e){return e.pendingChanges}function Fe(e){return e.aiDraftLoading}function Ve(e){return Fe(e)}function qe(e){return e.aiDraftError}function Ge(e){return e.aiDraftResult}function $e(e){return Ge(e)}function He(e){return e.sidebarOpen}function ze(e){return e.aiTourLoading}function We(e){return e.aiTourError}function Ke(e){return e.lastFailureContext}function Xe(e){return e.ephemeralTour}function Ye(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Ze=window.wp.apiFetch;var Je=o.n(Ze);function*Qe(){yield f(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield h(e)}catch(e){yield g(e.message||"Failed to fetch tours")}}function*et(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield m(t)}catch(e){yield g(e.message||"Failed to fetch tour")}}function*tt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield h(t)}catch(e){yield g(e.message||"Failed to fetch tours")}}function nt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,c.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,c.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,c.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function rt(){try{const e=(0,c.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:ot(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function ot(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function it(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:at(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:at(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:at(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:at(o)});const i=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of i){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function at(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function st(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function lt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let i=null;for(const e of o)if(i=r.querySelector(e),i)break;if(i||(i=r),i.scrollIntoView({behavior:"smooth",block:"center"}),i.focus(),"true"===i.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(i),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",i.tagName,i.className),!0}const ct={API_FETCH:e=>Je()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:rt(),visibleElements:nt(),uiSamples:it(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,c.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&st(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,c.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&st(e.attributes?.content));return t&&(await(0,c.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await lt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,c.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,c.select)("core/block-editor"),i=(r?.getBlocks()||[]).length;return await t.insertBlock(n,i,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await lt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Je()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Je()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Je()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Je()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:window.adminCoachTours?.locale||""}}),FETCH_AI_TASKS:()=>Je()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},ut="admin-coach-tours",dt=(0,c.createReduxStore)(ut,{reducer:function(e=u,t){switch(t.type){case d.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case d.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case d.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case d.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case d.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case d.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case d.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case d.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case d.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case d.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case d.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case d.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case d.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case d.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case d.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case d.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case d.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case d.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case d.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case d.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case d.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case d.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:i,selectors:a,resolvers:s,controls:ct,initialState:u});(0,c.select)(ut)||(0,c.register)(dt);const pt=window.wp.editor,ft=window.wp.element,gt=window.wp.i18n,ht=window.wp.components,mt=window.wp.primitives,vt=window.ReactJSXRuntime;var bt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M12 4a8 8 0 1 1 .001 16.001A8 8 0 0 1 12 4Zm0 1.5a6.5 6.5 0 1 0-.001 13.001A6.5 6.5 0 0 0 12 5.5Zm.75 11h-1.5V15h1.5v1.5Zm-.445-9.234a3 3 0 0 1 .445 5.89V14h-1.5v-1.25c0-.57.452-.958.917-1.01A1.5 1.5 0 0 0 12 8.75a1.5 1.5 0 0 0-1.5 1.5H9a3 3 0 0 1 3.305-2.984Z"})}),Et=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M16.5 7.5 10 13.9l-2.5-2.4-1 1 3.5 3.6 7.5-7.6z"})}),yt=(0,vt.jsx)(mt.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,vt.jsx)(mt.Path,{d:"M3.99961 13C4.67043 13.3354 4.6703 13.3357 4.67017 13.3359L4.67298 13.3305C4.67621 13.3242 4.68184 13.3135 4.68988 13.2985C4.70595 13.2686 4.7316 13.2218 4.76695 13.1608C4.8377 13.0385 4.94692 12.8592 5.09541 12.6419C5.39312 12.2062 5.84436 11.624 6.45435 11.0431C7.67308 9.88241 9.49719 8.75 11.9996 8.75C14.502 8.75 16.3261 9.88241 17.5449 11.0431C18.1549 11.624 18.6061 12.2062 18.9038 12.6419C19.0523 12.8592 19.1615 13.0385 19.2323 13.1608C19.2676 13.2218 19.2933 13.2686 19.3093 13.2985C19.3174 13.3135 19.323 13.3242 19.3262 13.3305L19.3291 13.3359C19.3289 13.3357 19.3288 13.3354 19.9996 13C20.6704 12.6646 20.6703 12.6643 20.6701 12.664L20.6697 12.6632L20.6688 12.6614L20.6662 12.6563L20.6583 12.6408C20.6517 12.6282 20.6427 12.6108 20.631 12.5892C20.6078 12.5459 20.5744 12.4852 20.5306 12.4096C20.4432 12.2584 20.3141 12.0471 20.1423 11.7956C19.7994 11.2938 19.2819 10.626 18.5794 9.9569C17.1731 8.61759 14.9972 7.25 11.9996 7.25C9.00203 7.25 6.82614 8.61759 5.41987 9.9569C4.71736 10.626 4.19984 11.2938 3.85694 11.7956C3.68511 12.0471 3.55605 12.2584 3.4686 12.4096C3.42484 12.4852 3.39142 12.5459 3.36818 12.5892C3.35656 12.6108 3.34748 12.6282 3.34092 12.6408L3.33297 12.6563L3.33041 12.6614L3.32948 12.6632L3.32911 12.664C3.32894 12.6643 3.32879 12.6646 3.99961 13ZM11.9996 16C13.9326 16 15.4996 14.433 15.4996 12.5C15.4996 10.567 13.9326 9 11.9996 9C10.0666 9 8.49961 10.567 8.49961 12.5C8.49961 14.433 10.0666 16 11.9996 16Z"})}),Tt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"})}),St=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"})}),xt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"})});const Ct=window.React;var wt=o.n(Ct);const _t=window.ReactDOM,Rt="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function At(e){const t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function It(e){return"nodeType"in e}function kt(e){var t,n;return e?At(e)?e:It(e)&&null!=(t=null==(n=e.ownerDocument)?void 0:n.defaultView)?t:window:window}function Dt(e){const{Document:t}=kt(e);return e instanceof t}function Ot(e){return!At(e)&&e instanceof kt(e).HTMLElement}function Lt(e){return e instanceof kt(e).SVGElement}function Nt(e){return e?At(e)?e.document:It(e)?Dt(e)?e:Ot(e)||Lt(e)?e.ownerDocument:document:document:document}const Pt=Rt?Ct.useLayoutEffect:Ct.useEffect;function Bt(e){const t=(0,Ct.useRef)(e);return Pt(()=>{t.current=e}),(0,Ct.useCallback)(function(){for(var e=arguments.length,n=new Array(e),r=0;r{n.current!==e&&(n.current=e)},t),n}function jt(e,t){const n=(0,Ct.useRef)();return(0,Ct.useMemo)(()=>{const t=e(n.current);return n.current=t,t},[...t])}function Ut(e){const t=Bt(e),n=(0,Ct.useRef)(null),r=(0,Ct.useCallback)(e=>{e!==n.current&&(null==t||t(e,n.current)),n.current=e},[]);return[n,r]}function Ft(e){const t=(0,Ct.useRef)();return(0,Ct.useEffect)(()=>{t.current=e},[e]),t.current}let Vt={};function qt(e,t){return(0,Ct.useMemo)(()=>{if(t)return t;const n=null==Vt[e]?0:Vt[e]+1;return Vt[e]=n,e+"-"+n},[e,t])}function Gt(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const r=Object.entries(n);for(const[n,o]of r){const r=t[n];null!=r&&(t[n]=r+e*o)}return t},{...t})}}const $t=Gt(1),Ht=Gt(-1);function zt(e){if(!e)return!1;const{KeyboardEvent:t}=kt(e.target);return t&&e instanceof t}function Wt(e){if(function(e){if(!e)return!1;const{TouchEvent:t}=kt(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return function(e){return"clientX"in e&&"clientY"in e}(e)?{x:e.clientX,y:e.clientY}:null}const Kt=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Kt.Translate.toString(e),Kt.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Xt="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Yt(e){return e.matches(Xt)?e:e.querySelector(Xt)}const Zt={display:"none"};function Jt(e){let{id:t,value:n}=e;return wt().createElement("div",{id:t,style:Zt},n)}function Qt(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;return wt().createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":r,"aria-atomic":!0},n)}const en=(0,Ct.createContext)(null),tn={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},nn={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function rn(e){let{announcements:t=nn,container:n,hiddenTextDescribedById:r,screenReaderInstructions:o=tn}=e;const{announce:i,announcement:a}=function(){const[e,t]=(0,Ct.useState)("");return{announce:(0,Ct.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),s=qt("DndLiveRegion"),[l,c]=(0,Ct.useState)(!1);if((0,Ct.useEffect)(()=>{c(!0)},[]),function(e){const t=(0,Ct.useContext)(en);(0,Ct.useEffect)(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}((0,Ct.useMemo)(()=>({onDragStart(e){let{active:n}=e;i(t.onDragStart({active:n}))},onDragMove(e){let{active:n,over:r}=e;t.onDragMove&&i(t.onDragMove({active:n,over:r}))},onDragOver(e){let{active:n,over:r}=e;i(t.onDragOver({active:n,over:r}))},onDragEnd(e){let{active:n,over:r}=e;i(t.onDragEnd({active:n,over:r}))},onDragCancel(e){let{active:n,over:r}=e;i(t.onDragCancel({active:n,over:r}))}}),[i,t])),!l)return null;const u=wt().createElement(wt().Fragment,null,wt().createElement(Jt,{id:r,value:o.draggable}),wt().createElement(Qt,{id:s,announcement:a}));return n?(0,_t.createPortal)(u,n):u}var on;function an(){}function sn(e,t){return(0,Ct.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(on||(on={}));const ln=Object.freeze({x:0,y:0});function cn(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function un(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function dn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function pn(e){let{left:t,top:n,height:r,width:o}=e;return[{x:t,y:n},{x:t+o,y:n},{x:t,y:n+r},{x:t+o,y:n+r}]}function fn(e,t){if(!e||0===e.length)return null;const[n]=e;return t?n[t]:n}function gn(e,t,n){return void 0===t&&(t=e.left),void 0===n&&(n=e.top),{x:t+.5*e.width,y:n+.5*e.height}}const hn=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=gn(t,t.left,t.top),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=cn(gn(r),o);i.push({id:t,data:{droppableContainer:e,value:n}})}}return i.sort(un)};function mn(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),i=Math.min(t.top+t.height,e.top+e.height),a=o-r,s=i-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=[];for(const e of r){const{id:r}=e,i=n.get(r);if(i){const n=mn(i,t);n>0&&o.push({id:r,data:{droppableContainer:e,value:n}})}}return o.sort(dn)};function bn(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:ln}function En(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}const yn=En(1);const Tn={ignoreTransform:!1};function Sn(e,t){void 0===t&&(t=Tn);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:t,transformOrigin:r}=kt(e).getComputedStyle(e);t&&(n=function(e,t,n){const r=function(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!r)return e;const{scaleX:o,scaleY:i,x:a,y:s}=r,l=e.left-a-(1-o)*parseFloat(n),c=e.top-s-(1-i)*parseFloat(n.slice(n.indexOf(" ")+1)),u=o?e.width/o:e.width,d=i?e.height/i:e.height;return{width:u,height:d,top:c,right:l+u,bottom:c+d,left:l}}(n,t,r))}const{top:r,left:o,width:i,height:a,bottom:s,right:l}=n;return{top:r,left:o,width:i,height:a,bottom:s,right:l}}function xn(e){return Sn(e,{ignoreTransform:!0})}function Cn(e,t){const n=[];return e?function r(o){if(null!=t&&n.length>=t)return n;if(!o)return n;if(Dt(o)&&null!=o.scrollingElement&&!n.includes(o.scrollingElement))return n.push(o.scrollingElement),n;if(!Ot(o)||Lt(o))return n;if(n.includes(o))return n;const i=kt(e).getComputedStyle(o);return o!==e&&function(e,t){void 0===t&&(t=kt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{const r=t[e];return"string"==typeof r&&n.test(r)})}(o,i)&&n.push(o),function(e,t){return void 0===t&&(t=kt(e).getComputedStyle(e)),"fixed"===t.position}(o,i)?n:r(o.parentNode)}(e):n}function wn(e){const[t]=Cn(e,1);return null!=t?t:null}function Rn(e){return Rt&&e?At(e)?e:It(e)?Dt(e)||e===Nt(e).scrollingElement?window:Ot(e)?e:null:null:null}function An(e){return At(e)?e.scrollX:e.scrollLeft}function In(e){return At(e)?e.scrollY:e.scrollTop}function kn(e){return{x:An(e),y:In(e)}}var Dn;function On(e){return!(!Rt||!e)&&e===document.scrollingElement}function Ln(e){const t={x:0,y:0},n=On(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(Dn||(Dn={}));const Nn={x:.2,y:.2};function Pn(e,t,n,r,o){let{top:i,left:a,right:s,bottom:l}=n;void 0===r&&(r=10),void 0===o&&(o=Nn);const{isTop:c,isBottom:u,isLeft:d,isRight:p}=Ln(e),f={x:0,y:0},g={x:0,y:0},h=t.height*o.y,m=t.width*o.x;return!c&&i<=t.top+h?(f.y=Dn.Backward,g.y=r*Math.abs((t.top+h-i)/h)):!u&&l>=t.bottom-h&&(f.y=Dn.Forward,g.y=r*Math.abs((t.bottom-h-l)/h)),!p&&s>=t.right-m?(f.x=Dn.Forward,g.x=r*Math.abs((t.right-m-s)/m)):!d&&a<=t.left+m&&(f.x=Dn.Backward,g.x=r*Math.abs((t.left+m-a)/m)),{direction:f,speed:g}}function Bn(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:o}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:o,width:e.clientWidth,height:e.clientHeight}}function Mn(e){return e.reduce((e,t)=>$t(e,kn(t)),ln)}const jn=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+An(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+In(t),0)}]];class Un{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=Cn(t),r=Mn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(const[e,t,o]of jn)for(const i of t)Object.defineProperty(this,i,{get:()=>{const t=o(n),a=r[e]-t;return this.rect[i]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Fn{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,n){var r;null==(r=this.target)||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}}function Vn(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var qn,Gn;function $n(e){e.preventDefault()}function Hn(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(qn||(qn={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(Gn||(Gn={}));const zn={start:[Gn.Space,Gn.Enter],cancel:[Gn.Esc],end:[Gn.Space,Gn.Enter,Gn.Tab]},Wn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Gn.Right:return{...n,x:n.x+25};case Gn.Left:return{...n,x:n.x-25};case Gn.Down:return{...n,y:n.y+25};case Gn.Up:return{...n,y:n.y-25}}};class Kn{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:t}}=e;this.props=e,this.listeners=new Fn(Nt(t)),this.windowListeners=new Fn(kt(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(qn.Resize,this.handleCancel),this.windowListeners.add(qn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(qn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&function(e,t){if(void 0===t&&(t=Sn),!e)return;const{top:n,left:r,bottom:o,right:i}=t(e);wn(e)&&(o<=0||i<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(n),t(ln)}handleKeyDown(e){if(zt(e)){const{active:t,context:n,options:r}=this.props,{keyboardCodes:o=zn,coordinateGetter:i=Wn,scrollBehavior:a="smooth"}=r,{code:s}=e;if(o.end.includes(s))return void this.handleEnd(e);if(o.cancel.includes(s))return void this.handleCancel(e);const{collisionRect:l}=n.current,c=l?{x:l.left,y:l.top}:ln;this.referenceCoordinates||(this.referenceCoordinates=c);const u=i(e,{active:t,context:n.current,currentCoordinates:c});if(u){const t=Ht(u,c),r={x:0,y:0},{scrollableAncestors:o}=n.current;for(const n of o){const o=e.code,{isTop:i,isRight:s,isLeft:l,isBottom:c,maxScroll:d,minScroll:p}=Ln(n),f=Bn(n),g={x:Math.min(o===Gn.Right?f.right-f.width/2:f.right,Math.max(o===Gn.Right?f.left:f.left+f.width/2,u.x)),y:Math.min(o===Gn.Down?f.bottom-f.height/2:f.bottom,Math.max(o===Gn.Down?f.top:f.top+f.height/2,u.y))},h=o===Gn.Right&&!s||o===Gn.Left&&!l,m=o===Gn.Down&&!c||o===Gn.Up&&!i;if(h&&g.x!==u.x){const e=n.scrollLeft+t.x,i=o===Gn.Right&&e<=d.x||o===Gn.Left&&e>=p.x;if(i&&!t.y)return void n.scrollTo({left:e,behavior:a});r.x=i?n.scrollLeft-e:o===Gn.Right?n.scrollLeft-d.x:n.scrollLeft-p.x,r.x&&n.scrollBy({left:-r.x,behavior:a});break}if(m&&g.y!==u.y){const e=n.scrollTop+t.y,i=o===Gn.Down&&e<=d.y||o===Gn.Up&&e>=p.y;if(i&&!t.x)return void n.scrollTo({top:e,behavior:a});r.y=i?n.scrollTop-e:o===Gn.Down?n.scrollTop-d.y:n.scrollTop-p.y,r.y&&n.scrollBy({top:-r.y,behavior:a});break}}this.handleMove(e,$t(Ht(u,this.referenceCoordinates),r))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function Xn(e){return Boolean(e&&"distance"in e)}function Yn(e){return Boolean(e&&"delay"in e)}Kn.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=zn,onActivation:o}=t,{active:i}=n;const{code:a}=e.nativeEvent;if(r.start.includes(a)){const t=i.activatorNode.current;return!(t&&e.target!==t||(e.preventDefault(),null==o||o({event:e.nativeEvent}),0))}return!1}}];class Zn{constructor(e,t,n){var r;void 0===n&&(n=function(e){const{EventTarget:t}=kt(e);return e instanceof t?e:Nt(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;const{event:o}=e,{target:i}=o;this.props=e,this.events=t,this.document=Nt(i),this.documentListeners=new Fn(this.document),this.listeners=new Fn(n),this.windowListeners=new Fn(kt(i)),this.initialCoordinates=null!=(r=Wt(o))?r:ln,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(qn.Resize,this.handleCancel),this.windowListeners.add(qn.DragStart,$n),this.windowListeners.add(qn.VisibilityChange,this.handleCancel),this.windowListeners.add(qn.ContextMenu,$n),this.documentListeners.add(qn.Keydown,this.handleKeydown),t){if(null!=n&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Yn(t))return this.timeoutId=setTimeout(this.handleStart,t.delay),void this.handlePending(t);if(Xn(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){const{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(qn.Click,Hn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(qn.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;const{activated:n,initialCoordinates:r,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!r)return;const s=null!=(t=Wt(e))?t:ln,l=Ht(r,s);if(!n&&a){if(Xn(a)){if(null!=a.tolerance&&Vn(l,a.tolerance))return this.handleCancel();if(Vn(l,a.distance))return this.handleStart()}return Yn(a)&&Vn(l,a.tolerance)?this.handleCancel():void this.handlePending(a,l)}e.cancelable&&e.preventDefault(),i(s)}handleEnd(){const{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){const{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===Gn.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}const Jn={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Qn extends Zn{constructor(e){const{event:t}=e,n=Nt(t.target);super(e,Jn,n)}}Qn.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!(!n.isPrimary||0!==n.button||(null==r||r({event:n}),0))}}];const er={move:{name:"mousemove"},end:{name:"mouseup"}};var tr;!function(e){e[e.RightClick=2]="RightClick"}(tr||(tr={})),class extends Zn{constructor(e){super(e,er,Nt(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button!==tr.RightClick&&(null==r||r({event:n}),!0)}}];const nr={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};var rr,or;(class extends Zn{constructor(e){super(e,nr)}static setup(){return window.addEventListener(nr.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(nr.move.name,e)};function e(){}}}).activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:o}=n;return!(o.length>1||(null==r||r({event:n}),0))}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(rr||(rr={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(or||(or={}));const ir={x:{[Dn.Backward]:!1,[Dn.Forward]:!1},y:{[Dn.Backward]:!1,[Dn.Forward]:!1}};var ar,sr;!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(ar||(ar={})),function(e){e.Optimized="optimized"}(sr||(sr={}));const lr=new Map;function cr(e,t){return jt(n=>e?n||("function"==typeof t?t(e):e):null,[t,e])}function ur(e){let{callback:t,disabled:n}=e;const r=Bt(t),o=(0,Ct.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.ResizeObserver)return;const{ResizeObserver:e}=window;return new e(r)},[n]);return(0,Ct.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}function dr(e){return new Un(Sn(e),e)}function pr(e,t,n){void 0===t&&(t=dr);const[r,o]=(0,Ct.useState)(null);function i(){o(r=>{if(!e)return null;var o;if(!1===e.isConnected)return null!=(o=null!=r?r:n)?o:null;const i=t(e);return JSON.stringify(r)===JSON.stringify(i)?r:i})}const a=function(e){let{callback:t,disabled:n}=e;const r=Bt(t),o=(0,Ct.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.MutationObserver)return;const{MutationObserver:e}=window;return new e(r)},[r,n]);return(0,Ct.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}({callback(t){if(e)for(const n of t){const{type:t,target:r}=n;if("childList"===t&&r instanceof HTMLElement&&r.contains(e)){i();break}}}}),s=ur({callback:i});return Pt(()=>{i(),e?(null==s||s.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==s||s.disconnect(),null==a||a.disconnect())},[e]),r}const fr=[];function gr(e,t){void 0===t&&(t=[]);const n=(0,Ct.useRef)(null);return(0,Ct.useEffect)(()=>{n.current=null},t),(0,Ct.useEffect)(()=>{const t=e!==ln;t&&!n.current&&(n.current=e),!t&&n.current&&(n.current=null)},[e]),n.current?Ht(e,n.current):ln}function hr(e){return(0,Ct.useMemo)(()=>e?function(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}(e):null,[e])}const mr=[];const vr=[{sensor:Qn,options:{}},{sensor:Kn,options:{}}],br={current:{}},Er={draggable:{measure:xn},droppable:{measure:xn,strategy:ar.WhileDragging,frequency:sr.Optimized},dragOverlay:{measure:Sn}};class yr extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,n;return null!=(t=null==(n=this.get(e))?void 0:n.node.current)?t:void 0}}const Tr={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new yr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:an},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Er,measureDroppableContainers:an,windowRect:null,measuringScheduled:!1},Sr={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:an,draggableNodes:new Map,over:null,measureDroppableContainers:an},xr=(0,Ct.createContext)(Sr),Cr=(0,Ct.createContext)(Tr);function wr(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new yr}}}function _r(e,t){switch(t.type){case on.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case on.DragMove:return null==e.draggable.active?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case on.DragEnd:case on.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case on.RegisterDroppable:{const{element:n}=t,{id:r}=n,o=new yr(e.droppable.containers);return o.set(r,n),{...e,droppable:{...e.droppable,containers:o}}}case on.SetDroppableDisabled:{const{id:n,key:r,disabled:o}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;const a=new yr(e.droppable.containers);return a.set(n,{...i,disabled:o}),{...e,droppable:{...e.droppable,containers:a}}}case on.UnregisterDroppable:{const{id:n,key:r}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new yr(e.droppable.containers);return i.delete(n),{...e,droppable:{...e.droppable,containers:i}}}default:return e}}function Rr(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:o}=(0,Ct.useContext)(xr),i=Ft(r),a=Ft(null==n?void 0:n.id);return(0,Ct.useEffect)(()=>{if(!t&&!r&&i&&null!=a){if(!zt(i))return;if(document.activeElement===i.target)return;const e=o.get(a);if(!e)return;const{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(const e of[t.current,n.current]){if(!e)continue;const t=Yt(e);if(t){t.focus();break}}})}},[r,t,o,a,i]),null}const Ar=(0,Ct.createContext)({...ln,scaleX:1,scaleY:1});var Ir;!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(Ir||(Ir={}));const kr=(0,Ct.memo)(function(e){var t,n,r,o;let{id:i,accessibility:a,autoScroll:s=!0,children:l,sensors:c=vr,collisionDetection:u=vn,measuring:d,modifiers:p,...f}=e;const g=(0,Ct.useReducer)(_r,void 0,wr),[h,m]=g,[v,b]=function(){const[e]=(0,Ct.useState)(()=>new Set),t=(0,Ct.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,Ct.useCallback)(t=>{let{type:n,event:r}=t;e.forEach(e=>{var t;return null==(t=e[n])?void 0:t.call(e,r)})},[e]),t]}(),[E,y]=(0,Ct.useState)(Ir.Uninitialized),T=E===Ir.Initialized,{draggable:{active:S,nodes:x,translate:C},droppable:{containers:w}}=h,_=null!=S?x.get(S):null,R=(0,Ct.useRef)({initial:null,translated:null}),A=(0,Ct.useMemo)(()=>{var e;return null!=S?{id:S,data:null!=(e=null==_?void 0:_.data)?e:br,rect:R}:null},[S,_]),I=(0,Ct.useRef)(null),[k,D]=(0,Ct.useState)(null),[O,L]=(0,Ct.useState)(null),N=Mt(f,Object.values(f)),P=qt("DndDescribedBy",i),B=(0,Ct.useMemo)(()=>w.getEnabled(),[w]),M=(j=d,(0,Ct.useMemo)(()=>({draggable:{...Er.draggable,...null==j?void 0:j.draggable},droppable:{...Er.droppable,...null==j?void 0:j.droppable},dragOverlay:{...Er.dragOverlay,...null==j?void 0:j.dragOverlay}}),[null==j?void 0:j.draggable,null==j?void 0:j.droppable,null==j?void 0:j.dragOverlay]));var j;const{droppableRects:U,measureDroppableContainers:F,measuringScheduled:V}=function(e,t){let{dragging:n,dependencies:r,config:o}=t;const[i,a]=(0,Ct.useState)(null),{frequency:s,measure:l,strategy:c}=o,u=(0,Ct.useRef)(e),d=function(){switch(c){case ar.Always:return!1;case ar.BeforeDragging:return n;default:return!n}}(),p=Mt(d),f=(0,Ct.useCallback)(function(e){void 0===e&&(e=[]),p.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),g=(0,Ct.useRef)(null),h=jt(t=>{if(d&&!n)return lr;if(!t||t===lr||u.current!==e||null!=i){const t=new Map;for(let n of e){if(!n)continue;if(i&&i.length>0&&!i.includes(n.id)&&n.rect.current){t.set(n.id,n.rect.current);continue}const e=n.node.current,r=e?new Un(l(e),e):null;n.rect.current=r,r&&t.set(n.id,r)}return t}return t},[e,i,n,d,l]);return(0,Ct.useEffect)(()=>{u.current=e},[e]),(0,Ct.useEffect)(()=>{d||f()},[n,d]),(0,Ct.useEffect)(()=>{i&&i.length>0&&a(null)},[JSON.stringify(i)]),(0,Ct.useEffect)(()=>{d||"number"!=typeof s||null!==g.current||(g.current=setTimeout(()=>{f(),g.current=null},s))},[s,d,f,...r]),{droppableRects:h,measureDroppableContainers:f,measuringScheduled:null!=i}}(B,{dragging:T,dependencies:[C.x,C.y],config:M.droppable}),q=function(e,t){const n=null!=t?e.get(t):void 0,r=n?n.node.current:null;return jt(e=>{var n;return null==t?null:null!=(n=null!=r?r:e)?n:null},[r,t])}(x,S),G=(0,Ct.useMemo)(()=>O?Wt(O):null,[O]),$=function(){const e=!1===(null==k?void 0:k.autoScrollEnabled),t="object"==typeof s?!1===s.enabled:!1===s,n=T&&!e&&!t;return"object"==typeof s?{...s,enabled:n}:{enabled:n}}(),H=function(e,t){return cr(e,t)}(q,M.draggable.measure);!function(e){let{activeNode:t,measure:n,initialRect:r,config:o=!0}=e;const i=(0,Ct.useRef)(!1),{x:a,y:s}="boolean"==typeof o?{x:o,y:o}:o;Pt(()=>{if(!a&&!s||!t)return void(i.current=!1);if(i.current||!r)return;const e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;const o=bn(n(e),r);if(a||(o.x=0),s||(o.y=0),i.current=!0,Math.abs(o.x)>0||Math.abs(o.y)>0){const t=wn(e);t&&t.scrollBy({top:o.y,left:o.x})}},[t,a,s,r,n])}({activeNode:null!=S?x.get(S):null,config:$.layoutShiftCompensation,initialRect:H,measure:M.draggable.measure});const z=pr(q,M.draggable.measure,H),W=pr(q?q.parentElement:null),K=(0,Ct.useRef)({activatorEvent:null,active:null,activeNode:q,collisionRect:null,collisions:null,droppableRects:U,draggableNodes:x,draggingNode:null,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),X=w.getNodeFor(null==(t=K.current.over)?void 0:t.id),Y=function(e){let{measure:t}=e;const[n,r]=(0,Ct.useState)(null),o=ur({callback:(0,Ct.useCallback)(e=>{for(const{target:n}of e)if(Ot(n)){r(e=>{const r=t(n);return e?{...e,width:r.width,height:r.height}:r});break}},[t])}),i=(0,Ct.useCallback)(e=>{const n=function(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Ot(t)?t:e}(e);null==o||o.disconnect(),n&&(null==o||o.observe(n)),r(n?t(n):null)},[t,o]),[a,s]=Ut(i);return(0,Ct.useMemo)(()=>({nodeRef:a,rect:n,setRef:s}),[n,a,s])}({measure:M.dragOverlay.measure}),Z=null!=(n=Y.nodeRef.current)?n:q,J=T?null!=(r=Y.rect)?r:z:null,Q=Boolean(Y.nodeRef.current&&Y.rect),ee=bn(te=Q?null:z,cr(te));var te;const ne=hr(Z?kt(Z):null),re=function(e){const t=(0,Ct.useRef)(e),n=jt(n=>e?n&&n!==fr&&e&&t.current&&e.parentNode===t.current.parentNode?n:Cn(e):fr,[e]);return(0,Ct.useEffect)(()=>{t.current=e},[e]),n}(T?null!=X?X:q:null),oe=function(e,t){void 0===t&&(t=Sn);const[n]=e,r=hr(n?kt(n):null),[o,i]=(0,Ct.useState)(mr);function a(){i(()=>e.length?e.map(e=>On(e)?r:new Un(t(e),e)):mr)}const s=ur({callback:a});return Pt(()=>{null==s||s.disconnect(),a(),e.forEach(e=>null==s?void 0:s.observe(e))},[e]),o}(re),ie=function(e,t){let{transform:n,...r}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}(p,{transform:{x:C.x-ee.x,y:C.y-ee.y,scaleX:1,scaleY:1},activatorEvent:O,active:A,activeNodeRect:z,containerNodeRect:W,draggingNodeRect:J,over:K.current.over,overlayNodeRect:Y.rect,scrollableAncestors:re,scrollableAncestorRects:oe,windowRect:ne}),ae=G?$t(G,C):null,se=function(e){const[t,n]=(0,Ct.useState)(null),r=(0,Ct.useRef)(e),o=(0,Ct.useCallback)(e=>{const t=Rn(e.target);t&&n(e=>e?(e.set(t,kn(t)),new Map(e)):null)},[]);return(0,Ct.useEffect)(()=>{const t=r.current;if(e!==t){i(t);const a=e.map(e=>{const t=Rn(e);return t?(t.addEventListener("scroll",o,{passive:!0}),[t,kn(t)]):null}).filter(e=>null!=e);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(t)};function i(e){e.forEach(e=>{const t=Rn(e);null==t||t.removeEventListener("scroll",o)})}},[o,e]),(0,Ct.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>$t(e,t),ln):Mn(e):ln,[e,t])}(re),le=gr(se),ce=gr(se,[z]),ue=$t(ie,le),de=J?yn(J,ie):null,pe=A&&de?u({active:A,collisionRect:de,droppableRects:U,droppableContainers:B,pointerCoordinates:ae}):null,fe=fn(pe,"id"),[ge,he]=(0,Ct.useState)(null),me=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(Q?ie:$t(ie,ce),null!=(o=null==ge?void 0:ge.rect)?o:null,z),ve=(0,Ct.useRef)(null),be=(0,Ct.useCallback)((e,t)=>{let{sensor:n,options:r}=t;if(null==I.current)return;const o=x.get(I.current);if(!o)return;const i=e.nativeEvent,a=new n({active:I.current,activeNode:o,event:i,options:r,context:K,onAbort(e){if(!x.get(e))return;const{onDragAbort:t}=N.current,n={id:e};null==t||t(n),v({type:"onDragAbort",event:n})},onPending(e,t,n,r){if(!x.get(e))return;const{onDragPending:o}=N.current,i={id:e,constraint:t,initialCoordinates:n,offset:r};null==o||o(i),v({type:"onDragPending",event:i})},onStart(e){const t=I.current;if(null==t)return;const n=x.get(t);if(!n)return;const{onDragStart:r}=N.current,o={activatorEvent:i,active:{id:t,data:n.data,rect:R}};(0,_t.unstable_batchedUpdates)(()=>{null==r||r(o),y(Ir.Initializing),m({type:on.DragStart,initialCoordinates:e,active:t}),v({type:"onDragStart",event:o}),D(ve.current),L(i)})},onMove(e){m({type:on.DragMove,coordinates:e})},onEnd:s(on.DragEnd),onCancel:s(on.DragCancel)});function s(e){return async function(){const{active:t,collisions:n,over:r,scrollAdjustedTranslate:o}=K.current;let a=null;if(t&&o){const{cancelDrop:s}=N.current;a={activatorEvent:i,active:t,collisions:n,delta:o,over:r},e===on.DragEnd&&"function"==typeof s&&await Promise.resolve(s(a))&&(e=on.DragCancel)}I.current=null,(0,_t.unstable_batchedUpdates)(()=>{m({type:e}),y(Ir.Uninitialized),he(null),D(null),L(null),ve.current=null;const t=e===on.DragEnd?"onDragEnd":"onDragCancel";if(a){const e=N.current[t];null==e||e(a),v({type:t,event:a})}})}}ve.current=a},[x]),Ee=(0,Ct.useCallback)((e,t)=>(n,r)=>{const o=n.nativeEvent,i=x.get(r);if(null!==I.current||!i||o.dndKit||o.defaultPrevented)return;const a={active:i};!0===e(n,t.options,a)&&(o.dndKit={capturedBy:t.sensor},I.current=r,be(n,t))},[x,be]),ye=function(e,t){return(0,Ct.useMemo)(()=>e.reduce((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:t(e.handler,n)}))]},[]),[e,t])}(c,Ee);!function(e){(0,Ct.useEffect)(()=>{if(!Rt)return;const t=e.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(const e of t)null==e||e()}},e.map(e=>{let{sensor:t}=e;return t}))}(c),Pt(()=>{z&&E===Ir.Initializing&&y(Ir.Initialized)},[z,E]),(0,Ct.useEffect)(()=>{const{onDragMove:e}=N.current,{active:t,activatorEvent:n,collisions:r,over:o}=K.current;if(!t||!n)return;const i={active:t,activatorEvent:n,collisions:r,delta:{x:ue.x,y:ue.y},over:o};(0,_t.unstable_batchedUpdates)(()=>{null==e||e(i),v({type:"onDragMove",event:i})})},[ue.x,ue.y]),(0,Ct.useEffect)(()=>{const{active:e,activatorEvent:t,collisions:n,droppableContainers:r,scrollAdjustedTranslate:o}=K.current;if(!e||null==I.current||!t||!o)return;const{onDragOver:i}=N.current,a=r.get(fe),s=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,l={active:e,activatorEvent:t,collisions:n,delta:{x:o.x,y:o.y},over:s};(0,_t.unstable_batchedUpdates)(()=>{he(s),null==i||i(l),v({type:"onDragOver",event:l})})},[fe]),Pt(()=>{K.current={activatorEvent:O,active:A,activeNode:q,collisionRect:de,collisions:pe,droppableRects:U,draggableNodes:x,draggingNode:Z,draggingNodeRect:J,droppableContainers:w,over:ge,scrollableAncestors:re,scrollAdjustedTranslate:ue},R.current={initial:J,translated:de}},[A,q,pe,de,x,Z,J,U,w,ge,re,ue]),function(e){let{acceleration:t,activator:n=rr.Pointer,canScroll:r,draggingRect:o,enabled:i,interval:a=5,order:s=or.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:u,delta:d,threshold:p}=e;const f=function(e){let{delta:t,disabled:n}=e;const r=Ft(t);return jt(e=>{if(n||!r||!e)return ir;const o=Math.sign(t.x-r.x),i=Math.sign(t.y-r.y);return{x:{[Dn.Backward]:e.x[Dn.Backward]||-1===o,[Dn.Forward]:e.x[Dn.Forward]||1===o},y:{[Dn.Backward]:e.y[Dn.Backward]||-1===i,[Dn.Forward]:e.y[Dn.Forward]||1===i}}},[n,t,r])}({delta:d,disabled:!i}),[g,h]=function(){const e=(0,Ct.useRef)(null);return[(0,Ct.useCallback)((t,n)=>{e.current=setInterval(t,n)},[]),(0,Ct.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}(),m=(0,Ct.useRef)({x:0,y:0}),v=(0,Ct.useRef)({x:0,y:0}),b=(0,Ct.useMemo)(()=>{switch(n){case rr.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case rr.DraggableRect:return o}},[n,o,l]),E=(0,Ct.useRef)(null),y=(0,Ct.useCallback)(()=>{const e=E.current;if(!e)return;const t=m.current.x*v.current.x,n=m.current.y*v.current.y;e.scrollBy(t,n)},[]),T=(0,Ct.useMemo)(()=>s===or.TreeOrder?[...c].reverse():c,[s,c]);(0,Ct.useEffect)(()=>{if(i&&c.length&&b){for(const e of T){if(!1===(null==r?void 0:r(e)))continue;const n=c.indexOf(e),o=u[n];if(!o)continue;const{direction:i,speed:s}=Pn(e,o,b,t,p);for(const e of["x","y"])f[e][i[e]]||(s[e]=0,i[e]=0);if(s.x>0||s.y>0)return h(),E.current=e,g(y,a),m.current=s,void(v.current=i)}m.current={x:0,y:0},v.current={x:0,y:0},h()}else h()},[t,y,r,h,i,a,JSON.stringify(b),JSON.stringify(f),g,c,T,u,JSON.stringify(p)])}({...$,delta:C,draggingRect:de,pointerCoordinates:ae,scrollableAncestors:re,scrollableAncestorRects:oe});const Te=(0,Ct.useMemo)(()=>({active:A,activeNode:q,activeNodeRect:z,activatorEvent:O,collisions:pe,containerNodeRect:W,dragOverlay:Y,draggableNodes:x,droppableContainers:w,droppableRects:U,over:ge,measureDroppableContainers:F,scrollableAncestors:re,scrollableAncestorRects:oe,measuringConfiguration:M,measuringScheduled:V,windowRect:ne}),[A,q,z,O,pe,W,Y,x,w,U,ge,F,re,oe,M,V,ne]),Se=(0,Ct.useMemo)(()=>({activatorEvent:O,activators:ye,active:A,activeNodeRect:z,ariaDescribedById:{draggable:P},dispatch:m,draggableNodes:x,over:ge,measureDroppableContainers:F}),[O,ye,A,z,m,P,x,ge,F]);return wt().createElement(en.Provider,{value:b},wt().createElement(xr.Provider,{value:Se},wt().createElement(Cr.Provider,{value:Te},wt().createElement(Ar.Provider,{value:me},l)),wt().createElement(Rr,{disabled:!1===(null==a?void 0:a.restoreFocus)})),wt().createElement(rn,{...a,hiddenTextDescribedById:P}))}),Dr=(0,Ct.createContext)(null),Or="button";const Lr={timeout:25};function Nr(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Pr(e,t){return e.reduce((e,n,r)=>{const o=t.get(n);return o&&(e[r]=o),e},Array(e.length))}function Br(e){return null!==e&&e>=0}const Mr=e=>{let{rects:t,activeIndex:n,overIndex:r,index:o}=e;const i=Nr(t,r,n),a=t[o],s=i[o];return s&&a?{x:s.left-a.left,y:s.top-a.top,scaleX:s.width/a.width,scaleY:s.height/a.height}:null},jr={scaleX:1,scaleY:1},Ur=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:o,rects:i,overIndex:a}=e;const s=null!=(t=i[n])?t:r;if(!s)return null;if(o===n){const e=i[a];return e?{x:0,y:nn&&o<=a?{x:0,y:-s.height-l,...jr}:o=a?{x:0,y:s.height+l,...jr}:{x:0,y:0,...jr}},Fr="Sortable",Vr=wt().createContext({activeIndex:-1,containerId:Fr,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:Mr,disabled:{draggable:!1,droppable:!1}});function qr(e){let{children:t,id:n,items:r,strategy:o=Mr,disabled:i=!1}=e;const{active:a,dragOverlay:s,droppableRects:l,over:c,measureDroppableContainers:u}=(0,Ct.useContext)(Cr),d=qt(Fr,n),p=Boolean(null!==s.rect),f=(0,Ct.useMemo)(()=>r.map(e=>"object"==typeof e&&"id"in e?e.id:e),[r]),g=null!=a,h=a?f.indexOf(a.id):-1,m=c?f.indexOf(c.id):-1,v=(0,Ct.useRef)(f),b=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{b&&g&&u(f)},[b,f,g,u]),(0,Ct.useEffect)(()=>{v.current=f},[f]);const T=(0,Ct.useMemo)(()=>({activeIndex:h,containerId:d,disabled:y,disableTransforms:E,items:f,overIndex:m,useDragOverlay:p,sortedRects:Pr(f,l),strategy:o}),[h,d,y.draggable,y.droppable,E,f,m,l,p,o]);return wt().createElement(Vr.Provider,{value:T},t)}const Gr=e=>{let{id:t,items:n,activeIndex:r,overIndex:o}=e;return Nr(n,r,o).indexOf(t)},$r=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:o,items:i,newIndex:a,previousItems:s,previousContainerId:l,transition:c}=e;return!(!c||!r||s!==i&&o===a||!n&&(a===o||t!==l))},Hr={duration:200,easing:"ease"},zr="transform",Wr=Kt.Transition.toString({property:zr,duration:0,easing:"linear"}),Kr={roleDescription:"sortable"};function Xr(e){let{animateLayoutChanges:t=$r,attributes:n,disabled:r,data:o,getNewIndex:i=Gr,id:a,strategy:s,resizeObserverConfig:l,transition:c=Hr}=e;const{items:u,containerId:d,activeIndex:p,disabled:f,disableTransforms:g,sortedRects:h,overIndex:m,useDragOverlay:v,strategy:b}=(0,Ct.useContext)(Vr),E=function(e,t){var n,r;return"boolean"==typeof e?{draggable:e,droppable:!1}:{draggable:null!=(n=null==e?void 0:e.draggable)?n:t.draggable,droppable:null!=(r=null==e?void 0:e.droppable)?r:t.droppable}}(r,f),y=u.indexOf(a),T=(0,Ct.useMemo)(()=>({sortable:{containerId:d,index:y,items:u},...o}),[d,o,y,u]),S=(0,Ct.useMemo)(()=>u.slice(u.indexOf(a)),[u,a]),{rect:x,node:C,isOver:w,setNodeRef:_}=function(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:o}=e;const i=qt("Droppable"),{active:a,dispatch:s,over:l,measureDroppableContainers:c}=(0,Ct.useContext)(xr),u=(0,Ct.useRef)({disabled:n}),d=(0,Ct.useRef)(!1),p=(0,Ct.useRef)(null),f=(0,Ct.useRef)(null),{disabled:g,updateMeasurementsFor:h,timeout:m}={...Lr,...o},v=Mt(null!=h?h:r),b=ur({callback:(0,Ct.useCallback)(()=>{d.current?(null!=f.current&&clearTimeout(f.current),f.current=setTimeout(()=>{c(Array.isArray(v.current)?v.current:[v.current]),f.current=null},m)):d.current=!0},[m]),disabled:g||!a}),E=(0,Ct.useCallback)((e,t)=>{b&&(t&&(b.unobserve(t),d.current=!1),e&&b.observe(e))},[b]),[y,T]=Ut(E),S=Mt(t);return(0,Ct.useEffect)(()=>{b&&y.current&&(b.disconnect(),d.current=!1,b.observe(y.current))},[y,b]),(0,Ct.useEffect)(()=>(s({type:on.RegisterDroppable,element:{id:r,key:i,disabled:n,node:y,rect:p,data:S}}),()=>s({type:on.UnregisterDroppable,key:i,id:r})),[r]),(0,Ct.useEffect)(()=>{n!==u.current.disabled&&(s({type:on.SetDroppableDisabled,id:r,key:i,disabled:n}),u.current.disabled=n)},[r,i,n,s]),{active:a,rect:p,isOver:(null==l?void 0:l.id)===r,node:y,over:l,setNodeRef:T}}({id:a,data:T,disabled:E.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...l}}),{active:R,activatorEvent:A,activeNodeRect:I,attributes:k,setNodeRef:D,listeners:O,isDragging:L,over:N,setActivatorNodeRef:P,transform:B}=function(e){let{id:t,data:n,disabled:r=!1,attributes:o}=e;const i=qt("Draggable"),{activators:a,activatorEvent:s,active:l,activeNodeRect:c,ariaDescribedById:u,draggableNodes:d,over:p}=(0,Ct.useContext)(xr),{role:f=Or,roleDescription:g="draggable",tabIndex:h=0}=null!=o?o:{},m=(null==l?void 0:l.id)===t,v=(0,Ct.useContext)(m?Ar:Dr),[b,E]=Ut(),[y,T]=Ut(),S=function(e,t){return(0,Ct.useMemo)(()=>e.reduce((e,n)=>{let{eventName:r,handler:o}=n;return e[r]=e=>{o(e,t)},e},{}),[e,t])}(a,t),x=Mt(n);return Pt(()=>(d.set(t,{id:t,key:i,node:b,activatorNode:y,data:x}),()=>{const e=d.get(t);e&&e.key===i&&d.delete(t)}),[d,t]),{active:l,activatorEvent:s,activeNodeRect:c,attributes:(0,Ct.useMemo)(()=>({role:f,tabIndex:h,"aria-disabled":r,"aria-pressed":!(!m||f!==Or)||void 0,"aria-roledescription":g,"aria-describedby":u.draggable}),[r,f,h,m,g,u.draggable]),isDragging:m,listeners:r?void 0:S,node:b,over:p,setNodeRef:E,setActivatorNodeRef:T,transform:v}}({id:a,data:T,attributes:{...Kr,...n},disabled:E.draggable}),M=function(){for(var e=arguments.length,t=new Array(e),n=0;ne=>{t.forEach(t=>t(e))},t)}(_,D),j=Boolean(R),U=j&&!g&&Br(p)&&Br(m),F=!v&&L,V=F&&U?B:null,q=U?null!=V?V:(null!=s?s:b)({rects:h,activeNodeRect:I,activeIndex:p,overIndex:m,index:y}):null,G=Br(p)&&Br(m)?i({id:a,items:u,activeIndex:p,overIndex:m}):y,$=null==R?void 0:R.id,H=(0,Ct.useRef)({activeId:$,items:u,newIndex:G,containerId:d}),z=u!==H.current.items,W=t({active:R,containerId:d,isDragging:L,isSorting:j,id:a,index:y,items:u,newIndex:H.current.newIndex,previousItems:H.current.items,previousContainerId:H.current.containerId,transition:c,wasDragging:null!=H.current.activeId}),K=function(e){let{disabled:t,index:n,node:r,rect:o}=e;const[i,a]=(0,Ct.useState)(null),s=(0,Ct.useRef)(n);return Pt(()=>{if(!t&&n!==s.current&&r.current){const e=o.current;if(e){const t=Sn(r.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&a(n)}}n!==s.current&&(s.current=n)},[t,n,r,o]),(0,Ct.useEffect)(()=>{i&&a(null)},[i]),i}({disabled:!W,index:y,node:C,rect:x});return(0,Ct.useEffect)(()=>{j&&H.current.newIndex!==G&&(H.current.newIndex=G),d!==H.current.containerId&&(H.current.containerId=d),u!==H.current.items&&(H.current.items=u)},[j,G,d,u]),(0,Ct.useEffect)(()=>{if($===H.current.activeId)return;if(null!=$&&null==H.current.activeId)return void(H.current.activeId=$);const e=setTimeout(()=>{H.current.activeId=$},50);return()=>clearTimeout(e)},[$]),{active:R,activeIndex:p,attributes:k,data:T,rect:x,index:y,newIndex:G,items:u,isOver:w,isSorting:j,isDragging:L,listeners:O,node:C,overIndex:m,over:N,setNodeRef:M,setActivatorNodeRef:P,setDroppableNodeRef:_,setDraggableNodeRef:D,transform:null!=K?K:q,transition:K||z&&H.current.newIndex===y?Wr:F&&!zt(A)||!c?void 0:j||W?Kt.Transition.toString({...c,property:zr}):void 0}}function Yr(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&"object"==typeof t.sortable&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const Zr=[Gn.Down,Gn.Right,Gn.Up,Gn.Left],Jr=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:o,droppableContainers:i,over:a,scrollableAncestors:s}}=t;if(Zr.includes(e.code)){if(e.preventDefault(),!n||!r)return;const t=[];i.getEnabled().forEach(n=>{if(!n||null!=n&&n.disabled)return;const i=o.get(n.id);if(i)switch(e.code){case Gn.Down:r.topi.top&&t.push(n);break;case Gn.Left:r.left>i.left&&t.push(n);break;case Gn.Right:r.left{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=pn(t),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=pn(r),a=o.reduce((e,t,r)=>e+cn(n[r],t),0),s=Number((a/4).toFixed(4));i.push({id:t,data:{droppableContainer:e,value:s}})}}return i.sort(un)})({active:n,collisionRect:r,droppableRects:o,droppableContainers:t,pointerCoordinates:null});let d=fn(u,"id");if(d===(null==a?void 0:a.id)&&u.length>1&&(d=u[1].id),null!=d){const e=i.get(n.id),t=i.get(d),a=t?o.get(t.id):null,u=null==t?void 0:t.node.current;if(u&&a&&e&&t){const n=Cn(u).some((e,t)=>s[t]!==e),o=Qr(e,t),i=(c=t,!(!Yr(l=e)||!Yr(c))&&!!Qr(l,c)&&l.data.current.sortable.index0&&(0,vt.jsx)("div",{className:"act-step-target-info",children:(0,vt.jsxs)("code",{children:[e.target.locators[0].value.substring(0,30),e.target.locators[0].value.length>30?"…":""]})})]}),(0,vt.jsxs)(ht.FlexItem,{className:"act-step-actions",children:[(0,vt.jsx)(ht.Button,{icon:Tt,label:(0,gt.__)("Edit step","admin-coach-tours"),onClick:()=>t(e),size:"small"}),(0,vt.jsx)(ht.Button,{icon:St,label:(0,gt.__)("Delete step","admin-coach-tours"),onClick:()=>n(e.id),size:"small",isDestructive:!0})]})]})})}function to({tourId:e,steps:t=[],onEditStep:n,onAddStep:r}){const{reorderSteps:o,deleteStep:i}=(0,c.useDispatch)("admin-coach-tours"),a=function(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(e=>null!=e),[...t])}(sn(Qn,{activationConstraint:{distance:8}}),sn(Kn,{coordinateGetter:Jr})),s=(0,ft.useCallback)(n=>{const{active:r,over:i}=n;if(r.id!==i?.id){const n=t.findIndex(e=>e.id===r.id),a=t.findIndex(e=>e.id===i?.id);if(-1!==n&&-1!==a){const r=Nr(t.map(e=>e.id),n,a);o(e,r)}}},[e,t,o]),l=(0,ft.useCallback)(t=>{window.confirm((0,gt.__)("Are you sure you want to delete this step?","admin-coach-tours"))&&i(e,t)},[e,i]);if(0===t.length)return(0,vt.jsxs)("div",{className:"act-step-list-empty",children:[(0,vt.jsx)("p",{children:(0,gt.__)("No steps yet. Click the button below to add your first step.","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"primary",icon:xt,onClick:r,children:(0,gt.__)("Add First Step","admin-coach-tours")})]});const u=[...t].sort((e,t)=>e.order-t.order);return(0,vt.jsxs)("div",{className:"act-step-list",children:[(0,vt.jsx)(kr,{sensors:a,collisionDetection:hn,onDragEnd:s,children:(0,vt.jsx)(qr,{items:u.map(e=>e.id),strategy:Ur,children:u.map(e=>(0,vt.jsx)(eo,{step:e,onEdit:n,onDelete:l},e.id))})}),(0,vt.jsx)("div",{className:"act-step-list-footer act-button-group",children:(0,vt.jsx)(ht.Button,{variant:"secondary",icon:xt,onClick:r,children:(0,gt.__)("Add Step","admin-coach-tours")})})]})}var no=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})}),ro=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})}),oo=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z"})});function io(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function ao(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const i=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",i),i}function so(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function lo(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function co(e,t=document){switch(e.type){case"css":return lo(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),i={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let a=[];i[n]&&(a=Array.from(t.querySelectorAll(i[n])));const s=[...o,...a];return r?s.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):s}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return lo(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let i=null;if("first"===e)i=o[0]?.clientId;else if("last"===e)i=o[o.length-1]?.clientId;else if("selected"===e)i=r.getSelectedBlockClientId();else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,a=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",a.length),i=a[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);i=o[t]?.clientId}if(!i)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",i);const a=t.querySelector(`[data-block="${i}"]`);return a?(console.log("[ACT findByWpBlock] Found element:",a.tagName),[a]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function uo(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&so(e,n.withinContainer)&&(r+=10),ao(e)&&(r+=100),io(e)&&(r+=5),r}const po="admin-coach-tours";function fo({step:e,tourId:t,postType:n,onClose:r}){const[o,i]=(0,ft.useState)(e.title||""),[a,s]=(0,ft.useState)(e.content||""),[l,u]=(0,ft.useState)(e.completion?.type||"manual"),[d,p]=(0,ft.useState)(e.completion?.params||{}),[f,g]=(0,ft.useState)(!1),[h,m]=(0,ft.useState)(null),[v,b]=(0,ft.useState)(null),{aiDraft:E,isAiDrafting:y,aiDraftError:T}=(0,c.useSelect)(e=>{const t=e(po);return{aiDraft:t.getAiDraft(),isAiDrafting:t.isAiDrafting(),aiDraftError:t.getAiDraftError()}},[]),{updateStep:S,requestAiDraft:x,clearAiDraft:C,startPicking:w}=(0,c.useDispatch)(po),_=[{type:"clickTarget",label:"Click Target",description:"Complete when user clicks the target element",requiresTarget:!0,params:[]},{type:"domValueChanged",label:"Value Changed",description:"Complete when element value changes",requiresTarget:!0,params:[{name:"expectedValue",type:"string",optional:!0,description:"Expected value (if not set, any change completes)"},{name:"attributeName",type:"string",optional:!0,description:"Attribute to watch (defaults to value/textContent)"}]},{type:"wpData",label:"Store Change",description:"Complete when @wordpress/data store value changes",requiresTarget:!1,params:[{name:"storeName",type:"string",required:!0,description:"Store name (e.g., core/block-editor)"},{name:"selector",type:"string",required:!0,description:"Selector function name"},{name:"args",type:"array",optional:!0,description:"Arguments for selector"},{name:"expectedValue",type:"any",optional:!0,description:"Expected value"},{name:"comparator",type:"string",optional:!0,description:"equals, notEquals, truthy, falsy, contains, greaterThan, lessThan"}]},{type:"manual",label:"Manual",description:"Complete when user clicks continue button",requiresTarget:!1,params:[]},{type:"elementAppear",label:"Element Appears",description:"Complete when an element appears in DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"elementDisappear",label:"Element Disappears",description:"Complete when an element is removed from DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"customEvent",label:"Custom Event",description:"Complete when a custom event is dispatched",requiresTarget:!1,params:[{name:"eventName",type:"string",required:!0,description:"Custom event name"}]}],R=(0,ft.useCallback)(()=>{if(e.target){const t=function(e){const t=function(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),i=o.filter(e=>!e.fallback),a=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",i.length,"primary +",a.length,"fallback locators");for(const e of[...i,...a]){let n=co(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(io),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(ao),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>so(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:uo(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}(e);return{success:t.success,element:t.element,usedLocator:t.usedLocator,error:t.error,elementInfo:t.element?{tagName:t.element.tagName.toLowerCase(),id:t.element.id||null,className:t.element.className||null,textContent:t.element.textContent?.slice(0,50)||null,rect:t.element.getBoundingClientRect()}:null}}(e.target);b(t),setTimeout(()=>b(null),5e3)}},[e.target]),A=(0,ft.useCallback)(async()=>{g(!0),m(null);try{await S(t,e.id,{title:o.trim(),content:a.trim(),completion:{type:l,params:d}}),r()}catch(e){m(e.message||(0,gt.__)("Failed to save step.","admin-coach-tours"))}finally{g(!1)}},[t,e.id,o,a,l,d,S,r]),I=(0,ft.useCallback)(()=>{if(e.target){const t={...e.elementContext||{selector:e.target,stepId:e.id},existingTitle:o,existingContent:a};x(t,n)}},[e.id,e.target,e.elementContext,o,a,n,x]),k=(0,ft.useCallback)(()=>{E&&(E.title&&i(E.title),E.content&&s(E.content),E.suggestedCompletion&&(u(E.suggestedCompletion.type),p(E.suggestedCompletion||{})),C())},[E,C]),D=(0,ft.useCallback)(()=>{w(e.id)},[e.id,w]),O=(0,ft.useCallback)((e,t)=>{p(n=>({...n,[e]:t}))},[]),L=_.find(e=>e.type===l);return(0,vt.jsxs)("div",{className:"act-step-editor",children:[h&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!1,children:h}),(0,vt.jsx)(ht.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("Target Element","admin-coach-tours"),className:"act-step-editor-target",children:(0,vt.jsx)("div",{className:"act-target-info",children:e.target?.locators?.length>0?(0,vt.jsxs)(vt.Fragment,{children:[(0,vt.jsx)("code",{className:"act-target-selector",children:e.target.locators[0].value}),(0,vt.jsxs)(ht.Flex,{gap:2,style:{marginTop:"8px"},children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"secondary",size:"small",icon:no,onClick:R,children:(0,gt.__)("Test","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",size:"small",onClick:D,children:(0,gt.__)("Re-pick","admin-coach-tours")})})]}),v&&(0,vt.jsx)(ht.Notice,{status:v.success?"success":"error",isDismissible:!1,className:"act-target-test-result",children:v.success?(0,gt.__)("Target found successfully!","admin-coach-tours"):(0,gt.__)("Target not found. Consider re-picking.","admin-coach-tours")})]}):(0,vt.jsx)(ht.Button,{variant:"primary",icon:no,onClick:D,children:(0,gt.__)("Pick Target Element","admin-coach-tours")})})}),(0,vt.jsx)(ht.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,gt.__)("Step Title","admin-coach-tours"),value:o,onChange:i,placeholder:(0,gt.__)("e.g., Click the Add Block button","admin-coach-tours")}),(0,vt.jsx)(ht.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("Step Content","admin-coach-tours"),value:a,onChange:s,placeholder:(0,gt.__)("Explain what the user should do and why…","admin-coach-tours"),rows:4}),e.target&&(0,vt.jsxs)(ht.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("AI Assistance","admin-coach-tours"),className:"act-ai-draft-section",children:[y?(0,vt.jsxs)(ht.Flex,{align:"center",gap:2,children:[(0,vt.jsx)(ht.Spinner,{}),(0,vt.jsx)("span",{children:(0,gt.__)("Generating draft…","admin-coach-tours")})]}):E?(0,vt.jsxs)("div",{className:"act-ai-draft",children:[(0,vt.jsxs)("div",{className:"act-ai-draft-preview",children:[(0,vt.jsx)("strong",{children:E.title}),(0,vt.jsxs)("p",{children:[E.content?.substring(0,100),"…"]})]}),(0,vt.jsxs)(ht.Flex,{gap:2,children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"primary",size:"small",icon:Et,onClick:k,children:(0,gt.__)("Apply","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",size:"small",icon:ro,onClick:()=>C(),children:(0,gt.__)("Dismiss","admin-coach-tours")})})]})]}):(0,vt.jsx)(ht.Button,{variant:"secondary",icon:oo,onClick:I,children:(0,gt.__)("Generate with AI","admin-coach-tours")}),T&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!1,children:T})]}),(0,vt.jsx)(ht.SelectControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,gt.__)("Completion Condition","admin-coach-tours"),value:l,options:_.map(e=>({value:e.type,label:e.label})),onChange:e=>{u(e),p({})},help:L?.description}),L?.params&&L.params.length>0&&(0,vt.jsx)("div",{className:"act-completion-params",children:L.params.map(e=>(0,vt.jsx)(ht.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:e.name,value:d[e.name]||"",onChange:t=>O(e.name,t),help:e.description,required:e.required},e.name))}),(0,vt.jsx)("div",{className:"act-step-editor-actions",children:(0,vt.jsxs)(ht.Flex,{justify:"flex-end",gap:2,children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",onClick:r,children:(0,gt.__)("Cancel","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"primary",onClick:A,isBusy:f,disabled:f||!o.trim(),children:(0,gt.__)("Save Step","admin-coach-tours")})})]})})]})}const go=[/^css-/i,/^sc-/i,/^emotion-/i,/^_/i,/^jsx-/i,/^styles?__/i,/^[a-z]{1,2}[0-9]+/i,/^[0-9]/i,/^svelte-/i];function ho(e){return e.filter(e=>{return!((t=e).length<=3||go.some(e=>e.test(t)));var t})}function mo(e){const t={},n=e.attributes;for(let e=0;ee.startsWith(t))||(t[e]=r.value)}}return t}function vo(e){if(e.getAttribute("role"))return e.getAttribute("role");const t=e.tagName.toLowerCase(),n=e.getAttribute("type"),r={button:"button",a:e.hasAttribute("href")?"link":null,input:{text:"textbox",search:"searchbox",email:"textbox",url:"textbox",tel:"textbox",password:"textbox",checkbox:"checkbox",radio:"radio",submit:"button",button:"button",reset:"button",range:"slider"},textarea:"textbox",select:"listbox",option:"option",img:e.hasAttribute("alt")?"img":null,nav:"navigation",main:"main",header:"banner",footer:"contentinfo",aside:"complementary",form:"form",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",ul:"list",ol:"list",li:"listitem",table:"table",dialog:"dialog"};return"input"===t&&r.input[n]?r.input[n]:r[t]||null}function bo(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||null}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||null}if(e.getAttribute("title"))return e.getAttribute("title");const n=e.tagName.toLowerCase();if("button"===n||"a"===n||"button"===e.getAttribute("role")){const t=e.textContent?.trim();if(t&&t.length<100)return t}return null}function Eo(e){return!(e&&!/^block-[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9]{8,}$/i.test(e)&&!/[-_][a-f0-9]{8,}/i.test(e))}const yo="admin-coach-tours",To=[".act-picker-overlay",".act-picker-highlight",".act-picker-toolbar",".edit-post-sidebar","#adminmenumain","#wpadminbar",".components-popover",".components-modal__screen-overlay",'iframe[name="editor-canvas"]'];function So(){return document.querySelector('iframe[name="editor-canvas"]')}function xo(e){for(const t of To){if(e.matches(t))return!0;if(e.closest(t))return!0}return!1}function Co({onCancel:e}){const[t,n]=(0,ft.useState)(null),[r,o]=(0,ft.useState)(null),i=(0,ft.useRef)(null),{pickingStepId:a,currentTourId:s}=(0,c.useSelect)(e=>{const t=e(yo);return{pickingStepId:t.getPickingStepId?.()||null,currentTourId:t.getCurrentTourId?.()||null}},[]),{stopPicking:l,addStep:u,updateStep:d}=(0,c.useDispatch)(yo),p=(0,ft.useRef)(!1),f=(0,ft.useRef)(null),g=(0,ft.useRef)(null),h=(0,ft.useCallback)((e,t=!1)=>{if(g.current)return;g.current=setTimeout(()=>{g.current=null},16);let r=null;if(t){if(r=e.target,r&&!xo(r)){if(r===f.current)return;f.current=r,p.current=!0;const e=So();if(e){const t=e.getBoundingClientRect(),i=r.getBoundingClientRect();n(r),o({top:t.top+i.top,left:t.left+i.left,width:i.width,height:i.height})}}}else if(r=document.elementsFromPoint(e.clientX,e.clientY).find(e=>!(e.closest(".act-picker-overlay")||xo(e)||"IFRAME"===e.tagName&&"editor-canvas"===e.name)),r&&r!==f.current){f.current=r,p.current=!1,n(r);const e=r.getBoundingClientRect();o({top:e.top,left:e.left,width:e.width,height:e.height})}},[]),m=(0,ft.useCallback)(e=>{e.preventDefault(),e.stopPropagation();const t=f.current;if(!t)return;const n=function(e,t={}){const{inEditorIframe:n=!1}=t,r=[],o={visible:!0};n&&(o.inEditorIframe=!0);const i=e.getAttribute("data-testid");i&&!Eo(i)&&r.push({type:"testId",value:i,weight:100,fallback:!1}),e.id&&!Eo(e.id)&&r.push({type:"css",value:`#${CSS.escape(e.id)}`,weight:95,fallback:!1});const a=vo(e),s=bo(e);if(a){const e=s?`${a}:${s}`:a;r.push({type:"role",value:e,weight:80,fallback:!1})}const l=mo(e);for(const[e,t]of Object.entries(l)){if("testid"===e||"reactid"===e)continue;if("block"===e&&t&&/^[a-f0-9-]{36}$/i.test(t))continue;const n="type"===e?85:e.startsWith("wp-")?75:70;if(r.push({type:"dataAttribute",value:t?`${e}:${t}`:e,weight:n,fallback:!1}),r.filter(e=>"dataAttribute"===e.type).length>=2)break}const c=function(e,t=3){const n=[];let r=e,o=0;for(;r&&r!==document.body&&o0&&(t+=i.slice(0,2).map(e=>`.${CSS.escape(e)}`).join(""));const a=r.getAttribute("data-testid");if(a&&0===o)t=`[data-testid="${a}"]`;else{const n=r.getAttribute("type");n&&"input"===e&&(t+=`[type="${n}"]`);const o=r.getAttribute("name");o&&["input","select","textarea"].includes(e)&&(t+=`[name="${o}"]`)}if(r.parentElement&&0===o){const e=Array.from(r.parentElement.children).filter(e=>e.tagName===r.tagName);e.length>1&&(t+=`:nth-of-type(${e.indexOf(r)+1})`)}n.unshift(t),r=r.parentElement,o++}return n.join(" > ")}(e,3);c&&r.push({type:"css",value:c,weight:60,fallback:!1});const u=e.getAttribute("aria-label");u&&r.push({type:"ariaLabel",value:u,weight:40,fallback:!0});const d=function(e,t=5){const n=["main","nav","aside","header","footer","section","article","form","dialog"],r=["main","navigation","complementary","banner","contentinfo","region","form","dialog","search"];let o=e.parentElement,i=0;for(;o&&o!==document.body&&i0){const e=ho(Array.from(o.classList));e.length>0&&(n+=`.${CSS.escape(e[0])}`)}return{element:o,selector:n,type:t||e}}const a=["edit-post-sidebar","block-editor","editor-styles-wrapper","components-popover","components-modal","interface-interface-skeleton"];for(const e of a)if(o.classList.contains(e))return{element:o,selector:`.${e}`,type:"editor-region"};o=o.parentElement,i++}return null}(e);if(d){o.withinContainer=d.selector;const t=e.tagName.toLowerCase(),n=ho(Array.from(e.classList));let i=t;n.length>0&&(i+=`.${CSS.escape(n[0])}`),r.push({type:"contextual",value:`${d.selector} >> ${i}`,weight:50,fallback:!0});const a=e.getAttribute("data-type");a&&r.push({type:"css",value:`[data-type="${a}"]:first-of-type`,weight:45,fallback:!0})}if(0===r.length){const t=e.tagName.toLowerCase(),n=e.parentElement;if(n){const o=Array.from(n.children).indexOf(e)+1;r.push({type:"css",value:`${t}:nth-child(${o})`,weight:10,fallback:!0})}}return r.sort((e,t)=>(t.weight||50)-(e.weight||50)),{locators:r,constraints:o}}(t,{inEditorIframe:p.current}),r=function(e){const t={tagName:e.tagName.toLowerCase()},n=vo(e);n&&(t.role=n),e.id&&!/^[a-z0-9_-]{20,}$/i.test(e.id)&&(t.id=e.id);const r=ho(Array.from(e.classList));r.length>0&&(t.classNames=r.slice(0,5));const o=e.textContent?.trim();o&&o.length<=200?t.textContent=o:o&&(t.textContent=`${o.slice(0,197)}...`),e.placeholder&&(t.placeholder=e.placeholder);const i=bo(e);i&&i!==t.textContent&&(t.label=i);const a=mo(e),s=Object.keys(a).filter(e=>e.startsWith("wp-")||"block"===e||"type"===e||"testid"===e);s.length>0&&(t.dataAttrs={},s.forEach(e=>{t.dataAttrs[e]=a[e]}));const l=[];let c=e.parentElement,u=0;for(;c&&c!==document.body&&u<3;){const e={tagName:c.tagName.toLowerCase()},t=vo(c);t&&(e.role=t),c.id&&!/^[a-z0-9_-]{20,}$/i.test(c.id)&&(e.id=c.id);const n=ho(Array.from(c.classList));n.length>0&&(e.classNames=n.slice(0,3)),l.push(e),c=c.parentElement,u++}return l.length>0&&(t.ancestors=l),t}(t),o={target:n,elementContext:r,completion:{type:"clickTarget",params:{}}};a?d(s,a,{target:n,elementContext:r}):u(s,o),l()},[a,s,u,d,l]),v=(0,ft.useCallback)(t=>{"Escape"===t.key&&(l(),e?.())},[l,e]),b=(0,ft.useRef)({handleMouseMove:h,handleClick:m,handleKeyDown:v});(0,ft.useEffect)(()=>{b.current={handleMouseMove:h,handleClick:m,handleKeyDown:v}},[h,m,v]),(0,ft.useEffect)(()=>{const e=e=>b.current.handleMouseMove(e,!1),t=e=>b.current.handleClick(e),n=e=>b.current.handleKeyDown(e),r=e=>b.current.handleMouseMove(e,!0),o=e=>{e.preventDefault(),e.stopPropagation(),b.current.handleClick(e)};document.addEventListener("mousemove",e,!0),document.addEventListener("click",t,!0),document.addEventListener("keydown",n,!0),document.body.style.overflow="hidden";let i=null,a=null;const s=()=>{const e=So();e?.contentDocument&&e.contentDocument!==i&&(i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0)),i=e.contentDocument,i.addEventListener("mousemove",r,!0),i.addEventListener("click",o,!0),i.addEventListener("keydown",n,!0))};return s(),a=setInterval(s,500),()=>{document.removeEventListener("mousemove",e,!0),document.removeEventListener("click",t,!0),document.removeEventListener("keydown",n,!0),document.body.style.overflow="",g.current&&clearTimeout(g.current),a&&clearInterval(a),i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0))}},[]);const E=(0,vt.jsxs)("div",{ref:i,className:"act-picker-overlay",style:{position:"fixed",top:0,left:0,right:0,bottom:0,zIndex:9999998,cursor:"crosshair",pointerEvents:"none"},children:[r?(0,vt.jsx)("div",{className:"act-picker-highlight",style:{position:"fixed",top:r.top,left:r.left,width:r.width,height:r.height,border:"3px solid #007cba",backgroundColor:"rgba(0, 124, 186, 0.15)",pointerEvents:"none",zIndex:9999999,boxSizing:"border-box",borderRadius:"3px",transition:"top 0.08s ease-out, left 0.08s ease-out, width 0.08s ease-out, height 0.08s ease-out",boxShadow:"0 0 0 2px rgba(0, 124, 186, 0.3)"}}):null,(()=>{if(!t)return null;const e=t.tagName.toLowerCase(),n=t.id,r=Array.from(t.classList).slice(0,3).join(".");let o=e;return n?o+=`#${n}`:r&&(o+=`.${r}`),(0,vt.jsx)("div",{className:"act-picker-element-info",style:{position:"fixed",bottom:"80px",left:"50%",transform:"translateX(-50%)",backgroundColor:"rgba(0, 0, 0, 0.8)",color:"#fff",padding:"8px 16px",borderRadius:"4px",fontFamily:"monospace",fontSize:"13px",maxWidth:"80%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",zIndex:9999999},children:o})})(),(0,vt.jsxs)("div",{className:"act-picker-toolbar",style:{position:"fixed",bottom:"20px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"12px",backgroundColor:"#fff",padding:"12px 20px",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",zIndex:9999999,pointerEvents:"auto"},children:[(0,vt.jsx)("span",{style:{alignSelf:"center",fontWeight:500},children:(0,gt.__)("Click an element to select it as target","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"tertiary",icon:ro,onClick:()=>{l(),e?.()},children:(0,gt.__)("Cancel","admin-coach-tours")})]})]});return(0,ft.createPortal)(E,document.body)}const wo="admin-coach-tours";(0,l.registerPlugin)("admin-coach-tours-educator",{render:function(){const[e,t]=(0,ft.useState)(null),[n,r]=(0,ft.useState)(!1),[o,i]=(0,ft.useState)(!1),{postType:a,postId:s,postTitle:l,postStatus:u,isSaving:d}=(0,c.useSelect)(e=>{const t=e("core/editor");return{postType:t?.getCurrentPostType?.()||"",postId:t?.getCurrentPostId?.()||0,postTitle:t?.getEditedPostAttribute?.("title")||"",postStatus:t?.getEditedPostAttribute?.("status")||"draft",isSaving:t?.isSavingPost?.()||!1}},[]),{currentTour:p,selectedStep:f,isPickerActive:g,isLoading:h}=(0,c.useSelect)(e=>{const t=e(wo);return{currentTour:t.getCurrentTour(),selectedStep:t.getSelectedStep(),isPickerActive:t.isPickerActive(),isLoading:t.isToursLoading()}},[]),{setCurrentTour:m,saveTour:v,startPicking:b,stopPicking:E,selectStep:y}=(0,c.useDispatch)(wo),{enableComplementaryArea:T}=(0,c.useDispatch)("core/interface");if((0,ft.useEffect)(()=>{"act_tour"===a&&s&&s!==p?.id&&m(s)},[a,s,p?.id,m]),(0,ft.useEffect)(()=>{if("act_tour"===a){const e=setTimeout(()=>{T("core","admin-coach-tours-educator/admin-coach-tours-sidebar")},100);return()=>clearTimeout(e)}},[a,T]),"act_tour"!==a)return null;const S=(0,ft.useCallback)(()=>{b()},[b]),x=(0,ft.useCallback)(()=>{E()},[E]),C=(0,ft.useCallback)(()=>{if(p?.id){const e=(p.postTypes||["post"])[0]||"post",t=new URL(window.location.origin+"/wp-admin/post-new.php");"post"!==e&&t.searchParams.set("post_type",e),t.searchParams.set("act_tour",p.id.toString()),console.log("[ACT Educator] Opening test URL:",t.toString()),window.open(t.toString(),"_blank")}},[p?.id,p?.postTypes]);if(h&&!p)return(0,vt.jsx)(vt.Fragment,{children:(0,vt.jsx)(pt.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,gt.__)("Tour Steps","admin-coach-tours"),icon:bt,children:(0,vt.jsx)("div",{className:"act-educator-sidebar",children:(0,vt.jsx)(ht.PanelBody,{children:(0,vt.jsx)(ht.Flex,{justify:"center",style:{padding:"24px"},children:(0,vt.jsx)(ht.Spinner,{})})})})})});const w=p?.steps||[];return(0,vt.jsxs)(vt.Fragment,{children:[(0,vt.jsx)(pt.PluginSidebarMoreMenuItem,{target:"admin-coach-tours-sidebar",icon:bt,children:(0,gt.__)("Tour Steps","admin-coach-tours")}),(0,vt.jsx)(pt.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,gt.__)("Tour Steps","admin-coach-tours"),icon:bt,children:(0,vt.jsxs)("div",{className:"act-educator-sidebar",children:[(0,vt.jsxs)(ht.PanelBody,{title:(0,gt.__)("Tour Info","admin-coach-tours"),initialOpen:!1,children:[(0,vt.jsxs)(ht.Flex,{justify:"space-between",align:"center",children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)("strong",{children:l||(0,gt.__)("Untitled Tour","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)("span",{className:"act-tour-status"+("publish"===u?" act-tour-status--published":""),children:"publish"===u?(0,gt.__)("Published","admin-coach-tours"):(0,gt.__)("Draft","admin-coach-tours")})})]}),(0,vt.jsx)("p",{className:"act-help-text",style:{marginTop:"8px"},children:(0,gt.__)("Use the block editor canvas as a sandbox to create your tour steps. Pick elements from the editor to target them in your tour.","admin-coach-tours")})]}),(0,vt.jsxs)(ht.PanelBody,{title:(0,gt.__)("Steps","admin-coach-tours")+` (${w.length})`,initialOpen:!0,children:[e&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!0,onRemove:()=>t(null),children:e}),o&&(0,vt.jsx)(ht.Notice,{status:"success",isDismissible:!1,children:(0,gt.__)("Steps saved successfully!","admin-coach-tours")}),(0,vt.jsx)(to,{tourId:s,steps:w,onEditStep:e=>y(e?.id??null),onAddStep:S}),w.length>0&&(0,vt.jsxs)("div",{className:"act-actions-footer",children:[(0,vt.jsx)(ht.Button,{variant:"primary",icon:Et,onClick:async()=>{if(p?.id){r(!0),t(null),i(!1);try{const e={steps:p.steps||[]};await v(p.id,e),i(!0),setTimeout(()=>i(!1),3e3)}catch(e){t(e.message||(0,gt.__)("Failed to save steps.","admin-coach-tours"))}finally{r(!1)}}},isBusy:n,disabled:n||d,children:n?(0,gt.__)("Saving…","admin-coach-tours"):(0,gt.__)("Save Steps","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"secondary",icon:yt,onClick:C,disabled:0===w.length,children:(0,gt.__)("Test Tour","admin-coach-tours")})]})]}),f&&(0,vt.jsx)(ht.PanelBody,{title:(0,gt.__)("Edit Step","admin-coach-tours"),initialOpen:!0,children:(0,vt.jsx)(fo,{step:f,tourId:s,postType:"act_tour",onClose:()=>y(null)})})]})}),g&&(0,vt.jsx)(Co,{onCancel:x})]})},icon:null})})(); \ No newline at end of file +(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={exports:{}};return n[e](i,i.exports,o),i.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);o.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,o.d(i,a),i},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};o.r(i),o.d(i,{activatePicker:()=>F,addStep:()=>W,clearAiDraft:()=>Q,clearEphemeralTour:()=>ae,clearResolvedTarget:()=>B,createTour:()=>T,deactivatePicker:()=>q,deleteStep:()=>K,endTour:()=>C,fetchAiTasks:()=>le,fetchTour:()=>E,fetchTours:()=>b,incrementResolutionAttempts:()=>j,markStepComplete:()=>N,nextStep:()=>R,previousStep:()=>A,receiveEphemeralTour:()=>ie,receiveTour:()=>m,receiveTours:()=>h,reorderSteps:()=>X,repeatStep:()=>k,requestAiDraft:()=>ee,requestAiTour:()=>se,resetCompletion:()=>L,saveTour:()=>y,selectStep:()=>H,setAiDraftError:()=>Z,setAiDraftLoading:()=>Y,setAiDraftResult:()=>J,setAiTourError:()=>re,setAiTourLoading:()=>ne,setCompletionSatisfied:()=>O,setCurrentStep:()=>_,setCurrentTour:()=>v,setLastError:()=>U,setLastFailureContext:()=>oe,setMode:()=>D,setPendingChanges:()=>$,setRecovering:()=>M,setResolvedTarget:()=>P,setSidebarOpen:()=>te,setToursError:()=>g,setToursLoading:()=>f,skipStep:()=>I,startEphemeralTour:()=>ce,startPicking:()=>V,startTour:()=>x,stopPicking:()=>G,stopTour:()=>w,updateStep:()=>z,updateTour:()=>S});var a={};o.r(a),o.d(a,{getAiDraft:()=>$e,getAiDraftError:()=>Ge,getAiDraftResult:()=>He,getAiTourError:()=>Ke,getCurrentStep:()=>ye,getCurrentStepIndex:()=>Ee,getCurrentTour:()=>be,getCurrentTourId:()=>ve,getEphemeralTour:()=>Ye,getLastError:()=>Pe,getLastFailureContext:()=>Xe,getMode:()=>we,getPickingStepId:()=>Me,getProgress:()=>Ce,getResolutionAttempts:()=>Ne,getResolvedTarget:()=>Oe,getSelectedStep:()=>Ue,getSelectedStepId:()=>je,getSkippedSteps:()=>ke,getTotalSteps:()=>Te,getTour:()=>pe,getTours:()=>de,getToursByEditor:()=>me,getToursById:()=>ue,getToursByPostType:()=>he,getToursError:()=>ge,hasNextStep:()=>Se,hasPendingChanges:()=>Fe,hasPreviousStep:()=>xe,isAiDraftLoading:()=>Ve,isAiDrafting:()=>qe,isAiTourLoading:()=>We,isCompletionSatisfied:()=>Ie,isEducatorMode:()=>_e,isEphemeralTourActive:()=>Ze,isPickerActive:()=>Be,isPupilMode:()=>Re,isRecovering:()=>Le,isSidebarOpen:()=>ze,isTourActive:()=>Ae,isToursLoading:()=>fe,wasStepSkipped:()=>De});var s={};o.r(s),o.d(s,{getTour:()=>tt,getTours:()=>et,getToursByPostType:()=>nt});const l=window.wp.plugins,c=window.wp.data,u={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},d={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},p=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function f(e){return{type:d.SET_TOURS_LOADING,isLoading:e}}function g(e){return{type:d.SET_TOURS_ERROR,error:e}}function h(e){return{type:d.RECEIVE_TOURS,tours:e}}function m(e){return{type:d.RECEIVE_TOUR,tour:e}}function*v(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield m(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield m({id:e,title:"",steps:[],status:"draft"})}yield{type:d.SET_CURRENT_TOUR,tourId:e}}else yield{type:d.SET_CURRENT_TOUR,tourId:null}}function b(e={}){return{type:"FETCH_TOURS",args:e}}function E(e){return{type:"FETCH_TOUR",tourId:e}}function*y(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function*T(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield m(t)),t}catch(e){throw e}}function*S(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function x(e,t="pupil"){return{type:d.START_TOUR,tourId:e,mode:t}}function C(){return{type:d.END_TOUR}}function w(){return C()}function _(e){return{type:d.SET_CURRENT_STEP,stepIndex:e}}function R(){return{type:d.NEXT_STEP}}function A(){return{type:d.PREVIOUS_STEP}}function I(){return{type:d.SKIP_STEP}}function k(){return{type:d.REPEAT_STEP}}function D(e){return{type:d.SET_MODE,mode:e}}function O(e){return{type:d.SET_COMPLETION_SATISFIED,satisfied:e}}function L(){return{type:d.RESET_COMPLETION}}function N(){return R()}function P(e){return{type:d.SET_RESOLVED_TARGET,target:e}}function B(){return{type:d.CLEAR_RESOLVED_TARGET}}function M(e){return{type:d.SET_RECOVERING,isRecovering:e}}function j(){return{type:d.INCREMENT_RESOLUTION_ATTEMPTS}}function U(e){return{type:d.SET_LAST_ERROR,error:e}}function F(e=null){return{type:d.ACTIVATE_PICKER,stepId:e}}function V(e=null){return F(e)}function q(){return{type:d.DEACTIVATE_PICKER}}function G(){return q()}function H(e){return{type:d.SELECT_STEP,stepId:e}}function $(e){return{type:d.SET_PENDING_CHANGES,pending:e}}function*z(e,t,n){yield{type:d.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*W(e,t={},n=null){const r={id:p(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:d.ADD_STEP,tourId:e,step:r,index:n}}function*K(e,t){yield{type:d.DELETE_STEP,tourId:e,stepId:t}}function*X(e,t){yield{type:d.REORDER_STEPS,tourId:e,stepIds:t}}function Y(e){return{type:d.SET_AI_DRAFT_LOADING,isLoading:e}}function Z(e){return{type:d.SET_AI_DRAFT_ERROR,error:e}}function J(e){return{type:d.SET_AI_DRAFT_RESULT,result:e}}function Q(){return{type:d.CLEAR_AI_DRAFT}}function*ee(e,t){yield Y(!0),yield Z(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield J(n),n}catch(e){throw yield Z(e.message||"Failed to generate AI draft"),e}finally{yield Y(!1)}}function te(e){return{type:d.SET_SIDEBAR_OPEN,isOpen:e}}function ne(e){return{type:d.SET_AI_TOUR_LOADING,isLoading:e}}function re(e){return{type:d.SET_AI_TOUR_ERROR,error:e}}function oe(e){return{type:d.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function ie(e){return{type:d.RECEIVE_EPHEMERAL_TOUR,tour:e}}function ae(){return{type:d.CLEAR_EPHEMERAL_TOUR}}function*se(e,t,n,r=null){yield ne(!0),yield re(null);const o=window.adminCoachTours?.locale||"";try{const i=yield{type:"GATHER_EDITOR_CONTEXT"},a=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:i,failureContext:r,locale:o};console.log("[ACT AI Response] Full result:",a),console.log("[ACT AI Response] Tour:",JSON.stringify(a.tour,null,2));const s={id:"ephemeral",...a.tour};return yield ie(s),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil"),s}catch(e){throw yield re(e.message||"Failed to generate tour"),e}finally{yield ne(!1)}}function*le(){return yield{type:"FETCH_AI_TASKS"}}function*ce(e){const t={id:"ephemeral",...e};yield ie(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil")}function ue(e){return e.tours}const de=(0,c.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function pe(e,t){return e.tours[t]||null}function fe(e){return e.toursLoading}function ge(e){return e.toursError}const he=(0,c.createSelector)((e,t)=>de(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),me=(0,c.createSelector)((e,t)=>de(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function ve(e){return e.currentTourId}function be(e){return e.currentTourId?e.tours[e.currentTourId]:null}function Ee(e){return e.currentStepIndex}const ye=(0,c.createSelector)(e=>{const t=be(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function Te(e){const t=be(e);return t?.steps?.length||0}function Se(e){return e.currentStepIndex0}function Ce(e){const t=Te(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function we(e){return e.mode}function _e(e){return"educator"===e.mode}function Re(e){return"pupil"===e.mode}function Ae(e){return null!==e.currentTourId&&null!==e.mode}function Ie(e){return e.completionSatisfied}function ke(e){return e.skippedSteps}function De(e,t){return e.skippedSteps.includes(t)}function Oe(e){return e.resolvedTarget}function Le(e){return e.isRecovering}function Ne(e){return e.resolutionAttempts}function Pe(e){return e.lastError}function Be(e){return e.isPickerActive}function Me(e){return e.pickingStepId||null}function je(e){return e.selectedStepId}const Ue=(0,c.createSelector)(e=>{const t=be(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function Fe(e){return e.pendingChanges}function Ve(e){return e.aiDraftLoading}function qe(e){return Ve(e)}function Ge(e){return e.aiDraftError}function He(e){return e.aiDraftResult}function $e(e){return He(e)}function ze(e){return e.sidebarOpen}function We(e){return e.aiTourLoading}function Ke(e){return e.aiTourError}function Xe(e){return e.lastFailureContext}function Ye(e){return e.ephemeralTour}function Ze(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Je=window.wp.apiFetch;var Qe=o.n(Je);function*et(){yield f(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield h(e)}catch(e){yield g(e.message||"Failed to fetch tours")}}function*tt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield m(t)}catch(e){yield g(e.message||"Failed to fetch tour")}}function*nt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield h(t)}catch(e){yield g(e.message||"Failed to fetch tours")}}function rt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,c.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,c.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,c.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function ot(){try{const e=(0,c.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:it(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function it(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function at(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:st(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:st(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:st(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:st(o)});const i=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of i){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function st(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function lt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function ct(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let i=null;for(const e of o)if(i=r.querySelector(e),i)break;if(i||(i=r),i.scrollIntoView({behavior:"smooth",block:"center"}),i.focus(),"true"===i.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(i),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",i.tagName,i.className),!0}const ut={API_FETCH:e=>Qe()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:ot(),visibleElements:rt(),uiSamples:at(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,c.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&<(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,c.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&<(e.attributes?.content));return t&&(await(0,c.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await ct(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,c.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,c.select)("core/block-editor"),i=(r?.getBlocks()||[]).length;return await t.insertBlock(n,i,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await ct(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Qe()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Qe()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Qe()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Qe()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:e.locale||""}}),FETCH_AI_TASKS:()=>Qe()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},dt="admin-coach-tours",pt=(0,c.createReduxStore)(dt,{reducer:function(e=u,t){switch(t.type){case d.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case d.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case d.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case d.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case d.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case d.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case d.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case d.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case d.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case d.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case d.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case d.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case d.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case d.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case d.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case d.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case d.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case d.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case d.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case d.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case d.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case d.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:i,selectors:a,resolvers:s,controls:ut,initialState:u});(0,c.select)(dt)||(0,c.register)(pt);const ft=window.wp.editor,gt=window.wp.element,ht=window.wp.i18n,mt=window.wp.components,vt=window.wp.primitives,bt=window.ReactJSXRuntime;var Et=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M12 4a8 8 0 1 1 .001 16.001A8 8 0 0 1 12 4Zm0 1.5a6.5 6.5 0 1 0-.001 13.001A6.5 6.5 0 0 0 12 5.5Zm.75 11h-1.5V15h1.5v1.5Zm-.445-9.234a3 3 0 0 1 .445 5.89V14h-1.5v-1.25c0-.57.452-.958.917-1.01A1.5 1.5 0 0 0 12 8.75a1.5 1.5 0 0 0-1.5 1.5H9a3 3 0 0 1 3.305-2.984Z"})}),yt=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M16.5 7.5 10 13.9l-2.5-2.4-1 1 3.5 3.6 7.5-7.6z"})}),Tt=(0,bt.jsx)(vt.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,bt.jsx)(vt.Path,{d:"M3.99961 13C4.67043 13.3354 4.6703 13.3357 4.67017 13.3359L4.67298 13.3305C4.67621 13.3242 4.68184 13.3135 4.68988 13.2985C4.70595 13.2686 4.7316 13.2218 4.76695 13.1608C4.8377 13.0385 4.94692 12.8592 5.09541 12.6419C5.39312 12.2062 5.84436 11.624 6.45435 11.0431C7.67308 9.88241 9.49719 8.75 11.9996 8.75C14.502 8.75 16.3261 9.88241 17.5449 11.0431C18.1549 11.624 18.6061 12.2062 18.9038 12.6419C19.0523 12.8592 19.1615 13.0385 19.2323 13.1608C19.2676 13.2218 19.2933 13.2686 19.3093 13.2985C19.3174 13.3135 19.323 13.3242 19.3262 13.3305L19.3291 13.3359C19.3289 13.3357 19.3288 13.3354 19.9996 13C20.6704 12.6646 20.6703 12.6643 20.6701 12.664L20.6697 12.6632L20.6688 12.6614L20.6662 12.6563L20.6583 12.6408C20.6517 12.6282 20.6427 12.6108 20.631 12.5892C20.6078 12.5459 20.5744 12.4852 20.5306 12.4096C20.4432 12.2584 20.3141 12.0471 20.1423 11.7956C19.7994 11.2938 19.2819 10.626 18.5794 9.9569C17.1731 8.61759 14.9972 7.25 11.9996 7.25C9.00203 7.25 6.82614 8.61759 5.41987 9.9569C4.71736 10.626 4.19984 11.2938 3.85694 11.7956C3.68511 12.0471 3.55605 12.2584 3.4686 12.4096C3.42484 12.4852 3.39142 12.5459 3.36818 12.5892C3.35656 12.6108 3.34748 12.6282 3.34092 12.6408L3.33297 12.6563L3.33041 12.6614L3.32948 12.6632L3.32911 12.664C3.32894 12.6643 3.32879 12.6646 3.99961 13ZM11.9996 16C13.9326 16 15.4996 14.433 15.4996 12.5C15.4996 10.567 13.9326 9 11.9996 9C10.0666 9 8.49961 10.567 8.49961 12.5C8.49961 14.433 10.0666 16 11.9996 16Z"})}),St=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"})}),xt=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"})}),Ct=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"})});const wt=window.React;var _t=o.n(wt);const Rt=window.ReactDOM,At="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function It(e){const t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function kt(e){return"nodeType"in e}function Dt(e){var t,n;return e?It(e)?e:kt(e)&&null!=(t=null==(n=e.ownerDocument)?void 0:n.defaultView)?t:window:window}function Ot(e){const{Document:t}=Dt(e);return e instanceof t}function Lt(e){return!It(e)&&e instanceof Dt(e).HTMLElement}function Nt(e){return e instanceof Dt(e).SVGElement}function Pt(e){return e?It(e)?e.document:kt(e)?Ot(e)?e:Lt(e)||Nt(e)?e.ownerDocument:document:document:document}const Bt=At?wt.useLayoutEffect:wt.useEffect;function Mt(e){const t=(0,wt.useRef)(e);return Bt(()=>{t.current=e}),(0,wt.useCallback)(function(){for(var e=arguments.length,n=new Array(e),r=0;r{n.current!==e&&(n.current=e)},t),n}function Ut(e,t){const n=(0,wt.useRef)();return(0,wt.useMemo)(()=>{const t=e(n.current);return n.current=t,t},[...t])}function Ft(e){const t=Mt(e),n=(0,wt.useRef)(null),r=(0,wt.useCallback)(e=>{e!==n.current&&(null==t||t(e,n.current)),n.current=e},[]);return[n,r]}function Vt(e){const t=(0,wt.useRef)();return(0,wt.useEffect)(()=>{t.current=e},[e]),t.current}let qt={};function Gt(e,t){return(0,wt.useMemo)(()=>{if(t)return t;const n=null==qt[e]?0:qt[e]+1;return qt[e]=n,e+"-"+n},[e,t])}function Ht(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const r=Object.entries(n);for(const[n,o]of r){const r=t[n];null!=r&&(t[n]=r+e*o)}return t},{...t})}}const $t=Ht(1),zt=Ht(-1);function Wt(e){if(!e)return!1;const{KeyboardEvent:t}=Dt(e.target);return t&&e instanceof t}function Kt(e){if(function(e){if(!e)return!1;const{TouchEvent:t}=Dt(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return function(e){return"clientX"in e&&"clientY"in e}(e)?{x:e.clientX,y:e.clientY}:null}const Xt=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Xt.Translate.toString(e),Xt.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Yt="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Zt(e){return e.matches(Yt)?e:e.querySelector(Yt)}const Jt={display:"none"};function Qt(e){let{id:t,value:n}=e;return _t().createElement("div",{id:t,style:Jt},n)}function en(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;return _t().createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":r,"aria-atomic":!0},n)}const tn=(0,wt.createContext)(null),nn={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},rn={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function on(e){let{announcements:t=rn,container:n,hiddenTextDescribedById:r,screenReaderInstructions:o=nn}=e;const{announce:i,announcement:a}=function(){const[e,t]=(0,wt.useState)("");return{announce:(0,wt.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),s=Gt("DndLiveRegion"),[l,c]=(0,wt.useState)(!1);if((0,wt.useEffect)(()=>{c(!0)},[]),function(e){const t=(0,wt.useContext)(tn);(0,wt.useEffect)(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}((0,wt.useMemo)(()=>({onDragStart(e){let{active:n}=e;i(t.onDragStart({active:n}))},onDragMove(e){let{active:n,over:r}=e;t.onDragMove&&i(t.onDragMove({active:n,over:r}))},onDragOver(e){let{active:n,over:r}=e;i(t.onDragOver({active:n,over:r}))},onDragEnd(e){let{active:n,over:r}=e;i(t.onDragEnd({active:n,over:r}))},onDragCancel(e){let{active:n,over:r}=e;i(t.onDragCancel({active:n,over:r}))}}),[i,t])),!l)return null;const u=_t().createElement(_t().Fragment,null,_t().createElement(Qt,{id:r,value:o.draggable}),_t().createElement(en,{id:s,announcement:a}));return n?(0,Rt.createPortal)(u,n):u}var an;function sn(){}function ln(e,t){return(0,wt.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(an||(an={}));const cn=Object.freeze({x:0,y:0});function un(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function dn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function pn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function fn(e){let{left:t,top:n,height:r,width:o}=e;return[{x:t,y:n},{x:t+o,y:n},{x:t,y:n+r},{x:t+o,y:n+r}]}function gn(e,t){if(!e||0===e.length)return null;const[n]=e;return t?n[t]:n}function hn(e,t,n){return void 0===t&&(t=e.left),void 0===n&&(n=e.top),{x:t+.5*e.width,y:n+.5*e.height}}const mn=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=hn(t,t.left,t.top),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=un(hn(r),o);i.push({id:t,data:{droppableContainer:e,value:n}})}}return i.sort(dn)};function vn(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),i=Math.min(t.top+t.height,e.top+e.height),a=o-r,s=i-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=[];for(const e of r){const{id:r}=e,i=n.get(r);if(i){const n=vn(i,t);n>0&&o.push({id:r,data:{droppableContainer:e,value:n}})}}return o.sort(pn)};function En(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:cn}function yn(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}const Tn=yn(1);const Sn={ignoreTransform:!1};function xn(e,t){void 0===t&&(t=Sn);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:t,transformOrigin:r}=Dt(e).getComputedStyle(e);t&&(n=function(e,t,n){const r=function(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!r)return e;const{scaleX:o,scaleY:i,x:a,y:s}=r,l=e.left-a-(1-o)*parseFloat(n),c=e.top-s-(1-i)*parseFloat(n.slice(n.indexOf(" ")+1)),u=o?e.width/o:e.width,d=i?e.height/i:e.height;return{width:u,height:d,top:c,right:l+u,bottom:c+d,left:l}}(n,t,r))}const{top:r,left:o,width:i,height:a,bottom:s,right:l}=n;return{top:r,left:o,width:i,height:a,bottom:s,right:l}}function Cn(e){return xn(e,{ignoreTransform:!0})}function wn(e,t){const n=[];return e?function r(o){if(null!=t&&n.length>=t)return n;if(!o)return n;if(Ot(o)&&null!=o.scrollingElement&&!n.includes(o.scrollingElement))return n.push(o.scrollingElement),n;if(!Lt(o)||Nt(o))return n;if(n.includes(o))return n;const i=Dt(e).getComputedStyle(o);return o!==e&&function(e,t){void 0===t&&(t=Dt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{const r=t[e];return"string"==typeof r&&n.test(r)})}(o,i)&&n.push(o),function(e,t){return void 0===t&&(t=Dt(e).getComputedStyle(e)),"fixed"===t.position}(o,i)?n:r(o.parentNode)}(e):n}function Rn(e){const[t]=wn(e,1);return null!=t?t:null}function An(e){return At&&e?It(e)?e:kt(e)?Ot(e)||e===Pt(e).scrollingElement?window:Lt(e)?e:null:null:null}function In(e){return It(e)?e.scrollX:e.scrollLeft}function kn(e){return It(e)?e.scrollY:e.scrollTop}function Dn(e){return{x:In(e),y:kn(e)}}var On;function Ln(e){return!(!At||!e)&&e===document.scrollingElement}function Nn(e){const t={x:0,y:0},n=Ln(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(On||(On={}));const Pn={x:.2,y:.2};function Bn(e,t,n,r,o){let{top:i,left:a,right:s,bottom:l}=n;void 0===r&&(r=10),void 0===o&&(o=Pn);const{isTop:c,isBottom:u,isLeft:d,isRight:p}=Nn(e),f={x:0,y:0},g={x:0,y:0},h=t.height*o.y,m=t.width*o.x;return!c&&i<=t.top+h?(f.y=On.Backward,g.y=r*Math.abs((t.top+h-i)/h)):!u&&l>=t.bottom-h&&(f.y=On.Forward,g.y=r*Math.abs((t.bottom-h-l)/h)),!p&&s>=t.right-m?(f.x=On.Forward,g.x=r*Math.abs((t.right-m-s)/m)):!d&&a<=t.left+m&&(f.x=On.Backward,g.x=r*Math.abs((t.left+m-a)/m)),{direction:f,speed:g}}function Mn(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:o}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:o,width:e.clientWidth,height:e.clientHeight}}function jn(e){return e.reduce((e,t)=>$t(e,Dn(t)),cn)}const Un=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+In(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+kn(t),0)}]];class Fn{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=wn(t),r=jn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(const[e,t,o]of Un)for(const i of t)Object.defineProperty(this,i,{get:()=>{const t=o(n),a=r[e]-t;return this.rect[i]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Vn{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,n){var r;null==(r=this.target)||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}}function qn(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var Gn,Hn;function $n(e){e.preventDefault()}function zn(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(Gn||(Gn={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(Hn||(Hn={}));const Wn={start:[Hn.Space,Hn.Enter],cancel:[Hn.Esc],end:[Hn.Space,Hn.Enter,Hn.Tab]},Kn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Hn.Right:return{...n,x:n.x+25};case Hn.Left:return{...n,x:n.x-25};case Hn.Down:return{...n,y:n.y+25};case Hn.Up:return{...n,y:n.y-25}}};class Xn{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:t}}=e;this.props=e,this.listeners=new Vn(Pt(t)),this.windowListeners=new Vn(Dt(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(Gn.Resize,this.handleCancel),this.windowListeners.add(Gn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(Gn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&function(e,t){if(void 0===t&&(t=xn),!e)return;const{top:n,left:r,bottom:o,right:i}=t(e);Rn(e)&&(o<=0||i<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(n),t(cn)}handleKeyDown(e){if(Wt(e)){const{active:t,context:n,options:r}=this.props,{keyboardCodes:o=Wn,coordinateGetter:i=Kn,scrollBehavior:a="smooth"}=r,{code:s}=e;if(o.end.includes(s))return void this.handleEnd(e);if(o.cancel.includes(s))return void this.handleCancel(e);const{collisionRect:l}=n.current,c=l?{x:l.left,y:l.top}:cn;this.referenceCoordinates||(this.referenceCoordinates=c);const u=i(e,{active:t,context:n.current,currentCoordinates:c});if(u){const t=zt(u,c),r={x:0,y:0},{scrollableAncestors:o}=n.current;for(const n of o){const o=e.code,{isTop:i,isRight:s,isLeft:l,isBottom:c,maxScroll:d,minScroll:p}=Nn(n),f=Mn(n),g={x:Math.min(o===Hn.Right?f.right-f.width/2:f.right,Math.max(o===Hn.Right?f.left:f.left+f.width/2,u.x)),y:Math.min(o===Hn.Down?f.bottom-f.height/2:f.bottom,Math.max(o===Hn.Down?f.top:f.top+f.height/2,u.y))},h=o===Hn.Right&&!s||o===Hn.Left&&!l,m=o===Hn.Down&&!c||o===Hn.Up&&!i;if(h&&g.x!==u.x){const e=n.scrollLeft+t.x,i=o===Hn.Right&&e<=d.x||o===Hn.Left&&e>=p.x;if(i&&!t.y)return void n.scrollTo({left:e,behavior:a});r.x=i?n.scrollLeft-e:o===Hn.Right?n.scrollLeft-d.x:n.scrollLeft-p.x,r.x&&n.scrollBy({left:-r.x,behavior:a});break}if(m&&g.y!==u.y){const e=n.scrollTop+t.y,i=o===Hn.Down&&e<=d.y||o===Hn.Up&&e>=p.y;if(i&&!t.x)return void n.scrollTo({top:e,behavior:a});r.y=i?n.scrollTop-e:o===Hn.Down?n.scrollTop-d.y:n.scrollTop-p.y,r.y&&n.scrollBy({top:-r.y,behavior:a});break}}this.handleMove(e,$t(zt(u,this.referenceCoordinates),r))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function Yn(e){return Boolean(e&&"distance"in e)}function Zn(e){return Boolean(e&&"delay"in e)}Xn.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=Wn,onActivation:o}=t,{active:i}=n;const{code:a}=e.nativeEvent;if(r.start.includes(a)){const t=i.activatorNode.current;return!(t&&e.target!==t||(e.preventDefault(),null==o||o({event:e.nativeEvent}),0))}return!1}}];class Jn{constructor(e,t,n){var r;void 0===n&&(n=function(e){const{EventTarget:t}=Dt(e);return e instanceof t?e:Pt(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;const{event:o}=e,{target:i}=o;this.props=e,this.events=t,this.document=Pt(i),this.documentListeners=new Vn(this.document),this.listeners=new Vn(n),this.windowListeners=new Vn(Dt(i)),this.initialCoordinates=null!=(r=Kt(o))?r:cn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(Gn.Resize,this.handleCancel),this.windowListeners.add(Gn.DragStart,$n),this.windowListeners.add(Gn.VisibilityChange,this.handleCancel),this.windowListeners.add(Gn.ContextMenu,$n),this.documentListeners.add(Gn.Keydown,this.handleKeydown),t){if(null!=n&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Zn(t))return this.timeoutId=setTimeout(this.handleStart,t.delay),void this.handlePending(t);if(Yn(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){const{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(Gn.Click,zn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(Gn.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;const{activated:n,initialCoordinates:r,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!r)return;const s=null!=(t=Kt(e))?t:cn,l=zt(r,s);if(!n&&a){if(Yn(a)){if(null!=a.tolerance&&qn(l,a.tolerance))return this.handleCancel();if(qn(l,a.distance))return this.handleStart()}return Zn(a)&&qn(l,a.tolerance)?this.handleCancel():void this.handlePending(a,l)}e.cancelable&&e.preventDefault(),i(s)}handleEnd(){const{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){const{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===Hn.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}const Qn={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class er extends Jn{constructor(e){const{event:t}=e,n=Pt(t.target);super(e,Qn,n)}}er.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!(!n.isPrimary||0!==n.button||(null==r||r({event:n}),0))}}];const tr={move:{name:"mousemove"},end:{name:"mouseup"}};var nr;!function(e){e[e.RightClick=2]="RightClick"}(nr||(nr={})),class extends Jn{constructor(e){super(e,tr,Pt(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button!==nr.RightClick&&(null==r||r({event:n}),!0)}}];const rr={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};var or,ir;(class extends Jn{constructor(e){super(e,rr)}static setup(){return window.addEventListener(rr.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(rr.move.name,e)};function e(){}}}).activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:o}=n;return!(o.length>1||(null==r||r({event:n}),0))}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(or||(or={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(ir||(ir={}));const ar={x:{[On.Backward]:!1,[On.Forward]:!1},y:{[On.Backward]:!1,[On.Forward]:!1}};var sr,lr;!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(sr||(sr={})),function(e){e.Optimized="optimized"}(lr||(lr={}));const cr=new Map;function ur(e,t){return Ut(n=>e?n||("function"==typeof t?t(e):e):null,[t,e])}function dr(e){let{callback:t,disabled:n}=e;const r=Mt(t),o=(0,wt.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.ResizeObserver)return;const{ResizeObserver:e}=window;return new e(r)},[n]);return(0,wt.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}function pr(e){return new Fn(xn(e),e)}function fr(e,t,n){void 0===t&&(t=pr);const[r,o]=(0,wt.useState)(null);function i(){o(r=>{if(!e)return null;var o;if(!1===e.isConnected)return null!=(o=null!=r?r:n)?o:null;const i=t(e);return JSON.stringify(r)===JSON.stringify(i)?r:i})}const a=function(e){let{callback:t,disabled:n}=e;const r=Mt(t),o=(0,wt.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.MutationObserver)return;const{MutationObserver:e}=window;return new e(r)},[r,n]);return(0,wt.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}({callback(t){if(e)for(const n of t){const{type:t,target:r}=n;if("childList"===t&&r instanceof HTMLElement&&r.contains(e)){i();break}}}}),s=dr({callback:i});return Bt(()=>{i(),e?(null==s||s.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==s||s.disconnect(),null==a||a.disconnect())},[e]),r}const gr=[];function hr(e,t){void 0===t&&(t=[]);const n=(0,wt.useRef)(null);return(0,wt.useEffect)(()=>{n.current=null},t),(0,wt.useEffect)(()=>{const t=e!==cn;t&&!n.current&&(n.current=e),!t&&n.current&&(n.current=null)},[e]),n.current?zt(e,n.current):cn}function mr(e){return(0,wt.useMemo)(()=>e?function(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}(e):null,[e])}const vr=[];const br=[{sensor:er,options:{}},{sensor:Xn,options:{}}],Er={current:{}},yr={draggable:{measure:Cn},droppable:{measure:Cn,strategy:sr.WhileDragging,frequency:lr.Optimized},dragOverlay:{measure:xn}};class Tr extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,n;return null!=(t=null==(n=this.get(e))?void 0:n.node.current)?t:void 0}}const Sr={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Tr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:sn},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:yr,measureDroppableContainers:sn,windowRect:null,measuringScheduled:!1},xr={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:sn,draggableNodes:new Map,over:null,measureDroppableContainers:sn},Cr=(0,wt.createContext)(xr),wr=(0,wt.createContext)(Sr);function _r(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Tr}}}function Rr(e,t){switch(t.type){case an.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case an.DragMove:return null==e.draggable.active?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case an.DragEnd:case an.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case an.RegisterDroppable:{const{element:n}=t,{id:r}=n,o=new Tr(e.droppable.containers);return o.set(r,n),{...e,droppable:{...e.droppable,containers:o}}}case an.SetDroppableDisabled:{const{id:n,key:r,disabled:o}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;const a=new Tr(e.droppable.containers);return a.set(n,{...i,disabled:o}),{...e,droppable:{...e.droppable,containers:a}}}case an.UnregisterDroppable:{const{id:n,key:r}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new Tr(e.droppable.containers);return i.delete(n),{...e,droppable:{...e.droppable,containers:i}}}default:return e}}function Ar(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:o}=(0,wt.useContext)(Cr),i=Vt(r),a=Vt(null==n?void 0:n.id);return(0,wt.useEffect)(()=>{if(!t&&!r&&i&&null!=a){if(!Wt(i))return;if(document.activeElement===i.target)return;const e=o.get(a);if(!e)return;const{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(const e of[t.current,n.current]){if(!e)continue;const t=Zt(e);if(t){t.focus();break}}})}},[r,t,o,a,i]),null}const Ir=(0,wt.createContext)({...cn,scaleX:1,scaleY:1});var kr;!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(kr||(kr={}));const Dr=(0,wt.memo)(function(e){var t,n,r,o;let{id:i,accessibility:a,autoScroll:s=!0,children:l,sensors:c=br,collisionDetection:u=bn,measuring:d,modifiers:p,...f}=e;const g=(0,wt.useReducer)(Rr,void 0,_r),[h,m]=g,[v,b]=function(){const[e]=(0,wt.useState)(()=>new Set),t=(0,wt.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,wt.useCallback)(t=>{let{type:n,event:r}=t;e.forEach(e=>{var t;return null==(t=e[n])?void 0:t.call(e,r)})},[e]),t]}(),[E,y]=(0,wt.useState)(kr.Uninitialized),T=E===kr.Initialized,{draggable:{active:S,nodes:x,translate:C},droppable:{containers:w}}=h,_=null!=S?x.get(S):null,R=(0,wt.useRef)({initial:null,translated:null}),A=(0,wt.useMemo)(()=>{var e;return null!=S?{id:S,data:null!=(e=null==_?void 0:_.data)?e:Er,rect:R}:null},[S,_]),I=(0,wt.useRef)(null),[k,D]=(0,wt.useState)(null),[O,L]=(0,wt.useState)(null),N=jt(f,Object.values(f)),P=Gt("DndDescribedBy",i),B=(0,wt.useMemo)(()=>w.getEnabled(),[w]),M=(j=d,(0,wt.useMemo)(()=>({draggable:{...yr.draggable,...null==j?void 0:j.draggable},droppable:{...yr.droppable,...null==j?void 0:j.droppable},dragOverlay:{...yr.dragOverlay,...null==j?void 0:j.dragOverlay}}),[null==j?void 0:j.draggable,null==j?void 0:j.droppable,null==j?void 0:j.dragOverlay]));var j;const{droppableRects:U,measureDroppableContainers:F,measuringScheduled:V}=function(e,t){let{dragging:n,dependencies:r,config:o}=t;const[i,a]=(0,wt.useState)(null),{frequency:s,measure:l,strategy:c}=o,u=(0,wt.useRef)(e),d=function(){switch(c){case sr.Always:return!1;case sr.BeforeDragging:return n;default:return!n}}(),p=jt(d),f=(0,wt.useCallback)(function(e){void 0===e&&(e=[]),p.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),g=(0,wt.useRef)(null),h=Ut(t=>{if(d&&!n)return cr;if(!t||t===cr||u.current!==e||null!=i){const t=new Map;for(let n of e){if(!n)continue;if(i&&i.length>0&&!i.includes(n.id)&&n.rect.current){t.set(n.id,n.rect.current);continue}const e=n.node.current,r=e?new Fn(l(e),e):null;n.rect.current=r,r&&t.set(n.id,r)}return t}return t},[e,i,n,d,l]);return(0,wt.useEffect)(()=>{u.current=e},[e]),(0,wt.useEffect)(()=>{d||f()},[n,d]),(0,wt.useEffect)(()=>{i&&i.length>0&&a(null)},[JSON.stringify(i)]),(0,wt.useEffect)(()=>{d||"number"!=typeof s||null!==g.current||(g.current=setTimeout(()=>{f(),g.current=null},s))},[s,d,f,...r]),{droppableRects:h,measureDroppableContainers:f,measuringScheduled:null!=i}}(B,{dragging:T,dependencies:[C.x,C.y],config:M.droppable}),q=function(e,t){const n=null!=t?e.get(t):void 0,r=n?n.node.current:null;return Ut(e=>{var n;return null==t?null:null!=(n=null!=r?r:e)?n:null},[r,t])}(x,S),G=(0,wt.useMemo)(()=>O?Kt(O):null,[O]),H=function(){const e=!1===(null==k?void 0:k.autoScrollEnabled),t="object"==typeof s?!1===s.enabled:!1===s,n=T&&!e&&!t;return"object"==typeof s?{...s,enabled:n}:{enabled:n}}(),$=function(e,t){return ur(e,t)}(q,M.draggable.measure);!function(e){let{activeNode:t,measure:n,initialRect:r,config:o=!0}=e;const i=(0,wt.useRef)(!1),{x:a,y:s}="boolean"==typeof o?{x:o,y:o}:o;Bt(()=>{if(!a&&!s||!t)return void(i.current=!1);if(i.current||!r)return;const e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;const o=En(n(e),r);if(a||(o.x=0),s||(o.y=0),i.current=!0,Math.abs(o.x)>0||Math.abs(o.y)>0){const t=Rn(e);t&&t.scrollBy({top:o.y,left:o.x})}},[t,a,s,r,n])}({activeNode:null!=S?x.get(S):null,config:H.layoutShiftCompensation,initialRect:$,measure:M.draggable.measure});const z=fr(q,M.draggable.measure,$),W=fr(q?q.parentElement:null),K=(0,wt.useRef)({activatorEvent:null,active:null,activeNode:q,collisionRect:null,collisions:null,droppableRects:U,draggableNodes:x,draggingNode:null,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),X=w.getNodeFor(null==(t=K.current.over)?void 0:t.id),Y=function(e){let{measure:t}=e;const[n,r]=(0,wt.useState)(null),o=dr({callback:(0,wt.useCallback)(e=>{for(const{target:n}of e)if(Lt(n)){r(e=>{const r=t(n);return e?{...e,width:r.width,height:r.height}:r});break}},[t])}),i=(0,wt.useCallback)(e=>{const n=function(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Lt(t)?t:e}(e);null==o||o.disconnect(),n&&(null==o||o.observe(n)),r(n?t(n):null)},[t,o]),[a,s]=Ft(i);return(0,wt.useMemo)(()=>({nodeRef:a,rect:n,setRef:s}),[n,a,s])}({measure:M.dragOverlay.measure}),Z=null!=(n=Y.nodeRef.current)?n:q,J=T?null!=(r=Y.rect)?r:z:null,Q=Boolean(Y.nodeRef.current&&Y.rect),ee=En(te=Q?null:z,ur(te));var te;const ne=mr(Z?Dt(Z):null),re=function(e){const t=(0,wt.useRef)(e),n=Ut(n=>e?n&&n!==gr&&e&&t.current&&e.parentNode===t.current.parentNode?n:wn(e):gr,[e]);return(0,wt.useEffect)(()=>{t.current=e},[e]),n}(T?null!=X?X:q:null),oe=function(e,t){void 0===t&&(t=xn);const[n]=e,r=mr(n?Dt(n):null),[o,i]=(0,wt.useState)(vr);function a(){i(()=>e.length?e.map(e=>Ln(e)?r:new Fn(t(e),e)):vr)}const s=dr({callback:a});return Bt(()=>{null==s||s.disconnect(),a(),e.forEach(e=>null==s?void 0:s.observe(e))},[e]),o}(re),ie=function(e,t){let{transform:n,...r}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}(p,{transform:{x:C.x-ee.x,y:C.y-ee.y,scaleX:1,scaleY:1},activatorEvent:O,active:A,activeNodeRect:z,containerNodeRect:W,draggingNodeRect:J,over:K.current.over,overlayNodeRect:Y.rect,scrollableAncestors:re,scrollableAncestorRects:oe,windowRect:ne}),ae=G?$t(G,C):null,se=function(e){const[t,n]=(0,wt.useState)(null),r=(0,wt.useRef)(e),o=(0,wt.useCallback)(e=>{const t=An(e.target);t&&n(e=>e?(e.set(t,Dn(t)),new Map(e)):null)},[]);return(0,wt.useEffect)(()=>{const t=r.current;if(e!==t){i(t);const a=e.map(e=>{const t=An(e);return t?(t.addEventListener("scroll",o,{passive:!0}),[t,Dn(t)]):null}).filter(e=>null!=e);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(t)};function i(e){e.forEach(e=>{const t=An(e);null==t||t.removeEventListener("scroll",o)})}},[o,e]),(0,wt.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>$t(e,t),cn):jn(e):cn,[e,t])}(re),le=hr(se),ce=hr(se,[z]),ue=$t(ie,le),de=J?Tn(J,ie):null,pe=A&&de?u({active:A,collisionRect:de,droppableRects:U,droppableContainers:B,pointerCoordinates:ae}):null,fe=gn(pe,"id"),[ge,he]=(0,wt.useState)(null),me=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(Q?ie:$t(ie,ce),null!=(o=null==ge?void 0:ge.rect)?o:null,z),ve=(0,wt.useRef)(null),be=(0,wt.useCallback)((e,t)=>{let{sensor:n,options:r}=t;if(null==I.current)return;const o=x.get(I.current);if(!o)return;const i=e.nativeEvent,a=new n({active:I.current,activeNode:o,event:i,options:r,context:K,onAbort(e){if(!x.get(e))return;const{onDragAbort:t}=N.current,n={id:e};null==t||t(n),v({type:"onDragAbort",event:n})},onPending(e,t,n,r){if(!x.get(e))return;const{onDragPending:o}=N.current,i={id:e,constraint:t,initialCoordinates:n,offset:r};null==o||o(i),v({type:"onDragPending",event:i})},onStart(e){const t=I.current;if(null==t)return;const n=x.get(t);if(!n)return;const{onDragStart:r}=N.current,o={activatorEvent:i,active:{id:t,data:n.data,rect:R}};(0,Rt.unstable_batchedUpdates)(()=>{null==r||r(o),y(kr.Initializing),m({type:an.DragStart,initialCoordinates:e,active:t}),v({type:"onDragStart",event:o}),D(ve.current),L(i)})},onMove(e){m({type:an.DragMove,coordinates:e})},onEnd:s(an.DragEnd),onCancel:s(an.DragCancel)});function s(e){return async function(){const{active:t,collisions:n,over:r,scrollAdjustedTranslate:o}=K.current;let a=null;if(t&&o){const{cancelDrop:s}=N.current;a={activatorEvent:i,active:t,collisions:n,delta:o,over:r},e===an.DragEnd&&"function"==typeof s&&await Promise.resolve(s(a))&&(e=an.DragCancel)}I.current=null,(0,Rt.unstable_batchedUpdates)(()=>{m({type:e}),y(kr.Uninitialized),he(null),D(null),L(null),ve.current=null;const t=e===an.DragEnd?"onDragEnd":"onDragCancel";if(a){const e=N.current[t];null==e||e(a),v({type:t,event:a})}})}}ve.current=a},[x]),Ee=(0,wt.useCallback)((e,t)=>(n,r)=>{const o=n.nativeEvent,i=x.get(r);if(null!==I.current||!i||o.dndKit||o.defaultPrevented)return;const a={active:i};!0===e(n,t.options,a)&&(o.dndKit={capturedBy:t.sensor},I.current=r,be(n,t))},[x,be]),ye=function(e,t){return(0,wt.useMemo)(()=>e.reduce((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:t(e.handler,n)}))]},[]),[e,t])}(c,Ee);!function(e){(0,wt.useEffect)(()=>{if(!At)return;const t=e.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(const e of t)null==e||e()}},e.map(e=>{let{sensor:t}=e;return t}))}(c),Bt(()=>{z&&E===kr.Initializing&&y(kr.Initialized)},[z,E]),(0,wt.useEffect)(()=>{const{onDragMove:e}=N.current,{active:t,activatorEvent:n,collisions:r,over:o}=K.current;if(!t||!n)return;const i={active:t,activatorEvent:n,collisions:r,delta:{x:ue.x,y:ue.y},over:o};(0,Rt.unstable_batchedUpdates)(()=>{null==e||e(i),v({type:"onDragMove",event:i})})},[ue.x,ue.y]),(0,wt.useEffect)(()=>{const{active:e,activatorEvent:t,collisions:n,droppableContainers:r,scrollAdjustedTranslate:o}=K.current;if(!e||null==I.current||!t||!o)return;const{onDragOver:i}=N.current,a=r.get(fe),s=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,l={active:e,activatorEvent:t,collisions:n,delta:{x:o.x,y:o.y},over:s};(0,Rt.unstable_batchedUpdates)(()=>{he(s),null==i||i(l),v({type:"onDragOver",event:l})})},[fe]),Bt(()=>{K.current={activatorEvent:O,active:A,activeNode:q,collisionRect:de,collisions:pe,droppableRects:U,draggableNodes:x,draggingNode:Z,draggingNodeRect:J,droppableContainers:w,over:ge,scrollableAncestors:re,scrollAdjustedTranslate:ue},R.current={initial:J,translated:de}},[A,q,pe,de,x,Z,J,U,w,ge,re,ue]),function(e){let{acceleration:t,activator:n=or.Pointer,canScroll:r,draggingRect:o,enabled:i,interval:a=5,order:s=ir.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:u,delta:d,threshold:p}=e;const f=function(e){let{delta:t,disabled:n}=e;const r=Vt(t);return Ut(e=>{if(n||!r||!e)return ar;const o=Math.sign(t.x-r.x),i=Math.sign(t.y-r.y);return{x:{[On.Backward]:e.x[On.Backward]||-1===o,[On.Forward]:e.x[On.Forward]||1===o},y:{[On.Backward]:e.y[On.Backward]||-1===i,[On.Forward]:e.y[On.Forward]||1===i}}},[n,t,r])}({delta:d,disabled:!i}),[g,h]=function(){const e=(0,wt.useRef)(null);return[(0,wt.useCallback)((t,n)=>{e.current=setInterval(t,n)},[]),(0,wt.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}(),m=(0,wt.useRef)({x:0,y:0}),v=(0,wt.useRef)({x:0,y:0}),b=(0,wt.useMemo)(()=>{switch(n){case or.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case or.DraggableRect:return o}},[n,o,l]),E=(0,wt.useRef)(null),y=(0,wt.useCallback)(()=>{const e=E.current;if(!e)return;const t=m.current.x*v.current.x,n=m.current.y*v.current.y;e.scrollBy(t,n)},[]),T=(0,wt.useMemo)(()=>s===ir.TreeOrder?[...c].reverse():c,[s,c]);(0,wt.useEffect)(()=>{if(i&&c.length&&b){for(const e of T){if(!1===(null==r?void 0:r(e)))continue;const n=c.indexOf(e),o=u[n];if(!o)continue;const{direction:i,speed:s}=Bn(e,o,b,t,p);for(const e of["x","y"])f[e][i[e]]||(s[e]=0,i[e]=0);if(s.x>0||s.y>0)return h(),E.current=e,g(y,a),m.current=s,void(v.current=i)}m.current={x:0,y:0},v.current={x:0,y:0},h()}else h()},[t,y,r,h,i,a,JSON.stringify(b),JSON.stringify(f),g,c,T,u,JSON.stringify(p)])}({...H,delta:C,draggingRect:de,pointerCoordinates:ae,scrollableAncestors:re,scrollableAncestorRects:oe});const Te=(0,wt.useMemo)(()=>({active:A,activeNode:q,activeNodeRect:z,activatorEvent:O,collisions:pe,containerNodeRect:W,dragOverlay:Y,draggableNodes:x,droppableContainers:w,droppableRects:U,over:ge,measureDroppableContainers:F,scrollableAncestors:re,scrollableAncestorRects:oe,measuringConfiguration:M,measuringScheduled:V,windowRect:ne}),[A,q,z,O,pe,W,Y,x,w,U,ge,F,re,oe,M,V,ne]),Se=(0,wt.useMemo)(()=>({activatorEvent:O,activators:ye,active:A,activeNodeRect:z,ariaDescribedById:{draggable:P},dispatch:m,draggableNodes:x,over:ge,measureDroppableContainers:F}),[O,ye,A,z,m,P,x,ge,F]);return _t().createElement(tn.Provider,{value:b},_t().createElement(Cr.Provider,{value:Se},_t().createElement(wr.Provider,{value:Te},_t().createElement(Ir.Provider,{value:me},l)),_t().createElement(Ar,{disabled:!1===(null==a?void 0:a.restoreFocus)})),_t().createElement(on,{...a,hiddenTextDescribedById:P}))}),Or=(0,wt.createContext)(null),Lr="button";const Nr={timeout:25};function Pr(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Br(e,t){return e.reduce((e,n,r)=>{const o=t.get(n);return o&&(e[r]=o),e},Array(e.length))}function Mr(e){return null!==e&&e>=0}const jr=e=>{let{rects:t,activeIndex:n,overIndex:r,index:o}=e;const i=Pr(t,r,n),a=t[o],s=i[o];return s&&a?{x:s.left-a.left,y:s.top-a.top,scaleX:s.width/a.width,scaleY:s.height/a.height}:null},Ur={scaleX:1,scaleY:1},Fr=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:o,rects:i,overIndex:a}=e;const s=null!=(t=i[n])?t:r;if(!s)return null;if(o===n){const e=i[a];return e?{x:0,y:nn&&o<=a?{x:0,y:-s.height-l,...Ur}:o=a?{x:0,y:s.height+l,...Ur}:{x:0,y:0,...Ur}},Vr="Sortable",qr=_t().createContext({activeIndex:-1,containerId:Vr,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:jr,disabled:{draggable:!1,droppable:!1}});function Gr(e){let{children:t,id:n,items:r,strategy:o=jr,disabled:i=!1}=e;const{active:a,dragOverlay:s,droppableRects:l,over:c,measureDroppableContainers:u}=(0,wt.useContext)(wr),d=Gt(Vr,n),p=Boolean(null!==s.rect),f=(0,wt.useMemo)(()=>r.map(e=>"object"==typeof e&&"id"in e?e.id:e),[r]),g=null!=a,h=a?f.indexOf(a.id):-1,m=c?f.indexOf(c.id):-1,v=(0,wt.useRef)(f),b=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{b&&g&&u(f)},[b,f,g,u]),(0,wt.useEffect)(()=>{v.current=f},[f]);const T=(0,wt.useMemo)(()=>({activeIndex:h,containerId:d,disabled:y,disableTransforms:E,items:f,overIndex:m,useDragOverlay:p,sortedRects:Br(f,l),strategy:o}),[h,d,y.draggable,y.droppable,E,f,m,l,p,o]);return _t().createElement(qr.Provider,{value:T},t)}const Hr=e=>{let{id:t,items:n,activeIndex:r,overIndex:o}=e;return Pr(n,r,o).indexOf(t)},$r=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:o,items:i,newIndex:a,previousItems:s,previousContainerId:l,transition:c}=e;return!(!c||!r||s!==i&&o===a||!n&&(a===o||t!==l))},zr={duration:200,easing:"ease"},Wr="transform",Kr=Xt.Transition.toString({property:Wr,duration:0,easing:"linear"}),Xr={roleDescription:"sortable"};function Yr(e){let{animateLayoutChanges:t=$r,attributes:n,disabled:r,data:o,getNewIndex:i=Hr,id:a,strategy:s,resizeObserverConfig:l,transition:c=zr}=e;const{items:u,containerId:d,activeIndex:p,disabled:f,disableTransforms:g,sortedRects:h,overIndex:m,useDragOverlay:v,strategy:b}=(0,wt.useContext)(qr),E=function(e,t){var n,r;return"boolean"==typeof e?{draggable:e,droppable:!1}:{draggable:null!=(n=null==e?void 0:e.draggable)?n:t.draggable,droppable:null!=(r=null==e?void 0:e.droppable)?r:t.droppable}}(r,f),y=u.indexOf(a),T=(0,wt.useMemo)(()=>({sortable:{containerId:d,index:y,items:u},...o}),[d,o,y,u]),S=(0,wt.useMemo)(()=>u.slice(u.indexOf(a)),[u,a]),{rect:x,node:C,isOver:w,setNodeRef:_}=function(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:o}=e;const i=Gt("Droppable"),{active:a,dispatch:s,over:l,measureDroppableContainers:c}=(0,wt.useContext)(Cr),u=(0,wt.useRef)({disabled:n}),d=(0,wt.useRef)(!1),p=(0,wt.useRef)(null),f=(0,wt.useRef)(null),{disabled:g,updateMeasurementsFor:h,timeout:m}={...Nr,...o},v=jt(null!=h?h:r),b=dr({callback:(0,wt.useCallback)(()=>{d.current?(null!=f.current&&clearTimeout(f.current),f.current=setTimeout(()=>{c(Array.isArray(v.current)?v.current:[v.current]),f.current=null},m)):d.current=!0},[m]),disabled:g||!a}),E=(0,wt.useCallback)((e,t)=>{b&&(t&&(b.unobserve(t),d.current=!1),e&&b.observe(e))},[b]),[y,T]=Ft(E),S=jt(t);return(0,wt.useEffect)(()=>{b&&y.current&&(b.disconnect(),d.current=!1,b.observe(y.current))},[y,b]),(0,wt.useEffect)(()=>(s({type:an.RegisterDroppable,element:{id:r,key:i,disabled:n,node:y,rect:p,data:S}}),()=>s({type:an.UnregisterDroppable,key:i,id:r})),[r]),(0,wt.useEffect)(()=>{n!==u.current.disabled&&(s({type:an.SetDroppableDisabled,id:r,key:i,disabled:n}),u.current.disabled=n)},[r,i,n,s]),{active:a,rect:p,isOver:(null==l?void 0:l.id)===r,node:y,over:l,setNodeRef:T}}({id:a,data:T,disabled:E.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...l}}),{active:R,activatorEvent:A,activeNodeRect:I,attributes:k,setNodeRef:D,listeners:O,isDragging:L,over:N,setActivatorNodeRef:P,transform:B}=function(e){let{id:t,data:n,disabled:r=!1,attributes:o}=e;const i=Gt("Draggable"),{activators:a,activatorEvent:s,active:l,activeNodeRect:c,ariaDescribedById:u,draggableNodes:d,over:p}=(0,wt.useContext)(Cr),{role:f=Lr,roleDescription:g="draggable",tabIndex:h=0}=null!=o?o:{},m=(null==l?void 0:l.id)===t,v=(0,wt.useContext)(m?Ir:Or),[b,E]=Ft(),[y,T]=Ft(),S=function(e,t){return(0,wt.useMemo)(()=>e.reduce((e,n)=>{let{eventName:r,handler:o}=n;return e[r]=e=>{o(e,t)},e},{}),[e,t])}(a,t),x=jt(n);return Bt(()=>(d.set(t,{id:t,key:i,node:b,activatorNode:y,data:x}),()=>{const e=d.get(t);e&&e.key===i&&d.delete(t)}),[d,t]),{active:l,activatorEvent:s,activeNodeRect:c,attributes:(0,wt.useMemo)(()=>({role:f,tabIndex:h,"aria-disabled":r,"aria-pressed":!(!m||f!==Lr)||void 0,"aria-roledescription":g,"aria-describedby":u.draggable}),[r,f,h,m,g,u.draggable]),isDragging:m,listeners:r?void 0:S,node:b,over:p,setNodeRef:E,setActivatorNodeRef:T,transform:v}}({id:a,data:T,attributes:{...Xr,...n},disabled:E.draggable}),M=function(){for(var e=arguments.length,t=new Array(e),n=0;ne=>{t.forEach(t=>t(e))},t)}(_,D),j=Boolean(R),U=j&&!g&&Mr(p)&&Mr(m),F=!v&&L,V=F&&U?B:null,q=U?null!=V?V:(null!=s?s:b)({rects:h,activeNodeRect:I,activeIndex:p,overIndex:m,index:y}):null,G=Mr(p)&&Mr(m)?i({id:a,items:u,activeIndex:p,overIndex:m}):y,H=null==R?void 0:R.id,$=(0,wt.useRef)({activeId:H,items:u,newIndex:G,containerId:d}),z=u!==$.current.items,W=t({active:R,containerId:d,isDragging:L,isSorting:j,id:a,index:y,items:u,newIndex:$.current.newIndex,previousItems:$.current.items,previousContainerId:$.current.containerId,transition:c,wasDragging:null!=$.current.activeId}),K=function(e){let{disabled:t,index:n,node:r,rect:o}=e;const[i,a]=(0,wt.useState)(null),s=(0,wt.useRef)(n);return Bt(()=>{if(!t&&n!==s.current&&r.current){const e=o.current;if(e){const t=xn(r.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&a(n)}}n!==s.current&&(s.current=n)},[t,n,r,o]),(0,wt.useEffect)(()=>{i&&a(null)},[i]),i}({disabled:!W,index:y,node:C,rect:x});return(0,wt.useEffect)(()=>{j&&$.current.newIndex!==G&&($.current.newIndex=G),d!==$.current.containerId&&($.current.containerId=d),u!==$.current.items&&($.current.items=u)},[j,G,d,u]),(0,wt.useEffect)(()=>{if(H===$.current.activeId)return;if(null!=H&&null==$.current.activeId)return void($.current.activeId=H);const e=setTimeout(()=>{$.current.activeId=H},50);return()=>clearTimeout(e)},[H]),{active:R,activeIndex:p,attributes:k,data:T,rect:x,index:y,newIndex:G,items:u,isOver:w,isSorting:j,isDragging:L,listeners:O,node:C,overIndex:m,over:N,setNodeRef:M,setActivatorNodeRef:P,setDroppableNodeRef:_,setDraggableNodeRef:D,transform:null!=K?K:q,transition:K||z&&$.current.newIndex===y?Kr:F&&!Wt(A)||!c?void 0:j||W?Xt.Transition.toString({...c,property:Wr}):void 0}}function Zr(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&"object"==typeof t.sortable&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const Jr=[Hn.Down,Hn.Right,Hn.Up,Hn.Left],Qr=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:o,droppableContainers:i,over:a,scrollableAncestors:s}}=t;if(Jr.includes(e.code)){if(e.preventDefault(),!n||!r)return;const t=[];i.getEnabled().forEach(n=>{if(!n||null!=n&&n.disabled)return;const i=o.get(n.id);if(i)switch(e.code){case Hn.Down:r.topi.top&&t.push(n);break;case Hn.Left:r.left>i.left&&t.push(n);break;case Hn.Right:r.left{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=fn(t),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=fn(r),a=o.reduce((e,t,r)=>e+un(n[r],t),0),s=Number((a/4).toFixed(4));i.push({id:t,data:{droppableContainer:e,value:s}})}}return i.sort(dn)})({active:n,collisionRect:r,droppableRects:o,droppableContainers:t,pointerCoordinates:null});let d=gn(u,"id");if(d===(null==a?void 0:a.id)&&u.length>1&&(d=u[1].id),null!=d){const e=i.get(n.id),t=i.get(d),a=t?o.get(t.id):null,u=null==t?void 0:t.node.current;if(u&&a&&e&&t){const n=wn(u).some((e,t)=>s[t]!==e),o=eo(e,t),i=(c=t,!(!Zr(l=e)||!Zr(c))&&!!eo(l,c)&&l.data.current.sortable.index0&&(0,bt.jsx)("div",{className:"act-step-target-info",children:(0,bt.jsxs)("code",{children:[e.target.locators[0].value.substring(0,30),e.target.locators[0].value.length>30?"…":""]})})]}),(0,bt.jsxs)(mt.FlexItem,{className:"act-step-actions",children:[(0,bt.jsx)(mt.Button,{icon:St,label:(0,ht.__)("Edit step","admin-coach-tours"),onClick:()=>t(e),size:"small"}),(0,bt.jsx)(mt.Button,{icon:xt,label:(0,ht.__)("Delete step","admin-coach-tours"),onClick:()=>n(e.id),size:"small",isDestructive:!0})]})]})})}function no({tourId:e,steps:t=[],onEditStep:n,onAddStep:r}){const{reorderSteps:o,deleteStep:i}=(0,c.useDispatch)("admin-coach-tours"),a=function(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(e=>null!=e),[...t])}(ln(er,{activationConstraint:{distance:8}}),ln(Xn,{coordinateGetter:Qr})),s=(0,gt.useCallback)(n=>{const{active:r,over:i}=n;if(r.id!==i?.id){const n=t.findIndex(e=>e.id===r.id),a=t.findIndex(e=>e.id===i?.id);if(-1!==n&&-1!==a){const r=Pr(t.map(e=>e.id),n,a);o(e,r)}}},[e,t,o]),l=(0,gt.useCallback)(t=>{window.confirm((0,ht.__)("Are you sure you want to delete this step?","admin-coach-tours"))&&i(e,t)},[e,i]);if(0===t.length)return(0,bt.jsxs)("div",{className:"act-step-list-empty",children:[(0,bt.jsx)("p",{children:(0,ht.__)("No steps yet. Click the button below to add your first step.","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"primary",icon:Ct,onClick:r,children:(0,ht.__)("Add First Step","admin-coach-tours")})]});const u=[...t].sort((e,t)=>e.order-t.order);return(0,bt.jsxs)("div",{className:"act-step-list",children:[(0,bt.jsx)(Dr,{sensors:a,collisionDetection:mn,onDragEnd:s,children:(0,bt.jsx)(Gr,{items:u.map(e=>e.id),strategy:Fr,children:u.map(e=>(0,bt.jsx)(to,{step:e,onEdit:n,onDelete:l},e.id))})}),(0,bt.jsx)("div",{className:"act-step-list-footer act-button-group",children:(0,bt.jsx)(mt.Button,{variant:"secondary",icon:Ct,onClick:r,children:(0,ht.__)("Add Step","admin-coach-tours")})})]})}var ro=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})}),oo=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})}),io=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z"})});function ao(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function so(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const i=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",i),i}function lo(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function co(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function uo(e,t=document){switch(e.type){case"css":return co(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),i={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let a=[];i[n]&&(a=Array.from(t.querySelectorAll(i[n])));const s=[...o,...a];return r?s.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):s}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return co(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let i=null;if("first"===e)i=o[0]?.clientId;else if("last"===e)i=o[o.length-1]?.clientId;else if("selected"===e)i=r.getSelectedBlockClientId(),i||(i=window.__actLastSelectedBlockClientId||window.__actLastAppearedBlockClientId||(1===o.length?o[0]?.clientId:null),i&&console.log('[ACT findByWpBlock] "selected" fell back to:',i));else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,a=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",a.length),i=a[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);i=o[t]?.clientId}if(!i)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",i);const a=t.querySelector(`[data-block="${i}"]`);return a?(console.log("[ACT findByWpBlock] Found element:",a.tagName),[a]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function po(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&lo(e,n.withinContainer)&&(r+=10),so(e)&&(r+=100),ao(e)&&(r+=5),r}const fo="admin-coach-tours";function go({step:e,tourId:t,postType:n,onClose:r}){const[o,i]=(0,gt.useState)(e.title||""),[a,s]=(0,gt.useState)(e.content||""),[l,u]=(0,gt.useState)(e.completion?.type||"manual"),[d,p]=(0,gt.useState)(e.completion?.params||{}),[f,g]=(0,gt.useState)(!1),[h,m]=(0,gt.useState)(null),[v,b]=(0,gt.useState)(null),{aiDraft:E,isAiDrafting:y,aiDraftError:T}=(0,c.useSelect)(e=>{const t=e(fo);return{aiDraft:t.getAiDraft(),isAiDrafting:t.isAiDrafting(),aiDraftError:t.getAiDraftError()}},[]),{updateStep:S,requestAiDraft:x,clearAiDraft:C,startPicking:w}=(0,c.useDispatch)(fo),_=[{type:"clickTarget",label:"Click Target",description:"Complete when user clicks the target element",requiresTarget:!0,params:[]},{type:"domValueChanged",label:"Value Changed",description:"Complete when element value changes",requiresTarget:!0,params:[{name:"expectedValue",type:"string",optional:!0,description:"Expected value (if not set, any change completes)"},{name:"attributeName",type:"string",optional:!0,description:"Attribute to watch (defaults to value/textContent)"}]},{type:"wpData",label:"Store Change",description:"Complete when @wordpress/data store value changes",requiresTarget:!1,params:[{name:"storeName",type:"string",required:!0,description:"Store name (e.g., core/block-editor)"},{name:"selector",type:"string",required:!0,description:"Selector function name"},{name:"args",type:"array",optional:!0,description:"Arguments for selector"},{name:"expectedValue",type:"any",optional:!0,description:"Expected value"},{name:"comparator",type:"string",optional:!0,description:"equals, notEquals, truthy, falsy, contains, greaterThan, lessThan"}]},{type:"manual",label:"Manual",description:"Complete when user clicks continue button",requiresTarget:!1,params:[]},{type:"elementAppear",label:"Element Appears",description:"Complete when an element appears in DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"elementDisappear",label:"Element Disappears",description:"Complete when an element is removed from DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"customEvent",label:"Custom Event",description:"Complete when a custom event is dispatched",requiresTarget:!1,params:[{name:"eventName",type:"string",required:!0,description:"Custom event name"}]}],R=(0,gt.useCallback)(()=>{if(e.target){const t=function(e){const t=function(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),i=o.filter(e=>!e.fallback),a=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",i.length,"primary +",a.length,"fallback locators");for(const e of[...i,...a]){let n=uo(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(ao),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(so),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>lo(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:po(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}(e);return{success:t.success,element:t.element,usedLocator:t.usedLocator,error:t.error,elementInfo:t.element?{tagName:t.element.tagName.toLowerCase(),id:t.element.id||null,className:t.element.className||null,textContent:t.element.textContent?.slice(0,50)||null,rect:t.element.getBoundingClientRect()}:null}}(e.target);b(t),setTimeout(()=>b(null),5e3)}},[e.target]),A=(0,gt.useCallback)(async()=>{g(!0),m(null);try{await S(t,e.id,{title:o.trim(),content:a.trim(),completion:{type:l,params:d}}),r()}catch(e){m(e.message||(0,ht.__)("Failed to save step.","admin-coach-tours"))}finally{g(!1)}},[t,e.id,o,a,l,d,S,r]),I=(0,gt.useCallback)(()=>{if(e.target){const t={...e.elementContext||{selector:e.target,stepId:e.id},existingTitle:o,existingContent:a};x(t,n)}},[e.id,e.target,e.elementContext,o,a,n,x]),k=(0,gt.useCallback)(()=>{E&&(E.title&&i(E.title),E.content&&s(E.content),E.suggestedCompletion&&(u(E.suggestedCompletion.type),p(E.suggestedCompletion||{})),C())},[E,C]),D=(0,gt.useCallback)(()=>{w(e.id)},[e.id,w]),O=(0,gt.useCallback)((e,t)=>{p(n=>({...n,[e]:t}))},[]),L=_.find(e=>e.type===l);return(0,bt.jsxs)("div",{className:"act-step-editor",children:[h&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!1,children:h}),(0,bt.jsx)(mt.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("Target Element","admin-coach-tours"),className:"act-step-editor-target",children:(0,bt.jsx)("div",{className:"act-target-info",children:e.target?.locators?.length>0?(0,bt.jsxs)(bt.Fragment,{children:[(0,bt.jsx)("code",{className:"act-target-selector",children:e.target.locators[0].value}),(0,bt.jsxs)(mt.Flex,{gap:2,style:{marginTop:"8px"},children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"secondary",size:"small",icon:ro,onClick:R,children:(0,ht.__)("Test","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",size:"small",onClick:D,children:(0,ht.__)("Re-pick","admin-coach-tours")})})]}),v&&(0,bt.jsx)(mt.Notice,{status:v.success?"success":"error",isDismissible:!1,className:"act-target-test-result",children:v.success?(0,ht.__)("Target found successfully!","admin-coach-tours"):(0,ht.__)("Target not found. Consider re-picking.","admin-coach-tours")})]}):(0,bt.jsx)(mt.Button,{variant:"primary",icon:ro,onClick:D,children:(0,ht.__)("Pick Target Element","admin-coach-tours")})})}),(0,bt.jsx)(mt.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,ht.__)("Step Title","admin-coach-tours"),value:o,onChange:i,placeholder:(0,ht.__)("e.g., Click the Add Block button","admin-coach-tours")}),(0,bt.jsx)(mt.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("Step Content","admin-coach-tours"),value:a,onChange:s,placeholder:(0,ht.__)("Explain what the user should do and why…","admin-coach-tours"),rows:4}),e.target&&(0,bt.jsxs)(mt.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("AI Assistance","admin-coach-tours"),className:"act-ai-draft-section",children:[y?(0,bt.jsxs)(mt.Flex,{align:"center",gap:2,children:[(0,bt.jsx)(mt.Spinner,{}),(0,bt.jsx)("span",{children:(0,ht.__)("Generating draft…","admin-coach-tours")})]}):E?(0,bt.jsxs)("div",{className:"act-ai-draft",children:[(0,bt.jsxs)("div",{className:"act-ai-draft-preview",children:[(0,bt.jsx)("strong",{children:E.title}),(0,bt.jsxs)("p",{children:[E.content?.substring(0,100),"…"]})]}),(0,bt.jsxs)(mt.Flex,{gap:2,children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"primary",size:"small",icon:yt,onClick:k,children:(0,ht.__)("Apply","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",size:"small",icon:oo,onClick:()=>C(),children:(0,ht.__)("Dismiss","admin-coach-tours")})})]})]}):(0,bt.jsx)(mt.Button,{variant:"secondary",icon:io,onClick:I,children:(0,ht.__)("Generate with AI","admin-coach-tours")}),T&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!1,children:T})]}),(0,bt.jsx)(mt.SelectControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,ht.__)("Completion Condition","admin-coach-tours"),value:l,options:_.map(e=>({value:e.type,label:e.label})),onChange:e=>{u(e),p({})},help:L?.description}),L?.params&&L.params.length>0&&(0,bt.jsx)("div",{className:"act-completion-params",children:L.params.map(e=>(0,bt.jsx)(mt.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:e.name,value:d[e.name]||"",onChange:t=>O(e.name,t),help:e.description,required:e.required},e.name))}),(0,bt.jsx)("div",{className:"act-step-editor-actions",children:(0,bt.jsxs)(mt.Flex,{justify:"flex-end",gap:2,children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",onClick:r,children:(0,ht.__)("Cancel","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"primary",onClick:A,isBusy:f,disabled:f||!o.trim(),children:(0,ht.__)("Save Step","admin-coach-tours")})})]})})]})}const ho=[/^css-/i,/^sc-/i,/^emotion-/i,/^_/i,/^jsx-/i,/^styles?__/i,/^[a-z]{1,2}[0-9]+/i,/^[0-9]/i,/^svelte-/i];function mo(e){return e.filter(e=>{return!((t=e).length<=3||ho.some(e=>e.test(t)));var t})}function vo(e){const t={},n=e.attributes;for(let e=0;ee.startsWith(t))||(t[e]=r.value)}}return t}function bo(e){if(e.getAttribute("role"))return e.getAttribute("role");const t=e.tagName.toLowerCase(),n=e.getAttribute("type"),r={button:"button",a:e.hasAttribute("href")?"link":null,input:{text:"textbox",search:"searchbox",email:"textbox",url:"textbox",tel:"textbox",password:"textbox",checkbox:"checkbox",radio:"radio",submit:"button",button:"button",reset:"button",range:"slider"},textarea:"textbox",select:"listbox",option:"option",img:e.hasAttribute("alt")?"img":null,nav:"navigation",main:"main",header:"banner",footer:"contentinfo",aside:"complementary",form:"form",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",ul:"list",ol:"list",li:"listitem",table:"table",dialog:"dialog"};return"input"===t&&r.input[n]?r.input[n]:r[t]||null}function Eo(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||null}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||null}if(e.getAttribute("title"))return e.getAttribute("title");const n=e.tagName.toLowerCase();if("button"===n||"a"===n||"button"===e.getAttribute("role")){const t=e.textContent?.trim();if(t&&t.length<100)return t}return null}function yo(e){return!(e&&!/^block-[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9]{8,}$/i.test(e)&&!/[-_][a-f0-9]{8,}/i.test(e))}const To="admin-coach-tours",So=[".act-picker-overlay",".act-picker-highlight",".act-picker-toolbar",".edit-post-sidebar","#adminmenumain","#wpadminbar",".components-popover",".components-modal__screen-overlay",'iframe[name="editor-canvas"]'];function xo(){return document.querySelector('iframe[name="editor-canvas"]')}function Co(e){for(const t of So){if(e.matches(t))return!0;if(e.closest(t))return!0}return!1}function wo({onCancel:e}){const[t,n]=(0,gt.useState)(null),[r,o]=(0,gt.useState)(null),i=(0,gt.useRef)(null),{pickingStepId:a,currentTourId:s}=(0,c.useSelect)(e=>{const t=e(To);return{pickingStepId:t.getPickingStepId?.()||null,currentTourId:t.getCurrentTourId?.()||null}},[]),{stopPicking:l,addStep:u,updateStep:d}=(0,c.useDispatch)(To),p=(0,gt.useRef)(!1),f=(0,gt.useRef)(null),g=(0,gt.useRef)(null),h=(0,gt.useCallback)((e,t=!1)=>{if(g.current)return;g.current=setTimeout(()=>{g.current=null},16);let r=null;if(t){if(r=e.target,r&&!Co(r)){if(r===f.current)return;f.current=r,p.current=!0;const e=xo();if(e){const t=e.getBoundingClientRect(),i=r.getBoundingClientRect();n(r),o({top:t.top+i.top,left:t.left+i.left,width:i.width,height:i.height})}}}else if(r=document.elementsFromPoint(e.clientX,e.clientY).find(e=>!(e.closest(".act-picker-overlay")||Co(e)||"IFRAME"===e.tagName&&"editor-canvas"===e.name)),r&&r!==f.current){f.current=r,p.current=!1,n(r);const e=r.getBoundingClientRect();o({top:e.top,left:e.left,width:e.width,height:e.height})}},[]),m=(0,gt.useCallback)(e=>{e.preventDefault(),e.stopPropagation();const t=f.current;if(!t)return;const n=function(e,t={}){const{inEditorIframe:n=!1}=t,r=[],o={visible:!0};n&&(o.inEditorIframe=!0);const i=e.getAttribute("data-testid");i&&!yo(i)&&r.push({type:"testId",value:i,weight:100,fallback:!1}),e.id&&!yo(e.id)&&r.push({type:"css",value:`#${CSS.escape(e.id)}`,weight:95,fallback:!1});const a=bo(e),s=Eo(e);if(a){const e=s?`${a}:${s}`:a;r.push({type:"role",value:e,weight:80,fallback:!1})}const l=vo(e);for(const[e,t]of Object.entries(l)){if("testid"===e||"reactid"===e)continue;if("block"===e&&t&&/^[a-f0-9-]{36}$/i.test(t))continue;const n="type"===e?85:e.startsWith("wp-")?75:70;if(r.push({type:"dataAttribute",value:t?`${e}:${t}`:e,weight:n,fallback:!1}),r.filter(e=>"dataAttribute"===e.type).length>=2)break}const c=function(e,t=3){const n=[];let r=e,o=0;for(;r&&r!==document.body&&o0&&(t+=i.slice(0,2).map(e=>`.${CSS.escape(e)}`).join(""));const a=r.getAttribute("data-testid");if(a&&0===o)t=`[data-testid="${a}"]`;else{const n=r.getAttribute("type");n&&"input"===e&&(t+=`[type="${n}"]`);const o=r.getAttribute("name");o&&["input","select","textarea"].includes(e)&&(t+=`[name="${o}"]`)}if(r.parentElement&&0===o){const e=Array.from(r.parentElement.children).filter(e=>e.tagName===r.tagName);e.length>1&&(t+=`:nth-of-type(${e.indexOf(r)+1})`)}n.unshift(t),r=r.parentElement,o++}return n.join(" > ")}(e,3);c&&r.push({type:"css",value:c,weight:60,fallback:!1});const u=e.getAttribute("aria-label");u&&r.push({type:"ariaLabel",value:u,weight:40,fallback:!0});const d=function(e,t=5){const n=["main","nav","aside","header","footer","section","article","form","dialog"],r=["main","navigation","complementary","banner","contentinfo","region","form","dialog","search"];let o=e.parentElement,i=0;for(;o&&o!==document.body&&i0){const e=mo(Array.from(o.classList));e.length>0&&(n+=`.${CSS.escape(e[0])}`)}return{element:o,selector:n,type:t||e}}const a=["edit-post-sidebar","block-editor","editor-styles-wrapper","components-popover","components-modal","interface-interface-skeleton"];for(const e of a)if(o.classList.contains(e))return{element:o,selector:`.${e}`,type:"editor-region"};o=o.parentElement,i++}return null}(e);if(d){o.withinContainer=d.selector;const t=e.tagName.toLowerCase(),n=mo(Array.from(e.classList));let i=t;n.length>0&&(i+=`.${CSS.escape(n[0])}`),r.push({type:"contextual",value:`${d.selector} >> ${i}`,weight:50,fallback:!0});const a=e.getAttribute("data-type");a&&r.push({type:"css",value:`[data-type="${a}"]:first-of-type`,weight:45,fallback:!0})}if(0===r.length){const t=e.tagName.toLowerCase(),n=e.parentElement;if(n){const o=Array.from(n.children).indexOf(e)+1;r.push({type:"css",value:`${t}:nth-child(${o})`,weight:10,fallback:!0})}}return r.sort((e,t)=>(t.weight||50)-(e.weight||50)),{locators:r,constraints:o}}(t,{inEditorIframe:p.current}),r=function(e){const t={tagName:e.tagName.toLowerCase()},n=bo(e);n&&(t.role=n),e.id&&!/^[a-z0-9_-]{20,}$/i.test(e.id)&&(t.id=e.id);const r=mo(Array.from(e.classList));r.length>0&&(t.classNames=r.slice(0,5));const o=e.textContent?.trim();o&&o.length<=200?t.textContent=o:o&&(t.textContent=`${o.slice(0,197)}...`),e.placeholder&&(t.placeholder=e.placeholder);const i=Eo(e);i&&i!==t.textContent&&(t.label=i);const a=vo(e),s=Object.keys(a).filter(e=>e.startsWith("wp-")||"block"===e||"type"===e||"testid"===e);s.length>0&&(t.dataAttrs={},s.forEach(e=>{t.dataAttrs[e]=a[e]}));const l=[];let c=e.parentElement,u=0;for(;c&&c!==document.body&&u<3;){const e={tagName:c.tagName.toLowerCase()},t=bo(c);t&&(e.role=t),c.id&&!/^[a-z0-9_-]{20,}$/i.test(c.id)&&(e.id=c.id);const n=mo(Array.from(c.classList));n.length>0&&(e.classNames=n.slice(0,3)),l.push(e),c=c.parentElement,u++}return l.length>0&&(t.ancestors=l),t}(t),o={target:n,elementContext:r,completion:{type:"clickTarget",params:{}}};a?d(s,a,{target:n,elementContext:r}):u(s,o),l()},[a,s,u,d,l]),v=(0,gt.useCallback)(t=>{"Escape"===t.key&&(l(),e?.())},[l,e]),b=(0,gt.useRef)({handleMouseMove:h,handleClick:m,handleKeyDown:v});(0,gt.useEffect)(()=>{b.current={handleMouseMove:h,handleClick:m,handleKeyDown:v}},[h,m,v]),(0,gt.useEffect)(()=>{const e=e=>b.current.handleMouseMove(e,!1),t=e=>b.current.handleClick(e),n=e=>b.current.handleKeyDown(e),r=e=>b.current.handleMouseMove(e,!0),o=e=>{e.preventDefault(),e.stopPropagation(),b.current.handleClick(e)};document.addEventListener("mousemove",e,!0),document.addEventListener("click",t,!0),document.addEventListener("keydown",n,!0),document.body.style.overflow="hidden";let i=null,a=null;const s=()=>{const e=xo();e?.contentDocument&&e.contentDocument!==i&&(i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0)),i=e.contentDocument,i.addEventListener("mousemove",r,!0),i.addEventListener("click",o,!0),i.addEventListener("keydown",n,!0))};return s(),a=setInterval(s,500),()=>{document.removeEventListener("mousemove",e,!0),document.removeEventListener("click",t,!0),document.removeEventListener("keydown",n,!0),document.body.style.overflow="",g.current&&clearTimeout(g.current),a&&clearInterval(a),i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0))}},[]);const E=(0,bt.jsxs)("div",{ref:i,className:"act-picker-overlay",style:{position:"fixed",top:0,left:0,right:0,bottom:0,zIndex:9999998,cursor:"crosshair",pointerEvents:"none"},children:[r?(0,bt.jsx)("div",{className:"act-picker-highlight",style:{position:"fixed",top:r.top,left:r.left,width:r.width,height:r.height,border:"3px solid #007cba",backgroundColor:"rgba(0, 124, 186, 0.15)",pointerEvents:"none",zIndex:9999999,boxSizing:"border-box",borderRadius:"3px",transition:"top 0.08s ease-out, left 0.08s ease-out, width 0.08s ease-out, height 0.08s ease-out",boxShadow:"0 0 0 2px rgba(0, 124, 186, 0.3)"}}):null,(()=>{if(!t)return null;const e=t.tagName.toLowerCase(),n=t.id,r=Array.from(t.classList).slice(0,3).join(".");let o=e;return n?o+=`#${n}`:r&&(o+=`.${r}`),(0,bt.jsx)("div",{className:"act-picker-element-info",style:{position:"fixed",bottom:"80px",left:"50%",transform:"translateX(-50%)",backgroundColor:"rgba(0, 0, 0, 0.8)",color:"#fff",padding:"8px 16px",borderRadius:"4px",fontFamily:"monospace",fontSize:"13px",maxWidth:"80%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",zIndex:9999999},children:o})})(),(0,bt.jsxs)("div",{className:"act-picker-toolbar",style:{position:"fixed",bottom:"20px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"12px",backgroundColor:"#fff",padding:"12px 20px",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",zIndex:9999999,pointerEvents:"auto"},children:[(0,bt.jsx)("span",{style:{alignSelf:"center",fontWeight:500},children:(0,ht.__)("Click an element to select it as target","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"tertiary",icon:oo,onClick:()=>{l(),e?.()},children:(0,ht.__)("Cancel","admin-coach-tours")})]})]});return(0,gt.createPortal)(E,document.body)}const _o="admin-coach-tours";(0,l.registerPlugin)("admin-coach-tours-educator",{render:function(){const[e,t]=(0,gt.useState)(null),[n,r]=(0,gt.useState)(!1),[o,i]=(0,gt.useState)(!1),{postType:a,postId:s,postTitle:l,postStatus:u,isSaving:d}=(0,c.useSelect)(e=>{const t=e("core/editor");return{postType:t?.getCurrentPostType?.()||"",postId:t?.getCurrentPostId?.()||0,postTitle:t?.getEditedPostAttribute?.("title")||"",postStatus:t?.getEditedPostAttribute?.("status")||"draft",isSaving:t?.isSavingPost?.()||!1}},[]),{currentTour:p,selectedStep:f,isPickerActive:g,isLoading:h}=(0,c.useSelect)(e=>{const t=e(_o);return{currentTour:t.getCurrentTour(),selectedStep:t.getSelectedStep(),isPickerActive:t.isPickerActive(),isLoading:t.isToursLoading()}},[]),{setCurrentTour:m,saveTour:v,startPicking:b,stopPicking:E,selectStep:y}=(0,c.useDispatch)(_o),{enableComplementaryArea:T}=(0,c.useDispatch)("core/interface");if((0,gt.useEffect)(()=>{"act_tour"===a&&s&&s!==p?.id&&m(s)},[a,s,p?.id,m]),(0,gt.useEffect)(()=>{if("act_tour"===a){const e=setTimeout(()=>{T("core","admin-coach-tours-educator/admin-coach-tours-sidebar")},100);return()=>clearTimeout(e)}},[a,T]),"act_tour"!==a)return null;const S=(0,gt.useCallback)(()=>{b()},[b]),x=(0,gt.useCallback)(()=>{E()},[E]),C=(0,gt.useCallback)(()=>{if(p?.id){const e=(p.postTypes||["post"])[0]||"post",t=new URL(window.location.origin+"/wp-admin/post-new.php");"post"!==e&&t.searchParams.set("post_type",e),t.searchParams.set("act_tour",p.id.toString()),console.log("[ACT Educator] Opening test URL:",t.toString()),window.open(t.toString(),"_blank")}},[p?.id,p?.postTypes]);if(h&&!p)return(0,bt.jsx)(bt.Fragment,{children:(0,bt.jsx)(ft.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,ht.__)("Tour Steps","admin-coach-tours"),icon:Et,children:(0,bt.jsx)("div",{className:"act-educator-sidebar",children:(0,bt.jsx)(mt.PanelBody,{children:(0,bt.jsx)(mt.Flex,{justify:"center",style:{padding:"24px"},children:(0,bt.jsx)(mt.Spinner,{})})})})})});const w=p?.steps||[];return(0,bt.jsxs)(bt.Fragment,{children:[(0,bt.jsx)(ft.PluginSidebarMoreMenuItem,{target:"admin-coach-tours-sidebar",icon:Et,children:(0,ht.__)("Tour Steps","admin-coach-tours")}),(0,bt.jsx)(ft.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,ht.__)("Tour Steps","admin-coach-tours"),icon:Et,children:(0,bt.jsxs)("div",{className:"act-educator-sidebar",children:[(0,bt.jsxs)(mt.PanelBody,{title:(0,ht.__)("Tour Info","admin-coach-tours"),initialOpen:!1,children:[(0,bt.jsxs)(mt.Flex,{justify:"space-between",align:"center",children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)("strong",{children:l||(0,ht.__)("Untitled Tour","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)("span",{className:"act-tour-status"+("publish"===u?" act-tour-status--published":""),children:"publish"===u?(0,ht.__)("Published","admin-coach-tours"):(0,ht.__)("Draft","admin-coach-tours")})})]}),(0,bt.jsx)("p",{className:"act-help-text",style:{marginTop:"8px"},children:(0,ht.__)("Use the block editor canvas as a sandbox to create your tour steps. Pick elements from the editor to target them in your tour.","admin-coach-tours")})]}),(0,bt.jsxs)(mt.PanelBody,{title:(0,ht.__)("Steps","admin-coach-tours")+` (${w.length})`,initialOpen:!0,children:[e&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!0,onRemove:()=>t(null),children:e}),o&&(0,bt.jsx)(mt.Notice,{status:"success",isDismissible:!1,children:(0,ht.__)("Steps saved successfully!","admin-coach-tours")}),(0,bt.jsx)(no,{tourId:s,steps:w,onEditStep:e=>y(e?.id??null),onAddStep:S}),w.length>0&&(0,bt.jsxs)("div",{className:"act-actions-footer",children:[(0,bt.jsx)(mt.Button,{variant:"primary",icon:yt,onClick:async()=>{if(p?.id){r(!0),t(null),i(!1);try{const e={steps:p.steps||[]};await v(p.id,e),i(!0),setTimeout(()=>i(!1),3e3)}catch(e){t(e.message||(0,ht.__)("Failed to save steps.","admin-coach-tours"))}finally{r(!1)}}},isBusy:n,disabled:n||d,children:n?(0,ht.__)("Saving…","admin-coach-tours"):(0,ht.__)("Save Steps","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"secondary",icon:Tt,onClick:C,disabled:0===w.length,children:(0,ht.__)("Test Tour","admin-coach-tours")})]})]}),f&&(0,bt.jsx)(mt.PanelBody,{title:(0,ht.__)("Edit Step","admin-coach-tours"),initialOpen:!0,children:(0,bt.jsx)(go,{step:f,tourId:s,postType:"act_tour",onClose:()=>y(null)})})]})}),g&&(0,bt.jsx)(wo,{onCancel:x})]})},icon:null})})(); \ No newline at end of file diff --git a/build/pupil/index.asset.php b/build/pupil/index.asset.php index 10eec17..b3a360e 100644 --- a/build/pupil/index.asset.php +++ b/build/pupil/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '108949c4cfd6402553b8'); + array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'bd85457ec89b275a7142'); diff --git a/build/pupil/index.js b/build/pupil/index.js index 699e1f8..a50580e 100644 --- a/build/pupil/index.js +++ b/build/pupil/index.js @@ -1,3 +1,4 @@ -(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var s=r[e]={exports:{}};return n[e](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var s=Object.create(null);o.r(s);var c={};e=e||[null,t({}),t([]),t(t)];for(var i=2&r&&n;("object"==typeof i||"function"==typeof i)&&!~e.indexOf(i);i=t(i))Object.getOwnPropertyNames(i).forEach(e=>c[e]=()=>n[e]);return c.default=()=>n,o.d(s,c),s},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var s={};o.r(s),o.d(s,{activatePicker:()=>Oe,addStep:()=>Ue,clearAiDraft:()=>He,clearEphemeralTour:()=>Qe,clearResolvedTarget:()=>we,createTour:()=>pe,deactivatePicker:()=>Ne,deleteStep:()=>Fe,endTour:()=>ge,fetchTour:()=>ue,fetchTours:()=>ae,incrementResolutionAttempts:()=>ve,markStepComplete:()=>ke,nextStep:()=>Ee,previousStep:()=>_e,receiveEphemeralTour:()=>Ye,receiveTour:()=>ie,receiveTours:()=>ce,reorderSteps:()=>qe,repeatStep:()=>Se,requestAiDraft:()=>We,requestAiTour:()=>Je,resetCompletion:()=>Ce,saveTour:()=>de,selectStep:()=>Be,setAiDraftError:()=>Ve,setAiDraftLoading:()=>Me,setAiDraftResult:()=>Ge,setAiTourError:()=>Ke,setAiTourLoading:()=>ze,setCompletionSatisfied:()=>Ae,setCurrentStep:()=>fe,setCurrentTour:()=>le,setLastError:()=>xe,setLastFailureContext:()=>Xe,setMode:()=>be,setPendingChanges:()=>De,setRecovering:()=>Re,setResolvedTarget:()=>Ie,setSidebarOpen:()=>$e,setToursError:()=>se,setToursLoading:()=>oe,skipStep:()=>ye,startEphemeralTour:()=>Ze,startPicking:()=>Pe,startTour:()=>he,stopPicking:()=>Le,stopTour:()=>Te,updateStep:()=>je,updateTour:()=>me});var c={};o.r(c),o.d(c,{getAiDraft:()=>Bt,getAiDraftError:()=>Nt,getAiDraftResult:()=>Lt,getAiTourError:()=>Ut,getCurrentStep:()=>ut,getCurrentStepIndex:()=>at,getCurrentTour:()=>lt,getCurrentTourId:()=>it,getEphemeralTour:()=>qt,getLastError:()=>kt,getLastFailureContext:()=>Ft,getMode:()=>gt,getPickingStepId:()=>wt,getProgress:()=>ht,getResolutionAttempts:()=>Ct,getResolvedTarget:()=>bt,getSelectedStep:()=>vt,getSelectedStepId:()=>Rt,getSkippedSteps:()=>yt,getTotalSteps:()=>dt,getTour:()=>nt,getTours:()=>tt,getToursByEditor:()=>ct,getToursById:()=>et,getToursByPostType:()=>st,getToursError:()=>ot,hasNextStep:()=>pt,hasPendingChanges:()=>xt,hasPreviousStep:()=>mt,isAiDraftLoading:()=>Ot,isAiDrafting:()=>Pt,isAiTourLoading:()=>jt,isCompletionSatisfied:()=>_t,isEducatorMode:()=>Tt,isEphemeralTourActive:()=>Mt,isPickerActive:()=>It,isPupilMode:()=>ft,isRecovering:()=>At,isSidebarOpen:()=>Dt,isTourActive:()=>Et,isToursLoading:()=>rt,wasStepSkipped:()=>St});var i={};o.r(i),o.d(i,{getTour:()=>Gt,getTours:()=>Vt,getToursByPostType:()=>Ht});const l=window.wp.element,a=window.wp.data,u=window.wp.i18n,d=window.wp.components,p=window.wp.primitives,m=window.ReactJSXRuntime;var h=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m14.5 6.5-1 1 3.7 3.7H4v1.6h13.2l-3.7 3.7 1 1 5.6-5.5z"})}),g=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})});function T({step:e,stepIndex:t,totalSteps:n,tourTitle:r,targetElement:o,resolutionError:s,expectedBlockType:c,isApplyingPreconditions:i,onContinue:a,onRepeat:p,onPrevious:T,onNext:f,onStop:E}){const[_,y]=(0,l.useState)({x:20,y:20}),[S,b]=(0,l.useState)(!1),[A,C]=(0,l.useState)({x:0,y:0}),[k,I]=(0,l.useState)(0),w=(0,l.useRef)(null),R=t===n-1,v="manual"===e?.completion?.type;(0,l.useEffect)(()=>{if(!o||S)return;const e=()=>{if(!o?.isConnected)return;const e=o.getBoundingClientRect();let t={top:0,left:0};if(o.ownerDocument!==document){const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const n=e.getBoundingClientRect();t={top:n.top,left:n.left}}}const n={top:e.top+t.top,bottom:e.bottom+t.top,left:e.left+t.left,right:e.right+t.left},r=320,s=200,c=20;let i,l;n.right+r+c0?(i=n.left-r-c,l=n.top):n.bottom+s+c0?(i=Math.max(c,n.left),l=n.top-s-c):(i=window.innerWidth-r-c,l=window.innerHeight-s-c),i=Math.max(c,Math.min(i,window.innerWidth-r-c)),l=Math.max(c,Math.min(l,window.innerHeight-s-c)),y({x:i,y:l})};e();const t=()=>{S||e()};window.addEventListener("scroll",t,{passive:!0}),window.addEventListener("resize",t,{passive:!0});const n=o.ownerDocument,r=n?.defaultView;return r&&r!==window&&r.addEventListener("scroll",t,{passive:!0}),()=>{if(window.removeEventListener("scroll",t),window.removeEventListener("resize",t),r&&r!==window)try{r.removeEventListener("scroll",t)}catch(e){}}},[o,S,k]);const x=(0,l.useCallback)(e=>{if(e.target.closest("button"))return;b(!0);const t=w.current.getBoundingClientRect();C({x:e.clientX-t.left,y:e.clientY-t.top})},[]),O=(0,l.useCallback)(e=>{if(!S)return;const t=e.clientX-A.x,n=e.clientY-A.y,r=w.current.getBoundingClientRect(),o=window.innerWidth-r.width,s=window.innerHeight-r.height;y({x:Math.max(0,Math.min(t,o)),y:Math.max(0,Math.min(n,s))})},[S,A]),P=(0,l.useCallback)(()=>{b(!1)},[]);return(0,l.useEffect)(()=>{if(S)return document.addEventListener("mousemove",O),document.addEventListener("mouseup",P),()=>{document.removeEventListener("mousemove",O),document.removeEventListener("mouseup",P)}},[S,O,P]),(0,m.jsxs)("div",{ref:w,className:"act-coach-panel",style:{position:"fixed",top:_.y,left:_.x,width:"360px",maxHeight:"450px",backgroundColor:"#fff",borderRadius:"12px",boxShadow:"0 8px 32px rgba(0, 0, 0, 0.18)",zIndex:9999990,display:"flex",flexDirection:"column",cursor:S?"grabbing":"default"},onMouseDown:x,children:[(0,m.jsx)("div",{className:"act-panel-header",style:{padding:"16px 20px",borderBottom:"1px solid #e0e0e0",cursor:"grab",userSelect:"none",background:"linear-gradient(to bottom, #fafafa, #fff)",borderRadius:"12px 12px 0 0"},children:(0,m.jsxs)(d.Flex,{justify:"space-between",align:"flex-start",children:[(0,m.jsxs)(d.FlexBlock,{children:[(0,m.jsx)("strong",{className:"act-panel-title",style:{fontSize:"16px",lineHeight:"1.4",display:"block",marginBottom:"6px",color:"#1e1e1e"},children:e?.title||(0,u.__)("Step","admin-coach-tours")+` ${t+1}`}),(0,m.jsxs)("div",{className:"act-panel-progress",style:{fontSize:"13px",color:"#757575",fontWeight:"500"},children:[`${t+1} / ${n}`,r&&` • ${r}`]})]}),(0,m.jsx)(d.FlexItem,{children:(0,m.jsx)(d.Button,{icon:g,label:(0,u.__)("Close tour","admin-coach-tours"),onClick:E,size:"small"})})]})}),(0,m.jsx)("div",{className:"act-panel-body",style:{padding:"20px",flex:1,overflow:"auto",fontSize:"14px",lineHeight:"1.6",color:"#1e1e1e"},children:i?(0,m.jsxs)("div",{className:"act-panel-loading",children:[(0,m.jsx)(d.Spinner,{}),(0,m.jsx)("span",{children:(0,u.__)("Preparing…","admin-coach-tours")})]}):s?(0,m.jsxs)("div",{className:"act-panel-error",children:[c?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("p",{style:{marginBottom:"8px"},children:(0,m.jsx)("strong",{children:(0,u.__)("This step expects a specific block type:","admin-coach-tours")})}),(0,m.jsx)("p",{style:{marginBottom:"12px",color:"#1e1e1e"},children:(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ -(0,u.__)("Please insert or select a %s block first, then try again.","admin-coach-tours"),c)})]}):(0,m.jsx)("p",{style:{marginBottom:"12px"},children:(0,u.__)("Could not find the target element. The UI may have changed.","admin-coach-tours")}),(0,m.jsx)(d.Button,{variant:"secondary",onClick:p,children:(0,u.__)("Try Again","admin-coach-tours")})]}):(0,m.jsx)(m.Fragment,{children:e?.content&&(0,m.jsx)("div",{className:"act-panel-content",dangerouslySetInnerHTML:{__html:e.content}})})}),(0,m.jsx)("div",{className:"act-panel-footer",style:{padding:"14px 20px",borderTop:"1px solid #e0e0e0",background:"#fafafa",borderRadius:"0 0 12px 12px"},children:(0,m.jsx)("div",{className:"act-panel-controls",children:(0,m.jsx)(d.Flex,{justify:"flex-end",align:"center",children:(0,m.jsx)(d.FlexItem,{children:v||R?(0,m.jsx)(d.Button,{variant:"primary",onClick:a,disabled:i||!!s,size:"small",children:R?(0,u.__)("Finish","admin-coach-tours"):(0,u.__)("Continue","admin-coach-tours")}):(0,m.jsx)(d.Button,{icon:h,label:(0,u.__)("Next","admin-coach-tours"),onClick:f,disabled:i||!!s,size:"small"})})})})})]})}const f={boxShadow:"0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5)",borderRadius:"4px",transition:"all 0.3s ease"};class E{constructor(e={}){this.options={usePulse:!0,overlayColor:"rgba(0, 0, 0, 0.5)",highlightColor:"#007cba",transitionDuration:300,...e},this.spotlightElement=null,this.targetElement=null,this.resizeObserver=null,this.styleElement=null,this._isAnimating=!1,this._init()}_init(){this.styleElement=document.createElement("style"),this.styleElement.textContent="\n@keyframes act-pulse {\n\t0% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t50% {\n\t\tbox-shadow: 0 0 0 8px rgba(0, 124, 186, 0.5), 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t100% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n}\n",document.head.appendChild(this.styleElement),this.spotlightElement=document.createElement("div"),this.spotlightElement.className="act-highlighter-spotlight",this.spotlightElement.setAttribute("aria-hidden","true"),Object.assign(this.spotlightElement.style,{position:"fixed",pointerEvents:"none",zIndex:9999980,...f}),this.spotlightElement.style.display="none",document.body.appendChild(this.spotlightElement),this.resizeObserver=new ResizeObserver(()=>{this._updatePosition()})}highlight(e){e&&e.isConnected?this.targetElement!==e&&(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,this.resizeObserver.observe(e),this._updatePosition(),this.spotlightElement.style.display="block",this.options.usePulse&&!this._isAnimating&&(this.spotlightElement.style.animation="act-pulse 2s infinite",this._isAnimating=!0),this._addScrollListeners()):this.clear()}_updatePosition(){if(!this.targetElement||!this.spotlightElement)return;const e=this.targetElement.getBoundingClientRect();let t={top:0,left:0};const n=this.targetElement.ownerDocument;if(n!==document){const e=document.querySelectorAll("iframe");for(const r of e)if(r.contentDocument===n){const e=r.getBoundingClientRect();t={top:e.top,left:e.left};break}}Object.assign(this.spotlightElement.style,{top:e.top+t.top-4+"px",left:e.left+t.left-4+"px",width:`${e.width+8}px`,height:`${e.height+8}px`})}_addScrollListeners(){this._scrollHandler=()=>{this._updatePosition()},this._scrollWindows=[];let e=this.targetElement?.parentElement;for(;e;)e.scrollHeight>e.clientHeight&&e.addEventListener("scroll",this._scrollHandler,{passive:!0}),e=e.parentElement;const t=this.targetElement?.ownerDocument,n=t?.defaultView;n&&n!==window&&(n.addEventListener("scroll",this._scrollHandler,{passive:!0}),n.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(n)),window.addEventListener("scroll",this._scrollHandler,{passive:!0}),window.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(window)}_removeScrollListeners(){if(!this._scrollHandler)return;let e=this.targetElement?.parentElement;for(;e;)e.removeEventListener("scroll",this._scrollHandler),e=e.parentElement;if(this._scrollWindows){for(const e of this._scrollWindows)try{e.removeEventListener("scroll",this._scrollHandler),e.removeEventListener("resize",this._scrollHandler)}catch(e){}this._scrollWindows=[]}this._scrollHandler=null}clear(){this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners(),this.targetElement=null),this.spotlightElement&&(this.spotlightElement.style.display="none",this.spotlightElement.style.animation="none",this._isAnimating=!1)}async transitionTo(e){this.targetElement?(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,e&&e.isConnected?(this.resizeObserver.observe(e),this._addScrollListeners(),await new Promise(e=>{requestAnimationFrame(()=>{this._updatePosition(),setTimeout(e,this.options.transitionDuration)})})):this.clear()):this.highlight(e)}setStyle(e){if(!this.spotlightElement)return;const t={default:"#007cba",success:"#00a32a",warning:"#dba617",error:"#d63638"},n=t[e]||t.default;this.spotlightElement.style.boxShadow=`0 0 0 4px ${n}, 0 0 0 9999px rgba(0, 0, 0, 0.5)`}async flash(){this.spotlightElement&&(this.setStyle("success"),await new Promise(e=>{setTimeout(()=>{this.setStyle("default"),e()},300)}))}destroy(){this.clear(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.spotlightElement&&this.spotlightElement.parentNode&&(this.spotlightElement.parentNode.removeChild(this.spotlightElement),this.spotlightElement=null),this.styleElement&&this.styleElement.parentNode&&(this.styleElement.parentNode.removeChild(this.styleElement),this.styleElement=null)}}function _(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function y(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const s=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",s),s}function S(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function b(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function A(e,t=document){switch(e.type){case"css":return b(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),s={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let c=[];s[n]&&(c=Array.from(t.querySelectorAll(s[n])));const i=[...o,...c];return r?i.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):i}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return b(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let s=null;if("first"===e)s=o[0]?.clientId;else if("last"===e)s=o[o.length-1]?.clientId;else if("selected"===e)s=r.getSelectedBlockClientId();else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,c=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",c.length),s=c[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);s=o[t]?.clientId}if(!s)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",s);const c=t.querySelector(`[data-block="${s}"]`);return c?(console.log("[ACT findByWpBlock] Found element:",c.tagName),[c]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function C(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&S(e,n.withinContainer)&&(r+=10),y(e)&&(r+=100),_(e)&&(r+=5),r}function k(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),s=o.filter(e=>!e.fallback),c=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",s.length,"primary +",c.length,"fallback locators");for(const e of[...s,...c]){let n=A(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(_),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(y),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>S(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:C(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}async function I(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{const s=document.querySelector(e);s?r(s):Date.now()-n>t?r(null):requestAnimationFrame(o)};o()})}async function w(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{try{if(e())return void r(!0)}catch{}Date.now()-n>t?r(!1):requestAnimationFrame(o)};o()})}const R=new Map;window.__actInsertedBlocks=R;const v=new Map;let x=0;function O(){R.clear(),v.clear(),x=0,delete window.__actLastAppearedBlockClientId,console.log("[ACT clearInsertedBlocks] Cleared all tracking")}function P(e,t,n){v.has(e)||v.set(e,{}),v.get(e)[t]=n}async function N(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlockElement] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]',"input:not([type])"];let s=null;for(const e of o)if(s=r.querySelector(e),s)break;if(s||(s=r),s.scrollIntoView({behavior:"smooth",block:"center"}),s.focus(),"true"===s.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(s),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlockElement] Focused:",s.tagName,s.className),!0}const L={ensureEditor:async function(){const e=(0,a.select)("core/block-editor");return!!e&&w(()=>{const t=e.getBlocks?.();return void 0!==t})},ensureSidebarOpen:async function(e=null){const t=(0,a.select)("core/edit-post"),n=(0,a.dispatch)("core/edit-post");if(!t||!n){const t=(0,a.select)("core/interface"),n=(0,a.dispatch)("core/interface");if(t&&n){const r=t.getActiveComplementaryArea?.("core/edit-post");return r||await(n.enableComplementaryArea?.("core/edit-post",e||"edit-post/document")),w(()=>!!t.getActiveComplementaryArea?.("core/edit-post"))}return!1}if(t.isEditorSidebarOpened?.()||t.isPluginSidebarOpened?.())return!0;try{return e?await(n.openGeneralSidebar?.(e)):await(n.openGeneralSidebar?.("edit-post/document")),w(()=>t.isEditorSidebarOpened?.())}catch{return!1}},ensureSidebarClosed:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e||!t){const e=(0,a.select)("core/interface"),t=(0,a.dispatch)("core/interface");if(e&&t){const n=e.getActiveComplementaryArea?.("core/edit-post");return n&&await(t.disableComplementaryArea?.("core/edit-post")),w(()=>!e.getActiveComplementaryArea?.("core/edit-post"))}return!1}const n=e.isEditorSidebarOpened?.();if(!n)return!0;try{return await(t.closeGeneralSidebar?.()),w(()=>!e.isEditorSidebarOpened?.())}catch{return!1}},selectSidebarTab:async function(e){const t=(0,a.dispatch)("core/edit-post");if(!t)return!1;const n={document:"edit-post/document",post:"edit-post/document",block:"edit-post/block"}[e]||e;try{return await(t.openGeneralSidebar?.(n)),w(()=>{const e=(0,a.select)("core/edit-post");return e?.getActiveGeneralSidebarName?.()===n})}catch{return!1}},openInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!0)),w(()=>e?.isInserterOpened?.())}catch{const e=document.querySelector('.edit-post-header-toolbar__inserter-toggle, button[aria-label*="inserter"], button[aria-label*="Add block"]');return!!e&&(e.click(),I(".block-editor-inserter__content, .editor-inserter__content").then(e=>!!e))}},closeInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!1)),w(()=>!e?.isInserterOpened?.())}catch{return!1}},selectBlock:async function(e){const t=(0,a.dispatch)("core/block-editor");if(!t||!e)return!1;try{return await(t.selectBlock?.(e)),w(()=>{const t=(0,a.select)("core/block-editor");return t?.getSelectedBlockClientId?.()===e})}catch{return!1}},focusElement:async function(e){const t=await I(e);if(!t)return!1;try{return t.focus(),document.activeElement===t}catch{return!1}},scrollIntoView:async function(e){const t=await I(e);if(!t)return!1;try{return t.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),await new Promise(e=>setTimeout(e,300)),!0}catch{return!1}},openModal:async function(e,t){if(document.querySelector(t))return!0;const n=await I(e);return!!n&&(n.click(),I(t).then(e=>!!e))},closeModal:async function(e){const t=document.querySelector(e);if(!t)return!0;const n=t.querySelector('button[aria-label="Close"], .components-modal__header button, .components-popover__close');return n?(n.click(),w(()=>!document.querySelector(e))):(document.dispatchEvent(new KeyboardEvent("keydown",{key:"Escape",code:"Escape",bubbles:!0})),w(()=>!document.querySelector(e)))},insertBlock:async function(e="core/paragraph",t={},n="act-inserted-block"){console.log("[ACT insertBlock] Called with:",{blockName:e,markerId:n,currentStepIndex:x}),console.log("[ACT insertBlock] insertedBlocks map:",Array.from(R.entries())),console.log("[ACT insertBlock] insertedBlocksByStep:",Array.from(v.entries()));try{const{createBlock:r}=await Promise.resolve().then(o.t.bind(o,997,23)),s=(0,a.dispatch)("core/block-editor"),c=(0,a.select)("core/block-editor");if(!s||!r)return console.warn("[ACT insertBlock] Block editor not available"),!1;const i=function(e,t){const n=v.get(e);return n&&n[t]?n[t]:null}(x,n);if(console.log("[ACT insertBlock] stepClientId from getStepInsertedBlock:",i),i){const e=c.getBlock(i);if(console.log("[ACT insertBlock] existingBlock from store:",e?.name),e)return await s.selectBlock(i),await N(i),console.log("[ACT insertBlock] Reusing step block:",i,"for step:",x),!0;const t=document.querySelector('iframe[name="editor-canvas"]');if((t?.contentDocument||document).querySelector(`[data-block="${i}"]`))return await s.selectBlock(i),await N(i),console.log("[ACT insertBlock] Block still in DOM, reusing:",i),!0;console.log("[ACT insertBlock] Block not in store or DOM, checking for any existing blocks of this type")}if(console.log("[ACT insertBlock] Checking global map for:",n,"has:",R.has(n)),R.has(n)){const e=R.get(n),t=c.getBlock(e);if(console.log("[ACT insertBlock] existingBlock from global map:",t?.name),t)return await s.selectBlock(e),await N(e),P(x,n,e),console.log("[ACT insertBlock] Reusing existing block:",e),!0;const r=document.querySelector('iframe[name="editor-canvas"]');if((r?.contentDocument||document).querySelector(`[data-block="${e}"]`))return await s.selectBlock(e),await N(e),P(x,n,e),console.log("[ACT insertBlock] Block from global map still in DOM:",e),!0}const l=c.getBlocks()||[];console.log("[ACT insertBlock] Checking for existing blocks of type:",e,"found:",l.length,"total blocks"),console.log("[ACT insertBlock] Block names in editor:",l.map(e=>e.name));const u=l.find(t=>t.name===e);if(u)return console.log("[ACT insertBlock] Found existing block of same type:",u.clientId),await s.selectBlock(u.clientId),await N(u.clientId),R.set(n,u.clientId),P(x,n,u.clientId),console.log("[ACT insertBlock] Reusing existing block of type:",e),!0;if(l.length>0){const t=l[l.length-1];console.log("[ACT insertBlock] Considering last block:",t.name,t.clientId);const r=["core/paragraph","core/heading","core/list","core/quote"],o=r.includes(e),c=r.includes(t.name);if(o&&c)return console.log("[ACT insertBlock] Reusing last text block instead of creating new:",t.clientId),await s.selectBlock(t.clientId),await N(t.clientId),R.set(n,t.clientId),P(x,n,t.clientId),!0}console.log("[ACT insertBlock] No existing block found, creating new one");const d={...t,metadata:{...t.metadata||{},actMarkerId:n}},p=r(e,d),m="";await s.insertBlock(p,void 0,m,!0),R.set(n,p.clientId),P(x,n,p.clientId);const h=await w(()=>{const e=document.querySelector('iframe[name="editor-canvas"]'),t=e?.contentDocument;return!!(t||document).querySelector(`[data-block="${p.clientId}"]`)},3e3);return h&&(await s.selectBlock(p.clientId),await N(p.clientId)),console.log("[ACT insertBlock] Inserted block:",p.clientId,"markerId:",n,"step:",x,"success:",h),h}catch(e){return console.error("[ACT insertBlock] Error:",e),!1}}};async function B(e){const{type:t,params:n={}}=e,r=L[t];if(!r)return{success:!1,error:`Unknown precondition type: ${t}`,type:t};try{let e;switch(t){case"ensureEditor":case"ensureSidebarClosed":case"openInserter":case"closeInserter":e=await r();break;case"ensureSidebarOpen":e=await r(n.sidebar);break;case"selectSidebarTab":e=await r(n.tab);break;case"selectBlock":e=await r(n.clientId);break;case"focusElement":case"scrollIntoView":e=await r(n.selector);break;case"openModal":e=await r(n.trigger,n.modal);break;case"closeModal":e=await r(n.modal);break;case"insertBlock":e=await r(n.blockName||"core/paragraph",n.attributes||{},n.markerId||"act-inserted-block");break;default:e=!1}return{success:e,type:t,error:e?null:`Precondition failed: ${t}`}}catch(e){return{success:!1,type:t,error:`Precondition error: ${e.message}`}}}async function D(e){if(!e||0===e.length)return{success:!0,results:[]};const t=[];let n=!0;for(const r of e){const e=await B(r);t.push(e),e.success||(n=!1)}return{success:n,results:t,failedPreconditions:t.filter(e=>!e.success)}}function j(e,t=0){return new Promise(n=>{let r=null,o=null,s=!1;const c=(e,t=!1)=>{s||(s=!0,r&&clearTimeout(r),o&&clearInterval(o),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{c(!1,!0)},t));const i=()=>{try{e()&&c(!0)}catch{}};i(),s||(o=setInterval(i,100))})}function U(e,t={}){return new Promise(n=>{let r=!1,o=!1;const{timeout:s=0,gracePeriod:c=300}=t;let i=null;const l=()=>{e.removeEventListener("click",a,!0),e.ownerDocument!==document&&e.ownerDocument.removeEventListener("click",a,!0),i&&clearTimeout(i)},a=t=>{r||(o?(e===t.target||e.contains(t.target))&&(console.log("[ACT watchClickTarget] Click detected on target:",e.tagName),r=!0,l(),n({success:!0,event:"click"})):console.log("[ACT watchClickTarget] Ignoring click during grace period"))};e.addEventListener("click",a,{capture:!0}),e.ownerDocument!==document&&e.ownerDocument.addEventListener("click",a,{capture:!0}),setTimeout(()=>{o=!0,console.log("[ACT watchClickTarget] Armed after grace period, watching:",e.tagName,e.className)},c),s>0&&(i=setTimeout(()=>{r||(r=!0,l(),n({success:!1,timedOut:!0}))},s))})}function F(e,t={}){return new Promise(n=>{let r=!1;const{timeout:o=0,expectedValue:s,attributeName:c=null}=t;let i=null,l=null;const a=()=>{l&&l.disconnect(),e.removeEventListener("input",m),e.removeEventListener("change",h),i&&clearTimeout(i)},u=()=>c?e.getAttribute(c):"value"in e?e.value:e.isContentEditable?e.textContent:"checkbox"===e.type||"radio"===e.type?e.checked:e.textContent,d=u(),p=()=>{r||(()=>{const e=u();return void 0!==s?e===s:e!==d})()&&(r=!0,a(),n({success:!0,event:"valueChanged"}))},m=()=>p(),h=()=>p();e.addEventListener("input",m),e.addEventListener("change",h),l=new MutationObserver(()=>p()),l.observe(e,{attributes:!0,characterData:!0,subtree:!0,childList:!0}),o>0&&(i=setTimeout(()=>{r||(r=!0,a(),n({success:!1,timedOut:!0}))},o))})}function q(e={}){const{storeName:t,selector:n,args:r=[],expectedValue:o,comparator:s="equals",timeout:c=0}=e;return new Promise(e=>{let i=!1,l=null,u=null;const d=()=>{u&&u(),l&&clearTimeout(l)},p=(0,a.select)(t);if(!p||!p[n])return void e({success:!1,error:`Invalid store or selector: ${t}.${n}`});const m=()=>{i||(()=>{try{const e=p[n](...r);switch(s){case"equals":default:return e===o;case"notEquals":return e!==o;case"truthy":return!!e;case"falsy":return!e;case"contains":return(Array.isArray(e)||"string"==typeof e)&&e.includes(o);case"greaterThan":return e>o;case"lessThan":return eo}}catch{return!1}})()&&(i=!0,d(),e({success:!0,event:"wpDataChanged"}))};m(),i||(u=(0,a.subscribe)(()=>{m()})),c>0&&(l=setTimeout(()=>{i||(i=!0,d(),e({success:!1,timedOut:!0}))},c))})}function M(e={}){const{timeout:t=0}=e;let n=null,r=null,o=!1;return{promise:new Promise(e=>{n=e,t>0&&(r=setTimeout(()=>{o||(o=!0,e({success:!1,timedOut:!0}))},t))}),confirm:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!0,event:"manual"}))},cancel:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!1,cancelled:!0}))}}}function V(e,t={}){const{timeout:n=0}=t,r=()=>{const t=new Set;document.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)});const n=document.querySelector('iframe[name="editor-canvas"]');return n?.contentDocument?.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)}),t},o=r();return console.log("[ACT watchElementAppear] Existing matches:",o.size,"for selector:",e),j(()=>{const e=r();for(const t of e)if(!o.has(t))return window.__actNewlyAppearedBlockClientId=t,console.log("[ACT watchElementAppear] NEW element appeared:",t),!0;return!1},n).then(e=>(e.success&&window.__actNewlyAppearedBlockClientId&&(window.__actLastAppearedBlockClientId=window.__actNewlyAppearedBlockClientId,console.log("[ACT watchElementAppear] Tracked appeared block:",window.__actLastAppearedBlockClientId),delete window.__actNewlyAppearedBlockClientId),{...e,event:e.success?"elementAppeared":null}))}function G(e,t={}){const{timeout:n=0}=t;return j(()=>null===document.querySelector(e),n).then(e=>({...e,event:e.success?"elementDisappeared":null}))}function H(e,t={}){const{timeout:n=0,target:r=document}=t;return new Promise(t=>{let o=!1,s=null;const c=()=>{r.removeEventListener(e,i),s&&clearTimeout(s)},i=n=>{o||(o=!0,c(),t({success:!0,event:e,detail:n.detail}))};r.addEventListener(e,i,{once:!0}),n>0&&(s=setTimeout(()=>{o||(o=!0,c(),t({success:!1,timedOut:!0}))},n))})}function W(e){try{const t=(0,a.select)("core/block-editor");if(!t?.getBlocks)return!1;const n=t.getBlocks(),r=t=>{for(const n of t){if(n.name===e)return!0;if(n.innerBlocks?.length>0&&r(n.innerBlocks))return!0}return!1};return r(n)}catch(e){return console.warn("[ACT] Error checking for block type:",e),!1}}async function $(e,t,n=5e3){const r=t+1;if(r>=e.length)return{waited:!1};const o=function(e){if(!e?.target)return null;const{target:t}=e;if(t.constraints?.blockType)return t.constraints.blockType;const n=t.locators||[];for(const e of n){if("dataAttribute"===e.type&&"data-type"===e.attribute)return e.value;if("block"===e.type&&e.blockName)return e.blockName}for(const e of n)if("css"===e.type&&e.value){const t=e.value.match(/\[data-type=["']([^"']+)["']\]/);if(t)return t[1];const n=e.value.match(/\.wp-block-(\w+)/);if(n)return`core/${n[1]}`}return null}(e[r]);if(!o)return console.log("[ACT waitForNextStepBlock] Next step does not expect a specific block"),{waited:!1};if(console.log("[ACT waitForNextStepBlock] Next step expects block:",o),W(o))return console.log("[ACT waitForNextStepBlock] Block already exists"),{waited:!1,blockType:o};const s=await function(e,t=5e3){return new Promise(n=>{if(W(e))return console.log("[ACT waitForBlock] Block already exists:",e),void n({success:!0});console.log("[ACT waitForBlock] Waiting for block:",e);let r=null,o=null,s=!1;const c=(e,t=!1)=>{s||(s=!0,r&&clearTimeout(r),o&&o(),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{console.log("[ACT waitForBlock] Timeout waiting for:",e),c(!1,!0)},t)),o=(0,a.subscribe)(()=>{W(e)&&(console.log("[ACT waitForBlock] Block appeared:",e),c(!0))})})}(o,n);return{waited:!0,blockType:o,success:s.success,timedOut:s.timedOut}}const z="admin-coach-tours";function K(e){if(!Array.isArray(e))return null;for(const t of e){if("css"===t.type&&"string"==typeof t.value){const e=t.value.match(/data-type=["']?(core\/[\w-]+)["']?/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}if("wpBlock"===t.type&&"string"==typeof t.value){const e=t.value.match(/type:(core\/[\w-]+)/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}}return null}function X(){console.log("[ACT TourRunner] Component function called");const[e,t]=(0,l.useState)(null),[n,r]=(0,l.useState)(null),[o,s]=(0,l.useState)(null),[c,i]=(0,l.useState)(null),[d,p]=(0,l.useState)(!1),[h,g]=(0,l.useState)(0),f=(0,l.useRef)(null),_=(0,l.useRef)(null),y=(0,l.useRef)(null),{isPlaying:S,currentTour:b,currentStep:A,stepIndex:C,totalSteps:I}=(0,a.useSelect)(e=>{const t=e(z),n={isPlaying:t.isPupilMode(),currentTour:t.getCurrentTour(),currentStep:t.getCurrentStep(),stepIndex:t.getCurrentStepIndex()||0,totalSteps:t.getTotalSteps()||0};return console.log("[ACT TourRunner] useSelect:",n),n},[]),{stopTour:w,nextStep:R,previousStep:v,repeatStep:P,setAiTourError:N,setLastFailureContext:L}=(0,a.useDispatch)(z);(0,l.useEffect)(()=>(f.current||(f.current=new E),()=>{f.current&&(f.current.destroy(),f.current=null)}),[]),(0,l.useEffect)(()=>{if(!S||!A)return f.current&&f.current.clear(),t(null),void O();let e=!0,n=null;const o=_.current;return(async()=>{if(r(null),p(!0),y.current?.cancel&&(console.log("[ACT TourRunner] Cancelling previous completion watcher"),y.current.cancel(),y.current=null),null!==o&&o!==C&&await async function(e){console.log("[ACT onLeaveStep] Leaving step:",e);const t=(0,a.dispatch)("core/block-editor"),n=(0,a.select)("core/block-editor");if(!t||!n)return;const r=n.getSelectedBlockClientId?.();if(r)try{await(t.clearSelectedBlock?.()),console.log("[ACT onLeaveStep] Deselected block:",r)}catch(e){console.warn("[ACT onLeaveStep] Could not deselect block:",e)}}(o),await async function(e){var t;console.log("[ACT onEnterStep] Entering step:",e),x=t=e,console.log("[ACT setCurrentStepIndex]",t)}(C),_.current=C,console.log("[ACT TourRunner] Preconditions for step:",C,A.preconditions),A.preconditions?.length>0){console.log("[ACT TourRunner] Applying",A.preconditions.length,"preconditions");const t=await D(A.preconditions);if(console.log("[ACT TourRunner] Precondition result:",t),!e)return;t.success||console.warn("Some preconditions failed:",t.failedPreconditions)}else console.log("[ACT TourRunner] No preconditions for this step");if(p(!1),A.target){const o=A.recovery?async()=>{await D(A.recovery)}:null,l=await async function(e,t=null){let n=k(e);if(n.success)return n;if(t)try{if(await t(),await new Promise(e=>setTimeout(e,100)),n=k(e),n.success)return{...n,recovered:!0}}catch(e){return{success:!1,error:`Recovery failed: ${e.message}`}}return n}(A.target,o);if(!e)return;if(l.success){n=l.element,t(l.element),r(null),s(null),i({success:!0,usedLocator:l.usedLocator,recovered:l.recovered||!1});let e=l.element.getAttribute("data-block");if(!e){const t=l.element.closest("[data-block]");t&&(e=t.getAttribute("data-block"),console.log("[ACT TourRunner] Found parent block:",e))}if(e)try{const t=(0,a.dispatch)("core/block-editor");t?.selectBlock&&(await t.selectBlock(e),console.log("[ACT TourRunner] Selected block:",e))}catch(e){console.warn("[ACT TourRunner] Could not select block:",e)}(c=l.element)&&(c.ownerDocument!==document?(c.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),setTimeout(()=>{const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const t=c.getBoundingClientRect(),n=e.getBoundingClientRect(),r=n.top+t.top,o=n.top+t.bottom,s=window.innerHeight;if(!(r>=100&&o<=s-100)){const e=r+window.scrollY-s/2;window.scrollTo({top:Math.max(0,e),behavior:"smooth"})}}},150)):c.scrollIntoView({behavior:"smooth",block:"center",inline:"center"})),setTimeout(()=>{f.current&&l.element?.isConnected&&f.current.highlight(l.element)},350)}else if(n=null,t(null),r(l.error),s(K(A.target?.locators)),i({success:!1,error:l.error}),console.log("[ACT TourRunner] Target resolution failed, NOT auto-advancing. Error:",l.error),f.current&&f.current.clear(),h>0){console.log("[ACT TourRunner] Retry also failed. Failing the entire tour."),L({stepIndex:C,stepId:A.id,stepTitle:A.title,targetLocators:A.target?.locators||[],error:l.error,reason:"Step retry failed - target element could not be found after second attempt"}),O(),_.current=null,w();const e=K(A.target?.locators),t=e?(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ -(0,u.__)("Could not find the %s block. Make sure to insert or select the correct block, then try again.","admin-coach-tours"),e):(0,u.__)("The generated tour could not complete. Try again to get a fresh set of instructions.","admin-coach-tours");N(t)}}var c;if(A.completion&&n){if(console.log("[ACT TourRunner] Setting up completion watcher for step:",C,"type:",A.completion.type,"params:",A.completion.params),await new Promise(e=>setTimeout(e,100)),!e)return;const t=function(e,t=null){if(!e||!e.type)return M();const{type:n,params:r={}}=e,o=e.timeout||0;switch(n){case"clickTarget":return t?{promise:U(t,{timeout:o}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for clickTarget"}),cancel:()=>{}};case"domValueChanged":return t?{promise:F(t,{timeout:o,...r}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for domValueChanged"}),cancel:()=>{}};case"wpData":return{promise:q({timeout:o,...r}),cancel:()=>{}};case"manual":default:return M({timeout:o});case"elementAppear":return{promise:V(r.selector,{timeout:o}),cancel:()=>{}};case"elementDisappear":return{promise:G(r.selector,{timeout:o}),cancel:()=>{}};case"customEvent":return{promise:H(r.eventName,{timeout:o}),cancel:()=>{}}}}(A.completion,n);y.current=t,t.promise.then(async t=>{if(e&&t.success)if(console.log("[ACT TourRunner] Completion detected for step:",C),C{e&&(console.log("[ACT TourRunner] Auto-advancing from step:",C),R())},300)}else console.log("[ACT TourRunner] Last step completed, ending tour"),O(),R()})}})(),()=>{e=!1,y.current?.cancel&&(console.log("[ACT TourRunner] Cleanup: Cancelling completion watcher"),y.current.cancel(),y.current=null)}},[S,A,C,h,w,N]);const B=(0,l.useCallback)(async()=>{if(y.current?.confirm)y.current.confirm();else{if(C{y.current?.cancel&&(y.current.cancel(),y.current=null),g(e=>e+1),P()},[P]),W=(0,l.useCallback)(()=>{y.current?.cancel&&(y.current.cancel(),y.current=null),O(),_.current=null,w()},[w]);if(!S||!b||!A)return null;const X=(0,m.jsx)(T,{step:A,stepIndex:C,totalSteps:I,tourTitle:b.title,targetElement:e,resolutionError:n,expectedBlockType:o,isApplyingPreconditions:d,onContinue:B,onRepeat:j,onPrevious:v,onNext:R,onStop:W});return(0,l.createPortal)(X,document.body)}const Y=window.wp.apiFetch;var Q=o.n(Y);const J="admin-coach-tours",Z={media:"🖼️",content:"📝",layout:"📐",formatting:"✨",default:"📚"};function ee(){const[e,t]=(0,l.useState)(!1),[n,r]=(0,l.useState)([]),[o,s]=(0,l.useState)(!1),[c,i]=(0,l.useState)(null),[p,h]=(0,l.useState)(""),[g,T]=(0,l.useState)("tasks"),[f,E]=(0,l.useState)(!1),[_,y]=(0,l.useState)(null),S=(0,l.useRef)(null),{storeLoading:b,aiTourError:A,isPlaying:C,aiAvailable:k,lastFailureContext:I}=(0,a.useSelect)(e=>{const t=e(J);return{storeLoading:t.isAiTourLoading?.()??!1,aiTourError:t.getAiTourError?.()??null,isPlaying:null!==t.getCurrentTour(),aiAvailable:window.adminCoachTours?.aiAvailable??!1,lastFailureContext:t.getLastFailureContext?.()??null}},[]),w=f||b;(0,l.useEffect)(()=>{C&&f&&E(!1)},[C,f]),(0,l.useEffect)(()=>{!A||C||e||t(!0)},[A,C,e]);const{requestAiTour:R,clearEphemeralTour:v,setAiTourError:x,setLastFailureContext:O}=(0,a.useDispatch)(J);(0,l.useEffect)(()=>{e&&0===n.length&&!o&&(s(!0),i(null),Q()({path:"/admin-coach-tours/v1/ai/tasks"}).then(e=>{e.available&&e.tasks?r(e.tasks):i((0,u.__)("AI is not available.","admin-coach-tours"))}).catch(e=>{i(e.message||(0,u.__)("Failed to load tasks.","admin-coach-tours"))}).finally(()=>{s(!1)}))},[e,n.length,o]),(0,l.useEffect)(()=>{"chat"===g&&S.current&&S.current.focus()},[g]);const P=(0,l.useCallback)(e=>{E(!0),t(!1);const n=window.adminCoachTours?.postType||"post";y({type:"task",taskId:e,postType:n}),R(e,"",n)},[R]),N=(0,l.useCallback)(e=>{if(e.preventDefault(),!p.trim())return;E(!0),t(!1);const n=window.adminCoachTours?.postType||"post",r=p.trim();y({type:"chat",query:r,postType:n}),R("",r,n),h("")},[p,R]),L=(0,l.useCallback)(()=>{_&&(x(null),E(!0),t(!1),"task"===_.type?R(_.taskId,"",_.postType,I):"chat"===_.type&&R("",_.query,_.postType,I),O(null))},[_,I,R,x,O]),B=(0,l.useCallback)(()=>{x(null),y(null),O(null),E(!1)},[x,O]),D=(0,l.useCallback)(()=>{i(null),r([])},[]),j=(0,l.useCallback)(()=>{t(e=>!e)},[]),U=(0,l.useCallback)(()=>{t(!1)},[]);if(!k)return(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:j,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),children:(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("AI Coach","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:U,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsx)("div",{className:"act-pupil-launcher__content",children:(0,m.jsxs)("div",{className:"act-pupil-launcher__not-configured",children:[(0,m.jsx)("p",{children:(0,u.__)("AI is not configured yet.","admin-coach-tours")}),(0,m.jsxs)("p",{children:[(0,u.__)("Go to ","admin-coach-tours"),(0,m.jsx)("a",{href:"/wp-admin/tools.php?page=admin-coach-tours",children:(0,u.__)("Tools → Coach Tours","admin-coach-tours")}),(0,u.__)(" to add your API key.","admin-coach-tours")]})]})})]})]});const F=n.reduce((e,t)=>{const n=t.category||"other";return e[n]||(e[n]=[]),e[n].push(t),e},{}),q=w&&(0,l.createPortal)((0,m.jsx)("div",{className:"act-ai-loading-overlay",children:(0,m.jsxs)("div",{className:"act-ai-loading-overlay__content",children:[(0,m.jsx)("div",{className:"act-ai-loading-overlay__spinner"}),(0,m.jsx)("h2",{className:"act-ai-loading-overlay__title",children:(0,u.__)("Creating your guided tour...","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__text",children:(0,u.__)("AI is analyzing the editor and generating step-by-step instructions.","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__hint",children:(0,u.__)("This usually takes a few seconds.","admin-coach-tours")})]})}),document.body);return C?q||null:(0,m.jsxs)(m.Fragment,{children:[q,(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:j,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),disabled:w,children:w?(0,m.jsx)("span",{className:"act-pupil-launcher__spinner"}):(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[w&&(0,m.jsxs)("div",{className:"act-pupil-launcher__ai-loading",children:[(0,m.jsx)("div",{className:"act-pupil-launcher__ai-loading-spinner"}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-text",children:(0,u.__)("Creating your tour...","admin-coach-tours")}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-hint",children:(0,u.__)("AI is generating step-by-step instructions","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("What would you like to learn?","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:U,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__tabs",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("tasks"===g?"is-active":""),onClick:()=>T("tasks"),children:(0,u.__)("Common Tasks","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("chat"===g?"is-active":""),onClick:()=>T("chat"),children:(0,u.__)("Ask a Question","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__content",children:[A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:A}),(0,m.jsx)("p",{className:"act-pupil-launcher__error-hint",children:(0,u.__)("This might be a temporary issue.","admin-coach-tours")}),(0,m.jsxs)("div",{className:"act-pupil-launcher__error-actions",children:[_&&(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:L,disabled:w,children:(0,u.__)("Try Again","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__dismiss-btn",onClick:B,children:(0,u.__)("Dismiss","admin-coach-tours")})]})]}),c&&!A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:c}),(0,m.jsx)("div",{className:"act-pupil-launcher__error-actions",children:(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:D,children:(0,u.__)("Try Again","admin-coach-tours")})})]}),"tasks"===g&&!c&&(0,m.jsxs)("div",{className:"act-pupil-launcher__tasks",children:[o&&(0,m.jsx)("div",{className:"act-pupil-launcher__loading",children:(0,u.__)("Loading tasks...","admin-coach-tours")}),!o&&Object.entries(F).map(([e,t])=>(0,m.jsxs)("div",{className:"act-pupil-launcher__category",children:[(0,m.jsxs)("h4",{children:[(0,m.jsx)("span",{className:"act-pupil-launcher__category-icon",children:Z[e]||Z.default}),e.charAt(0).toUpperCase()+e.slice(1)]}),(0,m.jsx)("div",{className:"act-pupil-launcher__task-list",children:t.map(e=>(0,m.jsxs)("button",{className:"act-pupil-launcher__task",onClick:()=>P(e.id),disabled:w,children:[(0,m.jsx)("span",{className:"act-pupil-launcher__task-icon",children:e.icon?(0,m.jsx)(d.Dashicon,{icon:e.icon}):"📌"}),(0,m.jsx)("span",{className:"act-pupil-launcher__task-label",children:e.label})]},e.id))})]},e))]}),"chat"===g&&(0,m.jsxs)("div",{className:"act-pupil-launcher__chat",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__chat-hint",children:(0,u.__)("Ask about the WordPress editor:","admin-coach-tours")}),(0,m.jsxs)("form",{onSubmit:N,children:[(0,m.jsx)("input",{ref:S,type:"text",className:"act-pupil-launcher__chat-input",value:p,onChange:e=>h(e.target.value),placeholder:(0,u.__)("e.g., How do I add a gallery?","admin-coach-tours"),disabled:w}),(0,m.jsx)("button",{type:"submit",className:"act-pupil-launcher__chat-submit",disabled:w||!p.trim(),children:w?(0,u.__)("Generating...","admin-coach-tours"):(0,u.__)("Get Tour","admin-coach-tours")})]}),(0,m.jsx)("p",{className:"act-pupil-launcher__chat-note",children:(0,u.__)("AI will create a step-by-step tour to guide you.","admin-coach-tours")})]})]})]})]})]})}const te={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},ne={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},re=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function oe(e){return{type:ne.SET_TOURS_LOADING,isLoading:e}}function se(e){return{type:ne.SET_TOURS_ERROR,error:e}}function ce(e){return{type:ne.RECEIVE_TOURS,tours:e}}function ie(e){return{type:ne.RECEIVE_TOUR,tour:e}}function*le(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield ie(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield ie({id:e,title:"",steps:[],status:"draft"})}yield{type:ne.SET_CURRENT_TOUR,tourId:e}}else yield{type:ne.SET_CURRENT_TOUR,tourId:null}}function ae(e={}){return{type:"FETCH_TOURS",args:e}}function ue(e){return{type:"FETCH_TOUR",tourId:e}}function*de(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield ie(n)),n}catch(e){throw e}}function*pe(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield ie(t)),t}catch(e){throw e}}function*me(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield ie(n)),n}catch(e){throw e}}function he(e,t="pupil"){return{type:ne.START_TOUR,tourId:e,mode:t}}function ge(){return{type:ne.END_TOUR}}function Te(){return ge()}function fe(e){return{type:ne.SET_CURRENT_STEP,stepIndex:e}}function Ee(){return{type:ne.NEXT_STEP}}function _e(){return{type:ne.PREVIOUS_STEP}}function ye(){return{type:ne.SKIP_STEP}}function Se(){return{type:ne.REPEAT_STEP}}function be(e){return{type:ne.SET_MODE,mode:e}}function Ae(e){return{type:ne.SET_COMPLETION_SATISFIED,satisfied:e}}function Ce(){return{type:ne.RESET_COMPLETION}}function ke(){return Ee()}function Ie(e){return{type:ne.SET_RESOLVED_TARGET,target:e}}function we(){return{type:ne.CLEAR_RESOLVED_TARGET}}function Re(e){return{type:ne.SET_RECOVERING,isRecovering:e}}function ve(){return{type:ne.INCREMENT_RESOLUTION_ATTEMPTS}}function xe(e){return{type:ne.SET_LAST_ERROR,error:e}}function Oe(e=null){return{type:ne.ACTIVATE_PICKER,stepId:e}}function Pe(e=null){return Oe(e)}function Ne(){return{type:ne.DEACTIVATE_PICKER}}function Le(){return Ne()}function Be(e){return{type:ne.SELECT_STEP,stepId:e}}function De(e){return{type:ne.SET_PENDING_CHANGES,pending:e}}function*je(e,t,n){yield{type:ne.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*Ue(e,t={},n=null){const r={id:re(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:ne.ADD_STEP,tourId:e,step:r,index:n}}function*Fe(e,t){yield{type:ne.DELETE_STEP,tourId:e,stepId:t}}function*qe(e,t){yield{type:ne.REORDER_STEPS,tourId:e,stepIds:t}}function Me(e){return{type:ne.SET_AI_DRAFT_LOADING,isLoading:e}}function Ve(e){return{type:ne.SET_AI_DRAFT_ERROR,error:e}}function Ge(e){return{type:ne.SET_AI_DRAFT_RESULT,result:e}}function He(){return{type:ne.CLEAR_AI_DRAFT}}function*We(e,t){yield Me(!0),yield Ve(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield Ge(n),n}catch(e){throw yield Ve(e.message||"Failed to generate AI draft"),e}finally{yield Me(!1)}}function $e(e){return{type:ne.SET_SIDEBAR_OPEN,isOpen:e}}function ze(e){return{type:ne.SET_AI_TOUR_LOADING,isLoading:e}}function Ke(e){return{type:ne.SET_AI_TOUR_ERROR,error:e}}function Xe(e){return{type:ne.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function Ye(e){return{type:ne.RECEIVE_EPHEMERAL_TOUR,tour:e}}function Qe(){return{type:ne.CLEAR_EPHEMERAL_TOUR}}function*Je(e,t,n,r=null){yield ze(!0),yield Ke(null);try{const o=yield{type:"GATHER_EDITOR_CONTEXT"},s=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:o,failureContext:r};console.log("[ACT AI Response] Full result:",s),console.log("[ACT AI Response] Tour:",JSON.stringify(s.tour,null,2));const c={id:"ephemeral",...s.tour};return yield Ye(c),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield he("ephemeral","pupil"),c}catch(e){throw yield Ke(e.message||"Failed to generate tour"),e}finally{yield ze(!1)}}function*Ze(e){const t={id:"ephemeral",...e};yield Ye(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield he("ephemeral","pupil")}function et(e){return e.tours}const tt=(0,a.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function nt(e,t){return e.tours[t]||null}function rt(e){return e.toursLoading}function ot(e){return e.toursError}const st=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),ct=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function it(e){return e.currentTourId}function lt(e){return e.currentTourId?e.tours[e.currentTourId]:null}function at(e){return e.currentStepIndex}const ut=(0,a.createSelector)(e=>{const t=lt(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function dt(e){const t=lt(e);return t?.steps?.length||0}function pt(e){return e.currentStepIndex0}function ht(e){const t=dt(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function gt(e){return e.mode}function Tt(e){return"educator"===e.mode}function ft(e){return"pupil"===e.mode}function Et(e){return null!==e.currentTourId&&null!==e.mode}function _t(e){return e.completionSatisfied}function yt(e){return e.skippedSteps}function St(e,t){return e.skippedSteps.includes(t)}function bt(e){return e.resolvedTarget}function At(e){return e.isRecovering}function Ct(e){return e.resolutionAttempts}function kt(e){return e.lastError}function It(e){return e.isPickerActive}function wt(e){return e.pickingStepId||null}function Rt(e){return e.selectedStepId}const vt=(0,a.createSelector)(e=>{const t=lt(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function xt(e){return e.pendingChanges}function Ot(e){return e.aiDraftLoading}function Pt(e){return Ot(e)}function Nt(e){return e.aiDraftError}function Lt(e){return e.aiDraftResult}function Bt(e){return Lt(e)}function Dt(e){return e.sidebarOpen}function jt(e){return e.aiTourLoading}function Ut(e){return e.aiTourError}function Ft(e){return e.lastFailureContext}function qt(e){return e.ephemeralTour}function Mt(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}function*Vt(){yield oe(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield ce(e)}catch(e){yield se(e.message||"Failed to fetch tours")}}function*Gt(e){yield oe(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield ie(t)}catch(e){yield se(e.message||"Failed to fetch tour")}}function*Ht(e){yield oe(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield ce(t)}catch(e){yield se(e.message||"Failed to fetch tours")}}function Wt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,a.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,a.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,a.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function $t(){try{const e=(0,a.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:zt(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function zt(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function Kt(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:Xt(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:Xt(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:Xt(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:Xt(o)});const s=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of s){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function Xt(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function Yt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function Qt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let s=null;for(const e of o)if(s=r.querySelector(e),s)break;if(s||(s=r),s.scrollIntoView({behavior:"smooth",block:"center"}),s.focus(),"true"===s.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(s),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",s.tagName,s.className),!0}const Jt={API_FETCH:e=>Q()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:$t(),visibleElements:Wt(),uiSamples:Kt(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,a.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&Yt(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,a.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&Yt(e.attributes?.content));return t&&(await(0,a.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await Qt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,a.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,a.select)("core/block-editor"),s=(r?.getBlocks()||[]).length;return await t.insertBlock(n,s,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await Qt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Q()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Q()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Q()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Q()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:window.adminCoachTours?.locale||""}}),FETCH_AI_TASKS:()=>Q()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},Zt="admin-coach-tours",en=(0,a.createReduxStore)(Zt,{reducer:function(e=te,t){switch(t.type){case ne.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case ne.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case ne.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case ne.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case ne.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case ne.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case ne.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case ne.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case ne.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case ne.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case ne.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case ne.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case ne.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case ne.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case ne.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case ne.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case ne.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case ne.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case ne.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case ne.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case ne.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case ne.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:s,selectors:c,resolvers:i,controls:Jt,initialState:te});(0,a.select)(Zt)||(0,a.register)(en);const tn="admin-coach-tours";function nn(){console.log("[ACT Pupil] Initializing... v4"),console.log("[ACT Pupil] TourRunner:",typeof X,X),console.log("[ACT Pupil] AI Available:",window.adminCoachTours?.aiAvailable);const e=document.createElement("div");e.id="admin-coach-tours-pupil",document.body.appendChild(e);try{(0,l.render)((0,m.jsx)(X,{}),e),console.log("[ACT Pupil] TourRunner rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering TourRunner:",e)}const t=document.createElement("div");t.id="admin-coach-tours-launcher",document.body.appendChild(t);try{(0,l.render)((0,m.jsx)(ee,{}),t),console.log("[ACT Pupil] PupilLauncher rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering PupilLauncher:",e)}const n=window.top||window,r=new URLSearchParams(n.location.search).get("act_tour");if(console.log("[ACT Pupil] URL search:",n.location.search),console.log("[ACT Pupil] act_tour param:",r),r){const e=parseInt(r,10);console.log("[ACT Pupil] Will fetch and start tour:",e);const t=(0,a.select)(tn).getTour(e);console.log("[ACT Pupil] Initial tour state:",t);const n=(0,a.subscribe)(()=>{const t=(0,a.select)(tn),r=t.getTour(e),o=t.isToursLoading();r&&!o&&(console.log("[ACT Pupil] Tour loaded:",r),console.log("[ACT Pupil] Tour steps:",r.steps,"count:",r.steps?.length),n(),(0,a.dispatch)(tn).startTour(e))});setTimeout(()=>{console.log("[ACT Pupil] Timeout reached, unsubscribing"),n()},1e4)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",nn):nn()})(); \ No newline at end of file +(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var c=r[e]={exports:{}};return n[e](c,c.exports,o),c.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var c=Object.create(null);o.r(c);var s={};e=e||[null,t({}),t([]),t(t)];for(var i=2&r&&n;("object"==typeof i||"function"==typeof i)&&!~e.indexOf(i);i=t(i))Object.getOwnPropertyNames(i).forEach(e=>s[e]=()=>n[e]);return s.default=()=>n,o.d(c,s),c},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var c={};o.r(c),o.d(c,{activatePicker:()=>xe,addStep:()=>je,clearAiDraft:()=>Ge,clearEphemeralTour:()=>Ye,clearResolvedTarget:()=>Ie,createTour:()=>de,deactivatePicker:()=>Le,deleteStep:()=>Ue,endTour:()=>he,fetchAiTasks:()=>Je,fetchTour:()=>ae,fetchTours:()=>le,incrementResolutionAttempts:()=>Re,markStepComplete:()=>ke,nextStep:()=>Te,previousStep:()=>Ee,receiveEphemeralTour:()=>Xe,receiveTour:()=>se,receiveTours:()=>ce,reorderSteps:()=>Fe,repeatStep:()=>ye,requestAiDraft:()=>He,requestAiTour:()=>Qe,resetCompletion:()=>Ae,saveTour:()=>ue,selectStep:()=>Be,setAiDraftError:()=>qe,setAiDraftLoading:()=>Me,setAiDraftResult:()=>Ve,setAiTourError:()=>ze,setAiTourLoading:()=>$e,setCompletionSatisfied:()=>be,setCurrentStep:()=>fe,setCurrentTour:()=>ie,setLastError:()=>ve,setLastFailureContext:()=>Ke,setMode:()=>Se,setPendingChanges:()=>Ne,setRecovering:()=>we,setResolvedTarget:()=>Ce,setSidebarOpen:()=>We,setToursError:()=>oe,setToursLoading:()=>re,skipStep:()=>_e,startEphemeralTour:()=>Ze,startPicking:()=>Oe,startTour:()=>me,stopPicking:()=>Pe,stopTour:()=>ge,updateStep:()=>De,updateTour:()=>pe});var s={};o.r(s),o.d(s,{getAiDraft:()=>Nt,getAiDraftError:()=>Pt,getAiDraftResult:()=>Bt,getAiTourError:()=>Ut,getCurrentStep:()=>ut,getCurrentStepIndex:()=>at,getCurrentTour:()=>lt,getCurrentTourId:()=>it,getEphemeralTour:()=>Mt,getLastError:()=>Ct,getLastFailureContext:()=>Ft,getMode:()=>gt,getPickingStepId:()=>wt,getProgress:()=>ht,getResolutionAttempts:()=>kt,getResolvedTarget:()=>bt,getSelectedStep:()=>vt,getSelectedStepId:()=>Rt,getSkippedSteps:()=>yt,getTotalSteps:()=>dt,getTour:()=>nt,getTours:()=>tt,getToursByEditor:()=>st,getToursById:()=>et,getToursByPostType:()=>ct,getToursError:()=>ot,hasNextStep:()=>pt,hasPendingChanges:()=>xt,hasPreviousStep:()=>mt,isAiDraftLoading:()=>Ot,isAiDrafting:()=>Lt,isAiTourLoading:()=>jt,isCompletionSatisfied:()=>_t,isEducatorMode:()=>ft,isEphemeralTourActive:()=>qt,isPickerActive:()=>It,isPupilMode:()=>Tt,isRecovering:()=>At,isSidebarOpen:()=>Dt,isTourActive:()=>Et,isToursLoading:()=>rt,wasStepSkipped:()=>St});var i={};o.r(i),o.d(i,{getTour:()=>Wt,getTours:()=>Ht,getToursByPostType:()=>$t});const l=window.wp.element,a=window.wp.data,u=window.wp.i18n,d=window.wp.components,p=window.wp.primitives,m=window.ReactJSXRuntime;var h=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})});function g({step:e,stepIndex:t,totalSteps:n,tourTitle:r,targetElement:o,resolutionError:c,expectedBlockType:s,isApplyingPreconditions:i,onContinue:a,onRepeat:p,onPrevious:g,onNext:f,onStop:T}){const[E,_]=(0,l.useState)({x:20,y:20}),[y,S]=(0,l.useState)(!1),[b,A]=(0,l.useState)({x:0,y:0}),[k,C]=(0,l.useState)(0),I=(0,l.useRef)(null),w=t===n-1,R="manual"===e?.completion?.type,v=(0,l.useMemo)(()=>{const t=(e?.content||"").replace(/<[^>]*>/g," ").match(/(?:^|\s)(\/[a-z][a-z0-9-]*)/i);return t?t[1]:null},[e?.content]),x=R||w;(0,l.useEffect)(()=>{if(!o||y)return;const e=()=>{if(!o?.isConnected)return;const e=o.getBoundingClientRect();let t={top:0,left:0};if(o.ownerDocument!==document){const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const n=e.getBoundingClientRect();t={top:n.top,left:n.left}}}const n={top:e.top+t.top,bottom:e.bottom+t.top,left:e.left+t.left,right:e.right+t.left},r=320,c=200,s=20;let i,l;n.right+r+s0?(i=n.left-r-s,l=n.top):n.bottom+c+s0?(i=Math.max(s,n.left),l=n.top-c-s):(i=window.innerWidth-r-s,l=window.innerHeight-c-s),i=Math.max(s,Math.min(i,window.innerWidth-r-s)),l=Math.max(s,Math.min(l,window.innerHeight-c-s)),_({x:i,y:l})};e();const t=()=>{y||e()};window.addEventListener("scroll",t,{passive:!0}),window.addEventListener("resize",t,{passive:!0});const n=o.ownerDocument,r=n?.defaultView;return r&&r!==window&&r.addEventListener("scroll",t,{passive:!0}),()=>{if(window.removeEventListener("scroll",t),window.removeEventListener("resize",t),r&&r!==window)try{r.removeEventListener("scroll",t)}catch(e){}}},[o,y,k]);const O=(0,l.useCallback)(e=>{if(e.target.closest("button"))return;S(!0);const t=I.current.getBoundingClientRect();A({x:e.clientX-t.left,y:e.clientY-t.top})},[]),L=(0,l.useCallback)(e=>{if(!y)return;const t=e.clientX-b.x,n=e.clientY-b.y,r=I.current.getBoundingClientRect(),o=window.innerWidth-r.width,c=window.innerHeight-r.height;_({x:Math.max(0,Math.min(t,o)),y:Math.max(0,Math.min(n,c))})},[y,b]),P=(0,l.useCallback)(()=>{S(!1)},[]);return(0,l.useEffect)(()=>{if(y)return document.addEventListener("mousemove",L),document.addEventListener("mouseup",P),()=>{document.removeEventListener("mousemove",L),document.removeEventListener("mouseup",P)}},[y,L,P]),(0,m.jsxs)("div",{ref:I,className:"act-coach-panel",style:{position:"fixed",top:E.y,left:E.x,width:"360px",maxHeight:"450px",backgroundColor:"#fff",borderRadius:"12px",boxShadow:"0 8px 32px rgba(0, 0, 0, 0.18)",zIndex:9999990,display:"flex",flexDirection:"column",cursor:y?"grabbing":"default"},onMouseDown:O,children:[(0,m.jsx)("div",{className:"act-panel-header",style:{padding:"16px 20px",borderBottom:"1px solid #e0e0e0",cursor:"grab",userSelect:"none",background:"linear-gradient(to bottom, #fafafa, #fff)",borderRadius:"12px 12px 0 0"},children:(0,m.jsxs)(d.Flex,{justify:"space-between",align:"flex-start",children:[(0,m.jsxs)(d.FlexBlock,{children:[(0,m.jsx)("strong",{className:"act-panel-title",style:{fontSize:"16px",lineHeight:"1.4",display:"block",marginBottom:"6px",color:"#1e1e1e"},children:e?.title||(0,u.__)("Step","admin-coach-tours")+` ${t+1}`}),(0,m.jsxs)("div",{className:"act-panel-progress",style:{fontSize:"13px",color:"#757575",fontWeight:"500"},children:[`${t+1} / ${n}`,r&&` • ${r}`]})]}),(0,m.jsx)(d.FlexItem,{children:(0,m.jsx)(d.Button,{icon:h,label:(0,u.__)("Close tour","admin-coach-tours"),onClick:T,size:"small"})})]})}),(0,m.jsx)("div",{className:"act-panel-body",style:{padding:"20px",flex:1,overflow:"auto",fontSize:"14px",lineHeight:"1.6",color:"#1e1e1e"},children:i?(0,m.jsxs)("div",{className:"act-panel-loading",children:[(0,m.jsx)(d.Spinner,{}),(0,m.jsx)("span",{children:(0,u.__)("Preparing…","admin-coach-tours")})]}):c?(0,m.jsxs)("div",{className:"act-panel-error",children:[s?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("p",{style:{marginBottom:"8px"},children:(0,m.jsx)("strong",{children:(0,u.__)("This step expects a specific block type:","admin-coach-tours")})}),(0,m.jsx)("p",{style:{marginBottom:"12px",color:"#1e1e1e"},children:(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ +(0,u.__)("Please insert or select a %s block first, then try again.","admin-coach-tours"),s)})]}):(0,m.jsx)("p",{style:{marginBottom:"12px"},children:(0,u.__)("Could not find the target element. The UI may have changed.","admin-coach-tours")}),(0,m.jsx)(d.Button,{variant:"secondary",onClick:p,children:(0,u.__)("Try Again","admin-coach-tours")})]}):(0,m.jsxs)(m.Fragment,{children:[v&&(0,m.jsxs)("div",{className:"act-panel-slash-command",style:{display:"flex",alignItems:"center",gap:"8px",marginBottom:"14px"},children:[(0,m.jsx)("span",{style:{fontSize:"11px",fontWeight:600,textTransform:"uppercase",letterSpacing:"0.05em",color:"#757575"},children:(0,u.__)("Type","admin-coach-tours")}),(0,m.jsx)("code",{style:{fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:"18px",fontWeight:700,color:"#0b5cad",background:"#f0f6fc",border:"1px solid #c5d9ed",borderRadius:"6px",padding:"4px 10px"},children:v})]}),e?.content&&(0,m.jsx)("div",{className:"act-panel-content",dangerouslySetInnerHTML:{__html:e.content}})]})}),x&&(0,m.jsx)("div",{className:"act-panel-footer",style:{padding:"14px 20px",borderTop:"1px solid #e0e0e0",background:"#fafafa",borderRadius:"0 0 12px 12px"},children:(0,m.jsx)("div",{className:"act-panel-controls",children:(0,m.jsx)(d.Flex,{justify:"flex-end",align:"center",children:(0,m.jsx)(d.FlexItem,{children:(0,m.jsx)(d.Button,{variant:"primary",onClick:a,disabled:i||!!c,size:"small",children:w?(0,u.__)("Finish","admin-coach-tours"):(0,u.__)("Continue","admin-coach-tours")})})})})})]})}const f={boxShadow:"0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5)",borderRadius:"4px",transition:"all 0.3s ease"};class T{constructor(e={}){this.options={usePulse:!0,overlayColor:"rgba(0, 0, 0, 0.5)",highlightColor:"#007cba",transitionDuration:300,...e},this.spotlightElement=null,this.targetElement=null,this.resizeObserver=null,this.styleElement=null,this._isAnimating=!1,this._init()}_init(){this.styleElement=document.createElement("style"),this.styleElement.textContent="\n@keyframes act-pulse {\n\t0% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t50% {\n\t\tbox-shadow: 0 0 0 8px rgba(0, 124, 186, 0.5), 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t100% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n}\n",document.head.appendChild(this.styleElement),this.spotlightElement=document.createElement("div"),this.spotlightElement.className="act-highlighter-spotlight",this.spotlightElement.setAttribute("aria-hidden","true"),Object.assign(this.spotlightElement.style,{position:"fixed",pointerEvents:"none",zIndex:9999980,...f}),this.spotlightElement.style.display="none",document.body.appendChild(this.spotlightElement),this.resizeObserver=new ResizeObserver(()=>{this._updatePosition()})}highlight(e){e&&e.isConnected?this.targetElement!==e&&(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,this.resizeObserver.observe(e),this._updatePosition(),this.spotlightElement.style.display="block",this.options.usePulse&&!this._isAnimating&&(this.spotlightElement.style.animation="act-pulse 2s infinite",this._isAnimating=!0),this._addScrollListeners()):this.clear()}_updatePosition(){if(!this.targetElement||!this.spotlightElement)return;const e=this.targetElement.getBoundingClientRect();let t={top:0,left:0};const n=this.targetElement.ownerDocument;if(n!==document){const e=document.querySelectorAll("iframe");for(const r of e)if(r.contentDocument===n){const e=r.getBoundingClientRect();t={top:e.top,left:e.left};break}}Object.assign(this.spotlightElement.style,{top:e.top+t.top-4+"px",left:e.left+t.left-4+"px",width:`${e.width+8}px`,height:`${e.height+8}px`})}_addScrollListeners(){this._scrollHandler=()=>{this._updatePosition()},this._scrollWindows=[];let e=this.targetElement?.parentElement;for(;e;)e.scrollHeight>e.clientHeight&&e.addEventListener("scroll",this._scrollHandler,{passive:!0}),e=e.parentElement;const t=this.targetElement?.ownerDocument,n=t?.defaultView;n&&n!==window&&(n.addEventListener("scroll",this._scrollHandler,{passive:!0}),n.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(n)),window.addEventListener("scroll",this._scrollHandler,{passive:!0}),window.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(window)}_removeScrollListeners(){if(!this._scrollHandler)return;let e=this.targetElement?.parentElement;for(;e;)e.removeEventListener("scroll",this._scrollHandler),e=e.parentElement;if(this._scrollWindows){for(const e of this._scrollWindows)try{e.removeEventListener("scroll",this._scrollHandler),e.removeEventListener("resize",this._scrollHandler)}catch(e){}this._scrollWindows=[]}this._scrollHandler=null}clear(){this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners(),this.targetElement=null),this.spotlightElement&&(this.spotlightElement.style.display="none",this.spotlightElement.style.animation="none",this._isAnimating=!1)}async transitionTo(e){this.targetElement?(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,e&&e.isConnected?(this.resizeObserver.observe(e),this._addScrollListeners(),await new Promise(e=>{requestAnimationFrame(()=>{this._updatePosition(),setTimeout(e,this.options.transitionDuration)})})):this.clear()):this.highlight(e)}setStyle(e){if(!this.spotlightElement)return;const t={default:"#007cba",success:"#00a32a",warning:"#dba617",error:"#d63638"},n=t[e]||t.default;this.spotlightElement.style.boxShadow=`0 0 0 4px ${n}, 0 0 0 9999px rgba(0, 0, 0, 0.5)`}async flash(){this.spotlightElement&&(this.setStyle("success"),await new Promise(e=>{setTimeout(()=>{this.setStyle("default"),e()},300)}))}destroy(){this.clear(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.spotlightElement&&this.spotlightElement.parentNode&&(this.spotlightElement.parentNode.removeChild(this.spotlightElement),this.spotlightElement=null),this.styleElement&&this.styleElement.parentNode&&(this.styleElement.parentNode.removeChild(this.styleElement),this.styleElement=null)}}function E(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function _(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const c=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",c),c}function y(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function S(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function b(e,t=document){switch(e.type){case"css":return S(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),c={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let s=[];c[n]&&(s=Array.from(t.querySelectorAll(c[n])));const i=[...o,...s];return r?i.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):i}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return S(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let c=null;if("first"===e)c=o[0]?.clientId;else if("last"===e)c=o[o.length-1]?.clientId;else if("selected"===e)c=r.getSelectedBlockClientId(),c||(c=window.__actLastSelectedBlockClientId||window.__actLastAppearedBlockClientId||(1===o.length?o[0]?.clientId:null),c&&console.log('[ACT findByWpBlock] "selected" fell back to:',c));else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,s=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",s.length),c=s[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);c=o[t]?.clientId}if(!c)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",c);const s=t.querySelector(`[data-block="${c}"]`);return s?(console.log("[ACT findByWpBlock] Found element:",s.tagName),[s]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function A(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&y(e,n.withinContainer)&&(r+=10),_(e)&&(r+=100),E(e)&&(r+=5),r}function k(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),c=o.filter(e=>!e.fallback),s=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",c.length,"primary +",s.length,"fallback locators");for(const e of[...c,...s]){let n=b(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(E),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(_),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>y(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:A(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}async function C(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{const c=document.querySelector(e);c?r(c):Date.now()-n>t?r(null):requestAnimationFrame(o)};o()})}async function I(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{try{if(e())return void r(!0)}catch{}Date.now()-n>t?r(!1):requestAnimationFrame(o)};o()})}const w=new Map;window.__actInsertedBlocks=w;const R=new Map;let v=0;function x(){w.clear(),R.clear(),v=0,delete window.__actLastAppearedBlockClientId,delete window.__actLastSelectedBlockClientId,console.log("[ACT clearInsertedBlocks] Cleared all tracking")}function O(e,t,n){R.has(e)||R.set(e,{}),R.get(e)[t]=n}async function L(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlockElement] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]',"input:not([type])"];let c=null;for(const e of o)if(c=r.querySelector(e),c)break;if(c||(c=r),c.scrollIntoView({behavior:"smooth",block:"center"}),c.focus(),"true"===c.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(c),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlockElement] Focused:",c.tagName,c.className),!0}async function P(){const e=(0,a.select)("core/block-editor"),t=e?.getSelectedBlockClientId?.();return!!t&&L(t)}const B={ensureEditor:async function(){const e=(0,a.select)("core/block-editor");return!!e&&I(()=>{const t=e.getBlocks?.();return void 0!==t})},ensureSidebarOpen:async function(e=null){const t=(0,a.select)("core/edit-post"),n=(0,a.dispatch)("core/edit-post");if(!t||!n){const t=(0,a.select)("core/interface"),n=(0,a.dispatch)("core/interface");if(t&&n){const r=t.getActiveComplementaryArea?.("core/edit-post");return r||await(n.enableComplementaryArea?.("core/edit-post",e||"edit-post/document")),I(()=>!!t.getActiveComplementaryArea?.("core/edit-post"))}return!1}if(t.isEditorSidebarOpened?.()||t.isPluginSidebarOpened?.())return!0;try{return e?await(n.openGeneralSidebar?.(e)):await(n.openGeneralSidebar?.("edit-post/document")),I(()=>t.isEditorSidebarOpened?.())}catch{return!1}},ensureSidebarClosed:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e||!t){const e=(0,a.select)("core/interface"),t=(0,a.dispatch)("core/interface");if(e&&t){const n=e.getActiveComplementaryArea?.("core/edit-post");return n&&await(t.disableComplementaryArea?.("core/edit-post")),I(()=>!e.getActiveComplementaryArea?.("core/edit-post"))}return!1}const n=e.isEditorSidebarOpened?.();if(!n)return!0;try{return await(t.closeGeneralSidebar?.()),I(()=>!e.isEditorSidebarOpened?.())}catch{return!1}},selectSidebarTab:async function(e){const t=(0,a.dispatch)("core/edit-post");if(!t)return!1;const n={document:"edit-post/document",post:"edit-post/document",block:"edit-post/block"}[e]||e;try{return await(t.openGeneralSidebar?.(n)),I(()=>{const e=(0,a.select)("core/edit-post");return e?.getActiveGeneralSidebarName?.()===n})}catch{return!1}},openInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!0)),I(()=>e?.isInserterOpened?.())}catch{const e=document.querySelector('.edit-post-header-toolbar__inserter-toggle, button[aria-label*="inserter"], button[aria-label*="Add block"]');return!!e&&(e.click(),C(".block-editor-inserter__content, .editor-inserter__content").then(e=>!!e))}},closeInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!1)),I(()=>!e?.isInserterOpened?.())}catch{return!1}},selectBlock:async function(e){const t=(0,a.dispatch)("core/block-editor");if(!t||!e)return!1;try{return await(t.selectBlock?.(e)),I(()=>{const t=(0,a.select)("core/block-editor");return t?.getSelectedBlockClientId?.()===e})}catch{return!1}},focusElement:async function(e){const t=await C(e);if(!t)return!1;try{return t.focus(),document.activeElement===t}catch{return!1}},scrollIntoView:async function(e){const t=await C(e);if(!t)return!1;try{return t.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),await new Promise(e=>setTimeout(e,300)),!0}catch{return!1}},openModal:async function(e,t){if(document.querySelector(t))return!0;const n=await C(e);return!!n&&(n.click(),C(t).then(e=>!!e))},closeModal:async function(e){const t=document.querySelector(e);if(!t)return!0;const n=t.querySelector('button[aria-label="Close"], .components-modal__header button, .components-popover__close');return n?(n.click(),I(()=>!document.querySelector(e))):(document.dispatchEvent(new KeyboardEvent("keydown",{key:"Escape",code:"Escape",bubbles:!0})),I(()=>!document.querySelector(e)))},insertBlock:async function(e="core/paragraph",t={},n="act-inserted-block"){console.log("[ACT insertBlock] Called with:",{blockName:e,markerId:n,currentStepIndex:v}),console.log("[ACT insertBlock] insertedBlocks map:",Array.from(w.entries())),console.log("[ACT insertBlock] insertedBlocksByStep:",Array.from(R.entries()));try{const{createBlock:r}=await Promise.resolve().then(o.t.bind(o,997,23)),c=(0,a.dispatch)("core/block-editor"),s=(0,a.select)("core/block-editor");if(!c||!r)return console.warn("[ACT insertBlock] Block editor not available"),!1;const i=function(e,t){const n=R.get(e);return n&&n[t]?n[t]:null}(v,n);if(console.log("[ACT insertBlock] stepClientId from getStepInsertedBlock:",i),i){const e=s.getBlock(i);if(console.log("[ACT insertBlock] existingBlock from store:",e?.name),e)return await c.selectBlock(i),await L(i),console.log("[ACT insertBlock] Reusing step block:",i,"for step:",v),!0;const t=document.querySelector('iframe[name="editor-canvas"]');if((t?.contentDocument||document).querySelector(`[data-block="${i}"]`))return await c.selectBlock(i),await L(i),console.log("[ACT insertBlock] Block still in DOM, reusing:",i),!0;console.log("[ACT insertBlock] Block not in store or DOM, checking for any existing blocks of this type")}if(console.log("[ACT insertBlock] Checking global map for:",n,"has:",w.has(n)),w.has(n)){const e=w.get(n),t=s.getBlock(e);if(console.log("[ACT insertBlock] existingBlock from global map:",t?.name),t)return await c.selectBlock(e),await L(e),O(v,n,e),console.log("[ACT insertBlock] Reusing existing block:",e),!0;const r=document.querySelector('iframe[name="editor-canvas"]');if((r?.contentDocument||document).querySelector(`[data-block="${e}"]`))return await c.selectBlock(e),await L(e),O(v,n,e),console.log("[ACT insertBlock] Block from global map still in DOM:",e),!0}const l=s.getBlocks()||[];console.log("[ACT insertBlock] Checking for existing blocks of type:",e,"found:",l.length,"total blocks"),console.log("[ACT insertBlock] Block names in editor:",l.map(e=>e.name));const u=l.find(t=>t.name===e);if(u)return console.log("[ACT insertBlock] Found existing block of same type:",u.clientId),await c.selectBlock(u.clientId),await L(u.clientId),w.set(n,u.clientId),O(v,n,u.clientId),console.log("[ACT insertBlock] Reusing existing block of type:",e),!0;if(l.length>0){const t=l[l.length-1];console.log("[ACT insertBlock] Considering last block:",t.name,t.clientId);const r=["core/paragraph","core/heading","core/list","core/quote"],o=r.includes(e),s=r.includes(t.name);if(o&&s)return console.log("[ACT insertBlock] Reusing last text block instead of creating new:",t.clientId),await c.selectBlock(t.clientId),await L(t.clientId),w.set(n,t.clientId),O(v,n,t.clientId),!0}console.log("[ACT insertBlock] No existing block found, creating new one");const d={...t,metadata:{...t.metadata||{},actMarkerId:n}},p=r(e,d),m="";await c.insertBlock(p,void 0,m,!0),w.set(n,p.clientId),O(v,n,p.clientId);const h=await I(()=>{const e=document.querySelector('iframe[name="editor-canvas"]'),t=e?.contentDocument;return!!(t||document).querySelector(`[data-block="${p.clientId}"]`)},3e3);return h&&(await c.selectBlock(p.clientId),await L(p.clientId)),console.log("[ACT insertBlock] Inserted block:",p.clientId,"markerId:",n,"step:",v,"success:",h),h}catch(e){return console.error("[ACT insertBlock] Error:",e),!1}}};async function N(e){const{type:t,params:n={}}=e,r=B[t];if(!r)return{success:!1,error:`Unknown precondition type: ${t}`,type:t};try{let e;switch(t){case"ensureEditor":case"ensureSidebarClosed":case"openInserter":case"closeInserter":e=await r();break;case"ensureSidebarOpen":e=await r(n.sidebar);break;case"selectSidebarTab":e=await r(n.tab);break;case"selectBlock":e=await r(n.clientId);break;case"focusElement":case"scrollIntoView":e=await r(n.selector);break;case"openModal":e=await r(n.trigger,n.modal);break;case"closeModal":e=await r(n.modal);break;case"insertBlock":e=await r(n.blockName||"core/paragraph",n.attributes||{},n.markerId||"act-inserted-block");break;default:e=!1}return{success:e,type:t,error:e?null:`Precondition failed: ${t}`}}catch(e){return{success:!1,type:t,error:`Precondition error: ${e.message}`}}}async function D(e){if(!e||0===e.length)return{success:!0,results:[]};const t=[];let n=!0;for(const r of e){const e=await N(r);t.push(e),e.success||(n=!1)}return{success:n,results:t,failedPreconditions:t.filter(e=>!e.success)}}function j(e,t=0){return new Promise(n=>{let r=null,o=null,c=!1;const s=(e,t=!1)=>{c||(c=!0,r&&clearTimeout(r),o&&clearInterval(o),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{s(!1,!0)},t));const i=()=>{try{e()&&s(!0)}catch{}};i(),c||(o=setInterval(i,100))})}function U(e,t={}){return new Promise(n=>{let r=!1,o=!1;const{timeout:c=0,gracePeriod:s=300}=t;let i=null;const l=()=>{e.removeEventListener("click",a,!0),e.ownerDocument!==document&&e.ownerDocument.removeEventListener("click",a,!0),i&&clearTimeout(i)},a=t=>{r||(o?(e===t.target||e.contains(t.target))&&(console.log("[ACT watchClickTarget] Click detected on target:",e.tagName),r=!0,l(),n({success:!0,event:"click"})):console.log("[ACT watchClickTarget] Ignoring click during grace period"))};e.addEventListener("click",a,{capture:!0}),e.ownerDocument!==document&&e.ownerDocument.addEventListener("click",a,{capture:!0}),setTimeout(()=>{o=!0,console.log("[ACT watchClickTarget] Armed after grace period, watching:",e.tagName,e.className)},s),c>0&&(i=setTimeout(()=>{r||(r=!0,l(),n({success:!1,timedOut:!0}))},c))})}function F(e,t={}){return new Promise(n=>{let r=!1;const{timeout:o=0,expectedValue:c,attributeName:s=null}=t;let i=null,l=null;const a=()=>{l&&l.disconnect(),e.removeEventListener("input",m),e.removeEventListener("change",h),i&&clearTimeout(i)},u=()=>s?e.getAttribute(s):"value"in e?e.value:e.isContentEditable?e.textContent:"checkbox"===e.type||"radio"===e.type?e.checked:e.textContent,d=u(),p=()=>{r||(()=>{const e=u();return void 0!==c?e===c:e!==d})()&&(r=!0,a(),n({success:!0,event:"valueChanged"}))},m=()=>p(),h=()=>p();e.addEventListener("input",m),e.addEventListener("change",h),l=new MutationObserver(()=>p()),l.observe(e,{attributes:!0,characterData:!0,subtree:!0,childList:!0}),o>0&&(i=setTimeout(()=>{r||(r=!0,a(),n({success:!1,timedOut:!0}))},o))})}function M(e={}){const{storeName:t,selector:n,args:r=[],expectedValue:o,comparator:c="equals",timeout:s=0}=e;return new Promise(e=>{let i=!1,l=null,u=null;const d=()=>{u&&u(),l&&clearTimeout(l)},p=(0,a.select)(t);if(!p||!p[n])return void e({success:!1,error:`Invalid store or selector: ${t}.${n}`});const m=()=>{i||(()=>{try{const e=p[n](...r);switch(c){case"equals":default:return e===o;case"notEquals":return e!==o;case"truthy":return!!e;case"falsy":return!e;case"contains":return(Array.isArray(e)||"string"==typeof e)&&e.includes(o);case"greaterThan":return e>o;case"lessThan":return eo}}catch{return!1}})()&&(i=!0,d(),e({success:!0,event:"wpDataChanged"}))};m(),i||(u=(0,a.subscribe)(()=>{m()})),s>0&&(l=setTimeout(()=>{i||(i=!0,d(),e({success:!1,timedOut:!0}))},s))})}function q(e={}){const{timeout:t=0}=e;let n=null,r=null,o=!1;return{promise:new Promise(e=>{n=e,t>0&&(r=setTimeout(()=>{o||(o=!0,e({success:!1,timedOut:!0}))},t))}),confirm:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!0,event:"manual"}))},cancel:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!1,cancelled:!0}))}}}function V(e,t={}){const{timeout:n=0}=t,r=()=>{const t=new Set;document.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)});const n=document.querySelector('iframe[name="editor-canvas"]');return n?.contentDocument?.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)}),t},o=r();return console.log("[ACT watchElementAppear] Existing matches:",o.size,"for selector:",e),j(()=>{const e=r();for(const t of e)if(!o.has(t))return window.__actNewlyAppearedBlockClientId=t,console.log("[ACT watchElementAppear] NEW element appeared:",t),!0;return!1},n).then(e=>(e.success&&window.__actNewlyAppearedBlockClientId&&(window.__actLastAppearedBlockClientId=window.__actNewlyAppearedBlockClientId,console.log("[ACT watchElementAppear] Tracked appeared block:",window.__actLastAppearedBlockClientId),delete window.__actNewlyAppearedBlockClientId),{...e,event:e.success?"elementAppeared":null}))}function G(e,t={}){const{timeout:n=0}=t;return j(()=>null===document.querySelector(e),n).then(e=>({...e,event:e.success?"elementDisappeared":null}))}function H(e,t={}){const{timeout:n=0,target:r=document}=t;return new Promise(t=>{let o=!1,c=null;const s=()=>{r.removeEventListener(e,i),c&&clearTimeout(c)},i=n=>{o||(o=!0,s(),t({success:!0,event:e,detail:n.detail}))};r.addEventListener(e,i,{once:!0}),n>0&&(c=setTimeout(()=>{o||(o=!0,s(),t({success:!1,timedOut:!0}))},n))})}function W(e){try{const t=(0,a.select)("core/block-editor");if(!t?.getBlocks)return!1;const n=t.getBlocks(),r=t=>{for(const n of t){if(n.name===e)return!0;if(n.innerBlocks?.length>0&&r(n.innerBlocks))return!0}return!1};return r(n)}catch(e){return console.warn("[ACT] Error checking for block type:",e),!1}}async function $(e,t,n=5e3){const r=t+1;if(r>=e.length)return{waited:!1};const o=function(e){if(!e?.target)return null;const{target:t}=e;if(t.constraints?.blockType)return t.constraints.blockType;const n=t.locators||[];for(const e of n){if("dataAttribute"===e.type&&"data-type"===e.attribute)return e.value;if("block"===e.type&&e.blockName)return e.blockName}for(const e of n)if("css"===e.type&&e.value){const t=e.value.match(/\[data-type=["']([^"']+)["']\]/);if(t)return t[1];const n=e.value.match(/\.wp-block-(\w+)/);if(n)return`core/${n[1]}`}return null}(e[r]);if(!o)return console.log("[ACT waitForNextStepBlock] Next step does not expect a specific block"),{waited:!1};if(console.log("[ACT waitForNextStepBlock] Next step expects block:",o),W(o))return console.log("[ACT waitForNextStepBlock] Block already exists"),{waited:!1,blockType:o};const c=await function(e,t=5e3){return new Promise(n=>{if(W(e))return console.log("[ACT waitForBlock] Block already exists:",e),void n({success:!0});console.log("[ACT waitForBlock] Waiting for block:",e);let r=null,o=null,c=!1;const s=(e,t=!1)=>{c||(c=!0,r&&clearTimeout(r),o&&o(),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{console.log("[ACT waitForBlock] Timeout waiting for:",e),s(!1,!0)},t)),o=(0,a.subscribe)(()=>{W(e)&&(console.log("[ACT waitForBlock] Block appeared:",e),s(!0))})})}(o,n);return{waited:!0,blockType:o,success:c.success,timedOut:c.timedOut}}const z="admin-coach-tours";function K(e){if(!Array.isArray(e))return null;for(const t of e){if("css"===t.type&&"string"==typeof t.value){const e=t.value.match(/data-type=["']?(core\/[\w-]+)["']?/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}if("wpBlock"===t.type&&"string"==typeof t.value){const e=t.value.match(/type:(core\/[\w-]+)/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}}return null}function X(){console.log("[ACT TourRunner] Component function called");const[e,t]=(0,l.useState)(null),[n,r]=(0,l.useState)(null),[o,c]=(0,l.useState)(null),[s,i]=(0,l.useState)(null),[d,p]=(0,l.useState)(!1),[h,f]=(0,l.useState)(0),E=(0,l.useRef)(null),_=(0,l.useRef)(null),y=(0,l.useRef)(null),S=(0,l.useRef)(null),{isPlaying:b,currentTour:A,currentStep:C,stepIndex:I,totalSteps:w}=(0,a.useSelect)(e=>{const t=e(z),n={isPlaying:t.isPupilMode(),currentTour:t.getCurrentTour(),currentStep:t.getCurrentStep(),stepIndex:t.getCurrentStepIndex()||0,totalSteps:t.getTotalSteps()||0};return console.log("[ACT TourRunner] useSelect:",n),n},[]),{stopTour:R,nextStep:O,previousStep:L,repeatStep:B,setAiTourError:N,setLastFailureContext:j}=(0,a.useDispatch)(z);(0,l.useEffect)(()=>(E.current||(E.current=new T),()=>{E.current&&(E.current.destroy(),E.current=null)}),[]),(0,l.useEffect)(()=>{if(!b||!C)return E.current&&E.current.clear(),t(null),void x();let e=!0,n=null;const o=_.current;return(async()=>{if(r(null),p(!0),y.current?.cancel&&(console.log("[ACT TourRunner] Cancelling previous completion watcher"),y.current.cancel(),y.current=null),null!==o&&o!==I&&await async function(e){console.log("[ACT onLeaveStep] Leaving step:",e);const t=(0,a.dispatch)("core/block-editor"),n=(0,a.select)("core/block-editor");if(!t||!n)return;const r=n.getSelectedBlockClientId?.();if(r){window.__actLastSelectedBlockClientId=r;try{await(t.clearSelectedBlock?.()),console.log("[ACT onLeaveStep] Deselected block:",r)}catch(e){console.warn("[ACT onLeaveStep] Could not deselect block:",e)}}}(o),await async function(e){var t;console.log("[ACT onEnterStep] Entering step:",e),v=t=e,console.log("[ACT setCurrentStepIndex]",t)}(I),_.current=I,console.log("[ACT TourRunner] Preconditions for step:",I,C.preconditions),C.preconditions?.length>0){console.log("[ACT TourRunner] Applying",C.preconditions.length,"preconditions");const t=await D(C.preconditions);if(console.log("[ACT TourRunner] Precondition result:",t),!e)return;t.success||console.warn("Some preconditions failed:",t.failedPreconditions)}else console.log("[ACT TourRunner] No preconditions for this step");if(p(!1),C.target){const o=C.recovery?async()=>{await D(C.recovery)}:null,l=await async function(e,t=null){let n=k(e);if(n.success)return n;if(t)try{if(await t(),await new Promise(e=>setTimeout(e,100)),n=k(e),n.success)return{...n,recovered:!0}}catch(e){return{success:!1,error:`Recovery failed: ${e.message}`}}return n}(C.target,o);if(!e)return;if(l.success){n=l.element,t(l.element),r(null),c(null),i({success:!0,usedLocator:l.usedLocator,recovered:l.recovered||!1});let e=l.element.getAttribute("data-block");if(!e){const t=l.element.closest("[data-block]");t&&(e=t.getAttribute("data-block"),console.log("[ACT TourRunner] Found parent block:",e))}if(e)try{const t=(0,a.dispatch)("core/block-editor");t?.selectBlock&&(await t.selectBlock(e),console.log("[ACT TourRunner] Selected block:",e))}catch(e){console.warn("[ACT TourRunner] Could not select block:",e)}(s=l.element)&&(s.ownerDocument!==document?(s.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),setTimeout(()=>{const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const t=s.getBoundingClientRect(),n=e.getBoundingClientRect(),r=n.top+t.top,o=n.top+t.bottom,c=window.innerHeight;if(!(r>=100&&o<=c-100)){const e=r+window.scrollY-c/2;window.scrollTo({top:Math.max(0,e),behavior:"smooth"})}}},150)):s.scrollIntoView({behavior:"smooth",block:"center",inline:"center"})),setTimeout(()=>{E.current&&l.element?.isConnected&&E.current.highlight(l.element)},350)}else{if(n=null,function(e){if(!e)return!1;const t=(0,a.select)("core/block-editor");if(!t?.getBlocks)return!1;const n=t.getBlocks()||[];return!((t.getBlocksByName?.(e)?.length||0)>0||n.some(t=>t.name===e))&&n.some(t=>t.name!==e&&("core/paragraph"!==t.name||(t.attributes?.content||"").toString().trim().length>0))}(function(e){if(!Array.isArray(e))return null;for(const t of e)if("string"==typeof t.value){if("css"===t.type){const e=t.value.match(/data-type=["']?(core\/[\w-]+)["']?/i);if(e)return e[1]}if("wpBlock"===t.type){const e=t.value.match(/type:(core\/[\w-]+)/i);if(e)return e[1]}}return null}(C.target?.locators))){const e=K(C.target?.locators);return console.log("[ACT TourRunner] User added a different block than expected; aborting tour."),E.current&&E.current.clear(),x(),_.current=null,R(),void N(e?(0,u.sprintf)(/* translators: %s: expected block type name. */ /* translators: %s: expected block type name. */ +(0,u.__)("This tour was for adding a %s block, but a different block was added. The tour has stopped — start a new tour for the block you added.","admin-coach-tours"),e):(0,u.__)("A different block than expected was added, so the tour has stopped.","admin-coach-tours"))}if(t(null),r(l.error),c(K(C.target?.locators)),i({success:!1,error:l.error}),console.log("[ACT TourRunner] Target resolution failed, NOT auto-advancing. Error:",l.error),E.current&&E.current.clear(),h>0){console.log("[ACT TourRunner] Retry also failed. Failing the entire tour."),j({stepIndex:I,stepId:C.id,stepTitle:C.title,targetLocators:C.target?.locators||[],error:l.error,reason:"Step retry failed - target element could not be found after second attempt"}),x(),_.current=null,R();const e=K(C.target?.locators),t=e?(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ +(0,u.__)("Could not find the %s block. Make sure to insert or select the correct block, then try again.","admin-coach-tours"),e):(0,u.__)("The generated tour could not complete. Try again to get a fresh set of instructions.","admin-coach-tours");N(t)}}}var s;if(C.completion&&n){if(console.log("[ACT TourRunner] Setting up completion watcher for step:",I,"type:",C.completion.type,"params:",C.completion.params),await new Promise(e=>setTimeout(e,100)),!e)return;const t=function(e,t=null){if(!e||!e.type)return q();const{type:n,params:r={}}=e,o=e.timeout||0;switch(n){case"clickTarget":return t?{promise:U(t,{timeout:o}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for clickTarget"}),cancel:()=>{}};case"domValueChanged":return t?{promise:F(t,{timeout:o,...r}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for domValueChanged"}),cancel:()=>{}};case"wpData":return{promise:M({timeout:o,...r}),cancel:()=>{}};case"manual":default:return q({timeout:o});case"elementAppear":return{promise:V(r.selector,{timeout:o}),cancel:()=>{}};case"elementDisappear":return{promise:G(r.selector,{timeout:o}),cancel:()=>{}};case"customEvent":return{promise:H(r.eventName,{timeout:o}),cancel:()=>{}}}}(C.completion,n);if(y.current=t,I===w-1&&n){const t=()=>{e&&(console.log("[ACT TourRunner] Last step: block clicked, finishing tour"),x(),P(),O())},r=setTimeout(()=>{e&&n.isConnected&&(n.addEventListener("click",t,{once:!0,capture:!0}),S.current=()=>{n.removeEventListener("click",t,{capture:!0})})},400),o=S.current;S.current=()=>{clearTimeout(r),o&&o(),n.removeEventListener("click",t,{capture:!0})}}t.promise.then(async t=>{if(e&&t.success)if(console.log("[ACT TourRunner] Completion detected for step:",I),I{e&&(console.log("[ACT TourRunner] Auto-advancing from step:",I),O())},300)}else console.log("[ACT TourRunner] Last step completed, ending tour"),x(),P(),O()})}})(),()=>{e=!1,y.current?.cancel&&(console.log("[ACT TourRunner] Cleanup: Cancelling completion watcher"),y.current.cancel(),y.current=null),S.current&&(S.current(),S.current=null)}},[b,C,I,h,R,N]);const W=(0,l.useCallback)(async()=>{if(y.current?.confirm)y.current.confirm();else{if(I{y.current?.cancel&&(y.current.cancel(),y.current=null),f(e=>e+1),B()},[B]),Y=(0,l.useCallback)(()=>{y.current?.cancel&&(y.current.cancel(),y.current=null),x(),_.current=null,R(),P()},[R]);if(!b||!A||!C)return null;const Q=(0,m.jsx)(g,{step:C,stepIndex:I,totalSteps:w,tourTitle:A.title,targetElement:e,resolutionError:n,expectedBlockType:o,isApplyingPreconditions:d,onContinue:W,onRepeat:X,onPrevious:L,onNext:O,onStop:Y});return(0,l.createPortal)(Q,document.body)}const Y="admin-coach-tours",Q={text:"📝",media:"🖼️",design:"📐",embed:"🔗",default:"📚"},J=["text","media","design","embed"];function Z(){const[e,t]=(0,l.useState)(!1),[n,r]=(0,l.useState)([]),[o,c]=(0,l.useState)(!1),[s,i]=(0,l.useState)(null),[p,h]=(0,l.useState)(""),[g,f]=(0,l.useState)("tasks"),[T,E]=(0,l.useState)(!1),[_,y]=(0,l.useState)(null),S=(0,l.useRef)(null),{storeLoading:b,aiTourError:A,isPlaying:k,aiAvailable:C,lastFailureContext:I,editorMode:w}=(0,a.useSelect)(e=>{const t=e(Y);return{storeLoading:t.isAiTourLoading?.()??!1,aiTourError:t.getAiTourError?.()??null,isPlaying:null!==t.getCurrentTour(),aiAvailable:window.adminCoachTours?.aiAvailable??!1,lastFailureContext:t.getLastFailureContext?.()??null,editorMode:e("core/edit-post")?.getEditorMode?.()??"visual"}},[]),R=T||b;(0,l.useEffect)(()=>{k&&T&&E(!1)},[k,T]),(0,l.useEffect)(()=>{!A||k||e||t(!0)},[A,k,e]);const{requestAiTour:v,clearEphemeralTour:x,setAiTourError:O,setLastFailureContext:L,fetchAiTasks:P}=(0,a.useDispatch)(Y);(0,l.useEffect)(()=>{e&&0===n.length&&!o&&(c(!0),i(null),P().then(e=>{e.available&&e.tasks?r(e.tasks):i((0,u.__)("AI is not available.","admin-coach-tours"))}).catch(e=>{i(e.message||(0,u.__)("Failed to load tasks.","admin-coach-tours"))}).finally(()=>{c(!1)}))},[e,n.length,o]),(0,l.useEffect)(()=>{"chat"===g&&S.current&&S.current.focus()},[g]);const B=(0,l.useCallback)(e=>{E(!0),t(!1);const n=window.adminCoachTours?.postType||"post";y({type:"task",taskId:e,postType:n}),v(e,"",n)},[v]),N=(0,l.useCallback)(e=>{if(e.preventDefault(),!p.trim())return;E(!0),t(!1);const n=window.adminCoachTours?.postType||"post",r=p.trim();y({type:"chat",query:r,postType:n}),v("",r,n),h("")},[p,v]),D=(0,l.useCallback)(()=>{_&&(O(null),E(!0),t(!1),"task"===_.type?v(_.taskId,"",_.postType,I):"chat"===_.type&&v("",_.query,_.postType,I),L(null))},[_,I,v,O,L]),j=(0,l.useCallback)(()=>{O(null),y(null),L(null),E(!1)},[O,L]),U=(0,l.useCallback)(()=>{i(null),r([])},[]),F=(0,l.useCallback)(()=>{t(e=>!e)},[]),M=(0,l.useCallback)(()=>{t(!1)},[]);if("text"===w)return null;if(!C)return(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:F,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),children:(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("AI Coach","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:M,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsx)("div",{className:"act-pupil-launcher__content",children:(0,m.jsxs)("div",{className:"act-pupil-launcher__not-configured",children:[(0,m.jsx)("p",{children:(0,u.__)("AI is not configured yet.","admin-coach-tours")}),(0,m.jsxs)("p",{children:[(0,u.__)("Go to ","admin-coach-tours"),(0,m.jsx)("a",{href:"/wp-admin/tools.php?page=admin-coach-tours",children:(0,u.__)("Tools → Coach Tours","admin-coach-tours")}),(0,u.__)(" to add your API key.","admin-coach-tours")]})]})})]})]});const q=n.reduce((e,t)=>{const n=t.category||"other";return e[n]||(e[n]=[]),e[n].push(t),e},{}),V=Object.entries(q).sort(([e],[t])=>{const n=J.indexOf(e),r=J.indexOf(t);return(-1===n?999:n)-(-1===r?999:r)}),G=R&&(0,l.createPortal)((0,m.jsx)("div",{className:"act-ai-loading-overlay",children:(0,m.jsxs)("div",{className:"act-ai-loading-overlay__content",children:[(0,m.jsx)("div",{className:"act-ai-loading-overlay__spinner"}),(0,m.jsx)("h2",{className:"act-ai-loading-overlay__title",children:(0,u.__)("Creating your guided tour...","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__text",children:(0,u.__)("AI is analyzing the editor and generating step-by-step instructions.","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__hint",children:(0,u.__)("This usually takes a few seconds.","admin-coach-tours")})]})}),document.body);return k?G||null:(0,m.jsxs)(m.Fragment,{children:[G,(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:F,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),disabled:R,children:R?(0,m.jsx)("span",{className:"act-pupil-launcher__spinner"}):(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[R&&(0,m.jsxs)("div",{className:"act-pupil-launcher__ai-loading",children:[(0,m.jsx)("div",{className:"act-pupil-launcher__ai-loading-spinner"}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-text",children:(0,u.__)("Creating your tour...","admin-coach-tours")}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-hint",children:(0,u.__)("AI is generating step-by-step instructions","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("What would you like to learn?","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:M,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__tabs",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("tasks"===g?"is-active":""),onClick:()=>f("tasks"),children:(0,u.__)("Common Tasks","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("chat"===g?"is-active":""),onClick:()=>f("chat"),children:(0,u.__)("Ask a Question","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__content",children:[A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:A}),(0,m.jsx)("p",{className:"act-pupil-launcher__error-hint",children:(0,u.__)("This might be a temporary issue.","admin-coach-tours")}),(0,m.jsxs)("div",{className:"act-pupil-launcher__error-actions",children:[_&&(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:D,disabled:R,children:(0,u.__)("Try Again","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__dismiss-btn",onClick:j,children:(0,u.__)("Dismiss","admin-coach-tours")})]})]}),s&&!A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:s}),(0,m.jsx)("div",{className:"act-pupil-launcher__error-actions",children:(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:U,children:(0,u.__)("Try Again","admin-coach-tours")})})]}),"tasks"===g&&!s&&(0,m.jsxs)("div",{className:"act-pupil-launcher__tasks",children:[o&&(0,m.jsx)("div",{className:"act-pupil-launcher__loading",children:(0,u.__)("Loading tasks...","admin-coach-tours")}),!o&&V.map(([e,t])=>(0,m.jsxs)("div",{className:"act-pupil-launcher__category",children:[(0,m.jsxs)("h4",{children:[(0,m.jsx)("span",{className:"act-pupil-launcher__category-icon",children:Q[e]||Q.default}),e.charAt(0).toUpperCase()+e.slice(1)]}),(0,m.jsx)("div",{className:"act-pupil-launcher__task-list",children:t.map(e=>(0,m.jsxs)("button",{className:"act-pupil-launcher__task",onClick:()=>B(e.id),disabled:R,children:[(0,m.jsx)("span",{className:"act-pupil-launcher__task-icon",children:e.icon?(0,m.jsx)(d.Dashicon,{icon:e.icon}):"📌"}),(0,m.jsx)("span",{className:"act-pupil-launcher__task-label",children:e.label})]},e.id))})]},e))]}),"chat"===g&&(0,m.jsxs)("div",{className:"act-pupil-launcher__chat",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__chat-hint",children:(0,u.__)("Ask about the WordPress editor:","admin-coach-tours")}),(0,m.jsxs)("form",{onSubmit:N,children:[(0,m.jsx)("input",{ref:S,type:"text",className:"act-pupil-launcher__chat-input",value:p,onChange:e=>h(e.target.value),placeholder:(0,u.__)("e.g., How do I add a gallery?","admin-coach-tours"),disabled:R}),(0,m.jsx)("button",{type:"submit",className:"act-pupil-launcher__chat-submit",disabled:R||!p.trim(),children:R?(0,u.__)("Generating...","admin-coach-tours"):(0,u.__)("Get Tour","admin-coach-tours")})]}),(0,m.jsx)("p",{className:"act-pupil-launcher__chat-note",children:(0,u.__)("AI will create a step-by-step tour to guide you.","admin-coach-tours")})]})]})]})]})]})}const ee={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},te={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},ne=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function re(e){return{type:te.SET_TOURS_LOADING,isLoading:e}}function oe(e){return{type:te.SET_TOURS_ERROR,error:e}}function ce(e){return{type:te.RECEIVE_TOURS,tours:e}}function se(e){return{type:te.RECEIVE_TOUR,tour:e}}function*ie(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield se(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield se({id:e,title:"",steps:[],status:"draft"})}yield{type:te.SET_CURRENT_TOUR,tourId:e}}else yield{type:te.SET_CURRENT_TOUR,tourId:null}}function le(e={}){return{type:"FETCH_TOURS",args:e}}function ae(e){return{type:"FETCH_TOUR",tourId:e}}function*ue(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield se(n)),n}catch(e){throw e}}function*de(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield se(t)),t}catch(e){throw e}}function*pe(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield se(n)),n}catch(e){throw e}}function me(e,t="pupil"){return{type:te.START_TOUR,tourId:e,mode:t}}function he(){return{type:te.END_TOUR}}function ge(){return he()}function fe(e){return{type:te.SET_CURRENT_STEP,stepIndex:e}}function Te(){return{type:te.NEXT_STEP}}function Ee(){return{type:te.PREVIOUS_STEP}}function _e(){return{type:te.SKIP_STEP}}function ye(){return{type:te.REPEAT_STEP}}function Se(e){return{type:te.SET_MODE,mode:e}}function be(e){return{type:te.SET_COMPLETION_SATISFIED,satisfied:e}}function Ae(){return{type:te.RESET_COMPLETION}}function ke(){return Te()}function Ce(e){return{type:te.SET_RESOLVED_TARGET,target:e}}function Ie(){return{type:te.CLEAR_RESOLVED_TARGET}}function we(e){return{type:te.SET_RECOVERING,isRecovering:e}}function Re(){return{type:te.INCREMENT_RESOLUTION_ATTEMPTS}}function ve(e){return{type:te.SET_LAST_ERROR,error:e}}function xe(e=null){return{type:te.ACTIVATE_PICKER,stepId:e}}function Oe(e=null){return xe(e)}function Le(){return{type:te.DEACTIVATE_PICKER}}function Pe(){return Le()}function Be(e){return{type:te.SELECT_STEP,stepId:e}}function Ne(e){return{type:te.SET_PENDING_CHANGES,pending:e}}function*De(e,t,n){yield{type:te.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*je(e,t={},n=null){const r={id:ne(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:te.ADD_STEP,tourId:e,step:r,index:n}}function*Ue(e,t){yield{type:te.DELETE_STEP,tourId:e,stepId:t}}function*Fe(e,t){yield{type:te.REORDER_STEPS,tourId:e,stepIds:t}}function Me(e){return{type:te.SET_AI_DRAFT_LOADING,isLoading:e}}function qe(e){return{type:te.SET_AI_DRAFT_ERROR,error:e}}function Ve(e){return{type:te.SET_AI_DRAFT_RESULT,result:e}}function Ge(){return{type:te.CLEAR_AI_DRAFT}}function*He(e,t){yield Me(!0),yield qe(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield Ve(n),n}catch(e){throw yield qe(e.message||"Failed to generate AI draft"),e}finally{yield Me(!1)}}function We(e){return{type:te.SET_SIDEBAR_OPEN,isOpen:e}}function $e(e){return{type:te.SET_AI_TOUR_LOADING,isLoading:e}}function ze(e){return{type:te.SET_AI_TOUR_ERROR,error:e}}function Ke(e){return{type:te.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function Xe(e){return{type:te.RECEIVE_EPHEMERAL_TOUR,tour:e}}function Ye(){return{type:te.CLEAR_EPHEMERAL_TOUR}}function*Qe(e,t,n,r=null){yield $e(!0),yield ze(null);const o=window.adminCoachTours?.locale||"";try{const c=yield{type:"GATHER_EDITOR_CONTEXT"},s=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:c,failureContext:r,locale:o};console.log("[ACT AI Response] Full result:",s),console.log("[ACT AI Response] Tour:",JSON.stringify(s.tour,null,2));const i={id:"ephemeral",...s.tour};return yield Xe(i),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield me("ephemeral","pupil"),i}catch(e){throw yield ze(e.message||"Failed to generate tour"),e}finally{yield $e(!1)}}function*Je(){return yield{type:"FETCH_AI_TASKS"}}function*Ze(e){const t={id:"ephemeral",...e};yield Xe(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield me("ephemeral","pupil")}function et(e){return e.tours}const tt=(0,a.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function nt(e,t){return e.tours[t]||null}function rt(e){return e.toursLoading}function ot(e){return e.toursError}const ct=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),st=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function it(e){return e.currentTourId}function lt(e){return e.currentTourId?e.tours[e.currentTourId]:null}function at(e){return e.currentStepIndex}const ut=(0,a.createSelector)(e=>{const t=lt(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function dt(e){const t=lt(e);return t?.steps?.length||0}function pt(e){return e.currentStepIndex0}function ht(e){const t=dt(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function gt(e){return e.mode}function ft(e){return"educator"===e.mode}function Tt(e){return"pupil"===e.mode}function Et(e){return null!==e.currentTourId&&null!==e.mode}function _t(e){return e.completionSatisfied}function yt(e){return e.skippedSteps}function St(e,t){return e.skippedSteps.includes(t)}function bt(e){return e.resolvedTarget}function At(e){return e.isRecovering}function kt(e){return e.resolutionAttempts}function Ct(e){return e.lastError}function It(e){return e.isPickerActive}function wt(e){return e.pickingStepId||null}function Rt(e){return e.selectedStepId}const vt=(0,a.createSelector)(e=>{const t=lt(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function xt(e){return e.pendingChanges}function Ot(e){return e.aiDraftLoading}function Lt(e){return Ot(e)}function Pt(e){return e.aiDraftError}function Bt(e){return e.aiDraftResult}function Nt(e){return Bt(e)}function Dt(e){return e.sidebarOpen}function jt(e){return e.aiTourLoading}function Ut(e){return e.aiTourError}function Ft(e){return e.lastFailureContext}function Mt(e){return e.ephemeralTour}function qt(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Vt=window.wp.apiFetch;var Gt=o.n(Vt);function*Ht(){yield re(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield ce(e)}catch(e){yield oe(e.message||"Failed to fetch tours")}}function*Wt(e){yield re(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield se(t)}catch(e){yield oe(e.message||"Failed to fetch tour")}}function*$t(e){yield re(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield ce(t)}catch(e){yield oe(e.message||"Failed to fetch tours")}}function zt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,a.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,a.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,a.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function Kt(){try{const e=(0,a.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:Xt(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function Xt(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function Yt(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:Qt(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:Qt(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:Qt(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:Qt(o)});const c=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of c){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function Qt(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function Jt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function Zt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let c=null;for(const e of o)if(c=r.querySelector(e),c)break;if(c||(c=r),c.scrollIntoView({behavior:"smooth",block:"center"}),c.focus(),"true"===c.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(c),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",c.tagName,c.className),!0}const en={API_FETCH:e=>Gt()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:Kt(),visibleElements:zt(),uiSamples:Yt(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,a.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&Jt(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,a.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&Jt(e.attributes?.content));return t&&(await(0,a.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await Zt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,a.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,a.select)("core/block-editor"),c=(r?.getBlocks()||[]).length;return await t.insertBlock(n,c,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await Zt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Gt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Gt()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Gt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Gt()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Gt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Gt()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Gt()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:e.locale||""}}),FETCH_AI_TASKS:()=>Gt()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},tn="admin-coach-tours",nn=(0,a.createReduxStore)(tn,{reducer:function(e=ee,t){switch(t.type){case te.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case te.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case te.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case te.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case te.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case te.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case te.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case te.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case te.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case te.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case te.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case te.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case te.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case te.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case te.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case te.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case te.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case te.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case te.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case te.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case te.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case te.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:c,selectors:s,resolvers:i,controls:en,initialState:ee});(0,a.select)(tn)||(0,a.register)(nn);const rn="admin-coach-tours";function on(){console.log("[ACT Pupil] Initializing... v4"),console.log("[ACT Pupil] TourRunner:",typeof X,X),console.log("[ACT Pupil] AI Available:",window.adminCoachTours?.aiAvailable);const e=document.createElement("div");e.id="admin-coach-tours-pupil",document.body.appendChild(e);try{(0,l.render)((0,m.jsx)(X,{}),e),console.log("[ACT Pupil] TourRunner rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering TourRunner:",e)}const t=document.createElement("div");t.id="admin-coach-tours-launcher",document.body.appendChild(t);try{(0,l.render)((0,m.jsx)(Z,{}),t),console.log("[ACT Pupil] PupilLauncher rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering PupilLauncher:",e)}const n=window.top||window,r=new URLSearchParams(n.location.search).get("act_tour");if(console.log("[ACT Pupil] URL search:",n.location.search),console.log("[ACT Pupil] act_tour param:",r),r){const e=parseInt(r,10);console.log("[ACT Pupil] Will fetch and start tour:",e);const t=(0,a.select)(rn).getTour(e);console.log("[ACT Pupil] Initial tour state:",t);const n=(0,a.subscribe)(()=>{const t=(0,a.select)(rn),r=t.getTour(e),o=t.isToursLoading();r&&!o&&(console.log("[ACT Pupil] Tour loaded:",r),console.log("[ACT Pupil] Tour steps:",r.steps,"count:",r.steps?.length),n(),(0,a.dispatch)(rn).startTour(e))});setTimeout(()=>{console.log("[ACT Pupil] Timeout reached, unsubscribing"),n()},1e4)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",on):on()})(); \ No newline at end of file diff --git a/build/settings.asset.php b/build/settings.asset.php index d22e62d..75e46fe 100644 --- a/build/settings.asset.php +++ b/build/settings.asset.php @@ -1 +1 @@ - array(), 'version' => '50e337b04598bcc38057'); + array(), 'version' => 'cc7122902a5a681b58ce'); diff --git a/composer.json b/composer.json index b796ab1..85111a4 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ } ], "require": { - "php": ">=8.3" + "php": ">=8.3", + "soderlind/wordpress-github-updater": "^2.0" }, "require-dev": { "brain/monkey": "^2.6", @@ -44,4 +45,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index f6bddad..e4520f4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,107 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8c1779be404e6ef79bddf008ed4edddd", - "packages": [], + "content-hash": "579e1b471411a525492837aa45d5819c", + "packages": [ + { + "name": "soderlind/wordpress-github-updater", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/soderlind/wordpress-plugin-gitHub-updater.git", + "reference": "9db855e8f49ed7d70688c9801e809137a141f182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/soderlind/wordpress-plugin-gitHub-updater/zipball/9db855e8f49ed7d70688c9801e809137a141f182", + "reference": "9db855e8f49ed7d70688c9801e809137a141f182", + "shasum": "" + }, + "require": { + "php": ">=7.4", + "yahnis-elsts/plugin-update-checker": "^5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Soderlind\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Per Soderlind", + "homepage": "https://soderlind.no" + } + ], + "description": "Reusable GitHub updater helper for WordPress plugins, built on top of yahnis-elsts/plugin-update-checker", + "homepage": "https://github.com/soderlind/wordpress-plugin-github-updater", + "keywords": [ + "github", + "plugin", + "updater", + "wordpress" + ], + "support": { + "issues": "https://github.com/soderlind/wordpress-plugin-github-updater/issues", + "source": "https://github.com/soderlind/wordpress-plugin-github-updater" + }, + "time": "2026-04-05T15:53:28+00:00" + }, + { + "name": "yahnis-elsts/plugin-update-checker", + "version": "v5.7", + "source": { + "type": "git", + "url": "https://github.com/YahnisElsts/plugin-update-checker.git", + "reference": "275a96a2a18d03c34c87f35cb68673c8c49ac3b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/YahnisElsts/plugin-update-checker/zipball/275a96a2a18d03c34c87f35cb68673c8c49ac3b1", + "reference": "275a96a2a18d03c34c87f35cb68673c8c49ac3b1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6.20" + }, + "type": "library", + "autoload": { + "files": [ + "load-v5p7.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Yahnis Elsts", + "email": "whiteshadow@w-shadow.com", + "homepage": "https://w-shadow.com/", + "role": "Developer" + } + ], + "description": "A custom update checker for WordPress plugins and themes. Useful if you can't host your plugin in the official WP repository but still want it to support automatic updates.", + "homepage": "https://github.com/YahnisElsts/plugin-update-checker/", + "keywords": [ + "automatic updates", + "plugin updates", + "theme updates", + "wordpress" + ], + "support": { + "issues": "https://github.com/YahnisElsts/plugin-update-checker/issues", + "source": "https://github.com/YahnisElsts/plugin-update-checker/tree/v5.7" + }, + "time": "2026-05-26T12:48:45+00:00" + } + ], "packages-dev": [ { "name": "antecedent/patchwork", @@ -2416,5 +2515,5 @@ "php": ">=8.3" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/docs/AI-ARCHITECTURE.md b/docs/AI-ARCHITECTURE.md index 6b23268..41b7139 100644 --- a/docs/AI-ARCHITECTURE.md +++ b/docs/AI-ARCHITECTURE.md @@ -20,17 +20,23 @@ This document explains how Admin Coach Tours uses AI to generate interactive tut ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Redux Store (requestAiTour) │ -│ • Dispatches REQUEST_AI_TOUR with context │ -│ • Passes failureContext on retry for AI learning │ +│ • Dispatches REQUEST_AI_TOUR with context (+ locale, failureContext) │ +│ • Task list fetched via the store control (fetchAiTasks → /ai/tasks) │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ REST API: POST /ai/generate-tour │ -│ (AiController.php) │ -│ • Sanitizes inputs │ -│ • Checks cache (skips on retry) │ -│ • Builds system prompt │ +│ AiController.php (thin HTTP adapter) │ +│ • TourRequest::from_rest() sanitizes the full input surface │ +│ • Delegates to TourGenerator; maps results/errors to HTTP │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ TourGenerator (deep module) │ +│ • Cache lookup (skips on retry) │ +│ • Retrieval (RAG) + prompt assembly + validation │ └─────────────────────────────────────────────────────────────────────────────┘ │ ┌─────────────────┼─────────────────┐ @@ -45,10 +51,10 @@ This document explains how Admin Coach Tours uses AI to generate interactive tut │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ -│ AI Provider (generate_tour) │ -│ • OpenAI (gpt-4o) │ -│ • Azure OpenAI │ -│ • Anthropic (claude-3-5-sonnet) │ +│ WordPress AI Connector (via AiManager) │ +│ • wp_get_connectors() detects configured AI provider connectors │ +│ • wp_ai_client_prompt(...)->generate_text() performs generation │ +│ • Provider/model resolved from options + filters │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ @@ -69,47 +75,51 @@ This document explains how Admin Coach Tours uses AI to generate interactive tut ## Components -### 1. AI Providers (`php/AI/`) +### 1. AI Connector & backend modules (`php/AI/`, `php/Rest/`) -The plugin supports multiple AI providers through a common interface: +As of 0.5.0 the plugin no longer bundles its own providers. It uses the +**WordPress 7 AI Connector** — the site owner configures an AI provider once in +WordPress, and the plugin calls it without managing API keys. -| Provider | Class | Model | -|----------|-------|-------| -| OpenAI | `OpenAiProvider.php` | gpt-4o | -| Azure OpenAI | `AzureOpenAiProvider.php` | Configurable | -| Anthropic | `AnthropicProvider.php` | claude-3-5-sonnet | +| Module | Responsibility | +|--------|----------------| +| `AiManager` | Thin orchestrator over `wp_get_connectors()` / `wp_ai_client_prompt()`. Detects configured connectors, resolves provider/model, calls `generate_text()`, parses JSON (tolerating code fences), validates output. | +| `TourRequest` | Immutable value object; `from_rest()` owns all input sanitization (including `editorContext`/`failureContext`). | +| `TourGenerator` | Deep module `generate(TourRequest): array\|WP_Error` — owns caching, RAG retrieval, prompt assembly, the AI call, and validation. | +| `TourSchema` | Single source of truth for the allowed locator / precondition / completion types, shared by the prompt schema and output validation. | +| `AiController` | Thin HTTP adapter: build `TourRequest`, call `TourGenerator`, map results/errors to HTTP. | -All providers implement `AiProviderInterface` with the key method: +**Availability:** AI is available when `act_ai_enabled` is on *and* at least one +AI provider connector is configured. Provider/model selection is resolved via +the `act_ai_provider` / `act_ai_model` options, overridable with filters (see +[Extending the AI](#extending-the-ai)). ```php -public function generate_tour(string $system_prompt, string $user_message = ''): array|\WP_Error; -``` - -**Provider Registration:** -```php -// Custom providers can be added via filter -add_filter('act_ai_providers', function($providers) { - $providers['my-provider'] = new MyCustomProvider(); - return $providers; -}); +// Generation happens through the WordPress AI client: +$text = wp_ai_client_prompt( $user_message ) + ->using_system_instruction( $system_prompt ) + ->using_temperature( 0.7 ) + ->using_provider( $connector_id ) // optional + ->generate_text(); ``` ### 2. Task Prompts (`php/AI/TaskPrompts.php`) -Defines 12 predefined tasks with optimized prompts: +Defines the predefined tasks with optimized prompts. Categories are displayed +Text-first in the launcher: | Category | Tasks | |----------|-------| -| Media | add-image, add-video, add-gallery, add-cover | -| Text | add-heading, create-list, add-quote, add-table, format-text | -| Design | add-button, add-columns | -| Embed | embed-youtube | +| Text & Content | add-paragraph, add-heading, create-list, add-quote, add-table, format-text, add-code, add-separator, add-details | +| Media | add-image, add-video, add-gallery, add-cover, add-audio, add-file | +| Design & Layout | add-button, add-columns, add-group, add-spacer | +| Embed | embed-youtube, embed-url | Each task includes: - **ID**: Unique identifier - **Label**: Human-readable name - **Category**: For UI grouping -- **Task-specific instructions**: Optimized step patterns +- **Task-specific instructions**: Optimized step patterns (falls back to a generic "/" quick-inserter pattern when a task has none) ### 3. Gutenberg Knowledge Base (`php/AI/GutenbergKnowledgeBase.php`) @@ -315,31 +325,33 @@ REQUIREMENTS FOR THIS RETRY: 5. Backend includes failure context in AI prompt 6. AI generates tour avoiding the failed selectors +**Divergence abort:** if a step expects a specific block but the user inserted a +different one (e.g. asked for an Image, added a Gallery), retrying can't succeed. +`TourRunner` detects this and stops the tour with a clear message instead of the +"Try Again" loop. + ## Caching Tours are cached to avoid redundant API calls: -- **Cache Key**: Hash of (taskId, query, postType, editorContext version) -- **TTL**: 1 hour (3600 seconds) -- **Cache Invalidation**: Version key changes when prompts are updated -- **Skip Cache**: When retry includes failure context +- **Cache Key**: Hash of (taskId, query, postType, cache-significant editorContext, version) +- **TTL**: 24 hours (`DAY_IN_SECONDS`), filterable via `admin_coach_tours_cache_expiration` +- **Cache Invalidation**: `TourGenerator::CACHE_VERSION` is bumped when prompts change +- **Skip Cache**: When the request includes failure context (a contextual retry) + +Caching lives in `TourGenerator` (not the REST controller): ```php -// Cache key generation -$cache_key = 'act_tour_' . md5(serialize([ - $task_id, - $query, - $post_type, - self::get_cache_version() -])); +// Cache key is built from the cache-significant parts of the TourRequest +$cache_key = 'act_tour_' . md5( wp_json_encode( $key_data ) ); ``` ## Security ### API Key Storage -- Keys encrypted with libsodium before storage -- `Encryption` class handles encrypt/decrypt -- Keys never logged or exposed in errors +- API keys are **owned by the WordPress AI connector**, not this plugin +- The plugin never stores, encrypts, logs, or exposes provider keys +- Legacy encrypted-key options from earlier versions are removed on upgrade/uninstall ### Input Sanitization - All user inputs sanitized before use @@ -352,22 +364,20 @@ $cache_key = 'act_tour_' . md5(serialize([ ## Extending the AI -### Custom Provider +### Provider / model selection + +Providers are configured in WordPress as AI connectors. This plugin only +chooses which configured connector to use, overridable via filters: ```php -add_filter('act_ai_providers', function($providers) { - $providers['my-provider'] = new class implements AiProviderInterface { - public function get_id(): string { return 'my-provider'; } - public function get_name(): string { return 'My Provider'; } - public function is_configured(): bool { /* check config */ } - public function generate_tour(string $system_prompt, string $user_message = ''): array|\WP_Error { - // Call your AI API - // Return ['title' => '...', 'steps' => [...]] - } - // ... implement other interface methods - }; - return $providers; -}); +// Force a specific connector (provider) ID; '' = auto (first configured). +add_filter('admin_coach_tours_ai_provider_id', fn() => 'azure-ai-foundry'); + +// Force a specific model ID; '' = provider default. +add_filter('admin_coach_tours_ai_model', fn() => 'gpt-4.1'); + +// Override connector-availability detection (return bool, or null to defer). +add_filter('admin_coach_tours_ai_connector_configured', fn() => true); ``` ### Custom Tasks @@ -403,17 +413,16 @@ add_filter('act_gutenberg_knowledge', function($data) { | File | Purpose | |------|---------| -| `php/AI/AiManager.php` | Singleton managing provider registration and selection | -| `php/AI/AiProviderInterface.php` | Contract for AI providers | -| `php/AI/OpenAiProvider.php` | OpenAI implementation | -| `php/AI/AzureOpenAiProvider.php` | Azure OpenAI implementation | -| `php/AI/AnthropicProvider.php` | Anthropic Claude implementation | +| `php/AI/AiManager.php` | Orchestrator over the WordPress AI connector (`wp_get_connectors` / `wp_ai_client_prompt`) | +| `php/AI/TourRequest.php` | Immutable, sanitized tour-generation request (`from_rest`) | +| `php/AI/TourGenerator.php` | Deep module: cache, RAG, prompt assembly, AI call, validation | +| `php/AI/TourSchema.php` | Allowed locator/precondition/completion types (single source) | | `php/AI/TaskPrompts.php` | Task definitions and system prompts | | `php/AI/GutenbergKnowledgeBase.php` | RAG context from static docs | | `php/AI/data/gutenberg-blocks.json` | Block and UI element definitions | -| `php/Rest/AiController.php` | REST API endpoints for AI | +| `php/Rest/AiController.php` | Thin HTTP adapter for the AI REST endpoints | | `assets/js/runtime/gatherEditorContext.js` | Frontend context gathering | -| `assets/js/store/actions.js` | Redux actions including `requestAiTour` | +| `assets/js/store/actions.js` | Redux actions including `requestAiTour` and `fetchAiTasks` | | `assets/js/store/controls.js` | Redux controls for API calls | | `assets/js/pupil/PupilLauncher.jsx` | UI for requesting tours | | `assets/js/pupil/TourRunner.jsx` | Tour execution and failure handling | diff --git a/docs/PLAN.md b/docs/PLAN.md index fb7e346..1af493f 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1,5 +1,12 @@ ## Plan: Admin Coach Tours MVP WordPress Plugin +> **Historical document.** This is the original MVP plan and does not reflect the +> current architecture. Notably, AI generation now uses the WordPress 7 AI +> Connector (`wp_get_connectors` / `wp_ai_client_prompt`) instead of bundled +> providers with sodium-encrypted API keys, and the minimum requirement is +> WordPress 7.0+. For the current design see [AI-ARCHITECTURE.md](AI-ARCHITECTURE.md) +> and the [CHANGELOG](../CHANGELOG.md). + Build a WordPress plugin enabling Educators to author interactive tours in Gutenberg and Pupils to run them with guided overlays, completion gating, and AI-assisted step drafting. Uses `@wordpress/data` store, captures locator bundles up to 3 ancestor levels prioritizing `data-*`/`id`, includes minimal AI settings page with sodium-encrypted keys, and REST-level tour scope filtering. **Plugin:** Admin Coach Tours | **Slug:** `admin-coach-tours` diff --git a/package-lock.json b/package-lock.json index fe9fab4..1964d84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "admin-coach-tours", - "version": "0.3.2", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "admin-coach-tours", - "version": "0.3.2", + "version": "0.5.0", "license": "GPL-2.0-or-later", "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -25456,7 +25456,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -26065,6 +26065,24 @@ } } }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index efa25c1..370f434 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "admin-coach-tours", - "version": "0.4.1", + "version": "0.5.0", "description": "Interactive guided tours for WordPress admin", "author": "Per Soderlind", "license": "GPL-2.0-or-later", @@ -56,4 +56,4 @@ "node": ">=20.0.0", "npm": ">=10.0.0" } -} \ No newline at end of file +} diff --git a/php/AI/AiManager.php b/php/AI/AiManager.php index aa6e15d..91d98ac 100644 --- a/php/AI/AiManager.php +++ b/php/AI/AiManager.php @@ -2,7 +2,9 @@ /** * AI Manager. * - * Manages AI providers and orchestrates AI operations. + * Thin orchestrator around the WordPress AI connector API + * (wp_get_connectors / wp_ai_client_prompt). API keys and provider + * configuration are owned by WordPress Core connectors, not this plugin. * * @package AdminCoachTours * @since 0.1.0 @@ -12,33 +14,31 @@ namespace AdminCoachTours\AI; -use AdminCoachTours\Security\Encryption; - /** * AI Manager class. */ class AiManager { /** - * Option name for AI settings. + * Option: whether AI features are enabled. * * @var string */ - private const OPTION_NAME = 'act_ai_settings'; + private const OPTION_ENABLED = 'act_ai_enabled'; /** - * Registered providers. + * Option: preferred connector (provider) ID. Empty = auto. * - * @var array + * @var string */ - private array $providers = []; + private const OPTION_PROVIDER = 'act_ai_provider'; /** - * Encryption helper. + * Option: preferred model ID override. Empty = provider default. * - * @var Encryption|null + * @var string */ - private ?Encryption $encryption = null; + private const OPTION_MODEL = 'act_ai_model'; /** * Singleton instance. @@ -62,271 +62,637 @@ public static function get_instance(): self { /** * Constructor. */ - private function __construct() { - $this->encryption = new Encryption(); - $this->register_default_providers(); - } + private function __construct() {} /** - * Register default AI providers. + * Check if AI features are enabled and a connector is configured. + * + * @return bool True if AI is available. */ - private function register_default_providers(): void { - $this->register_provider( new OpenAiProvider( $this->encryption ) ); - $this->register_provider( new AzureOpenAiProvider( $this->encryption ) ); - $this->register_provider( new AnthropicProvider( $this->encryption ) ); + public function is_available(): bool { + if ( ! (bool) get_option( self::OPTION_ENABLED, false ) ) { + return false; + } + + return $this->is_connector_configured(); + } + /** + * Check whether at least one usable AI provider connector exists. + * + * @return bool True if a configured connector is available. + */ + public function is_connector_configured(): bool { /** - * Filter to allow registering additional AI providers. + * Short-circuit connector detection. * - * @since 0.1.0 - * @param AiManager $manager The AI manager instance. + * @since 0.5.0 + * @param bool|null $configured Return a boolean to override detection, or null to let the plugin decide. */ - do_action( 'admin_coach_tours_register_ai_providers', $this ); + $filtered = apply_filters( 'admin_coach_tours_ai_connector_configured', null ); + if ( is_bool( $filtered ) ) { + return $filtered; + } + + return count( $this->get_configured_provider_ids() ) > 0; } /** - * Register an AI provider. + * Get the IDs of all configured AI provider connectors. * - * @param AiProviderInterface $provider Provider instance. + * @return array List of connector IDs. */ - public function register_provider( AiProviderInterface $provider ): void { - $this->providers[ $provider->get_id()] = $provider; + public function get_configured_provider_ids(): array { + if ( function_exists( 'wp_supports_ai' ) && ! wp_supports_ai() ) { + return []; + } + + if ( ! function_exists( 'wp_get_connectors' ) ) { + return []; + } + + try { + $connectors = wp_get_connectors(); + } catch ( \Throwable $e ) { + return []; + } + + $provider_ids = []; + + foreach ( (array) $connectors as $connector_id => $connector ) { + if ( ! is_array( $connector ) ) { + continue; + } + + if ( ( $connector[ 'type' ] ?? '' ) !== 'ai_provider' ) { + continue; + } + + $auth = $connector[ 'authentication' ] ?? []; + if ( ! is_array( $auth ) ) { + continue; + } + + $method = $auth[ 'method' ] ?? ''; + + if ( 'none' === $method ) { + $provider_ids[] = (string) $connector_id; + continue; + } + + if ( 'api_key' !== $method ) { + continue; + } + + $setting_name = (string) ( $auth[ 'setting_name' ] ?? '' ); + if ( '' === $setting_name ) { + continue; + } + + $env_var_name = (string) ( $auth[ 'env_var_name' ] ?? '' ); + $constant_name = (string) ( $auth[ 'constant_name' ] ?? '' ); + + if ( function_exists( '_wp_connectors_get_api_key_source' ) ) { + $source = _wp_connectors_get_api_key_source( $setting_name, $env_var_name, $constant_name ); + if ( 'none' !== $source ) { + $provider_ids[] = (string) $connector_id; + } + } + } + + return array_values( + array_unique( + array_filter( $provider_ids, static fn( string $id ): bool => '' !== $id ) + ) + ); } /** - * Get all registered providers. + * Get configured connectors as an id => label map (for the settings UI). * - * @return array + * @return array Map of connector ID to human-readable label. */ - public function get_providers(): array { - return $this->providers; + public function get_configured_connectors(): array { + $ids = $this->get_configured_provider_ids(); + + if ( empty( $ids ) || ! function_exists( 'wp_get_connectors' ) ) { + return []; + } + + try { + $connectors = wp_get_connectors(); + } catch ( \Throwable $e ) { + return []; + } + + $map = []; + + foreach ( $ids as $id ) { + $connector = $connectors[ $id ] ?? null; + $label = $id; + + if ( is_array( $connector ) ) { + $label = (string) ( $connector[ 'label' ] ?? $connector[ 'name' ] ?? $id ); + } + + $map[ $id ] = '' !== $label ? $label : $id; + } + + return $map; } /** - * Get a specific provider. + * Resolve the provider (connector) ID to use for generation. * - * @param string $provider_id Provider ID. - * @return AiProviderInterface|null Provider or null. + * @return string Connector ID, or empty string for automatic selection. */ - public function get_provider( string $provider_id ): ?AiProviderInterface { - return $this->providers[ $provider_id ] ?? null; + public function resolve_provider_id(): string { + /** + * Filter the AI provider (connector) ID. + * + * @since 0.5.0 + * @param string $provider_id Provider ID. Empty = use stored option / auto. + */ + $filtered = (string) apply_filters( 'admin_coach_tours_ai_provider_id', '' ); + if ( '' !== $filtered ) { + return $filtered; + } + + $ids = $this->get_configured_provider_ids(); + $stored = (string) get_option( self::OPTION_PROVIDER, '' ); + + if ( '' !== $stored && in_array( $stored, $ids, true ) ) { + return $stored; + } + + return $ids[ 0 ] ?? ''; } /** - * Get the active provider. + * Resolve the model ID override to use for generation. * - * @return AiProviderInterface|null Active provider or null. + * @return string Model ID, or empty string for the provider default. */ - public function get_active_provider(): ?AiProviderInterface { - $settings = $this->get_settings(); - $active_id = $settings[ 'active_provider' ] ?? ''; - - if ( $active_id && isset( $this->providers[ $active_id ] ) ) { - $provider = $this->providers[ $active_id ]; - if ( $provider->is_configured() ) { - return $provider; - } + public function resolve_model(): string { + /** + * Filter the AI model ID. + * + * @since 0.5.0 + * @param string $model Model ID. Empty = provider default. + */ + $filtered = (string) apply_filters( 'admin_coach_tours_ai_model', '' ); + if ( '' !== $filtered ) { + return $filtered; } - // Fall back to first configured provider. - foreach ( $this->providers as $provider ) { - if ( $provider->is_configured() ) { - return $provider; - } + return (string) get_option( self::OPTION_MODEL, '' ); + } + + /** + * Generate a step draft using AI. + * + * @param array $element_context Context about the target element. + * @param array $tour_context Context about the tour (optional). + * @return array|\WP_Error Generated draft or error. + */ + public function generate_step_draft( array $element_context, array $tour_context = [] ): array|\WP_Error { + if ( ! $this->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); } - return null; + $system_prompt = $this->build_draft_system_prompt(); + $user_prompt = $this->build_draft_user_prompt( $element_context, $tour_context ); + + $text = $this->run_prompt( $system_prompt, $user_prompt, 0.7, 800 ); + + if ( is_wp_error( $text ) ) { + return $text; + } + + $content = $this->decode_json( $text ); + + if ( null === $content ) { + return new \WP_Error( + 'parse_error', + __( 'Could not parse AI response.', 'admin-coach-tours' ) + ); + } + + return $this->validate_and_sanitize_draft( $content ); } /** - * Get AI settings. + * Generate a complete tour using AI. * - * @return array Settings. + * @param string $system_prompt The system prompt with task instructions. + * @param string $user_message Optional user message for freeform queries. + * @return array|\WP_Error Generated tour with title and steps, or error. */ - public function get_settings(): array { - return [ - 'active_provider' => get_option( 'act_ai_provider', '' ), - 'enabled' => (bool) get_option( 'act_ai_enabled', false ), - ]; + public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { + if ( ! $this->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } + + $user = '' !== $user_message + ? $user_message + : 'Generate the tour now. Return only valid JSON.'; + + $text = $this->run_prompt( $system_prompt, $user, 0.7, 4000 ); + + if ( is_wp_error( $text ) ) { + return $text; + } + + $content = $this->decode_json( $text ); + + if ( null === $content ) { + return new \WP_Error( + 'parse_error', + __( 'Could not parse AI response.', 'admin-coach-tours' ) + ); + } + + // Check for scope error from freeform queries. + if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { + return new \WP_Error( + 'out_of_scope', + $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) + ); + } + + return $this->validate_and_sanitize_tour( $content ); } /** - * Update AI settings. + * Suggest a completion condition based on element context (heuristics, no AI call). * - * @param array $settings Settings to save. - * @return bool True on success. + * @param array $element_context Context about the target element. + * @return array Suggested completion. */ - public function update_settings( array $settings ): bool { - // Sanitize settings. - $sanitized = [ - 'active_provider' => sanitize_key( $settings[ 'active_provider' ] ?? '' ), - 'enabled' => (bool) ( $settings[ 'enabled' ] ?? false ), - 'providers' => [], - ]; + public function suggest_completion( array $element_context ): array { + $tag = $element_context[ 'tagName' ] ?? ''; + $role = $element_context[ 'role' ] ?? ''; + + // Checkbox - suggest click. + if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { + return [ + 'type' => 'clickTarget', + 'params' => [], + ]; + } - // Sanitize provider-specific settings. - if ( isset( $settings[ 'providers' ] ) && is_array( $settings[ 'providers' ] ) ) { - foreach ( $settings[ 'providers' ] as $provider_id => $provider_settings ) { - $provider = $this->get_provider( $provider_id ); - if ( $provider ) { - $schema = $provider->get_settings_schema(); - $sanitized[ 'providers' ][ $provider_id ] = $this->sanitize_provider_settings( - $provider_settings, - $schema - ); - } - } + // Input fields - suggest value change. + if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { + return [ + 'type' => 'domValueChanged', + 'params' => [], + ]; + } + + // Button or link - suggest click. + if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { + return [ + 'type' => 'clickTarget', + 'params' => [], + ]; } - return update_option( self::OPTION_NAME, $sanitized ); + // Default to manual. + return [ + 'type' => 'manual', + 'params' => [], + ]; } /** - * Sanitize provider settings based on schema. + * Run a prompt through the WordPress AI client and return the text output. * - * @param array $settings Provider settings. - * @param array $schema Settings schema. - * @return array Sanitized settings. + * @param string $system_prompt System instruction. + * @param string $user_message User message / prompt. + * @param float $temperature Sampling temperature. + * @param int $max_tokens Maximum output tokens. + * @return string|\WP_Error Generated text or error. */ - private function sanitize_provider_settings( array $settings, array $schema ): array { - $sanitized = []; + private function run_prompt( string $system_prompt, string $user_message, float $temperature, int $max_tokens ): string|\WP_Error { + if ( ! function_exists( 'wp_ai_client_prompt' ) ) { + return new \WP_Error( + 'ai_unavailable', + __( 'The WordPress AI client is not available.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } - foreach ( $schema as $key => $config ) { - if ( ! isset( $settings[ $key ] ) ) { - if ( isset( $config[ 'default' ] ) ) { - $sanitized[ $key ] = $config[ 'default' ]; - } - continue; - } + try { + $builder = wp_ai_client_prompt( $user_message ); - $value = $settings[ $key ]; - $type = $config[ 'type' ] ?? 'string'; + if ( method_exists( $builder, 'using_system_instruction' ) ) { + $builder = $this->apply_builder( $builder, 'using_system_instruction', $system_prompt ); + } else { + // No system-instruction support: fold it into the prompt. + $builder = wp_ai_client_prompt( trim( $system_prompt . "\n\n" . $user_message ) ); + } - switch ( $type ) { - case 'string': - $sanitized[ $key ] = sanitize_text_field( $value ); - break; + $builder = $this->apply_builder( $builder, 'using_temperature', $temperature ); + $builder = $this->apply_builder( $builder, 'using_max_tokens', $max_tokens ); - case 'secret': - // Encrypt API keys. - if ( ! empty( $value ) ) { - $sanitized[ $key ] = $this->encryption->encrypt( $value ); - } - break; + $provider_id = $this->resolve_provider_id(); + if ( '' !== $provider_id ) { + $builder = $this->apply_builder( $builder, 'using_provider', $provider_id ); + } - case 'boolean': - $sanitized[ $key ] = (bool) $value; - break; + $model = $this->resolve_model(); + if ( '' !== $model ) { + $builder = $this->apply_builder( $builder, 'using_model', $model ); + } - case 'integer': - $sanitized[ $key ] = (int) $value; - break; + $result = $builder->generate_text(); + } catch ( \Throwable $e ) { + return new \WP_Error( 'api_error', $e->getMessage(), [ 'status' => 502 ] ); + } - case 'float': - $sanitized[ $key ] = (float) $value; - break; + if ( is_wp_error( $result ) ) { + return $result; + } - case 'select': - if ( in_array( $value, $config[ 'options' ] ?? [], true ) ) { - $sanitized[ $key ] = $value; - } elseif ( isset( $config[ 'default' ] ) ) { - $sanitized[ $key ] = $config[ 'default' ]; - } - break; + if ( is_string( $result ) && '' !== trim( $result ) ) { + return $result; + } - default: - $sanitized[ $key ] = sanitize_text_field( $value ); + if ( is_array( $result ) ) { + $candidate = $result[ 'text' ] ?? $result[ 'content' ] ?? ''; + if ( is_string( $candidate ) && '' !== trim( $candidate ) ) { + return $candidate; } } - return $sanitized; + return new \WP_Error( + 'invalid_response', + __( 'The AI client returned an empty response.', 'admin-coach-tours' ), + [ 'status' => 502 ] + ); } /** - * Check if AI is enabled and configured. + * Call a fluent builder method if it exists, returning the resulting builder. * - * @return bool True if AI is available. + * @param object $builder The prompt builder. + * @param string $method Method name. + * @param mixed $value Argument. + * @return object The (possibly new) builder. */ - public function is_available(): bool { - $settings = $this->get_settings(); - - if ( ! ( $settings[ 'enabled' ] ?? false ) ) { - return false; + private function apply_builder( object $builder, string $method, mixed $value ): object { + if ( ! method_exists( $builder, $method ) ) { + return $builder; } - return null !== $this->get_active_provider(); + $result = $builder->{$method}( $value ); + + return is_object( $result ) ? $result : $builder; } /** - * Generate a step draft using AI. + * Decode a JSON string from an AI response, tolerating Markdown code fences. * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour (optional). - * @return array|\WP_Error Generated draft or error. + * @param string $text Raw AI text. + * @return array|null Decoded array, or null on failure. */ - public function generate_step_draft( array $element_context, array $tour_context = [] ): array|\WP_Error { - if ( ! $this->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ) - ); + private function decode_json( string $text ): ?array { + $text = trim( $text ); + + // Strip ```json ... ``` fences if present. + if ( str_starts_with( $text, '```' ) ) { + $text = (string) preg_replace( '/^```(?:json)?\s*/i', '', $text ); + $text = (string) preg_replace( '/\s*```$/', '', $text ); + $text = trim( $text ); } - $provider = $this->get_active_provider(); + $data = json_decode( $text, true ); + if ( is_array( $data ) ) { + return $data; + } - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is configured.', 'admin-coach-tours' ) - ); + // Fallback: extract the first {...} block. + if ( preg_match( '/\{.*\}/s', $text, $matches ) ) { + $data = json_decode( $matches[ 0 ], true ); + if ( is_array( $data ) ) { + return $data; + } } - return $provider->generate_step_draft( $element_context, $tour_context ); + return null; } /** - * Suggest a completion condition. + * Build the system prompt for a single step draft. * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. + * @return string System prompt. */ - public function suggest_completion( array $element_context ): array|\WP_Error { - if ( ! $this->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ) - ); + private function build_draft_system_prompt(): string { + return <<<'PROMPT' +You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. + +Given information about a UI element, generate: +1. A concise title (5-10 words) describing the action +2. Helpful content (1-3 sentences) explaining what to do and why +3. A suggested completion condition type + +Respond ONLY with valid JSON in this exact format: +{ + "title": "Click the Publish button", + "content": "The Publish button makes your content live...", + "suggestedCompletion": { + "type": "clickTarget", + "params": {} + } +} + +Completion types available: +- clickTarget: User must click the highlighted element +- domValueChanged: User must change a form field value +- manual: User clicks continue button +- wpData: Watch for WordPress data store changes + +Be friendly but concise. Focus on the action and its purpose. +PROMPT; + } + + /** + * Build the user prompt for a single step draft. + * + * @param array $element_context Element context. + * @param array $tour_context Tour context. + * @return string User prompt. + */ + private function build_draft_user_prompt( array $element_context, array $tour_context ): string { + $parts = [ 'Generate step content for this UI element:' ]; + + $parts[] = 'Element: ' . wp_json_encode( $element_context ); + + if ( ! empty( $tour_context ) ) { + $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); } - $provider = $this->get_active_provider(); + return implode( "\n\n", $parts ); + } - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is configured.', 'admin-coach-tours' ) - ); + /** + * Validate and sanitize a single step draft. + * + * @param array $content Raw content from AI. + * @return array Sanitized draft. + */ + private function validate_and_sanitize_draft( array $content ): array { + $draft = [ + 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), + 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), + ]; + + if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { + $completion = $content[ 'suggestedCompletion' ]; + $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; + + if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { + $draft[ 'suggestedCompletion' ] = [ + 'type' => $completion[ 'type' ], + 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], + ]; + } } - return $provider->suggest_completion( $element_context ); + return $draft; } /** - * Get providers info for settings page. + * Validate and sanitize a generated tour. * - * @return array Provider info. + * @param array $content Raw tour content from AI. + * @return array|\WP_Error Sanitized tour or error. */ - public function get_providers_info(): array { - $info = []; - - foreach ( $this->providers as $id => $provider ) { - $info[ $id ] = [ - 'id' => $id, - 'name' => $provider->get_name(), - 'configured' => $provider->is_configured(), - 'schema' => $provider->get_settings_schema(), + private function validate_and_sanitize_tour( array $content ): array|\WP_Error { + if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { + return new \WP_Error( + 'invalid_tour_format', + __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) + ); + } + + if ( empty( $content[ 'steps' ] ) ) { + return new \WP_Error( + 'empty_tour', + __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) + ); + } + + $tour = [ + 'title' => sanitize_text_field( $content[ 'title' ] ), + 'steps' => [], + ]; + + $allowed_completion_types = TourSchema::COMPLETION_TYPES; + + $allowed_precondition_types = TourSchema::PRECONDITION_TYPES; + + $allowed_locator_types = TourSchema::LOCATOR_TYPES; + + foreach ( $content[ 'steps' ] as $index => $step ) { + $sanitized_step = [ + 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), + 'order' => (int) ( $step[ 'order' ] ?? $index ), + 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), + 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), + 'target' => [ + 'locators' => [], + 'constraints' => [ + 'visible' => true, + ], + ], + 'preconditions' => [], + 'completion' => [ + 'type' => 'manual', + ], ]; + + // Process locators. + if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { + foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { + if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { + continue; + } + + if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { + continue; + } + + $sanitized_step[ 'target' ][ 'locators' ][] = [ + 'type' => $locator[ 'type' ], + 'value' => sanitize_text_field( $locator[ 'value' ] ), + 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), + 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), + ]; + } + } + + // Process constraints. + if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { + $constraints = $step[ 'target' ][ 'constraints' ]; + $sanitized_step[ 'target' ][ 'constraints' ] = [ + 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), + 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), + ]; + } + + // Process preconditions. + if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { + foreach ( $step[ 'preconditions' ] as $precondition ) { + if ( ! isset( $precondition[ 'type' ] ) ) { + continue; + } + + if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { + continue; + } + + $sanitized_precondition = [ + 'type' => $precondition[ 'type' ], + ]; + + if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { + $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); + } + + $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; + } + } + + // Process completion. + if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { + $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; + + if ( in_array( $completion_type, $allowed_completion_types, true ) ) { + $sanitized_step[ 'completion' ] = [ + 'type' => $completion_type, + ]; + + if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { + $sanitized_step[ 'completion' ][ 'params' ] = array_map( + 'sanitize_text_field', + $step[ 'completion' ][ 'params' ] + ); + } + } + } + + $tour[ 'steps' ][] = $sanitized_step; } - return $info; + return $tour; } } diff --git a/php/AI/AiProviderInterface.php b/php/AI/AiProviderInterface.php deleted file mode 100644 index 5120a73..0000000 --- a/php/AI/AiProviderInterface.php +++ /dev/null @@ -1,82 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'Anthropic (Claude)', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - return ! empty( $api_key ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the model to use. - * - * @return string Model name. - */ - private function get_model(): string { - $model = get_option( 'act_ai_' . self::ID . '_model', '' ); - return ! empty( $model ) ? $model : 'claude-3-haiku-20240307'; - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'Anthropic API key is not configured.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 30, - 'headers' => [ - 'x-api-key' => $api_key, - 'anthropic-version' => self::API_VERSION, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'system' => $system_prompt, - 'messages' => [ - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'max_tokens' => 500, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'content' ][ 0 ][ 'text' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Anthropic.', 'admin-coach-tours' ) - ); - } - - // Extract JSON from response. - $text = $data[ 'content' ][ 0 ][ 'text' ]; - $json_match = []; - - if ( preg_match( '/\{[^{}]*\}/s', $text, $json_match ) ) { - $content = json_decode( $json_match[ 0 ], true ); - } else { - $content = json_decode( $text, true ); - } - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics for simplicity. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your Anthropic API key.', 'admin-coach-tours' ), - 'required' => true, - ], - 'model' => [ - 'type' => 'select', - 'label' => __( 'Model', 'admin-coach-tours' ), - 'description' => __( 'The Claude model to use.', 'admin-coach-tours' ), - 'options' => [ - 'claude-3-haiku-20240307', - 'claude-3-sonnet-20240229', - 'claude-3-opus-20240229', - 'claude-3-5-sonnet-20241022', - ], - 'default' => 'claude-3-haiku-20240307', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - // Basic format check. - if ( ! str_starts_with( $settings[ 'api_key' ], 'sk-ant-' ) ) { - return new \WP_Error( - 'invalid_api_key', - __( 'Invalid Anthropic API key format.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'Anthropic API key is not configured.', 'admin-coach-tours' ) - ); - } - - $user_content = ! empty( $user_message ) - ? $user_message - : 'Generate the tour now. Return only valid JSON.'; - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'x-api-key' => $api_key, - 'anthropic-version' => self::API_VERSION, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'system' => $system_prompt, - 'messages' => [ - [ - 'role' => 'user', - 'content' => $user_content, - ], - ], - 'max_tokens' => 4000, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'content' ][ 0 ][ 'text' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Anthropic.', 'admin-coach-tours' ) - ); - } - - // Extract JSON from response - Claude may wrap it in markdown code blocks. - $text = $data[ 'content' ][ 0 ][ 'text' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from Anthropic: ' . $text ); - - // Try to extract JSON from code blocks first. - if ( preg_match( '/```(?:json)?\s*(\{[\s\S]*?\})\s*```/', $text, $json_match ) ) { - $content = json_decode( $json_match[ 1 ], true ); - } elseif ( preg_match( '/(\{[\s\S]*\})/', $text, $json_match ) ) { - // Fall back to finding any JSON object. - $content = json_decode( $json_match[ 1 ], true ); - } else { - $content = json_decode( $text, true ); - } - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/AI/AzureOpenAiProvider.php b/php/AI/AzureOpenAiProvider.php deleted file mode 100644 index 5d020fb..0000000 --- a/php/AI/AzureOpenAiProvider.php +++ /dev/null @@ -1,683 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'Azure OpenAI', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - return ! empty( $api_key ) && ! empty( $endpoint ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the Azure endpoint URL. - * - * @return string|null Endpoint URL or null. - */ - private function get_endpoint(): ?string { - $endpoint = get_option( 'act_ai_' . self::ID . '_endpoint', '' ); - return ! empty( $endpoint ) ? $endpoint : null; - } - - /** - * Get the deployment name. - * - * @return string Deployment name. - */ - private function get_deployment(): string { - $deployment = get_option( 'act_ai_' . self::ID . '_deployment', '' ); - return ! empty( $deployment ) ? $deployment : 'gpt-4o-mini'; - } - - /** - * Get the API version. - * - * @return string API version. - */ - private function get_api_version(): string { - $api_version = get_option( 'act_ai_' . self::ID . '_api_version', '' ); - return ! empty( $api_version ) ? $api_version : '2024-08-01-preview'; - } - - /** - * Build the API URL. - * - * @return string Full API URL. - */ - private function build_api_url(): string { - $endpoint = rtrim( $this->get_endpoint() ?? '', '/' ); - $deployment = $this->get_deployment(); - $version = $this->get_api_version(); - - return sprintf( - '%s/openai/deployments/%s/chat/completions?api-version=%s', - $endpoint, - rawurlencode( $deployment ), - rawurlencode( $version ) - ); - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - - if ( ! $api_key || ! $endpoint ) { - return new \WP_Error( - 'not_configured', - __( 'Azure OpenAI is not fully configured. Please set both the API key and endpoint.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - $this->build_api_url(), - [ - 'timeout' => 30, - 'headers' => [ - 'api-key' => $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 500, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Azure OpenAI.', 'admin-coach-tours' ) - ); - } - - $content = json_decode( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ], true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics instead of AI for this simple case. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - // Button or link - suggest click. - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Input fields - suggest value change. - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - // Checkbox - suggest click. - if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Default to manual. - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'endpoint' => [ - 'type' => 'text', - 'label' => __( 'Endpoint URL', 'admin-coach-tours' ), - 'description' => __( 'Your Azure OpenAI resource endpoint (e.g., https://your-resource.openai.azure.com).', 'admin-coach-tours' ), - 'required' => true, - ], - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your Azure OpenAI API key.', 'admin-coach-tours' ), - 'required' => true, - 'sensitive' => true, - ], - 'deployment' => [ - 'type' => 'text', - 'label' => __( 'Deployment Name', 'admin-coach-tours' ), - 'description' => __( 'The name of your model deployment in Azure.', 'admin-coach-tours' ), - 'default' => 'gpt-4o-mini', - ], - 'api_version' => [ - 'type' => 'text', - 'label' => __( 'API Version', 'admin-coach-tours' ), - 'description' => __( 'The Azure OpenAI API version to use (e.g., 2024-08-01-preview).', 'admin-coach-tours' ), - 'default' => '2024-08-01-preview', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - if ( empty( $settings[ 'endpoint' ] ) ) { - return new \WP_Error( - 'missing_endpoint', - __( 'Endpoint URL is required.', 'admin-coach-tours' ) - ); - } - - // Validate endpoint URL format. - if ( ! filter_var( $settings[ 'endpoint' ], FILTER_VALIDATE_URL ) ) { - return new \WP_Error( - 'invalid_endpoint', - __( 'Endpoint must be a valid URL.', 'admin-coach-tours' ) - ); - } - - // Ensure endpoint uses HTTPS. - if ( ! str_starts_with( $settings[ 'endpoint' ], 'https://' ) ) { - return new \WP_Error( - 'insecure_endpoint', - __( 'Endpoint must use HTTPS.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - - if ( ! $api_key || ! $endpoint ) { - return new \WP_Error( - 'not_configured', - __( 'Azure OpenAI is not configured.', 'admin-coach-tours' ) - ); - } - - $user_content = ! empty( $user_message ) - ? $user_message - : 'Generate the tour now. Return only valid JSON.'; - - $response = wp_remote_post( - $this->build_api_url(), - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'api-key' => $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_content, - ], - ], - 'max_tokens' => 4000, - 'response_format' => [ 'type' => 'json_object' ], - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Azure OpenAI.', 'admin-coach-tours' ) - ); - } - - $raw_content = $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from Azure OpenAI: ' . $raw_content ); - - $content = json_decode( $raw_content, true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/AI/OpenAiProvider.php b/php/AI/OpenAiProvider.php deleted file mode 100644 index 6f03169..0000000 --- a/php/AI/OpenAiProvider.php +++ /dev/null @@ -1,633 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'OpenAI', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - return ! empty( $api_key ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the model to use. - * - * @return string Model name. - */ - private function get_model(): string { - $model = get_option( 'act_ai_' . self::ID . '_model', '' ); - return ! empty( $model ) ? $model : 'gpt-4o-mini'; - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'OpenAI API key is not configured.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 30, - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 500, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from OpenAI.', 'admin-coach-tours' ) - ); - } - - $content = json_decode( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ], true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics instead of AI for this simple case. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - // Button or link - suggest click. - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Input fields - suggest value change. - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - // Checkbox - suggest click. - if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Default to manual. - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your OpenAI API key.', 'admin-coach-tours' ), - 'required' => true, - ], - 'model' => [ - 'type' => 'select', - 'label' => __( 'Model', 'admin-coach-tours' ), - 'description' => __( 'The OpenAI model to use.', 'admin-coach-tours' ), - 'options' => [ 'gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo' ], - 'default' => 'gpt-4o-mini', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - // Basic format check. - if ( ! str_starts_with( $settings[ 'api_key' ], 'sk-' ) ) { - return new \WP_Error( - 'invalid_api_key', - __( 'Invalid API key format.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'OpenAI API key is not configured.', 'admin-coach-tours' ) - ); - } - - $messages = [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - ]; - - // Add user message for freeform queries. - if ( ! empty( $user_message ) ) { - $messages[] = [ - 'role' => 'user', - 'content' => $user_message, - ]; - } else { - // For predefined tasks, just request the tour. - $messages[] = [ - 'role' => 'user', - 'content' => 'Generate the tour now. Return only valid JSON.', - ]; - } - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'messages' => $messages, - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 4000, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from OpenAI.', 'admin-coach-tours' ) - ); - } - - $raw_content = $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from OpenAI: ' . $raw_content ); - - $content = json_decode( $raw_content, true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/AI/TaskPrompts.php b/php/AI/TaskPrompts.php index e63f18b..8dfc3e3 100644 --- a/php/AI/TaskPrompts.php +++ b/php/AI/TaskPrompts.php @@ -24,6 +24,13 @@ class TaskPrompts { */ public static function get_tasks(): array { $tasks = [ + [ + 'id' => 'add-paragraph', + 'label' => __( 'Add a paragraph', 'admin-coach-tours' ), + 'icon' => 'editor-paragraph', + 'category' => 'text', + 'description' => __( 'Learn how to add a paragraph block and write text.', 'admin-coach-tours' ), + ], [ 'id' => 'add-image', 'label' => __( 'Add an image', 'admin-coach-tours' ), @@ -108,6 +115,62 @@ public static function get_tasks(): array { 'category' => 'media', 'description' => __( 'Learn how to add a cover image with text overlay.', 'admin-coach-tours' ), ], + [ + 'id' => 'add-code', + 'label' => __( 'Add a code block', 'admin-coach-tours' ), + 'icon' => 'editor-code', + 'category' => 'text', + 'description' => __( 'Learn how to add a block for displaying code.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-separator', + 'label' => __( 'Add a separator', 'admin-coach-tours' ), + 'icon' => 'minus', + 'category' => 'text', + 'description' => __( 'Learn how to add a horizontal divider between sections.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-details', + 'label' => __( 'Add a details/accordion', 'admin-coach-tours' ), + 'icon' => 'arrow-down-alt2', + 'category' => 'text', + 'description' => __( 'Learn how to add expandable details (accordion) content.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-audio', + 'label' => __( 'Add audio', 'admin-coach-tours' ), + 'icon' => 'format-audio', + 'category' => 'media', + 'description' => __( 'Learn how to upload or embed an audio clip.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-file', + 'label' => __( 'Add a file download', 'admin-coach-tours' ), + 'icon' => 'media-document', + 'category' => 'media', + 'description' => __( 'Learn how to add a downloadable file.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-group', + 'label' => __( 'Group blocks in a container', 'admin-coach-tours' ), + 'icon' => 'grid-view', + 'category' => 'design', + 'description' => __( 'Learn how to wrap blocks in a group container.', 'admin-coach-tours' ), + ], + [ + 'id' => 'add-spacer', + 'label' => __( 'Add spacing', 'admin-coach-tours' ), + 'icon' => 'image-flip-vertical', + 'category' => 'design', + 'description' => __( 'Learn how to add vertical space between blocks.', 'admin-coach-tours' ), + ], + [ + 'id' => 'embed-url', + 'label' => __( 'Embed from a URL', 'admin-coach-tours' ), + 'icon' => 'admin-links', + 'category' => 'embed', + 'description' => __( 'Learn how to embed external content by pasting a URL.', 'admin-coach-tours' ), + ], ]; /** @@ -216,21 +279,25 @@ private static function get_language_name_from_locale( string $locale ): string /** * Get the system prompt for tour generation. * - * @param string $task_id The task ID or 'freeform' for custom queries. - * @param string $user_query The user's query (for freeform). - * @param string $gutenberg_context RAG context from GutenbergKnowledgeBase. - * @param string $post_type The current post type. - * @param string $editor_context Current editor state (blocks, UI elements). - * @param string $failure_context Context from previous failed attempt (for retry). - * @param string $locale User's WordPress locale for response language. + * @param string $task_id The task ID or 'freeform' for custom queries. + * @param string $user_query The user's query (for freeform). + * @param string $gutenberg_context RAG context from GutenbergKnowledgeBase. + * @param string $post_type The current post type. + * @param array $editor_context Sanitized editor state (blocks, UI elements). + * @param array|null $failure_context Sanitized context from a previous failed attempt. + * @param string $locale User's WordPress locale for response language. * @return string The system prompt. */ - public static function get_system_prompt( string $task_id, string $user_query, string $gutenberg_context, string $post_type, string $editor_context = '', string $failure_context = '', string $locale = '' ): string { + public static function get_system_prompt( string $task_id, string $user_query, string $gutenberg_context, string $post_type, array $editor_context = [], ?array $failure_context = null, string $locale = '' ): string { $task = self::get_task( $task_id ); // Determine the display language based on locale. $language_instruction = self::get_language_instruction( $locale ); + // Format the structured context arrays into prompt sub-sections. + $editor_context_str = ! empty( $editor_context ) ? self::format_editor_context( $editor_context ) : ''; + $failure_context_str = ! empty( $failure_context ) ? self::format_failure_context( $failure_context ) : ''; + // Base system prompt. $system_prompt = << <<<'INST' +Guide the user to add and write in a paragraph block. This is the simplest block — no slash command is needed. + +PATTERN: +- An empty paragraph block is already inserted and SELECTED before the tour starts. +- Step 1: Target the empty paragraph and instruct the user to click into it and start typing their text. + - Completion: manual +- Keep it to a SINGLE step. + +SELECTORS: +- wpBlock: "selected" or .block-editor-rich-text__editable (inEditorIframe = true) + +WORDING: +- Do NOT use positional words like "below" or "above" to refer to the block; the tour panel moves, so they are unreliable and often wrong. Just say "the empty paragraph block". + +Do NOT use the "/" quick inserter for this task — the paragraph already exists. +INST + , + 'add-image' => <<<'INST' Guide the user to add an image using the "/" quick inserter. @@ -601,9 +688,155 @@ private static function get_task_instructions( string $task_id ): string { return $instructions[ $task_id ] ?? 'Create a minimal tour using the "/" quick inserter pattern. Use elementAppear completion to wait for the block to appear, then show the result with manual completion.'; } + /** + * Format sanitized editor context into an AI prompt sub-section. + * + * @param array $context Sanitized editor context. + * @return string Formatted context for prompt. + */ + private static function format_editor_context( array $context ): string { + $lines = [ 'CURRENT EDITOR STATE:' ]; + + // Check for empty block placeholder first - it's a priority starting point. + $has_empty_placeholder = ! empty( $context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ); + + if ( $has_empty_placeholder ) { + $lines[] = '⭐ STARTING POINT AVAILABLE: Empty block placeholder is visible!'; + $lines[] = ' Users can click it and type "/" to add blocks - teach this workflow!'; + } + + // Blocks in editor with targeting options. + if ( ! empty( $context[ 'editorBlocks' ] ) ) { + $lines[] = ''; + $lines[] = 'BLOCKS IN EDITOR (with targeting options):'; + + foreach ( $context[ 'editorBlocks' ] as $block ) { + $status = []; + if ( $block[ 'isEmpty' ] ) { + $status[] = 'empty'; + } + if ( $block[ 'isSelected' ] ) { + $status[] = 'SELECTED'; + } + $status_str = empty( $status ) ? '' : ' (' . implode( ', ', $status ) . ')'; + $lines[] = "- #{$block[ 'order' ]}: {$block[ 'name' ]}{$status_str}"; + + // Show targeting options. + $targets = []; + if ( $block[ 'isSelected' ] ) { + $targets[] = 'wpBlock: "selected" (recommended - currently selected)'; + } + if ( ! empty( $block[ 'clientId' ] ) ) { + $targets[] = "wpBlock: \"clientId:{$block[ 'clientId' ]}\""; + } + if ( ! empty( $block[ 'domInfo' ][ 'editableSelector' ] ) ) { + $targets[] = "css: \"{$block[ 'domInfo' ][ 'editableSelector' ]}\" (in iframe)"; + } + if ( ! empty( $block[ 'domInfo' ][ 'dataType' ] ) ) { + $targets[] = "css: \"[data-type=\\\"{$block[ 'domInfo' ][ 'dataType' ]}\\\"]\" (in iframe)"; + } + + if ( ! empty( $targets ) ) { + $lines[] = ' Targeting options:'; + foreach ( $targets as $target ) { + $lines[] = " • {$target}"; + } + } + } + } else { + $lines[] = 'Blocks in editor: (empty editor or new post)'; + } + + // UI state. + if ( ! empty( $context[ 'visibleElements' ] ) ) { + $ve = $context[ 'visibleElements' ]; + $state = []; + $state[] = $ve[ 'inserterOpen' ] ? 'Inserter panel is OPEN' : 'Inserter panel is closed'; + $state[] = $ve[ 'sidebarOpen' ] ? 'Settings sidebar is OPEN' : 'Settings sidebar is closed'; + + if ( $ve[ 'hasSelectedBlock' ] && $ve[ 'selectedBlockType' ] ) { + $state[] = 'Selected block: ' . $ve[ 'selectedBlockType' ]; + } + $lines[] = ''; + $lines[] = 'UI State: ' . implode( '. ', $state ); + } + + // Verified selectors from page. + if ( ! empty( $context[ 'uiSamples' ] ) ) { + $lines[] = ''; + $lines[] = 'VERIFIED SELECTORS (confirmed working on this page):'; + $verified_samples = $context[ 'uiSamples' ]; + + if ( ! empty( $verified_samples[ 'inserterButton' ][ 'selector' ] ) && $verified_samples[ 'inserterButton' ][ 'visible' ] ) { + $lines[] = '- Inserter button: ' . $verified_samples[ 'inserterButton' ][ 'selector' ]; + } + if ( ! empty( $verified_samples[ 'publishButton' ][ 'selector' ] ) && $verified_samples[ 'publishButton' ][ 'visible' ] ) { + $lines[] = '- Publish/Save button: ' . $verified_samples[ 'publishButton' ][ 'selector' ]; + } + if ( ! empty( $verified_samples[ 'settingsButton' ][ 'selector' ] ) && $verified_samples[ 'settingsButton' ][ 'visible' ] ) { + $lines[] = '- Settings button: ' . $verified_samples[ 'settingsButton' ][ 'selector' ]; + } + if ( ! empty( $verified_samples[ 'searchInput' ][ 'selector' ] ) && $verified_samples[ 'searchInput' ][ 'visible' ] ) { + $lines[] = '- Search input: ' . $verified_samples[ 'searchInput' ][ 'selector' ]; + } + if ( ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) && $verified_samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) { + $in_iframe = ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? ' (in editor iframe)' : ''; + $lines[] = '- Empty block placeholder: ' . $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] . $in_iframe; + } + } + + return implode( "\n", $lines ); + } + + /** + * Format sanitized failure context into an AI prompt sub-section. + * + * This helps the AI learn from previous failures and generate better selectors. + * + * @param array $context Sanitized failure context. + * @return string Formatted context for prompt. + */ + private static function format_failure_context( array $context ): string { + $lines = [ + '', + '⚠️ PREVIOUS ATTEMPT FAILED - PLEASE FIX:', + '', + 'The previous tour generation failed at step ' . ( $context[ 'stepIndex' ] + 1 ) . '.', + ]; + + if ( ! empty( $context[ 'stepTitle' ] ) ) { + $lines[] = 'Step title: "' . $context[ 'stepTitle' ] . '"'; + } + + if ( ! empty( $context[ 'error' ] ) ) { + $lines[] = 'Error: ' . $context[ 'error' ]; + } + + if ( ! empty( $context[ 'targetLocators' ] ) ) { + $lines[] = ''; + $lines[] = 'The following selectors DID NOT WORK:'; + foreach ( $context[ 'targetLocators' ] as $locator ) { + $lines[] = ' ❌ ' . $locator[ 'type' ] . ': "' . $locator[ 'value' ] . '"'; + } + } + + $lines[] = ''; + $lines[] = 'REQUIREMENTS FOR THIS RETRY:'; + $lines[] = '1. Use DIFFERENT selectors than the ones that failed'; + $lines[] = '2. Prefer more general, reliable selectors (aria-label, data-type attributes)'; + $lines[] = '3. Consider if the step order is correct - maybe a precondition is missing'; + $lines[] = '4. Double-check inEditorIframe constraint - is the element really in/out of the iframe?'; + $lines[] = ''; + + return implode( "\n", $lines ); + } + /** * Get the JSON schema for tour generation response. * + * The allowed step type enums derive from TourSchema, the single source of + * truth shared with output validation. + * * @return array JSON Schema for structured output. */ public static function get_tour_schema(): array { @@ -631,7 +864,10 @@ public static function get_tour_schema(): array { 'items' => [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::LOCATOR_TYPES, + ], 'value' => [ 'type' => 'string' ], 'weight' => [ 'type' => 'integer' ], 'fallback' => [ 'type' => 'boolean' ], @@ -653,7 +889,10 @@ public static function get_tour_schema(): array { 'items' => [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::PRECONDITION_TYPES, + ], 'params' => [ 'type' => 'object' ], ], 'required' => [ 'type' ], @@ -662,7 +901,10 @@ public static function get_tour_schema(): array { 'completion' => [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::COMPLETION_TYPES, + ], 'params' => [ 'type' => 'object' ], ], 'required' => [ 'type' ], diff --git a/php/AI/TourGenerator.php b/php/AI/TourGenerator.php new file mode 100644 index 0000000..9b5e20b --- /dev/null +++ b/php/AI/TourGenerator.php @@ -0,0 +1,191 @@ +ai = $ai; + } + + /** + * Generate a tour for the given request. + * + * @param TourRequest $request Sanitized request. + * @return array|\WP_Error `[ 'tour' => array, 'cached' => bool ]` or error. + */ + public function generate( TourRequest $request ): array|\WP_Error { + if ( ! $this->ai->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } + + if ( ! $request->has_input() ) { + return new \WP_Error( + 'missing_input', + __( 'Please provide a task or question.', 'admin-coach-tours' ), + [ 'status' => 400 ] + ); + } + + // Cache lookup — skipped on contextual retries. + $cache_key = null; + if ( ! $request->has_failure_context() ) { + $cache_key = $this->cache_key( $request ); + $cached = $this->get_cached( $cache_key ); + + if ( false !== $cached ) { + return [ + 'tour' => $cached, + 'cached' => true, + ]; + } + } + + $system_prompt = $this->build_system_prompt( $request ); + + $tour = $this->ai->generate_tour( $system_prompt, $request->query() ); + + if ( is_wp_error( $tour ) ) { + return $tour; + } + + if ( null !== $cache_key ) { + $this->cache( $cache_key, $tour ); + } + + return [ + 'tour' => $tour, + 'cached' => false, + ]; + } + + /** + * Assemble the full system prompt from grounding + editor + failure context. + * + * @param TourRequest $request Sanitized request. + * @return string + */ + private function build_system_prompt( TourRequest $request ): string { + $task = '' !== $request->task_id() ? TaskPrompts::get_task( $request->task_id() ) : null; + $search_query = $task ? ( $task[ 'description' ] ?? $request->task_id() ) : $request->query(); + + $context_data = GutenbergKnowledgeBase::get_relevant_context( $search_query, 5 ); + $gutenberg_context = GutenbergKnowledgeBase::format_context_for_prompt( $context_data ); + + return TaskPrompts::get_system_prompt( + $request->task_id(), + $request->query(), + $gutenberg_context, + $request->post_type(), + $request->editor_context(), + $request->failure_context(), + $request->locale() + ); + } + + /** + * Build a cache key from the cache-significant parts of the request. + * + * @param TourRequest $request Sanitized request. + * @return string + */ + private function cache_key( TourRequest $request ): string { + $editor_context = $request->editor_context(); + + $key_data = [ + 'version' => self::CACHE_VERSION, + 'task' => $request->task_id(), + 'query' => $request->query(), + 'post_type' => $request->post_type(), + ]; + + if ( ! empty( $editor_context[ 'editorBlocks' ] ) ) { + $key_data[ 'blocks' ] = array_map( + static function ( $block ) { + return $block[ 'name' ] . ( ! empty( $block[ 'isEmpty' ] ) ? ':empty' : '' ); + }, + $editor_context[ 'editorBlocks' ] + ); + } + + if ( ! empty( $editor_context[ 'visibleElements' ] ) ) { + $key_data[ 'ui' ] = [ + 'inserterOpen' => $editor_context[ 'visibleElements' ][ 'inserterOpen' ] ?? false, + 'sidebarOpen' => $editor_context[ 'visibleElements' ][ 'sidebarOpen' ] ?? false, + ]; + } + + if ( ! empty( $editor_context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ) ) { + $key_data[ 'hasPlaceholder' ] = true; + } + + return 'act_tour_' . md5( (string) wp_json_encode( $key_data ) ); + } + + /** + * Read a cached tour. + * + * @param string $cache_key Cache key. + * @return array|false + */ + private function get_cached( string $cache_key ) { + $cached = get_transient( $cache_key ); + + if ( false !== $cached && is_array( $cached ) ) { + return $cached; + } + + return false; + } + + /** + * Cache a generated tour. + * + * @param string $cache_key Cache key. + * @param array $tour Tour data. + * @return void + */ + private function cache( string $cache_key, array $tour ): void { + $expiration = apply_filters( 'admin_coach_tours_cache_expiration', DAY_IN_SECONDS ); + set_transient( $cache_key, $tour, $expiration ); + } +} diff --git a/php/AI/TourRequest.php b/php/AI/TourRequest.php new file mode 100644 index 0000000..0a5f2d5 --- /dev/null +++ b/php/AI/TourRequest.php @@ -0,0 +1,244 @@ +get_param( 'taskId' ) ?? '' ); + $query = sanitize_text_field( $request->get_param( 'query' ) ?? '' ); + $post_type = sanitize_key( $request->get_param( 'postType' ) ?? 'post' ); + $locale = sanitize_text_field( $request->get_param( 'locale' ) ?? '' ); + + if ( '' === $locale ) { + $locale = get_user_locale(); + } + + $raw_editor = $request->get_param( 'editorContext' ); + $editor_context = is_array( $raw_editor ) ? self::sanitize_editor_context( $raw_editor ) : []; + + $raw_failure = $request->get_param( 'failureContext' ); + $failure_context = is_array( $raw_failure ) ? self::sanitize_failure_context( $raw_failure ) : null; + + return new self( $task_id, $query, $post_type, $locale, $editor_context, $failure_context ); + } + + /** + * Task ID. + * + * @return string + */ + public function task_id(): string { + return $this->task_id; + } + + /** + * Freeform query. + * + * @return string + */ + public function query(): string { + return $this->query; + } + + /** + * Post type. + * + * @return string + */ + public function post_type(): string { + return $this->post_type; + } + + /** + * Resolved locale. + * + * @return string + */ + public function locale(): string { + return $this->locale; + } + + /** + * Sanitized editor context. + * + * @return array + */ + public function editor_context(): array { + return $this->editor_context; + } + + /** + * Sanitized failure context, or null when not retrying. + * + * @return array|null + */ + public function failure_context(): ?array { + return $this->failure_context; + } + + /** + * Whether this request carries failure context (a contextual retry). + * + * @return bool + */ + public function has_failure_context(): bool { + return ! empty( $this->failure_context ); + } + + /** + * Whether the request has a task or a query to act on. + * + * @return bool + */ + public function has_input(): bool { + return '' !== $this->task_id || '' !== $this->query; + } + + /** + * Sanitize editor context from the frontend. + * + * @param array $context Raw editor context. + * @return array Sanitized context. + */ + private static function sanitize_editor_context( array $context ): array { + $sanitized = []; + + // Editor blocks with DOM info. + if ( isset( $context[ 'editorBlocks' ] ) && is_array( $context[ 'editorBlocks' ] ) ) { + $sanitized[ 'editorBlocks' ] = []; + foreach ( array_slice( $context[ 'editorBlocks' ], 0, 20 ) as $block ) { + $block_data = [ + 'name' => isset( $block[ 'name' ] ) ? sanitize_key( $block[ 'name' ] ) : '', + 'isEmpty' => isset( $block[ 'isEmpty' ] ) ? (bool) $block[ 'isEmpty' ] : false, + 'isSelected' => isset( $block[ 'isSelected' ] ) ? (bool) $block[ 'isSelected' ] : false, + 'order' => isset( $block[ 'order' ] ) ? absint( $block[ 'order' ] ) : 0, + 'clientId' => isset( $block[ 'clientId' ] ) ? sanitize_text_field( $block[ 'clientId' ] ) : '', + ]; + + // Include DOM info if available. + if ( isset( $block[ 'domInfo' ] ) && is_array( $block[ 'domInfo' ] ) ) { + $block_data[ 'domInfo' ] = [ + 'tagName' => isset( $block[ 'domInfo' ][ 'tagName' ] ) ? sanitize_key( $block[ 'domInfo' ][ 'tagName' ] ) : '', + 'dataType' => isset( $block[ 'domInfo' ][ 'dataType' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataType' ] ) : '', + 'dataBlock' => isset( $block[ 'domInfo' ][ 'dataBlock' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataBlock' ] ) : '', + 'hasRichText' => isset( $block[ 'domInfo' ][ 'hasRichText' ] ) ? (bool) $block[ 'domInfo' ][ 'hasRichText' ] : false, + 'editableSelector' => isset( $block[ 'domInfo' ][ 'editableSelector' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'editableSelector' ] ) : null, + ]; + } + + $sanitized[ 'editorBlocks' ][] = $block_data; + } + } + + // Visible elements. + if ( isset( $context[ 'visibleElements' ] ) && is_array( $context[ 'visibleElements' ] ) ) { + $ve = $context[ 'visibleElements' ]; + $sanitized[ 'visibleElements' ] = [ + 'inserterOpen' => isset( $ve[ 'inserterOpen' ] ) ? (bool) $ve[ 'inserterOpen' ] : false, + 'sidebarOpen' => isset( $ve[ 'sidebarOpen' ] ) ? (bool) $ve[ 'sidebarOpen' ] : false, + 'sidebarTab' => isset( $ve[ 'sidebarTab' ] ) ? sanitize_key( $ve[ 'sidebarTab' ] ) : null, + 'hasSelectedBlock' => isset( $ve[ 'hasSelectedBlock' ] ) ? (bool) $ve[ 'hasSelectedBlock' ] : false, + 'selectedBlockType' => isset( $ve[ 'selectedBlockType' ] ) ? sanitize_key( $ve[ 'selectedBlockType' ] ) : null, + ]; + } + + // UI samples. + if ( isset( $context[ 'uiSamples' ] ) && is_array( $context[ 'uiSamples' ] ) ) { + $samples = $context[ 'uiSamples' ]; + $sanitized[ 'uiSamples' ] = []; + + foreach ( [ 'inserterButton', 'publishButton', 'settingsButton', 'searchInput' ] as $key ) { + if ( isset( $samples[ $key ] ) && is_array( $samples[ $key ] ) ) { + $sanitized[ 'uiSamples' ][ $key ] = [ + 'selector' => isset( $samples[ $key ][ 'selector' ] ) ? sanitize_text_field( $samples[ $key ][ 'selector' ] ) : null, + 'visible' => isset( $samples[ $key ][ 'visible' ] ) ? (bool) $samples[ $key ][ 'visible' ] : false, + ]; + } + } + + // Handle emptyBlockPlaceholder separately (has additional inIframe property). + if ( isset( $samples[ 'emptyBlockPlaceholder' ] ) && is_array( $samples[ 'emptyBlockPlaceholder' ] ) ) { + $sanitized[ 'uiSamples' ][ 'emptyBlockPlaceholder' ] = [ + 'selector' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) ? sanitize_text_field( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) : null, + 'visible' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] : false, + 'inIframe' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] : false, + ]; + } + } + + return $sanitized; + } + + /** + * Sanitize failure context from the frontend. + * + * @param array $context Raw failure context. + * @return array Sanitized context. + */ + private static function sanitize_failure_context( array $context ): array { + $sanitized = [ + 'stepIndex' => isset( $context[ 'stepIndex' ] ) ? absint( $context[ 'stepIndex' ] ) : 0, + 'stepId' => isset( $context[ 'stepId' ] ) ? sanitize_text_field( $context[ 'stepId' ] ) : '', + 'stepTitle' => isset( $context[ 'stepTitle' ] ) ? sanitize_text_field( $context[ 'stepTitle' ] ) : '', + 'error' => isset( $context[ 'error' ] ) ? sanitize_text_field( $context[ 'error' ] ) : '', + 'reason' => isset( $context[ 'reason' ] ) ? sanitize_text_field( $context[ 'reason' ] ) : '', + ]; + + // Sanitize locators array. + if ( isset( $context[ 'targetLocators' ] ) && is_array( $context[ 'targetLocators' ] ) ) { + $sanitized[ 'targetLocators' ] = []; + foreach ( array_slice( $context[ 'targetLocators' ], 0, 5 ) as $locator ) { + $sanitized[ 'targetLocators' ][] = [ + 'type' => isset( $locator[ 'type' ] ) ? sanitize_key( $locator[ 'type' ] ) : '', + 'value' => isset( $locator[ 'value' ] ) ? sanitize_text_field( $locator[ 'value' ] ) : '', + 'weight' => isset( $locator[ 'weight' ] ) ? absint( $locator[ 'weight' ] ) : 0, + ]; + } + } + + return $sanitized; + } +} diff --git a/php/AI/TourSchema.php b/php/AI/TourSchema.php new file mode 100644 index 0000000..8adb097 --- /dev/null +++ b/php/AI/TourSchema.php @@ -0,0 +1,73 @@ + + */ + public const LOCATOR_TYPES = [ + 'css', + 'role', + 'testId', + 'dataAttribute', + 'ariaLabel', + 'contextual', + 'wpBlock', + ]; + + /** + * Allowed precondition types for a step. + * + * @var array + */ + public const PRECONDITION_TYPES = [ + 'ensureEditor', + 'ensureSidebarOpen', + 'ensureSidebarClosed', + 'selectSidebarTab', + 'openInserter', + 'closeInserter', + 'selectBlock', + 'focusElement', + 'scrollIntoView', + 'openModal', + 'closeModal', + 'insertBlock', + ]; + + /** + * Allowed completion types for a step. + * + * @var array + */ + public const COMPLETION_TYPES = [ + 'clickTarget', + 'domValueChanged', + 'manual', + 'wpData', + 'elementAppear', + 'elementDisappear', + 'customEvent', + ]; +} diff --git a/php/Rest/AiController.php b/php/Rest/AiController.php index ee1e265..197430f 100644 --- a/php/Rest/AiController.php +++ b/php/Rest/AiController.php @@ -2,7 +2,8 @@ /** * AI Controller. * - * Handles AI-related REST API endpoints. + * Thin HTTP adapter over the AI modules. Parses REST requests, delegates to + * AiManager (drafts) and TourGenerator (tours), and maps results to HTTP. * * @package AdminCoachTours * @since 0.1.0 @@ -14,7 +15,8 @@ use AdminCoachTours\AI\AiManager; use AdminCoachTours\AI\TaskPrompts; -use AdminCoachTours\AI\GutenbergKnowledgeBase; +use AdminCoachTours\AI\TourGenerator; +use AdminCoachTours\AI\TourRequest; /** * AI Controller class. @@ -57,20 +59,7 @@ public static function generate_draft( \WP_REST_Request $request ) { $result = $ai_manager->generate_step_draft( $sanitized_context, $tour_context ); if ( is_wp_error( $result ) ) { - $status = 500; - - if ( 'not_configured' === $result->get_error_code() ) { - $status = 503; - } elseif ( 'api_error' === $result->get_error_code() ) { - $error_data = $result->get_error_data(); - $status = $error_data[ 'status' ] ?? 500; - } - - return new \WP_Error( - $result->get_error_code(), - $result->get_error_message(), - [ 'status' => $status ] - ); + return self::map_error( $result ); } return rest_ensure_response( $result ); @@ -84,26 +73,25 @@ public static function generate_draft( \WP_REST_Request $request ) { public static function get_status() { $ai_manager = AiManager::get_instance(); + $connectors = $ai_manager->get_configured_connectors(); + $active_provider = $ai_manager->resolve_provider_id(); + $status = [ 'available' => $ai_manager->is_available(), - 'activeProvider' => null, + 'activeProvider' => '' !== $active_provider + ? [ + 'id' => $active_provider, + 'name' => $connectors[ $active_provider ] ?? $active_provider, + ] + : null, 'providers' => [], ]; - $active_provider = $ai_manager->get_active_provider(); - - if ( $active_provider ) { - $status[ 'activeProvider' ] = [ - 'id' => $active_provider->get_id(), - 'name' => $active_provider->get_name(), - ]; - } - - foreach ( $ai_manager->get_providers() as $provider ) { + foreach ( $connectors as $id => $label ) { $status[ 'providers' ][] = [ - 'id' => $provider->get_id(), - 'name' => $provider->get_name(), - 'configured' => $provider->is_configured(), + 'id' => $id, + 'name' => $label, + 'configured' => true, ]; } @@ -111,7 +99,84 @@ public static function get_status() { } /** - * Sanitize element context. + * Get available AI tasks for pupils. + * + * @since 0.3.0 + * @return \WP_REST_Response + */ + public static function get_tasks() { + $ai_manager = AiManager::get_instance(); + + $available = $ai_manager->is_available(); + + $response = [ + 'available' => $available, + 'tasks' => [], + ]; + + if ( $available ) { + $response[ 'tasks' ] = TaskPrompts::get_tasks(); + } + + return rest_ensure_response( $response ); + } + + /** + * Generate an AI tour from task or freeform query. + * + * @since 0.3.0 + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response|\WP_Error + */ + public static function generate_tour( \WP_REST_Request $request ) { + $generator = new TourGenerator( AiManager::get_instance() ); + $result = $generator->generate( TourRequest::from_rest( $request ) ); + + if ( is_wp_error( $result ) ) { + return self::map_error( $result ); + } + + return rest_ensure_response( + [ + 'tour' => $result[ 'tour' ], + 'ephemeral' => true, + 'cached' => $result[ 'cached' ], + ] + ); + } + + /** + * Map a generation WP_Error to an HTTP-status-bearing WP_Error. + * + * @param \WP_Error $error The error from the AI layer. + * @return \WP_Error + */ + private static function map_error( \WP_Error $error ): \WP_Error { + $code = $error->get_error_code(); + $status = 500; + + switch ( $code ) { + case 'ai_not_available': + case 'not_configured': + $status = 503; + break; + case 'missing_input': + $status = 400; + break; + case 'out_of_scope': + $status = 422; + break; + case 'api_error': + $data = $error->get_error_data(); + $status = is_array( $data ) ? ( $data[ 'status' ] ?? 500 ) : 500; + break; + } + + return new \WP_Error( $code, $error->get_error_message(), [ 'status' => $status ] ); + } + + /** + * Sanitize element context (single-element draft requests). * * @param array $context Raw context. * @return array Sanitized context. @@ -198,507 +263,4 @@ private static function sanitize_element_context( array $context ): array { return $sanitized; } - - /** - * Sanitize editor context from frontend. - * - * @since 0.3.0 - * @param array $context Raw editor context. - * @return array Sanitized context. - */ - private static function sanitize_editor_context( array $context ): array { - $sanitized = []; - - // Editor blocks with DOM info. - if ( isset( $context[ 'editorBlocks' ] ) && is_array( $context[ 'editorBlocks' ] ) ) { - $sanitized[ 'editorBlocks' ] = []; - foreach ( array_slice( $context[ 'editorBlocks' ], 0, 20 ) as $block ) { - $block_data = [ - 'name' => isset( $block[ 'name' ] ) ? sanitize_key( $block[ 'name' ] ) : '', - 'isEmpty' => isset( $block[ 'isEmpty' ] ) ? (bool) $block[ 'isEmpty' ] : false, - 'isSelected' => isset( $block[ 'isSelected' ] ) ? (bool) $block[ 'isSelected' ] : false, - 'order' => isset( $block[ 'order' ] ) ? absint( $block[ 'order' ] ) : 0, - 'clientId' => isset( $block[ 'clientId' ] ) ? sanitize_text_field( $block[ 'clientId' ] ) : '', - ]; - - // Include DOM info if available. - if ( isset( $block[ 'domInfo' ] ) && is_array( $block[ 'domInfo' ] ) ) { - $block_data[ 'domInfo' ] = [ - 'tagName' => isset( $block[ 'domInfo' ][ 'tagName' ] ) ? sanitize_key( $block[ 'domInfo' ][ 'tagName' ] ) : '', - 'dataType' => isset( $block[ 'domInfo' ][ 'dataType' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataType' ] ) : '', - 'dataBlock' => isset( $block[ 'domInfo' ][ 'dataBlock' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataBlock' ] ) : '', - 'hasRichText' => isset( $block[ 'domInfo' ][ 'hasRichText' ] ) ? (bool) $block[ 'domInfo' ][ 'hasRichText' ] : false, - 'editableSelector' => isset( $block[ 'domInfo' ][ 'editableSelector' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'editableSelector' ] ) : null, - ]; - } - - $sanitized[ 'editorBlocks' ][] = $block_data; - } - } - - // Visible elements. - if ( isset( $context[ 'visibleElements' ] ) && is_array( $context[ 'visibleElements' ] ) ) { - $ve = $context[ 'visibleElements' ]; - $sanitized[ 'visibleElements' ] = [ - 'inserterOpen' => isset( $ve[ 'inserterOpen' ] ) ? (bool) $ve[ 'inserterOpen' ] : false, - 'sidebarOpen' => isset( $ve[ 'sidebarOpen' ] ) ? (bool) $ve[ 'sidebarOpen' ] : false, - 'sidebarTab' => isset( $ve[ 'sidebarTab' ] ) ? sanitize_key( $ve[ 'sidebarTab' ] ) : null, - 'hasSelectedBlock' => isset( $ve[ 'hasSelectedBlock' ] ) ? (bool) $ve[ 'hasSelectedBlock' ] : false, - 'selectedBlockType' => isset( $ve[ 'selectedBlockType' ] ) ? sanitize_key( $ve[ 'selectedBlockType' ] ) : null, - ]; - } - - // UI samples. - if ( isset( $context[ 'uiSamples' ] ) && is_array( $context[ 'uiSamples' ] ) ) { - $samples = $context[ 'uiSamples' ]; - $sanitized[ 'uiSamples' ] = []; - - foreach ( [ 'inserterButton', 'publishButton', 'settingsButton', 'searchInput' ] as $key ) { - if ( isset( $samples[ $key ] ) && is_array( $samples[ $key ] ) ) { - $sanitized[ 'uiSamples' ][ $key ] = [ - 'selector' => isset( $samples[ $key ][ 'selector' ] ) ? sanitize_text_field( $samples[ $key ][ 'selector' ] ) : null, - 'visible' => isset( $samples[ $key ][ 'visible' ] ) ? (bool) $samples[ $key ][ 'visible' ] : false, - ]; - } - } - - // Handle emptyBlockPlaceholder separately (has additional inIframe property). - if ( isset( $samples[ 'emptyBlockPlaceholder' ] ) && is_array( $samples[ 'emptyBlockPlaceholder' ] ) ) { - $sanitized[ 'uiSamples' ][ 'emptyBlockPlaceholder' ] = [ - 'selector' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) ? sanitize_text_field( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) : null, - 'visible' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] : false, - 'inIframe' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] : false, - ]; - } - } - - return $sanitized; - } - - /** - * Format editor context for AI prompt. - * - * @since 0.3.0 - * @param array $context Sanitized editor context. - * @return string Formatted context for prompt. - */ - private static function format_editor_context_for_prompt( array $context ): string { - $lines = [ 'CURRENT EDITOR STATE:' ]; - - // Check for empty block placeholder first - it's a priority starting point. - $has_empty_placeholder = ! empty( $context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ); - - if ( $has_empty_placeholder ) { - $lines[] = '⭐ STARTING POINT AVAILABLE: Empty block placeholder is visible!'; - $lines[] = ' Users can click it and type "/" to add blocks - teach this workflow!'; - } - - // Blocks in editor with targeting options. - if ( ! empty( $context[ 'editorBlocks' ] ) ) { - $lines[] = ''; - $lines[] = 'BLOCKS IN EDITOR (with targeting options):'; - - foreach ( $context[ 'editorBlocks' ] as $block ) { - $status = []; - if ( $block[ 'isEmpty' ] ) { - $status[] = 'empty'; - } - if ( $block[ 'isSelected' ] ) { - $status[] = 'SELECTED'; - } - $status_str = empty( $status ) ? '' : ' (' . implode( ', ', $status ) . ')'; - $lines[] = "- #{$block[ 'order' ]}: {$block[ 'name' ]}{$status_str}"; - - // Show targeting options. - $targets = []; - if ( $block[ 'isSelected' ] ) { - $targets[] = 'wpBlock: "selected" (recommended - currently selected)'; - } - if ( ! empty( $block[ 'clientId' ] ) ) { - $targets[] = "wpBlock: \"clientId:{$block[ 'clientId' ]}\""; - } - if ( ! empty( $block[ 'domInfo' ][ 'editableSelector' ] ) ) { - $targets[] = "css: \"{$block[ 'domInfo' ][ 'editableSelector' ]}\" (in iframe)"; - } - if ( ! empty( $block[ 'domInfo' ][ 'dataType' ] ) ) { - $targets[] = "css: \"[data-type=\\\"{$block[ 'domInfo' ][ 'dataType' ]}\\\"]\" (in iframe)"; - } - - if ( ! empty( $targets ) ) { - $lines[] = ' Targeting options:'; - foreach ( $targets as $target ) { - $lines[] = " • {$target}"; - } - } - } - } else { - $lines[] = 'Blocks in editor: (empty editor or new post)'; - } - - // UI state. - if ( ! empty( $context[ 'visibleElements' ] ) ) { - $ve = $context[ 'visibleElements' ]; - $state = []; - $state[] = $ve[ 'inserterOpen' ] ? 'Inserter panel is OPEN' : 'Inserter panel is closed'; - $state[] = $ve[ 'sidebarOpen' ] ? 'Settings sidebar is OPEN' : 'Settings sidebar is closed'; - - if ( $ve[ 'hasSelectedBlock' ] && $ve[ 'selectedBlockType' ] ) { - $state[] = 'Selected block: ' . $ve[ 'selectedBlockType' ]; - } - $lines[] = ''; - $lines[] = 'UI State: ' . implode( '. ', $state ); - } - - // Verified selectors from page. - if ( ! empty( $context[ 'uiSamples' ] ) ) { - $lines[] = ''; - $lines[] = 'VERIFIED SELECTORS (confirmed working on this page):'; - $verified_samples = $context[ 'uiSamples' ]; - - if ( ! empty( $verified_samples[ 'inserterButton' ][ 'selector' ] ) && $verified_samples[ 'inserterButton' ][ 'visible' ] ) { - $lines[] = '- Inserter button: ' . $verified_samples[ 'inserterButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'publishButton' ][ 'selector' ] ) && $verified_samples[ 'publishButton' ][ 'visible' ] ) { - $lines[] = '- Publish/Save button: ' . $verified_samples[ 'publishButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'settingsButton' ][ 'selector' ] ) && $verified_samples[ 'settingsButton' ][ 'visible' ] ) { - $lines[] = '- Settings button: ' . $verified_samples[ 'settingsButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'searchInput' ][ 'selector' ] ) && $verified_samples[ 'searchInput' ][ 'visible' ] ) { - $lines[] = '- Search input: ' . $verified_samples[ 'searchInput' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) && $verified_samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) { - $in_iframe = ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? ' (in editor iframe)' : ''; - $lines[] = '- Empty block placeholder: ' . $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] . $in_iframe; - } - } - - return implode( "\n", $lines ); - } - - /** - * Sanitize failure context from frontend. - * - * @since 0.3.6 - * @param array $context Raw failure context. - * @return array Sanitized context. - */ - private static function sanitize_failure_context( array $context ): array { - $sanitized = [ - 'stepIndex' => isset( $context[ 'stepIndex' ] ) ? absint( $context[ 'stepIndex' ] ) : 0, - 'stepId' => isset( $context[ 'stepId' ] ) ? sanitize_text_field( $context[ 'stepId' ] ) : '', - 'stepTitle' => isset( $context[ 'stepTitle' ] ) ? sanitize_text_field( $context[ 'stepTitle' ] ) : '', - 'error' => isset( $context[ 'error' ] ) ? sanitize_text_field( $context[ 'error' ] ) : '', - 'reason' => isset( $context[ 'reason' ] ) ? sanitize_text_field( $context[ 'reason' ] ) : '', - ]; - - // Sanitize locators array. - if ( isset( $context[ 'targetLocators' ] ) && is_array( $context[ 'targetLocators' ] ) ) { - $sanitized[ 'targetLocators' ] = []; - foreach ( array_slice( $context[ 'targetLocators' ], 0, 5 ) as $locator ) { - $sanitized[ 'targetLocators' ][] = [ - 'type' => isset( $locator[ 'type' ] ) ? sanitize_key( $locator[ 'type' ] ) : '', - 'value' => isset( $locator[ 'value' ] ) ? sanitize_text_field( $locator[ 'value' ] ) : '', - 'weight' => isset( $locator[ 'weight' ] ) ? absint( $locator[ 'weight' ] ) : 0, - ]; - } - } - - return $sanitized; - } - - /** - * Format failure context for AI prompt. - * - * This helps the AI learn from previous failures and generate better selectors. - * - * @since 0.3.6 - * @param array $context Sanitized failure context. - * @return string Formatted context for prompt. - */ - private static function format_failure_context_for_prompt( array $context ): string { - $lines = [ - '', - '⚠️ PREVIOUS ATTEMPT FAILED - PLEASE FIX:', - '', - 'The previous tour generation failed at step ' . ( $context[ 'stepIndex' ] + 1 ) . '.', - ]; - - if ( ! empty( $context[ 'stepTitle' ] ) ) { - $lines[] = 'Step title: "' . $context[ 'stepTitle' ] . '"'; - } - - if ( ! empty( $context[ 'error' ] ) ) { - $lines[] = 'Error: ' . $context[ 'error' ]; - } - - if ( ! empty( $context[ 'targetLocators' ] ) ) { - $lines[] = ''; - $lines[] = 'The following selectors DID NOT WORK:'; - foreach ( $context[ 'targetLocators' ] as $locator ) { - $lines[] = ' ❌ ' . $locator[ 'type' ] . ': "' . $locator[ 'value' ] . '"'; - } - } - - $lines[] = ''; - $lines[] = 'REQUIREMENTS FOR THIS RETRY:'; - $lines[] = '1. Use DIFFERENT selectors than the ones that failed'; - $lines[] = '2. Prefer more general, reliable selectors (aria-label, data-type attributes)'; - $lines[] = '3. Consider if the step order is correct - maybe a precondition is missing'; - $lines[] = '4. Double-check inEditorIframe constraint - is the element really in/out of the iframe?'; - $lines[] = ''; - - return implode( "\n", $lines ); - } - - /** - * Get available AI tasks for pupils. - * - * @since 0.3.0 - * @return \WP_REST_Response - */ - public static function get_tasks() { - $ai_manager = AiManager::get_instance(); - - // Check if AI is available. - $available = $ai_manager->is_available(); - - $response = [ - 'available' => $available, - 'tasks' => [], - ]; - - if ( $available ) { - $response[ 'tasks' ] = TaskPrompts::get_tasks(); - } - - return rest_ensure_response( $response ); - } - - /** - * Generate a cache key for tour requests. - * - * @since 0.3.0 - * @param string $task_id Task ID. - * @param string $query Freeform query. - * @param string $post_type Post type. - * @param array $editor_context Editor context. - * @return string Cache key. - */ - private static function generate_cache_key( string $task_id, string $query, string $post_type, array $editor_context ): string { - // Build cache key from relevant context. - // Include version to invalidate cache when prompts change. - $key_data = [ - 'version' => '2', // Increment when prompt instructions change. - 'task' => $task_id, - 'query' => $query, - 'post_type' => $post_type, - ]; - - // Include key editor state that affects tour generation. - if ( ! empty( $editor_context[ 'editorBlocks' ] ) ) { - // Just include block names and empty status, not client IDs. - $key_data[ 'blocks' ] = array_map( - function ( $b ) { - return $b[ 'name' ] . ( $b[ 'isEmpty' ] ? ':empty' : '' ); - }, - $editor_context[ 'editorBlocks' ] - ); - } - - if ( ! empty( $editor_context[ 'visibleElements' ] ) ) { - $key_data[ 'ui' ] = [ - 'inserterOpen' => $editor_context[ 'visibleElements' ][ 'inserterOpen' ] ?? false, - 'sidebarOpen' => $editor_context[ 'visibleElements' ][ 'sidebarOpen' ] ?? false, - ]; - } - - // Check if empty placeholder is visible (important for workflow choice). - if ( ! empty( $editor_context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ) ) { - $key_data[ 'hasPlaceholder' ] = true; - } - - // Generate hash. - $key_string = wp_json_encode( $key_data ); - $hash = md5( $key_string ); - - return 'act_tour_' . $hash; - } - - /** - * Get cached tour if available. - * - * @since 0.3.0 - * @param string $cache_key Cache key. - * @return array|false Cached tour or false. - */ - private static function get_cached_tour( string $cache_key ) { - $cached = get_transient( $cache_key ); - - if ( false !== $cached && is_array( $cached ) ) { - return $cached; - } - - return false; - } - - /** - * Cache a generated tour. - * - * @since 0.3.0 - * @param string $cache_key Cache key. - * @param array $tour Tour data. - * @return void - */ - private static function cache_tour( string $cache_key, array $tour ): void { - // Cache for 24 hours. - $expiration = apply_filters( 'admin_coach_tours_cache_expiration', DAY_IN_SECONDS ); - set_transient( $cache_key, $tour, $expiration ); - } - - /** - * Generate an AI tour from task or freeform query. - * - * @since 0.3.0 - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response|\WP_Error - */ - public static function generate_tour( \WP_REST_Request $request ) { - $ai_manager = AiManager::get_instance(); - - if ( ! $ai_manager->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ), - [ 'status' => 503 ] - ); - } - - $task_id = sanitize_key( $request->get_param( 'taskId' ) ?? '' ); - $query = sanitize_text_field( $request->get_param( 'query' ) ?? '' ); - $post_type = sanitize_key( $request->get_param( 'postType' ) ?? 'post' ); - $locale = sanitize_text_field( $request->get_param( 'locale' ) ?? '' ); - - // Fall back to WordPress user locale if not provided. - if ( empty( $locale ) ) { - $locale = get_user_locale(); - } - - // Get and sanitize editor context. - $raw_editor_context = $request->get_param( 'editorContext' ); - $editor_context = []; - if ( is_array( $raw_editor_context ) ) { - $editor_context = self::sanitize_editor_context( $raw_editor_context ); - } - - // Get and sanitize failure context (for retry with learning). - $raw_failure_context = $request->get_param( 'failureContext' ); - $failure_context = null; - if ( is_array( $raw_failure_context ) ) { - $failure_context = self::sanitize_failure_context( $raw_failure_context ); - } - - // Require either a task or a query. - if ( empty( $task_id ) && empty( $query ) ) { - return new \WP_Error( - 'missing_input', - __( 'Please provide a task or question.', 'admin-coach-tours' ), - [ 'status' => 400 ] - ); - } - - // Don't use cache when retrying with failure context. - if ( empty( $failure_context ) ) { - // Check cache first. - $cache_key = self::generate_cache_key( $task_id, $query, $post_type, $editor_context ); - $cached_tour = self::get_cached_tour( $cache_key ); - - if ( false !== $cached_tour ) { - // Return cached tour. - return rest_ensure_response( - [ - 'tour' => $cached_tour, - 'ephemeral' => true, - 'cached' => true, - ] - ); - } - } - - // Get relevant Gutenberg context based on task/query. - $task = ! empty( $task_id ) ? TaskPrompts::get_task( $task_id ) : null; - $search_query = $task ? ( $task[ 'description' ] ?? $task_id ) : $query; - - $context_data = GutenbergKnowledgeBase::get_relevant_context( $search_query, 5 ); - $gutenberg_context = GutenbergKnowledgeBase::format_context_for_prompt( $context_data ); - - // Format editor context for prompt. - $editor_context_prompt = ''; - if ( ! empty( $editor_context ) ) { - $editor_context_prompt = self::format_editor_context_for_prompt( $editor_context ); - } - - // Format failure context for prompt (contextual retry). - $failure_context_prompt = ''; - if ( ! empty( $failure_context ) ) { - $failure_context_prompt = self::format_failure_context_for_prompt( $failure_context ); - } - - // Build the system prompt with editor context and failure context. - $system_prompt = TaskPrompts::get_system_prompt( - $task_id, - $query, - $gutenberg_context, - $post_type, - $editor_context_prompt, - $failure_context_prompt, - $locale - ); - - // Generate the tour. - $provider = $ai_manager->get_active_provider(); - - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is available.', 'admin-coach-tours' ), - [ 'status' => 503 ] - ); - } - - $result = $provider->generate_tour( $system_prompt, $query ); - - if ( is_wp_error( $result ) ) { - $status = 500; - - if ( 'not_configured' === $result->get_error_code() ) { - $status = 503; - } elseif ( 'api_error' === $result->get_error_code() ) { - $error_data = $result->get_error_data(); - $status = $error_data[ 'status' ] ?? 500; - } elseif ( 'out_of_scope' === $result->get_error_code() ) { - $status = 422; - } - - return new \WP_Error( - $result->get_error_code(), - $result->get_error_message(), - [ 'status' => $status ] - ); - } - - // Cache the successful result (only if we have a cache key). - if ( isset( $cache_key ) ) { - self::cache_tour( $cache_key, $result ); - } - - // Return the ephemeral tour (not persisted). - return rest_ensure_response( - [ - 'tour' => $result, - 'ephemeral' => true, - 'cached' => false, - ] - ); - } } diff --git a/php/Rest/Routes.php b/php/Rest/Routes.php index 7ceb466..99a1fd0 100644 --- a/php/Rest/Routes.php +++ b/php/Rest/Routes.php @@ -262,6 +262,16 @@ public function register_routes(): void { 'default' => '', 'description' => __( 'User locale for AI response language.', 'admin-coach-tours' ), ], + 'editorContext' => [ + 'type' => [ 'object', 'null' ], + 'default' => [], + 'description' => __( 'Current editor state (blocks, UI elements). Sanitized by TourRequest.', 'admin-coach-tours' ), + ], + 'failureContext' => [ + 'type' => [ 'object', 'null' ], + 'default' => null, + 'description' => __( 'Context from a previous failed attempt, for contextual retry. Sanitized by TourRequest.', 'admin-coach-tours' ), + ], ], ], ] diff --git a/php/Security/Encryption.php b/php/Security/Encryption.php deleted file mode 100644 index a90d1c2..0000000 --- a/php/Security/Encryption.php +++ /dev/null @@ -1,133 +0,0 @@ -key_cache ) { - return $this->key_cache; - } - - // Derive key from wp_salt('auth'). - $salt = wp_salt( 'auth' ); - - // Use BLAKE2b to derive a 32-byte key. - $this->key_cache = sodium_crypto_generichash( $salt, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES ); - - return $this->key_cache; - } - - /** - * Encrypt a string. - * - * @param string $plaintext String to encrypt. - * @return string Base64-encoded encrypted string with nonce prefix. - */ - public function encrypt( string $plaintext ): string { - if ( empty( $plaintext ) ) { - return ''; - } - - $key = $this->get_key(); - $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - - $ciphertext = sodium_crypto_secretbox( $plaintext, $nonce, $key ); - - // Prepend nonce to ciphertext. - $combined = $nonce . $ciphertext; - - // Base64 encode for storage. - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - return base64_encode( $combined ); - } - - /** - * Decrypt a string. - * - * @param string $encrypted Base64-encoded encrypted string. - * @return string|null Decrypted string or null on failure. - */ - public function decrypt( string $encrypted ): ?string { - if ( empty( $encrypted ) ) { - return null; - } - - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode - $combined = base64_decode( $encrypted, true ); - - if ( false === $combined ) { - return null; - } - - // Check minimum length. - $min_length = SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES; - - if ( strlen( $combined ) < $min_length ) { - return null; - } - - $key = $this->get_key(); - - // Extract nonce and ciphertext. - $nonce = substr( $combined, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - $ciphertext = substr( $combined, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - - // Decrypt. - $plaintext = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key ); - - if ( false === $plaintext ) { - return null; - } - - return $plaintext; - } - - /** - * Check if encryption is available. - * - * @return bool True if sodium is available. - */ - public static function is_available(): bool { - return extension_loaded( 'sodium' ) || function_exists( 'sodium_crypto_secretbox' ); - } - - /** - * Securely wipe sensitive data from memory. - * - * @param string $data Data to wipe. - */ - public function wipe( string &$data ): void { - if ( function_exists( 'sodium_memzero' ) ) { - sodium_memzero( $data ); - } else { - // Fallback: overwrite with zeros. - $data = str_repeat( "\0", strlen( $data ) ); - } - } -} diff --git a/php/Settings/SettingsPage.php b/php/Settings/SettingsPage.php index d7b452f..8a8454e 100644 --- a/php/Settings/SettingsPage.php +++ b/php/Settings/SettingsPage.php @@ -2,7 +2,8 @@ /** * Settings Page. * - * Provides admin settings page for plugin configuration. + * Provides admin settings page for plugin configuration. AI provider API keys + * are managed by the WordPress AI connector (wp_get_connectors), not here. * * @package AdminCoachTours * @since 0.1.0 @@ -13,7 +14,6 @@ namespace AdminCoachTours\Settings; use AdminCoachTours\AI\AiManager; -use AdminCoachTours\Security\Encryption; /** * Settings Page class. @@ -41,20 +41,6 @@ class SettingsPage { */ private static ?self $instance = null; - /** - * Tracks which option names are sensitive (need encryption). - * - * @var array - */ - private array $sensitive_options = []; - - /** - * Encryption helper. - * - * @var Encryption|null - */ - private ?Encryption $encryption = null; - /** * Get singleton instance. * @@ -70,9 +56,7 @@ public static function get_instance(): self { /** * Constructor. */ - private function __construct() { - $this->encryption = new Encryption(); - } + private function __construct() {} /** * Initialize settings. @@ -158,7 +142,7 @@ public function register_settings(): void { 'act_ai', [ 'name' => 'act_ai_enabled', - 'description' => __( 'Enable AI-powered step draft generation.', 'admin-coach-tours' ), + 'description' => __( 'Enable AI-powered tour and step draft generation.', 'admin-coach-tours' ), ] ); @@ -167,59 +151,36 @@ public function register_settings(): void { 'act_ai_provider', [ 'type' => 'string', - 'sanitize_callback' => 'sanitize_key', - 'default' => 'openai', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', ] ); add_settings_field( 'act_ai_provider', - __( 'AI Provider', 'admin-coach-tours' ), + __( 'Preferred Provider', 'admin-coach-tours' ), [ $this, 'render_provider_field' ], self::MENU_SLUG, 'act_ai' ); - // Provider-specific settings. - $ai_manager = AiManager::get_instance(); + register_setting( + self::OPTION_GROUP, + 'act_ai_model', + [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ] + ); - foreach ( $ai_manager->get_providers() as $provider ) { - $provider_id = $provider->get_id(); - $schema = $provider->get_settings_schema(); - - foreach ( $schema as $field_id => $field_config ) { - $option_name = "act_ai_{$provider_id}_{$field_id}"; - - // Track sensitive fields for encryption. - if ( ! empty( $field_config[ 'sensitive' ] ) ) { - $this->sensitive_options[ $option_name ] = true; - } - - register_setting( - self::OPTION_GROUP, - $option_name, - [ - 'type' => $field_config[ 'type' ] ?? 'string', - 'sanitize_callback' => [ $this, 'sanitize_provider_field' ], - 'default' => $field_config[ 'default' ] ?? '', - ] - ); - - add_settings_field( - $option_name, - $field_config[ 'label' ] ?? $field_id, - [ $this, 'render_provider_setting_field' ], - self::MENU_SLUG, - 'act_ai', - [ - 'provider' => $provider_id, - 'field_id' => $field_id, - 'field' => $field_config, - 'name' => $option_name, - ] - ); - } - } + add_settings_field( + 'act_ai_model', + __( 'Model Override', 'admin-coach-tours' ), + [ $this, 'render_model_field' ], + self::MENU_SLUG, + 'act_ai' + ); } /** @@ -228,7 +189,7 @@ public function register_settings(): void { * @param string $hook Current admin page. */ public function enqueue_scripts( string $hook ): void { - // Settings page is now under Tools menu. + // Settings page is under the Tools menu. if ( 'tools_page_' . self::MENU_SLUG !== $hook ) { return; } @@ -275,21 +236,31 @@ public function render_page(): void { } /** - * Render AI section description. + * Render AI section description and connector status. */ public function render_ai_section(): void { echo '

' . esc_html__( - 'Configure AI providers for generating step drafts and completion suggestions.', + 'AI features use the WordPress AI connector. Configure at least one AI provider connector in WordPress, then enable AI features below.', 'admin-coach-tours' ) . '

'; $ai_manager = AiManager::get_instance(); + $connectors = $ai_manager->get_configured_connectors(); - if ( ! $ai_manager->is_available() ) { + if ( empty( $connectors ) ) { echo '

'; - esc_html_e( 'No AI provider is currently configured. Add an API key below to enable AI features.', 'admin-coach-tours' ); + esc_html_e( 'No configured AI provider connector was detected. AI features are unavailable until a connector is set up.', 'admin-coach-tours' ); echo '

'; + return; } + + echo '

'; + printf( + /* translators: %s: comma-separated list of configured provider labels. */ + esc_html__( 'Configured AI providers: %s', 'admin-coach-tours' ), + esc_html( implode( ', ', $connectors ) ) + ); + echo '

'; } /** @@ -311,133 +282,43 @@ public function render_checkbox_field( array $args ): void { } /** - * Render provider field. + * Render the preferred-provider dropdown, populated from configured connectors. */ public function render_provider_field(): void { $ai_manager = AiManager::get_instance(); - $providers = $ai_manager->get_providers(); - $current = get_option( 'act_ai_provider', 'openai' ); + $connectors = $ai_manager->get_configured_connectors(); + $current = (string) get_option( 'act_ai_provider', '' ); ?>

- +

'; - - if ( 'select' === $type && ! empty( $options ) ) { - echo ''; - } else { - $input_type = 'password' === $type ? 'password' : 'text'; - - if ( $is_sensitive ) { - // Show a masked field with option to reveal/update. - printf( - '', - esc_attr( $input_type ), - esc_attr( $name ), - esc_attr( $name ), - ! empty( $value ) ? esc_attr__( 'Leave empty to keep current', 'admin-coach-tours' ) : '' - ); - - if ( ! empty( $value ) ) { - echo ''; - } - } else { - printf( - '', - esc_attr( $input_type ), - esc_attr( $name ), - esc_attr( $name ), - esc_attr( $display_value ) - ); - } - } - - if ( ! empty( $desc ) ) { - echo '

' . esc_html( $desc ) . '

'; - } - - echo '

'; - } - - /** - * Sanitize provider field. - * - * @param mixed $value Field value. - * @return mixed Sanitized value. + * Render the optional model-override text field. */ - public function sanitize_provider_field( $value ) { - // Get the option name from the filter. - $filter_name = current_filter(); - $option_name = ''; + public function render_model_field(): void { + $current = (string) get_option( 'act_ai_model', '' ); - if ( preg_match( '/^sanitize_option_(.+)$/', $filter_name, $matches ) ) { - $option_name = $matches[ 1 ]; - } - - // Keep existing value if empty (for sensitive fields). - if ( '' === $value && ! empty( $option_name ) ) { - $existing = get_option( $option_name ); - if ( ! empty( $existing ) ) { - return $existing; - } - } - - // Sanitize. - $value = sanitize_text_field( $value ); - - // Encrypt sensitive fields. - if ( ! empty( $option_name ) && ! empty( $this->sensitive_options[ $option_name ] ) && ! empty( $value ) ) { - $value = $this->encryption->encrypt( $value ); - } - - return $value; + printf( + '', + esc_attr( $current ), + esc_attr__( 'Provider default', 'admin-coach-tours' ) + ); + echo '

'; + esc_html_e( 'Optional model ID to use (e.g., a specific model name). Leave empty to use the provider default.', 'admin-coach-tours' ); + echo '

'; } } diff --git a/readme.txt b/readme.txt index ab8f60b..a1065f6 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === Admin Coach Tours === Contributors: PerS Tags: gutenberg, block editor, tutorial, guided tour, ai, learning -Requires at least: 6.8 +Requires at least: 7.0 Tested up to: 6.9 Requires PHP: 8.3 -Stable tag: 0.4.1 +Stable tag: 0.5.0 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -17,7 +17,7 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera = Features = * **AI-Generated Tours** — On-demand tutorials created by AI based on your request -* **12 Predefined Tasks** — Common tasks like adding images, videos, headings, and more +* **Ready-made Task Library** — 20+ common tasks like adding images, videos, headings, paragraphs, and more * **Freeform Questions** — Ask anything about the block editor * **Interactive Overlay** — Visual highlighting guides you through each step * **Smart Block Targeting** — Accurately identifies and highlights the correct elements @@ -26,48 +26,67 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera = Predefined Tasks = -**Media:** -* Add an image -* Add a video -* Create a gallery -* Add a cover image - **Text & Content:** +* Add a paragraph * Add a heading * Create a list * Add a quote * Create a table -* Add a button +* Format text (bold, italic, links) +* Add a code block +* Add a separator +* Add a details/accordion + +**Media:** +* Add an image +* Add a video +* Create a gallery +* Add a cover image +* Add audio +* Add a file download **Design & Layout:** +* Add a button * Create columns -* Add a group block +* Group blocks in a container +* Add spacing **Embeds:** * Embed a YouTube video +* Embed from a URL = Requirements = -* WordPress 6.8 or later +* WordPress 7.0 or later * PHP 8.3 or later -* sodium extension (for API key encryption) -* AI provider API key (OpenAI, Azure OpenAI, or Anthropic) +* At least one WordPress AI provider connector configured == Installation == -1. Upload the `admin-coach-tours` folder to `/wp-content/plugins/` -2. Activate the plugin through the 'Plugins' menu in WordPress -3. Go to **Tools → Coach Tours** to configure your AI provider += From a release zip (recommended) = + +1. Download `admin-coach-tours.zip` from the latest GitHub release: https://github.com/soderlind/admin-coach-tours/releases/latest +2. In wp-admin, go to Plugins > Add New > Upload Plugin, choose the zip, and click Install Now. +3. Activate the plugin. +4. Go to **Tools → Coach Tours** to enable AI features. + += From source = + +1. `git clone https://github.com/soderlind/admin-coach-tours.git` +2. `composer install --no-dev` and `npm ci && npm run build` +3. Copy the folder into `wp-content/plugins/` and activate it. + += Updates = + +The plugin updates itself from GitHub releases — new versions appear under Plugins and Dashboard > Updates like any other plugin. = Configure AI Provider = -1. Navigate to **Tools → Coach Tours** -2. Enable AI Features -3. Select your provider: - * **OpenAI** — Add your API key - * **Azure OpenAI** — Add endpoint URL, API key, and deployment name - * **Anthropic** — Add your API key -4. Save settings +1. Configure at least one WordPress AI provider connector +2. Navigate to **Tools → Coach Tours** +3. Enable AI Features +4. Optionally choose a preferred provider and model override +5. Save settings == Usage == @@ -92,14 +111,11 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera = What AI providers are supported? = -Admin Coach Tours supports: -* OpenAI (GPT-4 and GPT-4o models) -* Azure OpenAI -* Anthropic (Claude models) +Admin Coach Tours uses the WordPress AI connector, so it works with whatever AI provider connectors you have configured in WordPress (for example, an Azure AI Foundry or OpenAI connector). Configure at least one connector, then enable AI features in the plugin. = Are API keys stored securely? = -Yes, all API keys are encrypted using PHP's sodium extension before being stored in the database. +This plugin does not store API keys. Keys are managed by the WordPress AI connector you configure; the plugin only calls the connector. = Do I need to create my own tours? = @@ -122,6 +138,22 @@ Yes, the plugin can be activated network-wide or on individual sites. == Changelog == += 0.5.0 = +* Changed: Reworked the AI layer to use the WordPress 7 AI Connector (wp_get_connectors / wp_ai_client_prompt); removed bundled providers and API-key encryption. Now requires WordPress 7.0+ and at least one configured AI provider connector. +* Changed: Refactored the AI tour-generation flow into deep modules; the REST controller is now a thin adapter. +* Changed: Slimmed the settings page to connector status, an enable toggle, and an optional provider/model override. +* Changed: Reordered task categories (Text first) and expanded the task list (Paragraph, Code, Separator, Details, Audio, File, Group, Spacer, Embed-from-URL). +* Added: Coach panel shows the "/command" as a header above the instruction. +* Added: Self-updates from GitHub releases via the WordPress Plugin GitHub Updater, plus release-zip build workflows. +* Added: Refocus the current block when a tour closes. +* Added: Finish the tour by clicking the highlighted block on the last step. +* Added: Hide the launcher when the editor is in code editor mode. +* Added: Stop the tour with a clear message when a different block than expected is inserted. +* Fixed: generate-tour REST route rejected null editorContext/failureContext. +* Fixed: wpBlock "selected" now falls back to the last-selected block so multi-step tours don't fail on confirmation steps. +* Fixed: Removed the arrow "Next" button that let users skip ahead before a block existed. +* Fixed: Tour copy no longer uses positional words ("below"/"above"). + = 0.4.0 = * Added: Localized AI responses - tours are now generated in the user's WordPress language * Added: Language detection using WordPress format_code_lang() for accurate language names diff --git a/tests/php/AiControllerTest.php b/tests/php/AiControllerTest.php index 5413567..80cec85 100644 --- a/tests/php/AiControllerTest.php +++ b/tests/php/AiControllerTest.php @@ -2,6 +2,11 @@ /** * Test AI Controller. * + * The controller is now a thin HTTP adapter; sanitization of editor/failure + * context lives in TourRequest and cache/orchestration in TourGenerator, each + * tested in their own files. This covers the element-context sanitization the + * controller still owns, plus the endpoint surface. + * * @package AdminCoachTours */ @@ -28,8 +33,6 @@ protected function setUp(): void { Functions\stubTranslationFunctions(); Functions\stubEscapeFunctions(); - - // Mock apply_filters to return the first argument. Functions\when( 'apply_filters' )->returnArg(); } @@ -42,48 +45,17 @@ protected function tearDown(): void { } /** - * Test generate_draft returns error when AI not available. + * Test the REST endpoint surface exists. */ - public function test_generate_draft_returns_error_when_ai_unavailable(): void { - // Create mock request. - $request = $this->createMock( \WP_REST_Request::class); - $request->method( 'get_param' ) - ->willReturnCallback( - function ( $param ) { - if ( 'elementContext' === $param ) { - return [ - 'tagName' => 'button', - 'role' => 'button', - ]; - } - return null; - } - ); - - // Mock AiManager to return not available. - $ai_manager = $this->getMockBuilder( \stdClass::class) - ->addMethods( [ 'is_available' ] ) - ->getMock(); - $ai_manager->method( 'is_available' )->willReturn( false ); - - // This test demonstrates the expected behavior. - // In a real scenario, we'd mock the singleton properly. - $this->assertTrue( true ); - } - - /** - * Test get_status returns proper structure. - */ - public function test_get_status_returns_structure(): void { - Functions\when( 'rest_ensure_response' )->returnArg(); - - // This would need proper mocking of AiManager. - // For now, verify the method exists and is callable. + public function test_endpoint_methods_exist(): void { + $this->assertTrue( method_exists( AiController::class, 'generate_draft' ) ); $this->assertTrue( method_exists( AiController::class, 'get_status' ) ); + $this->assertTrue( method_exists( AiController::class, 'get_tasks' ) ); + $this->assertTrue( method_exists( AiController::class, 'generate_tour' ) ); } /** - * Test sanitization of element context. + * Test sanitization of element context strips unsafe values. */ public function test_element_context_sanitization(): void { Functions\when( 'sanitize_key' )->alias( @@ -102,7 +74,6 @@ function ( $str ) { } ); - // Use reflection to test private sanitize method. $reflection = new \ReflectionClass( AiController::class); $method = $reflection->getMethod( 'sanitize_element_context' ); $method->setAccessible( true ); @@ -121,52 +92,6 @@ function ( $str ) { $this->assertStringNotContainsString( '