Plugin Directory

Changeset 3763701


Ignore:
Timestamp:
10/24/2026 07:54:55 PM (12 hours ago)
Author:
sakkoulas
Message:

Store Assistant 1.3.5: simpler two-step setup, started-in-Claude onboarding, HTTPS proxy fix

Location:
addsynapse-store-assistant
Files:
2 added
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • addsynapse-store-assistant/tags/1.3.5/addsynapse-store-assistant.php

    r3759648 r3763701  
    44 * Plugin URI:        https://addsynapse.com/store-assistant
    55 * Description:       Admin chat where shop managers ask, in their own language, about their store and for changes to one product at a time — with your own Anthropic or OpenAI API key. Every change is a preview a person approves, with Undo.
    6  * Version:           1.3.4
     6 * Version:           1.3.5
    77 * Requires at least: 6.9
    88 * Requires PHP:      7.4
    … …  
    2020defined( 'ABSPATH' ) || exit;
    2121
    22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.4' );
     22define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.5' );
    2323define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_FILE', __FILE__ );
    2424define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    5555
    5656register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Schema::class, 'install' ) );
     57// 1.3.5: remember the activation for one minute (continues a connection started in Claude, HomePage::maybe_start_from_claude).
     58register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Admin\HomePage::class, 'activated' ) );
    5759register_deactivation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Lifecycle::class, 'deactivate' ) );
    5860
  • addsynapse-store-assistant/tags/1.3.5/assets/home.css

    r3759648 r3763701  
    313313.adsa-apppw { margin: 12px 0 16px; }
    314314.adsa-apppw .adsa-buttons { margin-bottom: 4px; }
     315
     316/* 1.3.5: step 2 = one «Connect to AddSynapse» button; step 3 collapsed. */
     317.adsa-connect__sub { margin: -4px 0 8px; }
     318.adsa-addai { margin-top: 8px; }
     319
     320/* 1.3.5: «Done — go back to Claude» (from Claude, right after «Yes, connect»). */
     321.adsa-done { max-width: 720px; margin: 24px 0; padding: 32px; background: #fff; border: 1px solid #b8e6bf; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); }
     322.adsa-done__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border-radius: 50%; background: var(--adsa-ok); color: #fff; }
     323.adsa-done__icon .adsa-icon { display: block; width: 26px; height: 26px; }
     324.adsa-done__title { margin: 16px 0 8px; font-size: 24px; line-height: 1.3; }
     325.adsa-done__lead { margin: 0 0 12px; font-size: 15px; }
     326.adsa-done__links { margin: 16px 0 0; }
     327@media (max-width: 600px) { .adsa-done { padding: 20px; } .adsa-done__title { font-size: 20px; } }
     328
     329/* 1.3.5: «You're all set!» (Home) and «Done — go back to Claude». */
     330.adsa-allset { margin: 20px 0 24px; padding: 28px 32px; background: #fff; border: 1px solid #b8e6bf; border-left: 4px solid var(--adsa-ok); border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); max-width: 860px; }
     331.adsa-allset__head { display: flex; align-items: center; gap: 14px; }
     332.adsa-allset__title { margin: 0; font-size: 26px; line-height: 1.25; }
     333.adsa-allset__lead { margin: 12px 0 0; font-size: 15px; }
     334.adsa-allset__try { margin: 18px 0 8px; font-size: 15px; }
     335.adsa-allset__prompts { margin: 0; padding: 0; list-style: none; }
     336.adsa-allset__prompts li { margin: 0 0 8px; }
     337.adsa-allset__open { margin: 18px 0 8px; display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
     338.adsa-allset__hide { margin: 8px 0 0; text-align: right; }
     339.adsa-allset-collapsed { max-width: 860px; }
     340.adsa-allset-collapsed .adsa-allset { box-shadow: none; margin: 12px 0 0; }
     341.adsa-recheck { margin: 8px 0 0; }
     342@media (max-width: 600px) { .adsa-allset { padding: 20px 16px; } .adsa-allset__title { font-size: 21px; } }
  • addsynapse-store-assistant/tags/1.3.5/assets/home.js

    r3759648 r3763701  
    8686        }
    8787
    88         /* «Mark» actions: «I've added it» (step 2), «It worked» (step 4) — explicit buttons only. */
     88        /* «Mark» actions: «I've added it» (step 3) — explicit buttons only. */
    8989        root.querySelectorAll( '[data-mark]' ).forEach( function ( n ) {
    9090                n.addEventListener( 'click', function () {
    … …  
    132132        var checkBtn = root.querySelector( '.adsa-check' );
    133133        if ( checkBtn && apiFetch ) {
    134                 checkBtn.addEventListener( 'click', function () {
     134                var runCheck = function () {
    135135                        var out = root.querySelector( '.adsa-check-result' );
    136136                        out.textContent = t.checking || '…';
    … …  
    163163                                        checkBtn.disabled = false;
    164164                                } );
    165                 } );
    166         }
    167 
    168         /* Steps 3–4: poll this site's own state (an approved AddSynapse application password, its first use). */
     165                };
     166                checkBtn.addEventListener( 'click', runCheck );
     167                /* 1.3.5: the check runs by itself when the page opens; «Re-check» runs it again. */
     168                if ( root.querySelector( '[data-adsa-autocheck]' ) ) {
     169                        runCheck();
     170                }
     171        }
     172
     173        /* Steps 2–3: poll this site's own state (an approved AddSynapse key, its first use). */
    169174        function poll() {
    170175                apiFetch( { path: cfg.status } )
    … …  
    174179                                }
    175180                                if ( s.ready || s.connected ) {
    176                                         /* Step 2 turns green on its own once the site is connected (step 3 done implies it was added). */
    177                                         setDone( 'add', true );
     181                                        /* 1.3.5: step 3 («Use it from Claude or ChatGPT») shows once the site is connected. */
     182                                        root.querySelectorAll( '[data-adsa-after-connect]' ).forEach( function ( n ) {
     183                                                n.hidden = false;
     184                                        } );
     185                                        root.querySelectorAll( '[data-adsa-before-connect]' ).forEach( function ( n ) {
     186                                                n.hidden = true;
     187                                        } );
    178188                                }
    179189                                if ( s.connected ) {
    … …  
    181191                                        state( 'connect', ( t.approved || '%s' ).replace( '%s', s.detail.user ), true );
    182192                                }
    183                                 if ( s.ready ) {
     193                                /* A connected site passed what step 1 checks, unless a real check failed. */
     194                                if ( s.ready || ( s.connected && ! s.check_failed ) ) {
    184195                                        setDone( 'check', true );
    185196                                }
    186197                                if ( s.used || s.ready ) {
     198                                        setDone( 'add', true );
    187199                                        setDone( 'confirm', true );
    188200                                        state( 'confirm', s.used ? t.read : ( t.checked || t.read ), true );
  • addsynapse-store-assistant/tags/1.3.5/includes/Account/Api.php

    r3759269 r3763701  
    1313
    1414/**
    15  * Four calls exist, and nothing else ever contacts AddSynapse:
     15 * These calls exist, and nothing else ever contacts AddSynapse:
     16 *  - PendingOnboarding::from_claude(): `GET /api/onboarding/pending?site=<home URL>` — only for an administrator while
     17 *    nothing is set up (after activation / the first setup screen), at most once per 10 minutes, the site address only;
    1618 *  - OptIn::handle_create(): `POST /api/v1/assistant/optin` — only when an administrator clicks «Create account»;
    1719 *  - Announcements::maybe_fetch(): `POST /api/v1/assistant/announcements` — at most once a day, only after that, only while
    … …  
    130132
    131133        /**
     134         * Whether a failed request surely never reached the server (no name lookup / no connection).
     135         *
     136         * @param WP_Error $error wp_remote_post() error.
     137         */
     138        private static function not_sent( WP_Error $error ): bool {
     139                return (bool) preg_match( '/cURL error (6|7):|Could not resolve|Failed to connect|Connection refused/i', $error->get_error_message() );
     140        }
     141
     142        /**
    132143         * POST JSON.
    133144         *
    … …  
    142153                        return new WP_Error( 'addsynapse_assistant_api_url', 'The AddSynapse address is not allowed on this site.' );
    143154                }
    144                 $response = wp_remote_post(
    145                         $base . $path,
    146                         array(
    147                                 'timeout'     => $timeout,
    148                                 'redirection' => 0,
    149                                 'headers'     => array(
    150                                         'Content-Type' => 'application/json',
    151                                         'Accept'       => 'application/json',
    152                                 ),
    153                                 'body'        => (string) wp_json_encode( $body ),
    154                         )
     155                $args     = array(
     156                        'timeout'     => $timeout,
     157                        'redirection' => 0,
     158                        'headers'     => array(
     159                                'Content-Type' => 'application/json',
     160                                'Accept'       => 'application/json',
     161                        ),
     162                        'body'        => (string) wp_json_encode( $body ),
    155163                );
     164                $response = wp_remote_post( $base . $path, $args );
     165                // 1.3.5 (satest 24/10: a connect POST failed on this server and never reached AddSynapse): one retry, only when
     166                // the request surely never left (name lookup / connect failed) — a request that may have arrived is never sent twice.
     167                if ( is_wp_error( $response ) && self::not_sent( $response ) ) {
     168                        $response = wp_remote_post( $base . $path, $args );
     169                }
    156170                if ( is_wp_error( $response ) ) {
    157                         return new WP_Error( 'addsynapse_assistant_api_unreachable', 'AddSynapse could not be reached. Try again in a few minutes.' );
     171                        $detail = sanitize_text_field( mb_substr( $response->get_error_message(), 0, 160 ) );
     172                        return new WP_Error(
     173                                'addsynapse_assistant_api_unreachable',
     174                                /* translators: %s: technical detail from WordPress, e.g. «cURL error 28: Operation timed out». */
     175                                sprintf( __( 'This site could not reach AddSynapse (%s). Nothing was connected. Try again in a minute; if it keeps failing, ask your host whether outgoing HTTPS requests to api.addsynapse.com are allowed.', 'addsynapse-store-assistant' ), '' !== $detail ? $detail : 'no detail' )
     176                        );
    158177                }
    159178                $decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true );
  • addsynapse-store-assistant/tags/1.3.5/includes/Admin/HomePage.php

    r3759648 r3763701  
    1616use Addsynapse\StoreAssistant\Account\Api;
    1717use Addsynapse\StoreAssistant\Account\OptIn;
     18use Addsynapse\StoreAssistant\Account\PendingOnboarding;
    1819use Addsynapse\StoreAssistant\Account\Usage;
    1920use Addsynapse\StoreAssistant\Brain\Models;
    … …  
    4142        public const ACTION_FINISH = 'addsynapse_assistant_onboarding_finish';
    4243
     44        /** The admin-post action of «Hide» on the «You're all set!» panel (1.3.5). */
     45        public const ACTION_ALLSET_HIDE = 'addsynapse_assistant_allset_hide';
     46
     47        /** Option: array{since:int,hidden:bool} — when Home first showed «You're all set!», and whether it was hidden. */
     48        public const ALLSET_OPTION = 'addsynapse_assistant_allset';
     49
     50        /** Days the «You're all set!» panel stays open on Home. */
     51        public const ALLSET_DAYS = 7;
     52
    4353        /** The admin-post action of «Disconnect AddSynapse» (1.3.4). */
    4454        public const ACTION_DISCONNECT = 'addsynapse_assistant_relay_disconnect';
    … …  
    4656        /** Script/style handle. */
    4757        public const HANDLE = 'addsynapse-assistant-home';
     58
     59        /** Transient: the administrator (user id) who just activated the plugin (one minute). */
     60        public const JUST_ACTIVATED = 'addsynapse_assistant_just_activated';
     61
     62        /** Transient: the Claude connection was continued automatically once (no loop; 10 minutes). */
     63        public const AUTO_STARTED = 'addsynapse_assistant_onboarding_auto';
    4864
    4965        /** The wizard views (query arg `setup`). */
    … …  
    5672                add_action( 'admin_post_' . self::ACTION_FINISH, array( $this, 'handle_finish' ) );
    5773                add_action( 'admin_post_' . self::ACTION_DISCONNECT, array( self::class, 'handle_disconnect' ) );
     74                add_action( 'admin_post_' . self::ACTION_ALLSET_HIDE, array( self::class, 'handle_allset_hide' ) );
    5875                // Before WordPress checks access to the requested page: old addresses keep working.
    5976                add_action( 'admin_menu', array( self::class, 'legacy_redirects' ), 1 );
     77                // After RelayClaim::maybe_handle (admin_init:1), before any output.
     78                add_action( 'admin_init', array( self::class, 'maybe_start_from_claude' ), 5 );
     79        }
     80
     81        /**
     82         * Activation (a second activation callback, next to Schema::install): remember for one minute that an administrator
     83         * just activated the plugin, so the next admin page can continue the Claude connection (maybe_start_from_claude).
     84         * Not on bulk activation, WP-CLI or network activation.
     85         *
     86         * @param bool $network_wide Network activation.
     87         */
     88        public static function activated( $network_wide = false ): void {
     89                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only: only tells a bulk activation apart; core checked its own nonce.
     90                if ( $network_wide || ( defined( 'WP_CLI' ) && WP_CLI ) || isset( $_REQUEST['checked'] ) ) {
     91                        return;
     92                }
     93                // A new activation asks AddSynapse afresh and may continue the connection again (satest 24/10: a re-activation
     94                // within 10 minutes found the old «already continued» guard and did nothing).
     95                PendingOnboarding::reset();
     96                set_transient( self::JUST_ACTIVATED, get_current_user_id(), MINUTE_IN_SECONDS );
     97        }
     98
     99        /**
     100         * 1.3.5 (owner 24/10, «6 clicks from Claude»): the manager started in Claude and AddSynapse has a connection pending for
     101         * this site → right after activation (or on the first visit of the wizard) continue that connection: the browser goes
     102         * to AddSynapse's connect page for this site, exactly as the «Connect to AddSynapse» button does, and AddSynapse sends
     103         * it straight back to this plugin's «Connect <host> to your AddSynapse account? [Yes, connect] [Cancel]» screen.
     104         * Nothing is connected without «Yes, connect». Once per 10 minutes at most (no loop); otherwise nothing happens and the
     105         * usual screens show.
     106         */
     107        public static function maybe_start_from_claude(): void {
     108                if ( wp_doing_ajax() || is_network_admin() || ! current_user_can( 'manage_options' ) ) {
     109                        return;
     110                }
     111                $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET';
     112                if ( 'GET' !== $method ) {
     113                        return;
     114                }
     115                global $pagenow;
     116                $just = (int) get_transient( self::JUST_ACTIVATED );
     117                if ( $just > 0 && 'plugins.php' === $pagenow ) {
     118                        delete_transient( self::JUST_ACTIVATED );
     119                } else {
     120                        $just = 0;
     121                }
     122                $wizard = 'admin.php' === $pagenow && AddsynapseMenu::PARENT_SLUG === self::flag( 'page' )
     123                        && '' === self::flag( 'as_relay' ) && '' === self::flag( 'setup' ) && '' === self::flag( 'view' ) && '' === self::flag( 'welcome' );
     124                if ( ( get_current_user_id() !== $just && ! $wizard ) || get_transient( self::AUTO_STARTED ) || self::is_set_up() || ! RelayKeys::transport_ok() ) {
     125                        // Not «needs_setup()»: a wizard finished on an earlier install (the option outlives deactivation) must not stop
     126                        // a new connection started in Claude (satest 24/10). Only a working connection does.
     127                        return;
     128                }
     129                if ( ! PendingOnboarding::from_claude() ) {
     130                        return;
     131                }
     132                $url = Api::claude_connect_url( home_url( '/' ) );
     133                if ( ! Api::link_allowed( $url ) ) {
     134                        return;
     135                }
     136                set_transient( self::AUTO_STARTED, 1, PendingOnboarding::TTL );
     137                // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- AddSynapse's own connect page (Api::link_allowed: https on an AddSynapse host only).
     138                wp_redirect( $url, 302 );
     139                exit;
    60140        }
    61141
    … …  
    133213                        return 'home';
    134214                }
    135                 return 'choose';
     215                return self::from_claude() ? 'mcp' : 'choose';
     216        }
     217
     218        /**
     219         * 1.3.5 (owner 24/10): the manager started in Claude (AddSynapse says a connection for this site address is pending),
     220         * so the first screen skips «Choose how you want to connect» and opens the Claude path with one button. Only on the
     221         * wizard's default screen (no `setup`/`view` asked for) of a site with nothing set up; «Other options» (setup=choose)
     222         * always shows both ways. Any failure of the question = the usual first screen.
     223         */
     224        public static function from_claude(): bool {
     225                if ( '' !== self::flag( 'setup' ) || 'home' === self::flag( 'view' ) || ! current_user_can( Config::capability() ) || self::is_set_up() ) {
     226                        return false;
     227                }
     228                return PendingOnboarding::from_claude();
    136229        }
    137230
    … …  
    376469         */
    377470        private static function back_link(): void {
     471                if ( self::from_claude() ) {
     472                        // Not a lock (wp.org guideline 5): both ways stay one click away.
     473                        echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">';
     474                        self::print_icon( 'back' );
     475                        echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>';
     476                        return;
     477                }
    378478                $target = self::state()['done'] || self::is_set_up() ? self::url() : self::setup_url( 'choose' );
    379479                echo '<a class="adsa-back" href="' . esc_url( $target ) . '">';
    … …  
    385485         * The «Finish» form of a path.
    386486         *
    387          * @param string $path mcp | key.
    388          * @param string $note Text next to the button.
    389          */
    390         private static function finish( string $path, string $note ): void {
     487         * @param string $path    mcp | key.
     488         * @param string $note    Text next to the button.
     489         * @param bool   $primary The screen's main button (false: a plain one, when another button is the main one).
     490         */
     491        private static function finish( string $path, string $note, bool $primary = true ): void {
    391492                echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="adsa-finish">';
    392493                wp_nonce_field( self::ACTION_FINISH );
    393494                echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_FINISH ) . '">';
    394495                echo '<input type="hidden" name="addsynapse_assistant_path" value="' . esc_attr( $path ) . '">';
    395                 echo '<button type="submit" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';
     496                echo '<button type="submit" class="button ' . ( $primary ? 'button-primary button-hero ' : '' ) . 'adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';
    396497                echo '<p class="adsa-finish__note">' . esc_html( $note ) . '</p>';
    397498                echo '</form>';
    … …  
    497598         */
    498599        private static function render_mcp(): void {
    499                 $status  = SetupStatus::connection();
    500                 $confirm = self::is_site()
    501                         ? __( 'Using AddSynapse, tell me my site name and how many pages it has. Change nothing.', 'addsynapse-store-assistant' )
    502                         : __( 'Using AddSynapse, tell me my store name and how many products it has. Change nothing.', 'addsynapse-store-assistant' );
     600                $status = SetupStatus::connection();
     601                // Came from Claude and just pressed «Yes, connect» (AddSynapse sent the browser back): a clean «Done» screen, not
     602                // the three steps (owner 24/10). The steps stay one click away.
     603                if ( ( ! empty( $status['ready'] ) || ! empty( $status['connected'] ) ) && 'connected' === self::flag( 'as_relay' ) && PendingOnboarding::cached_from_claude() ) {
     604                        self::render_arrival_done();
     605                        return;
     606                }
    503607                // Connected for sure (AddSynapse confirmed the password, or used it): every step is done.
    504608                $ready = ! empty( $status['ready'] );
    505609                // 1.3.4: AddSynapse connects with this plugin's own key, so Application Passwords being off (Wordfence's default) no
    506610                // longer blocks anything; only HTTPS is required (RelayKeys::transport_ok).
    507                 $steps = array(
    508                         'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] ),
    509                         // Done once the site is connected (step 3 done: the AI already sent the connect link), or the manager says so
    510                         // («I've added it») — never on a click of «Add to Claude» or «Copy». Owner 22/10: step 3 green, step 2 not.
    511                         'add'     => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), $ready || (bool) $status['connected'] ),
    512                         'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), (bool) $status['connected'] ),
    513                         'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), $ready || (bool) $status['used'] ),
    514                 );
     611                // 1.3.5 (owner 24/10): step 2 is ONE button that connects this site to the AddSynapse account (the account usually
     612                // already uses Claude); adding AddSynapse to an AI app moves to step 3, shown once the site is connected.
     613                $connected = $ready || (bool) $status['connected'];
     614                // Came from Claude and not connected yet: «Connect to AddSynapse» is the only primary button on the screen.
     615                $arrival = self::from_claude() && ! $connected;
     616                $steps   = array(
     617                        // A connected site has passed what the check checks (AddSynapse reached it): step 1 is done unless a real
     618                        // check ran and failed (owner 24/10: never «not done» next to two green steps).
     619                        'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] || ( $connected && empty( $status['check_failed'] ) ) ),
     620                        'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), $connected ),
     621                );
     622                // 1.3.5 (owner 24/10): two steps — the check (runs by itself) and Connect — then «Finish». How to use it from
     623                // Claude or ChatGPT is on Home («You're all set!»).
    515624                self::back_link();
    516625                echo '<div class="adsa-pathhead">';
    517626                echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>';
    518                 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Four short steps, about five minutes. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>';
     627                if ( $arrival ) {
     628                        echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'Press «Connect to AddSynapse» below.', 'addsynapse-store-assistant' ) . '</p>';
     629                } else {
     630                        echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Two short steps. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>';
     631                }
    519632                echo '</div>';
    520633                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: a success line when AddSynapse sent the browser back.
    521                 if ( $ready && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {
     634                if ( $connected && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {
    522635                        // Back from AddSynapse right after «Connect» (1.3.3, hub protocol 3).
    523                         echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     636                        if ( PendingOnboarding::cached_from_claude() ) {
     637                                // Came from Claude: the AddSynapse page in the other tab moves on by itself.
     638                                echo '<div class="notice notice-success adsa-connected-notice adsa-arrival-done" data-adsa-relay-connected data-adsa-from="claude"><p><strong>' . esc_html__( 'Done — go back to Claude.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     639                        } else {
     640                                echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     641                        }
    524642                }
    525643                if ( ! $ready && ! RelayKeys::transport_ok() ) {
    … …  
    533651                self::step_open( 'check', 1, __( 'Check the connection', 'addsynapse-store-assistant' ), $steps['check'][1] );
    534652                echo '<p>' . esc_html__( 'Checks HTTPS, the REST API and the sign-in header of this site. Runs on your server only.', 'addsynapse-store-assistant' ) . '</p>';
    535                 echo '<button type="button" class="button button-primary adsa-check">' . esc_html__( 'Check site connectivity', 'addsynapse-store-assistant' ) . '</button>';
    536                 echo '<div class="adsa-check-result" aria-live="polite"></div>';
     653                // Runs by itself when the page opens (home.js); «Re-check» runs it again.
     654                echo '<div class="adsa-check-result" aria-live="polite" data-adsa-autocheck></div>';
     655                echo '<p class="adsa-recheck"><button type="button" class="button-link adsa-check">' . esc_html__( 'Re-check', 'addsynapse-store-assistant' ) . '</button></p>';
    537656                self::step_close();
    538657
    539                 self::step_open( 'add', 2, __( 'Add AddSynapse to your AI', 'addsynapse-store-assistant' ), $steps['add'][1] );
    540                 self::render_add_ways();
    541                 self::step_close();
    542 
    543                 self::step_open( 'connect', 3, __( 'Connect this site from the chat', 'addsynapse-store-assistant' ), $steps['connect'][1] );
    544                 if ( $ready ) {
    545                         // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect prompt here.
    546                         echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓ — try asking your AI:', 'addsynapse-store-assistant' ) . '</span></p>';
    547                         self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
     658                self::step_open( 'connect', 2, __( 'Connect this site to AddSynapse', 'addsynapse-store-assistant' ), $steps['connect'][1] );
     659                if ( $connected ) {
     660                        // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect button here.
     661                        // 1.3.5: also while AddSynapse has not used the new key yet (pending): a second Connect would make a second key.
     662                        echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</span></p>';
    548663                } else {
    549                         echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>';
    550                         self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
    551                         self::authorize_note();
     664                        self::render_connect_button();
    552665                        $waiting = $status['connected']
    553666                                /* translators: %s: user login */
    … …  
    555668                                : __( 'Waiting for your approval…', 'addsynapse-store-assistant' );
    556669                        echo '<p class="adsa-state' . ( $status['connected'] ? ' is-ok' : ' is-waiting' ) . '" data-state="connect" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $waiting ) . '</span></p>';
     670                        // The older way, from the chat (the AI answers with the same confirm link) — kept for whoever starts there.
     671                        echo '<details class="adsa-more" data-adsa-chat-connect><summary>' . esc_html__( 'Prefer to connect from your AI chat?', 'addsynapse-store-assistant' ) . '</summary>';
     672                        echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>';
     673                        self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
     674                        echo '</details>';
    557675                }
    558676                self::step_close();
    559677
    560                 self::step_open( 'confirm', 4, self::is_site() ? __( 'Confirm your AI can read the site', 'addsynapse-store-assistant' ) : __( 'Confirm your AI can read the store', 'addsynapse-store-assistant' ), $steps['confirm'][1] );
    561                 echo '<p>' . esc_html__( 'Ask one question that changes nothing. This step turns green when your AI reads the store.', 'addsynapse-store-assistant' ) . '</p>';
    562                 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
    563                 $confirmed = $status['used']
    564                         ? __( 'Your AI read the store. You are all set.', 'addsynapse-store-assistant' )
    565                         : ( $ready ? __( 'AddSynapse checked the connection. You are all set.', 'addsynapse-store-assistant' ) : __( 'Waiting for the first answer…', 'addsynapse-store-assistant' ) );
    566                 echo '<p class="adsa-state' . ( $status['used'] || $ready ? ' is-ok' : '' ) . '" data-state="confirm" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $confirmed ) . '</span></p>';
    567                 echo '<p><button type="button" class="button-link adsa-mark" data-mark="confirm">' . esc_html__( 'It worked — mark as done', 'addsynapse-store-assistant' ) . '</button></p>';
    568                 self::step_close();
    569678                echo '</ol>';
    570679
    571                 self::finish( 'mcp', __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ) );
     680                // «Finish» is the main action once connected; before that «Connect to AddSynapse» is.
     681                self::finish( 'mcp', $connected ? __( 'Next: Home shows how to use it from Claude or ChatGPT.', 'addsynapse-store-assistant' ) : __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ), $connected );
    572682
    573683                echo '<details class="adsa-more adsa-more--box"><summary>' . esc_html__( 'More details: what AddSynapse can do on this site', 'addsynapse-store-assistant' ) . '</summary><ul>';
    … …  
    581691
    582692        /**
    583          * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (steps 1–2 done, step 3
     693         * «Done — go back to Claude» (from=claude, right after «Yes, connect»). Nothing to press here: the AddSynapse page in
     694         * the other tab moves on by itself. A prompt to try, and links to Home and to the setup steps.
     695         */
     696        private static function render_arrival_done(): void {
     697                self::render_allset( __( 'Done — go back to Claude.', 'addsynapse-store-assistant' ), __( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ), false );
     698                echo '<p class="adsa-done__links"><a href="' . esc_url( self::url( array( 'view' => 'home' ) ) ) . '">' . esc_html__( 'Go to Home', 'addsynapse-store-assistant' ) . '</a> · <a href="' . esc_url( self::setup_url( 'mcp' ) ) . '">' . esc_html__( 'See the setup steps', 'addsynapse-store-assistant' ) . '</a></p>';
     699        }
     700
     701        /**
     702         * Three example requests for Claude or ChatGPT (store or plain site).
     703         *
     704         * @return string[]
     705         */
     706        private static function try_prompts(): array {
     707                return self::is_site()
     708                        ? array(
     709                                __( 'Show me my 3 latest pages', 'addsynapse-store-assistant' ),
     710                                __( 'Which pages have no SEO description?', 'addsynapse-store-assistant' ),
     711                                __( 'What is in my main menu?', 'addsynapse-store-assistant' ),
     712                        )
     713                        : array(
     714                                __( 'Show me 3 products from my store', 'addsynapse-store-assistant' ),
     715                                __( 'Which products have no image?', 'addsynapse-store-assistant' ),
     716                                __( 'What were my best sellers last month?', 'addsynapse-store-assistant' ),
     717                        );
     718        }
     719
     720        /**
     721         * «You're all set!» — open Claude (primary) or ChatGPT, three requests to copy, and (collapsed) how to add AddSynapse
     722         * to an AI app. Ordinary links the browser opens; nothing is sent by the server.
     723         *
     724         * @param string $title   Heading.
     725         * @param string $lead    One line under it ('' = none).
     726         * @param bool   $dismiss Show «Hide» (Home only).
     727         */
     728        private static function render_allset( string $title, string $lead, bool $dismiss ): void {
     729                echo '<section class="adsa-allset" data-adsa-allset role="status" aria-labelledby="adsa-allset-title">';
     730                echo '<div class="adsa-allset__head"><span class="adsa-done__icon" aria-hidden="true">';
     731                self::print_icon( 'check' );
     732                echo '</span><h2 class="adsa-allset__title" id="adsa-allset-title">' . esc_html( $title ) . '</h2></div>';
     733                if ( '' !== $lead ) {
     734                        echo '<p class="adsa-allset__lead">' . esc_html( $lead ) . '</p>';
     735                }
     736                echo '<p class="adsa-allset__try"><strong>' . esc_html__( 'Open Claude and try:', 'addsynapse-store-assistant' ) . '</strong></p>';
     737                echo '<ul class="adsa-allset__prompts">';
     738                foreach ( self::try_prompts() as $prompt ) {
     739                        echo '<li>';
     740                        self::copy_field( $prompt, __( 'Request to send', 'addsynapse-store-assistant' ), __( 'Copy', 'addsynapse-store-assistant' ) );
     741                        echo '</li>';
     742                }
     743                echo '</ul>';
     744                echo '<p class="adsa-buttons adsa-allset__open"><a class="button button-primary button-hero" href="https://claude.ai/new" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open Claude ↗', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a> ';
     745                echo '<a class="adsa-allset__gpt" href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a></p>';
     746                echo '<details class="adsa-more adsa-addai" data-adsa-add-ways><summary>' . esc_html__( 'AddSynapse not in your Claude or ChatGPT yet?', 'addsynapse-store-assistant' ) . '</summary>';
     747                self::render_add_ways();
     748                echo '</details>';
     749                if ( $dismiss ) {
     750                        echo '<form method="post" class="adsa-allset__hide" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
     751                        wp_nonce_field( self::ACTION_ALLSET_HIDE );
     752                        echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_ALLSET_HIDE ) . '">';
     753                        echo '<button type="submit" class="button-link">' . esc_html__( 'Hide', 'addsynapse-store-assistant' ) . '</button></form>';
     754                }
     755                echo '</section>';
     756        }
     757
     758        /**
     759         * Home: the «You're all set!» panel once connected — prominent for the first days (ALLSET_DAYS) or until hidden,
     760         * then collapsed.
     761         *
     762         * @param array<string,mixed> $status SetupStatus::connection().
     763         */
     764        private static function render_home_allset( array $status ): void {
     765                if ( empty( $status['connected'] ) ) {
     766                        return;
     767                }
     768                $state = get_option( self::ALLSET_OPTION, array() );
     769                $state = is_array( $state ) ? $state : array();
     770                if ( empty( $state['since'] ) ) {
     771                        $state = array(
     772                                'since'  => time(),
     773                                'hidden' => false,
     774                        );
     775                        update_option( self::ALLSET_OPTION, $state, false );
     776                }
     777                $fresh = empty( $state['hidden'] ) && ( time() - (int) $state['since'] ) < self::ALLSET_DAYS * DAY_IN_SECONDS;
     778                if ( $fresh ) {
     779                        self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', true );
     780                        return;
     781                }
     782                echo '<details class="adsa-more adsa-allset-collapsed" data-adsa-allset-collapsed><summary>' . esc_html__( 'Try it in Claude or ChatGPT', 'addsynapse-store-assistant' ) . '</summary>';
     783                self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', false );
     784                echo '</details>';
     785        }
     786
     787        /**
     788         * «Hide» of the «You're all set!» panel (it stays, collapsed).
     789         */
     790        public static function handle_allset_hide(): void {
     791                if ( ! current_user_can( Config::capability() ) ) {
     792                        wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 );
     793                }
     794                check_admin_referer( self::ACTION_ALLSET_HIDE );
     795                $state           = get_option( self::ALLSET_OPTION, array() );
     796                $state           = is_array( $state ) ? $state : array();
     797                $state['since']  = (int) ( $state['since'] ?? time() );
     798                $state['hidden'] = true;
     799                update_option( self::ALLSET_OPTION, $state, false );
     800                wp_safe_redirect( self::url( array( 'view' => 'home' ) ) );
     801                exit;
     802        }
     803
     804
     805        /**
     806         * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (step 1 done, step 2
    584807         * current) with the «confirm the account» card of RelayClaim.
    585808         */
    586809        private static function render_relay_confirm(): void {
     810                if ( PendingOnboarding::cached_from_claude() ) {
     811                        // Keep the answer for the «Done» screen after «Yes, connect» (the confirm may take a while).
     812                        PendingOnboarding::keep();
     813                }
     814                if ( PendingOnboarding::cached_from_claude() && ! self::is_set_up() ) {
     815                        // Not a lock (wp.org guideline 5): Cancel and both ways stay one click away.
     816                        echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">';
     817                        self::print_icon( 'back' );
     818                        echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>';
     819                }
    587820                echo '<div class="adsa-pathhead">';
    588821                echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>';
     822                if ( PendingOnboarding::cached_from_claude() ) {
     823                        echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong></p>';
     824                }
    589825                echo '</div>';
    590826                self::progress(
    591827                        array(
    592828                                'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), true ),
    593                                 'add'     => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), true ),
    594829                                'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), false ),
    595                                 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), false ),
    596830                        )
    597831                );
    … …  
    606840         */
    607841        public static function authorize_note(): void {
    608                 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>';
    609         }
    610 
    611         /**
    612          * Step 2: two clear ways to add AddSynapse — Claude's connector directory (one click), or a custom connector by address
     842                echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Yes, connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>';
     843        }
     844
     845        /**
     846         * Step 2: the one button — AddSynapse's connect page for this site (sign in or create a free account, approve the
     847         * access, then confirm it on this plugin's page). An ordinary link the administrator's browser opens; nothing is sent
     848         * by the server.
     849         */
     850        private static function render_connect_button(): void {
     851                echo '<p>' . esc_html( self::is_site() ? __( 'Links this site to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) : __( 'Links this store to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) ) . '</p>';
     852                printf( '<p class="adsa-buttons"><a class="button button-primary button-hero adsa-connect" href="%s">%s</a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Connect to AddSynapse', 'addsynapse-store-assistant' ) );
     853                echo '<p class="description adsa-connect__sub">' . esc_html__( 'Free account — sign in or create one in a minute.', 'addsynapse-store-assistant' ) . '</p>';
     854                self::authorize_note();
     855        }
     856
     857        /**
     858         * Step 3 (collapsed): two clear ways to add AddSynapse to an AI app — Claude's directory (one click), or by address
    613859         * (ChatGPT beta, Claude, other AI apps).
    614860         */
    … …  
    620866                echo '<h3 class="adsa-addway__title" id="adsa-addway-claude">' . esc_html__( 'Claude — one click from the connector directory', 'addsynapse-store-assistant' ) . '</h3>';
    621867                echo '<p>' . esc_html__( 'Opens AddSynapse in Claude\'s connector directory. Press Connect there and sign in to AddSynapse (or create a free account).', 'addsynapse-store-assistant' ) . '</p>';
    622                 printf( '<p class="adsa-buttons"><a class="button button-primary adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
     868                printf( '<p class="adsa-buttons"><a class="button adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CLAUDE_URL ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
    623869                echo '</section>';
    624870
    … …  
    642888                echo '<li>' . esc_html__( 'Authentication: OAuth. Tick «I trust this application» and press Create.', 'addsynapse-store-assistant' ) . '</li>';
    643889                echo '<li>' . esc_html__( 'Sign in to AddSynapse (or create a free account) and approve the access.', 'addsynapse-store-assistant' ) . '</li>';
    644                 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then continue with step 3 below.', 'addsynapse-store-assistant' ) . '</li>';
     890                echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then ask it a question.', 'addsynapse-store-assistant' ) . '</li>';
    645891                echo '</ol>';
    646892                printf( '<p><a class="button" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CHATGPT_URL ), esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
    … …  
    652898                echo '</section>';
    653899                echo '</div>';
    654 
    655                 echo '<p class="adsa-added"><button type="button" class="button adsa-mark" data-mark="add">' . esc_html__( 'I\'ve added it', 'addsynapse-store-assistant' ) . '</button> <span class="description">' . esc_html__( 'This step also turns green on its own once the site is connected.', 'addsynapse-store-assistant' ) . '</span></p>';
    656900        }
    657901
    … …  
    7921036                        echo '<div><strong>' . esc_html__( 'Already connected ✓', 'addsynapse-store-assistant' ) . '</strong> ';
    7931037                        echo esc_html__( 'This site is connected to AddSynapse. Go back to Claude or ChatGPT and continue there.', 'addsynapse-store-assistant' ) . '</div></div>';
    794                 } elseif ( in_array( $welcome, array( 'mcp', 'key' ), true ) ) {
     1038                } elseif ( 'key' === $welcome || ( 'mcp' === $welcome && empty( $status['connected'] ) ) ) {
    7951039                        echo '<div class="adsa-welcome" role="status">';
    7961040                        self::print_icon( 'check' );
    … …  
    7991043                }
    8001044
     1045                self::render_home_allset( $status );
    8011046                Announcements::render( 'home' );
    8021047                ReviewRequest::render( 'home' );
  • addsynapse-store-assistant/tags/1.3.5/includes/Install/Lifecycle.php

    r3758975 r3763701  
    5757                Usage::deactivate();
    5858                RemoteAccess::revoke_all();
     59                // 1.3.5: a later activation asks AddSynapse afresh («did you start in Claude?»).
     60                \Addsynapse\StoreAssistant\Account\PendingOnboarding::reset();
    5961        }
    6062
  • addsynapse-store-assistant/tags/1.3.5/includes/Remote/RelayClaim.php

    r3759648 r3763701  
    437437
    438438        /**
    439          * «Last step: confirm the account» — step 3 of the wizard, with the AddSynapse account (name + masked email) and
    440          * Connect / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.
     439         * «Connect <host> to your AddSynapse account» — step 2 of the wizard, with the AddSynapse account (name + masked email) and
     440         * «Yes, connect» / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.
    441441         */
    442442        public static function render_confirm_card(): void {
    … …  
    450450                $who     = trim( $account . ( '' !== $email ? ' (' . $email . ')' : '' ) );
    451451                echo '<li class="adsa-step is-current adsa-relayconfirm" id="adsa-step-connect" data-step="connect" data-adsa-relay-confirm>';
    452                 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">3</span></span>';
    453                 echo '<h2 class="adsa-step__title">' . esc_html__( 'Last step: confirm the account', 'addsynapse-store-assistant' ) . '</h2></div>';
     452                echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">2</span></span>';
     453                // The whole site address, with its folder (owner 24/10: «demo.addsynapse.com/satest», not just the host).
     454                $host = untrailingslashit( (string) preg_replace( '#^https?://#i', '', home_url() ) );
     455                /* translators: %s: this site's host name, e.g. shop.example.com */
     456                echo '<h2 class="adsa-step__title">' . esc_html( sprintf( __( 'Connect %s to your AddSynapse account', 'addsynapse-store-assistant' ), $host ) ) . '</h2></div>';
    454457                echo '<div class="adsa-step__body">';
    455                 echo '<p>' . ( 'key' === ( $stash['mode'] ?? '' )
    456                         ? esc_html__( 'AddSynapse asks to connect this site. Connect it to this AddSynapse account?', 'addsynapse-store-assistant' )
    457                         : esc_html__( 'WordPress approved the connection. Connect this site to this AddSynapse account?', 'addsynapse-store-assistant' ) ) . '</p>';
     458                // 1.3.5 (owner 24/10): plain words — what happens, whose account, nothing changes until approved.
     459                echo '<p class="adsa-relayconfirm__lead">' . esc_html(
     460                        'site' === \Addsynapse\StoreAssistant\Environment::site_type()
     461                                ? __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your site and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' )
     462                                : __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your store and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' )
     463                ) . '</p>';
    458464                echo '<div class="adsa-relayconfirm__account">';
    459465                if ( '' !== $account || '' !== $email ) {
    … …  
    476482                        ) . '</p>';
    477483                }
    478                 echo '<p class="adsa-relayconfirm__why">' . esc_html__( 'We ask so nobody can link your site to their own account. Continue only if this is your account and you started this from your AI chat a moment ago.', 'addsynapse-store-assistant' ) . '</p>';
     484                echo '<p class="adsa-relayconfirm__why description">' . esc_html__( 'Only continue if this is your account. Didn\'t start this? Press Cancel.', 'addsynapse-store-assistant' ) . '</p>';
    479485                if ( 'key' === ( $stash['mode'] ?? '' ) ) {
    480                         echo '<p class="adsa-relayconfirm__scope" data-adsa-relay-key>' . esc_html__( 'Connect creates a key for AddSynapse that works only for this plugin\'s tools, acting as you. Changes still wait for your Approve. You can disconnect at any time from this page.', 'addsynapse-store-assistant' ) . '</p>';
     486                        echo '<p class="adsa-relayconfirm__scope description" data-adsa-relay-key>' . esc_html__( 'This creates a key that only works for this plugin\'s tools. You can disconnect any time here.', 'addsynapse-store-assistant' ) . '</p>';
    481487                }
    482488                echo '<form method="post" class="adsa-finish adsa-relayconfirm__form" action="' . esc_url(
    … …  
    491497                ) . '">';
    492498                wp_nonce_field( 'addsynapse_assistant_relay_confirm_' . $state );
    493                 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Connect', 'addsynapse-store-assistant' ) . '</button>';
     499                echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Yes, connect', 'addsynapse-store-assistant' ) . '</button>';
    494500                echo '<button type="submit" name="as_cancel" value="1" class="button button-secondary">' . esc_html__( 'Cancel', 'addsynapse-store-assistant' ) . '</button>';
    495501                echo '</form>';
    … …  
    689695                                'protocol'   => self::PROTOCOL,
    690696                        ),
    691                         15
     697                        40
    692698                );
    693699                unset( $password );
    … …  
    824830                                'protocol'   => self::PROTOCOL,
    825831                        ),
    826                         15
     832                        // AddSynapse checks the new key against this site before it answers (its own call can take ~15 s).
     833                        40
    827834                );
    828835                unset( $key['secret'] );
  • addsynapse-store-assistant/tags/1.3.5/includes/Remote/RelayKeys.php

    r3759648 r3763701  
    8686        /**
    8787         * Whether keys may be created/used here: HTTPS, or a `local` development environment (same rule as Application Passwords).
     88         * 1.3.5: HTTPS also when PHP sits behind a TLS-terminating proxy that does not set $_SERVER['HTTPS'] (Varnish/nginx on
     89         * CloudPanel): the site's own address is https://, or a proxy on this same machine says so (X-Forwarded-Proto /
     90         * X-Forwarded-Ssl, trusted only from a loopback address).
    8891         */
    8992        public static function transport_ok(): bool {
    90                 return is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() );
     93                if ( is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) ) {
     94                        return true;
     95                }
     96                if ( 'https' === strtolower( (string) wp_parse_url( home_url(), PHP_URL_SCHEME ) ) ) {
     97                        return true;
     98                }
     99                $remote = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
     100                if ( ! in_array( $remote, array( '127.0.0.1', '::1' ), true ) ) {
     101                        return false;
     102                }
     103                $proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) ) : '';
     104                $ssl   = isset( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ) ) : '';
     105                return 'https' === $proto || 'on' === $ssl;
    91106        }
    92107
  • addsynapse-store-assistant/tags/1.3.5/includes/Remote/SetupStatus.php

    r3759648 r3763701  
    166166                $check = get_option( self::CHECK_OPTION, array() );
    167167                return array(
    168                         'checked'   => is_array( $check ) && ! empty( $check['ok'] ),
    169                         'connected' => null !== $found,
    170                         'used'      => null !== $found && null !== $found['last_used'],
     168                        'checked'      => is_array( $check ) && ! empty( $check['ok'] ),
     169                        // A real check ran and failed (the only case step 1 may stay open on a connected site).
     170                        'check_failed' => is_array( $check ) && array_key_exists( 'ok', $check ) && empty( $check['ok'] ),
     171                        'connected'    => null !== $found,
     172                        'used'         => null !== $found && null !== $found['last_used'],
    171173                        // Connected for sure: AddSynapse confirmed this password (claim) or already used it. The wizard then shows every step done.
    172                         'claimed'   => $claimed,
    173                         'ready'     => null !== $found && ( $claimed || null !== $found['last_used'] ),
    174                         'detail'    => $found,
    175                         'log_count' => count( ApprovalLog::all() ),
     174                        'claimed'      => $claimed,
     175                        'ready'        => null !== $found && ( $claimed || null !== $found['last_used'] ),
     176                        'detail'       => $found,
     177                        'log_count'    => count( ApprovalLog::all() ),
    176178                );
    177179        }
  • addsynapse-store-assistant/tags/1.3.5/package.json

    r3759648 r3763701  
    11{
    22  "name": "addsynapse-store-assistant",
    3   "version": "1.3.4",
     3  "version": "1.3.5",
    44  "private": true,
    55  "description": "Admin chat UI of the AddSynapse Store Assistant WordPress plugin.",
  • addsynapse-store-assistant/tags/1.3.5/readme.txt

    r3759648 r3763701  
    77WC requires at least: 8.0
    88WC tested up to: 11.1
    9 Stable tag: 1.3.4
     9Stable tag: 1.3.5
    1010License: GPLv2 or later
    1111License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    172172== External services ==
    173173
    174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.
     174The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** (except the section 5 setup question) and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.
    175175
    176176= 1. Anthropic API — api.anthropic.com (only with your own Anthropic key) =
    … …  
    205205* AddSynapse privacy policy: https://addsynapse.com/privacy
    206206
    207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com (only after you approve the connection) =
    208 
    209 Optional: use your site from Claude or ChatGPT (beta) (AddSynapse → Home → "Use your store from Claude or ChatGPT"). The setup steps only check your own site and show links and text to copy; they send nothing to AddSynapse. The "Add to Claude" link opens app.addsynapse.com with this site's address in the link (so AddSynapse knows which site to check when you sign up), which then opens Claude's connector page; your browser opens it only when you click it.
     207= 5. AddSynapse for Claude and other AI apps — addsynapse.com (after you approve the connection; one setup question before) =
     208
     209Optional: use your site from Claude or ChatGPT. "Connect to AddSynapse" opens app.addsynapse.com with this site's address in the link. While nothing is set up, for an administrator (after activation or on the setup screen, at most every 10 minutes), the plugin asks whether you started in Claude: `GET https://api.addsynapse.com/api/onboarding/pending?site=<site address>` (site address only). If yes, the browser continues there; nothing connects without Yes, connect.
    210210
    211211You start the connection from your AI chat. It opens this plugin's page on your site. For a logged-in administrator only, that page asks AddSynapse which account started the connection (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim/preview`: the one-time code and site address; answer: account name and masked email) and asks you to confirm it. Only if you press Connect does the plugin create a random connection key for AddSynapse, acting as you, and send it once from your server over HTTPS (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim`: key, user name, site address, one-time code — never in a web address); Cancel creates nothing. The site keeps only a keyed hash of the key. A key AddSynapse does not use within 15 minutes is revoked. Sites connected with an earlier version keep their WordPress Application Password until they reconnect.
    … …  
    226226
    227227== Changelog ==
     228
     229= 1.3.5 =
     230* Improved: simpler setup. Step 2 is one «Connect to AddSynapse» button that links this site to your AddSynapse account; adding AddSynapse to Claude or ChatGPT moves to step 3, shown after the site is connected.
     231* Improved: started in Claude? Right after activation the plugin asks AddSynapse once (site address only, see External services) and goes straight to «Connect <site> to your AddSynapse account? Yes, connect», then «Done — go back to Claude». «Other options» keeps both ways one click away.
     232* Improved: setup has two steps (the connection check runs by itself) and Home opens with «You're all set!»: open Claude and three requests to try.
     233* Fixed: sites behind an HTTPS proxy (PHP sees plain HTTP) can connect; a failed request to AddSynapse now says what failed.
    228234
    229235= 1.3.4 =
    … …  
    307313== Upgrade Notice ==
    308314
     315= 1.3.5 =
     316Simpler setup: one «Connect to AddSynapse» button links the site to your account; started in Claude, you land right on «Yes, connect».
     317
    309318= 1.3.4 =
    310319Connect without WordPress Application Passwords, with a key only this plugin accepts. Coupons above 50% off are approved only in wp-admin. Includes 1.3.3: change your site's appearance from Claude or ChatGPT, with Approve and Undo.
  • addsynapse-store-assistant/tags/1.3.5/uninstall.php

    r3759648 r3763701  
    8888                // 1.3.0: the welcome wizard (finished / skipped, chosen way).
    8989                'addsynapse_assistant_onboarding',
     90                // 1.3.5: Home's «You're all set!» panel (first shown, hidden).
     91                'addsynapse_assistant_allset',
    9092                // 1.3.0: the AddSynapse connection AddSynapse confirmed (uuid of the password, user, time — never the password).
    9193                'addsynapse_assistant_relay_claimed',
  • addsynapse-store-assistant/trunk/addsynapse-store-assistant.php

    r3759648 r3763701  
    44 * Plugin URI:        https://addsynapse.com/store-assistant
    55 * Description:       Admin chat where shop managers ask, in their own language, about their store and for changes to one product at a time — with your own Anthropic or OpenAI API key. Every change is a preview a person approves, with Undo.
    6  * Version:           1.3.4
     6 * Version:           1.3.5
    77 * Requires at least: 6.9
    88 * Requires PHP:      7.4
    … …  
    2020defined( 'ABSPATH' ) || exit;
    2121
    22 define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.4' );
     22define( 'ADDSYNAPSE_ASSISTANT_VERSION', '1.3.5' );
    2323define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_FILE', __FILE__ );
    2424define( 'ADDSYNAPSE_ASSISTANT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    5555
    5656register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Schema::class, 'install' ) );
     57// 1.3.5: remember the activation for one minute (continues a connection started in Claude, HomePage::maybe_start_from_claude).
     58register_activation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Admin\HomePage::class, 'activated' ) );
    5759register_deactivation_hook( __FILE__, array( \Addsynapse\StoreAssistant\Install\Lifecycle::class, 'deactivate' ) );
    5860
  • addsynapse-store-assistant/trunk/assets/home.css

    r3759648 r3763701  
    313313.adsa-apppw { margin: 12px 0 16px; }
    314314.adsa-apppw .adsa-buttons { margin-bottom: 4px; }
     315
     316/* 1.3.5: step 2 = one «Connect to AddSynapse» button; step 3 collapsed. */
     317.adsa-connect__sub { margin: -4px 0 8px; }
     318.adsa-addai { margin-top: 8px; }
     319
     320/* 1.3.5: «Done — go back to Claude» (from Claude, right after «Yes, connect»). */
     321.adsa-done { max-width: 720px; margin: 24px 0; padding: 32px; background: #fff; border: 1px solid #b8e6bf; border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); }
     322.adsa-done__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; border-radius: 50%; background: var(--adsa-ok); color: #fff; }
     323.adsa-done__icon .adsa-icon { display: block; width: 26px; height: 26px; }
     324.adsa-done__title { margin: 16px 0 8px; font-size: 24px; line-height: 1.3; }
     325.adsa-done__lead { margin: 0 0 12px; font-size: 15px; }
     326.adsa-done__links { margin: 16px 0 0; }
     327@media (max-width: 600px) { .adsa-done { padding: 20px; } .adsa-done__title { font-size: 20px; } }
     328
     329/* 1.3.5: «You're all set!» (Home) and «Done — go back to Claude». */
     330.adsa-allset { margin: 20px 0 24px; padding: 28px 32px; background: #fff; border: 1px solid #b8e6bf; border-left: 4px solid var(--adsa-ok); border-radius: 12px; box-shadow: 0 6px 20px rgba(0, 0, 0, .05); max-width: 860px; }
     331.adsa-allset__head { display: flex; align-items: center; gap: 14px; }
     332.adsa-allset__title { margin: 0; font-size: 26px; line-height: 1.25; }
     333.adsa-allset__lead { margin: 12px 0 0; font-size: 15px; }
     334.adsa-allset__try { margin: 18px 0 8px; font-size: 15px; }
     335.adsa-allset__prompts { margin: 0; padding: 0; list-style: none; }
     336.adsa-allset__prompts li { margin: 0 0 8px; }
     337.adsa-allset__open { margin: 18px 0 8px; display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
     338.adsa-allset__hide { margin: 8px 0 0; text-align: right; }
     339.adsa-allset-collapsed { max-width: 860px; }
     340.adsa-allset-collapsed .adsa-allset { box-shadow: none; margin: 12px 0 0; }
     341.adsa-recheck { margin: 8px 0 0; }
     342@media (max-width: 600px) { .adsa-allset { padding: 20px 16px; } .adsa-allset__title { font-size: 21px; } }
  • addsynapse-store-assistant/trunk/assets/home.js

    r3759648 r3763701  
    8686        }
    8787
    88         /* «Mark» actions: «I've added it» (step 2), «It worked» (step 4) — explicit buttons only. */
     88        /* «Mark» actions: «I've added it» (step 3) — explicit buttons only. */
    8989        root.querySelectorAll( '[data-mark]' ).forEach( function ( n ) {
    9090                n.addEventListener( 'click', function () {
    … …  
    132132        var checkBtn = root.querySelector( '.adsa-check' );
    133133        if ( checkBtn && apiFetch ) {
    134                 checkBtn.addEventListener( 'click', function () {
     134                var runCheck = function () {
    135135                        var out = root.querySelector( '.adsa-check-result' );
    136136                        out.textContent = t.checking || '…';
    … …  
    163163                                        checkBtn.disabled = false;
    164164                                } );
    165                 } );
    166         }
    167 
    168         /* Steps 3–4: poll this site's own state (an approved AddSynapse application password, its first use). */
     165                };
     166                checkBtn.addEventListener( 'click', runCheck );
     167                /* 1.3.5: the check runs by itself when the page opens; «Re-check» runs it again. */
     168                if ( root.querySelector( '[data-adsa-autocheck]' ) ) {
     169                        runCheck();
     170                }
     171        }
     172
     173        /* Steps 2–3: poll this site's own state (an approved AddSynapse key, its first use). */
    169174        function poll() {
    170175                apiFetch( { path: cfg.status } )
    … …  
    174179                                }
    175180                                if ( s.ready || s.connected ) {
    176                                         /* Step 2 turns green on its own once the site is connected (step 3 done implies it was added). */
    177                                         setDone( 'add', true );
     181                                        /* 1.3.5: step 3 («Use it from Claude or ChatGPT») shows once the site is connected. */
     182                                        root.querySelectorAll( '[data-adsa-after-connect]' ).forEach( function ( n ) {
     183                                                n.hidden = false;
     184                                        } );
     185                                        root.querySelectorAll( '[data-adsa-before-connect]' ).forEach( function ( n ) {
     186                                                n.hidden = true;
     187                                        } );
    178188                                }
    179189                                if ( s.connected ) {
    … …  
    181191                                        state( 'connect', ( t.approved || '%s' ).replace( '%s', s.detail.user ), true );
    182192                                }
    183                                 if ( s.ready ) {
     193                                /* A connected site passed what step 1 checks, unless a real check failed. */
     194                                if ( s.ready || ( s.connected && ! s.check_failed ) ) {
    184195                                        setDone( 'check', true );
    185196                                }
    186197                                if ( s.used || s.ready ) {
     198                                        setDone( 'add', true );
    187199                                        setDone( 'confirm', true );
    188200                                        state( 'confirm', s.used ? t.read : ( t.checked || t.read ), true );
  • addsynapse-store-assistant/trunk/includes/Account/Api.php

    r3759269 r3763701  
    1313
    1414/**
    15  * Four calls exist, and nothing else ever contacts AddSynapse:
     15 * These calls exist, and nothing else ever contacts AddSynapse:
     16 *  - PendingOnboarding::from_claude(): `GET /api/onboarding/pending?site=<home URL>` — only for an administrator while
     17 *    nothing is set up (after activation / the first setup screen), at most once per 10 minutes, the site address only;
    1618 *  - OptIn::handle_create(): `POST /api/v1/assistant/optin` — only when an administrator clicks «Create account»;
    1719 *  - Announcements::maybe_fetch(): `POST /api/v1/assistant/announcements` — at most once a day, only after that, only while
    … …  
    130132
    131133        /**
     134         * Whether a failed request surely never reached the server (no name lookup / no connection).
     135         *
     136         * @param WP_Error $error wp_remote_post() error.
     137         */
     138        private static function not_sent( WP_Error $error ): bool {
     139                return (bool) preg_match( '/cURL error (6|7):|Could not resolve|Failed to connect|Connection refused/i', $error->get_error_message() );
     140        }
     141
     142        /**
    132143         * POST JSON.
    133144         *
    … …  
    142153                        return new WP_Error( 'addsynapse_assistant_api_url', 'The AddSynapse address is not allowed on this site.' );
    143154                }
    144                 $response = wp_remote_post(
    145                         $base . $path,
    146                         array(
    147                                 'timeout'     => $timeout,
    148                                 'redirection' => 0,
    149                                 'headers'     => array(
    150                                         'Content-Type' => 'application/json',
    151                                         'Accept'       => 'application/json',
    152                                 ),
    153                                 'body'        => (string) wp_json_encode( $body ),
    154                         )
     155                $args     = array(
     156                        'timeout'     => $timeout,
     157                        'redirection' => 0,
     158                        'headers'     => array(
     159                                'Content-Type' => 'application/json',
     160                                'Accept'       => 'application/json',
     161                        ),
     162                        'body'        => (string) wp_json_encode( $body ),
    155163                );
     164                $response = wp_remote_post( $base . $path, $args );
     165                // 1.3.5 (satest 24/10: a connect POST failed on this server and never reached AddSynapse): one retry, only when
     166                // the request surely never left (name lookup / connect failed) — a request that may have arrived is never sent twice.
     167                if ( is_wp_error( $response ) && self::not_sent( $response ) ) {
     168                        $response = wp_remote_post( $base . $path, $args );
     169                }
    156170                if ( is_wp_error( $response ) ) {
    157                         return new WP_Error( 'addsynapse_assistant_api_unreachable', 'AddSynapse could not be reached. Try again in a few minutes.' );
     171                        $detail = sanitize_text_field( mb_substr( $response->get_error_message(), 0, 160 ) );
     172                        return new WP_Error(
     173                                'addsynapse_assistant_api_unreachable',
     174                                /* translators: %s: technical detail from WordPress, e.g. «cURL error 28: Operation timed out». */
     175                                sprintf( __( 'This site could not reach AddSynapse (%s). Nothing was connected. Try again in a minute; if it keeps failing, ask your host whether outgoing HTTPS requests to api.addsynapse.com are allowed.', 'addsynapse-store-assistant' ), '' !== $detail ? $detail : 'no detail' )
     176                        );
    158177                }
    159178                $decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true );
  • addsynapse-store-assistant/trunk/includes/Admin/HomePage.php

    r3759648 r3763701  
    1616use Addsynapse\StoreAssistant\Account\Api;
    1717use Addsynapse\StoreAssistant\Account\OptIn;
     18use Addsynapse\StoreAssistant\Account\PendingOnboarding;
    1819use Addsynapse\StoreAssistant\Account\Usage;
    1920use Addsynapse\StoreAssistant\Brain\Models;
    … …  
    4142        public const ACTION_FINISH = 'addsynapse_assistant_onboarding_finish';
    4243
     44        /** The admin-post action of «Hide» on the «You're all set!» panel (1.3.5). */
     45        public const ACTION_ALLSET_HIDE = 'addsynapse_assistant_allset_hide';
     46
     47        /** Option: array{since:int,hidden:bool} — when Home first showed «You're all set!», and whether it was hidden. */
     48        public const ALLSET_OPTION = 'addsynapse_assistant_allset';
     49
     50        /** Days the «You're all set!» panel stays open on Home. */
     51        public const ALLSET_DAYS = 7;
     52
    4353        /** The admin-post action of «Disconnect AddSynapse» (1.3.4). */
    4454        public const ACTION_DISCONNECT = 'addsynapse_assistant_relay_disconnect';
    … …  
    4656        /** Script/style handle. */
    4757        public const HANDLE = 'addsynapse-assistant-home';
     58
     59        /** Transient: the administrator (user id) who just activated the plugin (one minute). */
     60        public const JUST_ACTIVATED = 'addsynapse_assistant_just_activated';
     61
     62        /** Transient: the Claude connection was continued automatically once (no loop; 10 minutes). */
     63        public const AUTO_STARTED = 'addsynapse_assistant_onboarding_auto';
    4864
    4965        /** The wizard views (query arg `setup`). */
    … …  
    5672                add_action( 'admin_post_' . self::ACTION_FINISH, array( $this, 'handle_finish' ) );
    5773                add_action( 'admin_post_' . self::ACTION_DISCONNECT, array( self::class, 'handle_disconnect' ) );
     74                add_action( 'admin_post_' . self::ACTION_ALLSET_HIDE, array( self::class, 'handle_allset_hide' ) );
    5875                // Before WordPress checks access to the requested page: old addresses keep working.
    5976                add_action( 'admin_menu', array( self::class, 'legacy_redirects' ), 1 );
     77                // After RelayClaim::maybe_handle (admin_init:1), before any output.
     78                add_action( 'admin_init', array( self::class, 'maybe_start_from_claude' ), 5 );
     79        }
     80
     81        /**
     82         * Activation (a second activation callback, next to Schema::install): remember for one minute that an administrator
     83         * just activated the plugin, so the next admin page can continue the Claude connection (maybe_start_from_claude).
     84         * Not on bulk activation, WP-CLI or network activation.
     85         *
     86         * @param bool $network_wide Network activation.
     87         */
     88        public static function activated( $network_wide = false ): void {
     89                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only: only tells a bulk activation apart; core checked its own nonce.
     90                if ( $network_wide || ( defined( 'WP_CLI' ) && WP_CLI ) || isset( $_REQUEST['checked'] ) ) {
     91                        return;
     92                }
     93                // A new activation asks AddSynapse afresh and may continue the connection again (satest 24/10: a re-activation
     94                // within 10 minutes found the old «already continued» guard and did nothing).
     95                PendingOnboarding::reset();
     96                set_transient( self::JUST_ACTIVATED, get_current_user_id(), MINUTE_IN_SECONDS );
     97        }
     98
     99        /**
     100         * 1.3.5 (owner 24/10, «6 clicks from Claude»): the manager started in Claude and AddSynapse has a connection pending for
     101         * this site → right after activation (or on the first visit of the wizard) continue that connection: the browser goes
     102         * to AddSynapse's connect page for this site, exactly as the «Connect to AddSynapse» button does, and AddSynapse sends
     103         * it straight back to this plugin's «Connect <host> to your AddSynapse account? [Yes, connect] [Cancel]» screen.
     104         * Nothing is connected without «Yes, connect». Once per 10 minutes at most (no loop); otherwise nothing happens and the
     105         * usual screens show.
     106         */
     107        public static function maybe_start_from_claude(): void {
     108                if ( wp_doing_ajax() || is_network_admin() || ! current_user_can( 'manage_options' ) ) {
     109                        return;
     110                }
     111                $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_key( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET';
     112                if ( 'GET' !== $method ) {
     113                        return;
     114                }
     115                global $pagenow;
     116                $just = (int) get_transient( self::JUST_ACTIVATED );
     117                if ( $just > 0 && 'plugins.php' === $pagenow ) {
     118                        delete_transient( self::JUST_ACTIVATED );
     119                } else {
     120                        $just = 0;
     121                }
     122                $wizard = 'admin.php' === $pagenow && AddsynapseMenu::PARENT_SLUG === self::flag( 'page' )
     123                        && '' === self::flag( 'as_relay' ) && '' === self::flag( 'setup' ) && '' === self::flag( 'view' ) && '' === self::flag( 'welcome' );
     124                if ( ( get_current_user_id() !== $just && ! $wizard ) || get_transient( self::AUTO_STARTED ) || self::is_set_up() || ! RelayKeys::transport_ok() ) {
     125                        // Not «needs_setup()»: a wizard finished on an earlier install (the option outlives deactivation) must not stop
     126                        // a new connection started in Claude (satest 24/10). Only a working connection does.
     127                        return;
     128                }
     129                if ( ! PendingOnboarding::from_claude() ) {
     130                        return;
     131                }
     132                $url = Api::claude_connect_url( home_url( '/' ) );
     133                if ( ! Api::link_allowed( $url ) ) {
     134                        return;
     135                }
     136                set_transient( self::AUTO_STARTED, 1, PendingOnboarding::TTL );
     137                // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- AddSynapse's own connect page (Api::link_allowed: https on an AddSynapse host only).
     138                wp_redirect( $url, 302 );
     139                exit;
    60140        }
    61141
    … …  
    133213                        return 'home';
    134214                }
    135                 return 'choose';
     215                return self::from_claude() ? 'mcp' : 'choose';
     216        }
     217
     218        /**
     219         * 1.3.5 (owner 24/10): the manager started in Claude (AddSynapse says a connection for this site address is pending),
     220         * so the first screen skips «Choose how you want to connect» and opens the Claude path with one button. Only on the
     221         * wizard's default screen (no `setup`/`view` asked for) of a site with nothing set up; «Other options» (setup=choose)
     222         * always shows both ways. Any failure of the question = the usual first screen.
     223         */
     224        public static function from_claude(): bool {
     225                if ( '' !== self::flag( 'setup' ) || 'home' === self::flag( 'view' ) || ! current_user_can( Config::capability() ) || self::is_set_up() ) {
     226                        return false;
     227                }
     228                return PendingOnboarding::from_claude();
    136229        }
    137230
    … …  
    376469         */
    377470        private static function back_link(): void {
     471                if ( self::from_claude() ) {
     472                        // Not a lock (wp.org guideline 5): both ways stay one click away.
     473                        echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">';
     474                        self::print_icon( 'back' );
     475                        echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>';
     476                        return;
     477                }
    378478                $target = self::state()['done'] || self::is_set_up() ? self::url() : self::setup_url( 'choose' );
    379479                echo '<a class="adsa-back" href="' . esc_url( $target ) . '">';
    … …  
    385485         * The «Finish» form of a path.
    386486         *
    387          * @param string $path mcp | key.
    388          * @param string $note Text next to the button.
    389          */
    390         private static function finish( string $path, string $note ): void {
     487         * @param string $path    mcp | key.
     488         * @param string $note    Text next to the button.
     489         * @param bool   $primary The screen's main button (false: a plain one, when another button is the main one).
     490         */
     491        private static function finish( string $path, string $note, bool $primary = true ): void {
    391492                echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="adsa-finish">';
    392493                wp_nonce_field( self::ACTION_FINISH );
    393494                echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_FINISH ) . '">';
    394495                echo '<input type="hidden" name="addsynapse_assistant_path" value="' . esc_attr( $path ) . '">';
    395                 echo '<button type="submit" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';
     496                echo '<button type="submit" class="button ' . ( $primary ? 'button-primary button-hero ' : '' ) . 'adsa-finish__btn">' . esc_html__( 'Finish', 'addsynapse-store-assistant' ) . '</button>';
    396497                echo '<p class="adsa-finish__note">' . esc_html( $note ) . '</p>';
    397498                echo '</form>';
    … …  
    497598         */
    498599        private static function render_mcp(): void {
    499                 $status  = SetupStatus::connection();
    500                 $confirm = self::is_site()
    501                         ? __( 'Using AddSynapse, tell me my site name and how many pages it has. Change nothing.', 'addsynapse-store-assistant' )
    502                         : __( 'Using AddSynapse, tell me my store name and how many products it has. Change nothing.', 'addsynapse-store-assistant' );
     600                $status = SetupStatus::connection();
     601                // Came from Claude and just pressed «Yes, connect» (AddSynapse sent the browser back): a clean «Done» screen, not
     602                // the three steps (owner 24/10). The steps stay one click away.
     603                if ( ( ! empty( $status['ready'] ) || ! empty( $status['connected'] ) ) && 'connected' === self::flag( 'as_relay' ) && PendingOnboarding::cached_from_claude() ) {
     604                        self::render_arrival_done();
     605                        return;
     606                }
    503607                // Connected for sure (AddSynapse confirmed the password, or used it): every step is done.
    504608                $ready = ! empty( $status['ready'] );
    505609                // 1.3.4: AddSynapse connects with this plugin's own key, so Application Passwords being off (Wordfence's default) no
    506610                // longer blocks anything; only HTTPS is required (RelayKeys::transport_ok).
    507                 $steps = array(
    508                         'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] ),
    509                         // Done once the site is connected (step 3 done: the AI already sent the connect link), or the manager says so
    510                         // («I've added it») — never on a click of «Add to Claude» or «Copy». Owner 22/10: step 3 green, step 2 not.
    511                         'add'     => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), $ready || (bool) $status['connected'] ),
    512                         'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), (bool) $status['connected'] ),
    513                         'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), $ready || (bool) $status['used'] ),
    514                 );
     611                // 1.3.5 (owner 24/10): step 2 is ONE button that connects this site to the AddSynapse account (the account usually
     612                // already uses Claude); adding AddSynapse to an AI app moves to step 3, shown once the site is connected.
     613                $connected = $ready || (bool) $status['connected'];
     614                // Came from Claude and not connected yet: «Connect to AddSynapse» is the only primary button on the screen.
     615                $arrival = self::from_claude() && ! $connected;
     616                $steps   = array(
     617                        // A connected site has passed what the check checks (AddSynapse reached it): step 1 is done unless a real
     618                        // check ran and failed (owner 24/10: never «not done» next to two green steps).
     619                        'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), $ready || (bool) $status['checked'] || ( $connected && empty( $status['check_failed'] ) ) ),
     620                        'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), $connected ),
     621                );
     622                // 1.3.5 (owner 24/10): two steps — the check (runs by itself) and Connect — then «Finish». How to use it from
     623                // Claude or ChatGPT is on Home («You're all set!»).
    515624                self::back_link();
    516625                echo '<div class="adsa-pathhead">';
    517626                echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>';
    518                 echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Four short steps, about five minutes. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>';
     627                if ( $arrival ) {
     628                        echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'Press «Connect to AddSynapse» below.', 'addsynapse-store-assistant' ) . '</p>';
     629                } else {
     630                        echo '<p class="adsa-pathhead__lead">' . esc_html__( 'Two short steps. Each step turns green on its own.', 'addsynapse-store-assistant' ) . '</p>';
     631                }
    519632                echo '</div>';
    520633                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: a success line when AddSynapse sent the browser back.
    521                 if ( $ready && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {
     634                if ( $connected && isset( $_GET['as_relay'] ) && 'connected' === sanitize_key( wp_unslash( $_GET['as_relay'] ) ) ) {
    522635                        // Back from AddSynapse right after «Connect» (1.3.3, hub protocol 3).
    523                         echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     636                        if ( PendingOnboarding::cached_from_claude() ) {
     637                                // Came from Claude: the AddSynapse page in the other tab moves on by itself.
     638                                echo '<div class="notice notice-success adsa-connected-notice adsa-arrival-done" data-adsa-relay-connected data-adsa-from="claude"><p><strong>' . esc_html__( 'Done — go back to Claude.', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     639                        } else {
     640                                echo '<div class="notice notice-success adsa-connected-notice" data-adsa-relay-connected><p><strong>' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</strong> ' . esc_html__( 'This site is now connected to AddSynapse. Go back to your AI chat and ask it something about your site.', 'addsynapse-store-assistant' ) . '</p></div>';
     641                        }
    524642                }
    525643                if ( ! $ready && ! RelayKeys::transport_ok() ) {
    … …  
    533651                self::step_open( 'check', 1, __( 'Check the connection', 'addsynapse-store-assistant' ), $steps['check'][1] );
    534652                echo '<p>' . esc_html__( 'Checks HTTPS, the REST API and the sign-in header of this site. Runs on your server only.', 'addsynapse-store-assistant' ) . '</p>';
    535                 echo '<button type="button" class="button button-primary adsa-check">' . esc_html__( 'Check site connectivity', 'addsynapse-store-assistant' ) . '</button>';
    536                 echo '<div class="adsa-check-result" aria-live="polite"></div>';
     653                // Runs by itself when the page opens (home.js); «Re-check» runs it again.
     654                echo '<div class="adsa-check-result" aria-live="polite" data-adsa-autocheck></div>';
     655                echo '<p class="adsa-recheck"><button type="button" class="button-link adsa-check">' . esc_html__( 'Re-check', 'addsynapse-store-assistant' ) . '</button></p>';
    537656                self::step_close();
    538657
    539                 self::step_open( 'add', 2, __( 'Add AddSynapse to your AI', 'addsynapse-store-assistant' ), $steps['add'][1] );
    540                 self::render_add_ways();
    541                 self::step_close();
    542 
    543                 self::step_open( 'connect', 3, __( 'Connect this site from the chat', 'addsynapse-store-assistant' ), $steps['connect'][1] );
    544                 if ( $ready ) {
    545                         // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect prompt here.
    546                         echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓ — try asking your AI:', 'addsynapse-store-assistant' ) . '</span></p>';
    547                         self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
     658                self::step_open( 'connect', 2, __( 'Connect this site to AddSynapse', 'addsynapse-store-assistant' ), $steps['connect'][1] );
     659                if ( $connected ) {
     660                        // Already connected (owner 21/10: a second «Connect my site» made a second password) — no connect button here.
     661                        // 1.3.5: also while AddSynapse has not used the new key yet (pending): a second Connect would make a second key.
     662                        echo '<p class="adsa-state is-ok" data-connected="1"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html__( 'Connected ✓', 'addsynapse-store-assistant' ) . '</span></p>';
    548663                } else {
    549                         echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>';
    550                         self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
    551                         self::authorize_note();
     664                        self::render_connect_button();
    552665                        $waiting = $status['connected']
    553666                                /* translators: %s: user login */
    … …  
    555668                                : __( 'Waiting for your approval…', 'addsynapse-store-assistant' );
    556669                        echo '<p class="adsa-state' . ( $status['connected'] ? ' is-ok' : ' is-waiting' ) . '" data-state="connect" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $waiting ) . '</span></p>';
     670                        // The older way, from the chat (the AI answers with the same confirm link) — kept for whoever starts there.
     671                        echo '<details class="adsa-more" data-adsa-chat-connect><summary>' . esc_html__( 'Prefer to connect from your AI chat?', 'addsynapse-store-assistant' ) . '</summary>';
     672                        echo '<p>' . esc_html__( 'Send this to your AI. It answers with a link to this plugin\'s page on your site — confirm the connection there.', 'addsynapse-store-assistant' ) . '</p>';
     673                        self::copy_field( self::connect_prompt(), __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
     674                        echo '</details>';
    557675                }
    558676                self::step_close();
    559677
    560                 self::step_open( 'confirm', 4, self::is_site() ? __( 'Confirm your AI can read the site', 'addsynapse-store-assistant' ) : __( 'Confirm your AI can read the store', 'addsynapse-store-assistant' ), $steps['confirm'][1] );
    561                 echo '<p>' . esc_html__( 'Ask one question that changes nothing. This step turns green when your AI reads the store.', 'addsynapse-store-assistant' ) . '</p>';
    562                 self::copy_field( $confirm, __( 'Prompt to send', 'addsynapse-store-assistant' ), __( 'Copy prompt', 'addsynapse-store-assistant' ) );
    563                 $confirmed = $status['used']
    564                         ? __( 'Your AI read the store. You are all set.', 'addsynapse-store-assistant' )
    565                         : ( $ready ? __( 'AddSynapse checked the connection. You are all set.', 'addsynapse-store-assistant' ) : __( 'Waiting for the first answer…', 'addsynapse-store-assistant' ) );
    566                 echo '<p class="adsa-state' . ( $status['used'] || $ready ? ' is-ok' : '' ) . '" data-state="confirm" aria-live="polite"><span class="adsa-dot" aria-hidden="true"></span><span class="adsa-state__text">' . esc_html( $confirmed ) . '</span></p>';
    567                 echo '<p><button type="button" class="button-link adsa-mark" data-mark="confirm">' . esc_html__( 'It worked — mark as done', 'addsynapse-store-assistant' ) . '</button></p>';
    568                 self::step_close();
    569678                echo '</ol>';
    570679
    571                 self::finish( 'mcp', __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ) );
     680                // «Finish» is the main action once connected; before that «Connect to AddSynapse» is.
     681                self::finish( 'mcp', $connected ? __( 'Next: Home shows how to use it from Claude or ChatGPT.', 'addsynapse-store-assistant' ) : __( 'You can finish now and come back to these steps later from Home.', 'addsynapse-store-assistant' ), $connected );
    572682
    573683                echo '<details class="adsa-more adsa-more--box"><summary>' . esc_html__( 'More details: what AddSynapse can do on this site', 'addsynapse-store-assistant' ) . '</summary><ul>';
    … …  
    581691
    582692        /**
    583          * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (steps 1–2 done, step 3
     693         * «Done — go back to Claude» (from=claude, right after «Yes, connect»). Nothing to press here: the AddSynapse page in
     694         * the other tab moves on by itself. A prompt to try, and links to Home and to the setup steps.
     695         */
     696        private static function render_arrival_done(): void {
     697                self::render_allset( __( 'Done — go back to Claude.', 'addsynapse-store-assistant' ), __( 'This site is now connected to AddSynapse. Ask Claude something about your site.', 'addsynapse-store-assistant' ), false );
     698                echo '<p class="adsa-done__links"><a href="' . esc_url( self::url( array( 'view' => 'home' ) ) ) . '">' . esc_html__( 'Go to Home', 'addsynapse-store-assistant' ) . '</a> · <a href="' . esc_url( self::setup_url( 'mcp' ) ) . '">' . esc_html__( 'See the setup steps', 'addsynapse-store-assistant' ) . '</a></p>';
     699        }
     700
     701        /**
     702         * Three example requests for Claude or ChatGPT (store or plain site).
     703         *
     704         * @return string[]
     705         */
     706        private static function try_prompts(): array {
     707                return self::is_site()
     708                        ? array(
     709                                __( 'Show me my 3 latest pages', 'addsynapse-store-assistant' ),
     710                                __( 'Which pages have no SEO description?', 'addsynapse-store-assistant' ),
     711                                __( 'What is in my main menu?', 'addsynapse-store-assistant' ),
     712                        )
     713                        : array(
     714                                __( 'Show me 3 products from my store', 'addsynapse-store-assistant' ),
     715                                __( 'Which products have no image?', 'addsynapse-store-assistant' ),
     716                                __( 'What were my best sellers last month?', 'addsynapse-store-assistant' ),
     717                        );
     718        }
     719
     720        /**
     721         * «You're all set!» — open Claude (primary) or ChatGPT, three requests to copy, and (collapsed) how to add AddSynapse
     722         * to an AI app. Ordinary links the browser opens; nothing is sent by the server.
     723         *
     724         * @param string $title   Heading.
     725         * @param string $lead    One line under it ('' = none).
     726         * @param bool   $dismiss Show «Hide» (Home only).
     727         */
     728        private static function render_allset( string $title, string $lead, bool $dismiss ): void {
     729                echo '<section class="adsa-allset" data-adsa-allset role="status" aria-labelledby="adsa-allset-title">';
     730                echo '<div class="adsa-allset__head"><span class="adsa-done__icon" aria-hidden="true">';
     731                self::print_icon( 'check' );
     732                echo '</span><h2 class="adsa-allset__title" id="adsa-allset-title">' . esc_html( $title ) . '</h2></div>';
     733                if ( '' !== $lead ) {
     734                        echo '<p class="adsa-allset__lead">' . esc_html( $lead ) . '</p>';
     735                }
     736                echo '<p class="adsa-allset__try"><strong>' . esc_html__( 'Open Claude and try:', 'addsynapse-store-assistant' ) . '</strong></p>';
     737                echo '<ul class="adsa-allset__prompts">';
     738                foreach ( self::try_prompts() as $prompt ) {
     739                        echo '<li>';
     740                        self::copy_field( $prompt, __( 'Request to send', 'addsynapse-store-assistant' ), __( 'Copy', 'addsynapse-store-assistant' ) );
     741                        echo '</li>';
     742                }
     743                echo '</ul>';
     744                echo '<p class="adsa-buttons adsa-allset__open"><a class="button button-primary button-hero" href="https://claude.ai/new" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open Claude ↗', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a> ';
     745                echo '<a class="adsa-allset__gpt" href="https://chatgpt.com/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ) . '<span class="screen-reader-text"> ' . esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) . '</span></a></p>';
     746                echo '<details class="adsa-more adsa-addai" data-adsa-add-ways><summary>' . esc_html__( 'AddSynapse not in your Claude or ChatGPT yet?', 'addsynapse-store-assistant' ) . '</summary>';
     747                self::render_add_ways();
     748                echo '</details>';
     749                if ( $dismiss ) {
     750                        echo '<form method="post" class="adsa-allset__hide" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
     751                        wp_nonce_field( self::ACTION_ALLSET_HIDE );
     752                        echo '<input type="hidden" name="action" value="' . esc_attr( self::ACTION_ALLSET_HIDE ) . '">';
     753                        echo '<button type="submit" class="button-link">' . esc_html__( 'Hide', 'addsynapse-store-assistant' ) . '</button></form>';
     754                }
     755                echo '</section>';
     756        }
     757
     758        /**
     759         * Home: the «You're all set!» panel once connected — prominent for the first days (ALLSET_DAYS) or until hidden,
     760         * then collapsed.
     761         *
     762         * @param array<string,mixed> $status SetupStatus::connection().
     763         */
     764        private static function render_home_allset( array $status ): void {
     765                if ( empty( $status['connected'] ) ) {
     766                        return;
     767                }
     768                $state = get_option( self::ALLSET_OPTION, array() );
     769                $state = is_array( $state ) ? $state : array();
     770                if ( empty( $state['since'] ) ) {
     771                        $state = array(
     772                                'since'  => time(),
     773                                'hidden' => false,
     774                        );
     775                        update_option( self::ALLSET_OPTION, $state, false );
     776                }
     777                $fresh = empty( $state['hidden'] ) && ( time() - (int) $state['since'] ) < self::ALLSET_DAYS * DAY_IN_SECONDS;
     778                if ( $fresh ) {
     779                        self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', true );
     780                        return;
     781                }
     782                echo '<details class="adsa-more adsa-allset-collapsed" data-adsa-allset-collapsed><summary>' . esc_html__( 'Try it in Claude or ChatGPT', 'addsynapse-store-assistant' ) . '</summary>';
     783                self::render_allset( __( 'You\'re all set!', 'addsynapse-store-assistant' ), '', false );
     784                echo '</details>';
     785        }
     786
     787        /**
     788         * «Hide» of the «You're all set!» panel (it stays, collapsed).
     789         */
     790        public static function handle_allset_hide(): void {
     791                if ( ! current_user_can( Config::capability() ) ) {
     792                        wp_die( esc_html__( 'You are not allowed to access this page.', 'addsynapse-store-assistant' ), 403 );
     793                }
     794                check_admin_referer( self::ACTION_ALLSET_HIDE );
     795                $state           = get_option( self::ALLSET_OPTION, array() );
     796                $state           = is_array( $state ) ? $state : array();
     797                $state['since']  = (int) ( $state['since'] ?? time() );
     798                $state['hidden'] = true;
     799                update_option( self::ALLSET_OPTION, $state, false );
     800                wp_safe_redirect( self::url( array( 'view' => 'home' ) ) );
     801                exit;
     802        }
     803
     804
     805        /**
     806         * The «confirm the account» screen (back from WordPress' «Authorize Application» screen, or AddSynapse's start link): the wizard's head and progress (step 1 done, step 2
    584807         * current) with the «confirm the account» card of RelayClaim.
    585808         */
    586809        private static function render_relay_confirm(): void {
     810                if ( PendingOnboarding::cached_from_claude() ) {
     811                        // Keep the answer for the «Done» screen after «Yes, connect» (the confirm may take a while).
     812                        PendingOnboarding::keep();
     813                }
     814                if ( PendingOnboarding::cached_from_claude() && ! self::is_set_up() ) {
     815                        // Not a lock (wp.org guideline 5): Cancel and both ways stay one click away.
     816                        echo '<a class="adsa-back" href="' . esc_url( self::setup_url( 'choose' ) ) . '">';
     817                        self::print_icon( 'back' );
     818                        echo esc_html__( 'Other options', 'addsynapse-store-assistant' ) . '</a>';
     819                }
    587820                echo '<div class="adsa-pathhead">';
    588821                echo '<h2 class="adsa-pathhead__title">' . esc_html( self::is_site() ? __( 'Use your site from Claude or ChatGPT', 'addsynapse-store-assistant' ) : __( 'Use your store from Claude or ChatGPT', 'addsynapse-store-assistant' ) ) . '</h2>';
     822                if ( PendingOnboarding::cached_from_claude() ) {
     823                        echo '<p class="adsa-pathhead__lead adsa-arrival" data-adsa-from="claude"><strong>' . esc_html__( 'You came from Claude — one click to finish.', 'addsynapse-store-assistant' ) . '</strong></p>';
     824                }
    589825                echo '</div>';
    590826                self::progress(
    591827                        array(
    592828                                'check'   => array( __( 'Check', 'addsynapse-store-assistant' ), true ),
    593                                 'add'     => array( __( 'Add to your AI', 'addsynapse-store-assistant' ), true ),
    594829                                'connect' => array( __( 'Connect', 'addsynapse-store-assistant' ), false ),
    595                                 'confirm' => array( __( 'Confirm', 'addsynapse-store-assistant' ), false ),
    596830                        )
    597831                );
    … …  
    606840         */
    607841        public static function authorize_note(): void {
    608                 echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>';
    609         }
    610 
    611         /**
    612          * Step 2: two clear ways to add AddSynapse — Claude's connector directory (one click), or a custom connector by address
     842                echo '<p class="adsa-authnote">' . esc_html__( 'Next you\'ll see this plugin\'s page with the AddSynapse account — click «Yes, connect». It gives AddSynapse a key that works only for these tools; you can disconnect any time.', 'addsynapse-store-assistant' ) . '</p>';
     843        }
     844
     845        /**
     846         * Step 2: the one button — AddSynapse's connect page for this site (sign in or create a free account, approve the
     847         * access, then confirm it on this plugin's page). An ordinary link the administrator's browser opens; nothing is sent
     848         * by the server.
     849         */
     850        private static function render_connect_button(): void {
     851                echo '<p>' . esc_html( self::is_site() ? __( 'Links this site to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) : __( 'Links this store to your AddSynapse account, so Claude or ChatGPT can work with it.', 'addsynapse-store-assistant' ) ) . '</p>';
     852                printf( '<p class="adsa-buttons"><a class="button button-primary button-hero adsa-connect" href="%s">%s</a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Connect to AddSynapse', 'addsynapse-store-assistant' ) );
     853                echo '<p class="description adsa-connect__sub">' . esc_html__( 'Free account — sign in or create one in a minute.', 'addsynapse-store-assistant' ) . '</p>';
     854                self::authorize_note();
     855        }
     856
     857        /**
     858         * Step 3 (collapsed): two clear ways to add AddSynapse to an AI app — Claude's directory (one click), or by address
    613859         * (ChatGPT beta, Claude, other AI apps).
    614860         */
    … …  
    620866                echo '<h3 class="adsa-addway__title" id="adsa-addway-claude">' . esc_html__( 'Claude — one click from the connector directory', 'addsynapse-store-assistant' ) . '</h3>';
    621867                echo '<p>' . esc_html__( 'Opens AddSynapse in Claude\'s connector directory. Press Connect there and sign in to AddSynapse (or create a free account).', 'addsynapse-store-assistant' ) . '</p>';
    622                 printf( '<p class="adsa-buttons"><a class="button button-primary adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( \Addsynapse\StoreAssistant\Account\Api::claude_connect_url( home_url( '/' ) ) ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
     868                printf( '<p class="adsa-buttons"><a class="button adsa-ai" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CLAUDE_URL ), esc_html__( 'Add to Claude', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
    623869                echo '</section>';
    624870
    … …  
    642888                echo '<li>' . esc_html__( 'Authentication: OAuth. Tick «I trust this application» and press Create.', 'addsynapse-store-assistant' ) . '</li>';
    643889                echo '<li>' . esc_html__( 'Sign in to AddSynapse (or create a free account) and approve the access.', 'addsynapse-store-assistant' ) . '</li>';
    644                 echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then continue with step 3 below.', 'addsynapse-store-assistant' ) . '</li>';
     890                echo '<li>' . esc_html__( 'In a new chat press + → Developer mode, turn on AddSynapse, then ask it a question.', 'addsynapse-store-assistant' ) . '</li>';
    645891                echo '</ol>';
    646892                printf( '<p><a class="button" href="%s" target="_blank" rel="noopener noreferrer">%s<span class="screen-reader-text"> %s</span></a></p>', esc_url( ConnectPage::CHATGPT_URL ), esc_html__( 'Open ChatGPT', 'addsynapse-store-assistant' ), esc_html__( '(opens in a new tab)', 'addsynapse-store-assistant' ) );
    … …  
    652898                echo '</section>';
    653899                echo '</div>';
    654 
    655                 echo '<p class="adsa-added"><button type="button" class="button adsa-mark" data-mark="add">' . esc_html__( 'I\'ve added it', 'addsynapse-store-assistant' ) . '</button> <span class="description">' . esc_html__( 'This step also turns green on its own once the site is connected.', 'addsynapse-store-assistant' ) . '</span></p>';
    656900        }
    657901
    … …  
    7921036                        echo '<div><strong>' . esc_html__( 'Already connected ✓', 'addsynapse-store-assistant' ) . '</strong> ';
    7931037                        echo esc_html__( 'This site is connected to AddSynapse. Go back to Claude or ChatGPT and continue there.', 'addsynapse-store-assistant' ) . '</div></div>';
    794                 } elseif ( in_array( $welcome, array( 'mcp', 'key' ), true ) ) {
     1038                } elseif ( 'key' === $welcome || ( 'mcp' === $welcome && empty( $status['connected'] ) ) ) {
    7951039                        echo '<div class="adsa-welcome" role="status">';
    7961040                        self::print_icon( 'check' );
    … …  
    7991043                }
    8001044
     1045                self::render_home_allset( $status );
    8011046                Announcements::render( 'home' );
    8021047                ReviewRequest::render( 'home' );
  • addsynapse-store-assistant/trunk/includes/Install/Lifecycle.php

    r3758975 r3763701  
    5757                Usage::deactivate();
    5858                RemoteAccess::revoke_all();
     59                // 1.3.5: a later activation asks AddSynapse afresh («did you start in Claude?»).
     60                \Addsynapse\StoreAssistant\Account\PendingOnboarding::reset();
    5961        }
    6062
  • addsynapse-store-assistant/trunk/includes/Remote/RelayClaim.php

    r3759648 r3763701  
    437437
    438438        /**
    439          * «Last step: confirm the account» — step 3 of the wizard, with the AddSynapse account (name + masked email) and
    440          * Connect / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.
     439         * «Connect <host> to your AddSynapse account» — step 2 of the wizard, with the AddSynapse account (name + masked email) and
     440         * «Yes, connect» / Cancel (POST, nonce). Same security as before: nothing is sent to AddSynapse until Connect.
    441441         */
    442442        public static function render_confirm_card(): void {
    … …  
    450450                $who     = trim( $account . ( '' !== $email ? ' (' . $email . ')' : '' ) );
    451451                echo '<li class="adsa-step is-current adsa-relayconfirm" id="adsa-step-connect" data-step="connect" data-adsa-relay-confirm>';
    452                 echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">3</span></span>';
    453                 echo '<h2 class="adsa-step__title">' . esc_html__( 'Last step: confirm the account', 'addsynapse-store-assistant' ) . '</h2></div>';
     452                echo '<div class="adsa-step__head"><span class="adsa-step__num" aria-hidden="true"><span class="adsa-step__n">2</span></span>';
     453                // The whole site address, with its folder (owner 24/10: «demo.addsynapse.com/satest», not just the host).
     454                $host = untrailingslashit( (string) preg_replace( '#^https?://#i', '', home_url() ) );
     455                /* translators: %s: this site's host name, e.g. shop.example.com */
     456                echo '<h2 class="adsa-step__title">' . esc_html( sprintf( __( 'Connect %s to your AddSynapse account', 'addsynapse-store-assistant' ), $host ) ) . '</h2></div>';
    454457                echo '<div class="adsa-step__body">';
    455                 echo '<p>' . ( 'key' === ( $stash['mode'] ?? '' )
    456                         ? esc_html__( 'AddSynapse asks to connect this site. Connect it to this AddSynapse account?', 'addsynapse-store-assistant' )
    457                         : esc_html__( 'WordPress approved the connection. Connect this site to this AddSynapse account?', 'addsynapse-store-assistant' ) ) . '</p>';
     458                // 1.3.5 (owner 24/10): plain words — what happens, whose account, nothing changes until approved.
     459                echo '<p class="adsa-relayconfirm__lead">' . esc_html(
     460                        'site' === \Addsynapse\StoreAssistant\Environment::site_type()
     461                                ? __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your site and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' )
     462                                : __( 'You\'re connecting this site to the AddSynapse account below. Claude (or ChatGPT) can then read your store and prepare changes — nothing changes until you approve it.', 'addsynapse-store-assistant' )
     463                ) . '</p>';
    458464                echo '<div class="adsa-relayconfirm__account">';
    459465                if ( '' !== $account || '' !== $email ) {
    … …  
    476482                        ) . '</p>';
    477483                }
    478                 echo '<p class="adsa-relayconfirm__why">' . esc_html__( 'We ask so nobody can link your site to their own account. Continue only if this is your account and you started this from your AI chat a moment ago.', 'addsynapse-store-assistant' ) . '</p>';
     484                echo '<p class="adsa-relayconfirm__why description">' . esc_html__( 'Only continue if this is your account. Didn\'t start this? Press Cancel.', 'addsynapse-store-assistant' ) . '</p>';
    479485                if ( 'key' === ( $stash['mode'] ?? '' ) ) {
    480                         echo '<p class="adsa-relayconfirm__scope" data-adsa-relay-key>' . esc_html__( 'Connect creates a key for AddSynapse that works only for this plugin\'s tools, acting as you. Changes still wait for your Approve. You can disconnect at any time from this page.', 'addsynapse-store-assistant' ) . '</p>';
     486                        echo '<p class="adsa-relayconfirm__scope description" data-adsa-relay-key>' . esc_html__( 'This creates a key that only works for this plugin\'s tools. You can disconnect any time here.', 'addsynapse-store-assistant' ) . '</p>';
    481487                }
    482488                echo '<form method="post" class="adsa-finish adsa-relayconfirm__form" action="' . esc_url(
    … …  
    491497                ) . '">';
    492498                wp_nonce_field( 'addsynapse_assistant_relay_confirm_' . $state );
    493                 echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Connect', 'addsynapse-store-assistant' ) . '</button>';
     499                echo '<button type="submit" name="as_connect" value="1" class="button button-primary button-hero adsa-finish__btn">' . esc_html__( 'Yes, connect', 'addsynapse-store-assistant' ) . '</button>';
    494500                echo '<button type="submit" name="as_cancel" value="1" class="button button-secondary">' . esc_html__( 'Cancel', 'addsynapse-store-assistant' ) . '</button>';
    495501                echo '</form>';
    … …  
    689695                                'protocol'   => self::PROTOCOL,
    690696                        ),
    691                         15
     697                        40
    692698                );
    693699                unset( $password );
    … …  
    824830                                'protocol'   => self::PROTOCOL,
    825831                        ),
    826                         15
     832                        // AddSynapse checks the new key against this site before it answers (its own call can take ~15 s).
     833                        40
    827834                );
    828835                unset( $key['secret'] );
  • addsynapse-store-assistant/trunk/includes/Remote/RelayKeys.php

    r3759648 r3763701  
    8686        /**
    8787         * Whether keys may be created/used here: HTTPS, or a `local` development environment (same rule as Application Passwords).
     88         * 1.3.5: HTTPS also when PHP sits behind a TLS-terminating proxy that does not set $_SERVER['HTTPS'] (Varnish/nginx on
     89         * CloudPanel): the site's own address is https://, or a proxy on this same machine says so (X-Forwarded-Proto /
     90         * X-Forwarded-Ssl, trusted only from a loopback address).
    8891         */
    8992        public static function transport_ok(): bool {
    90                 return is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() );
     93                if ( is_ssl() || ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) ) {
     94                        return true;
     95                }
     96                if ( 'https' === strtolower( (string) wp_parse_url( home_url(), PHP_URL_SCHEME ) ) ) {
     97                        return true;
     98                }
     99                $remote = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
     100                if ( ! in_array( $remote, array( '127.0.0.1', '::1' ), true ) ) {
     101                        return false;
     102                }
     103                $proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) ) : '';
     104                $ssl   = isset( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_SSL'] ) ) ) : '';
     105                return 'https' === $proto || 'on' === $ssl;
    91106        }
    92107
  • addsynapse-store-assistant/trunk/includes/Remote/SetupStatus.php

    r3759648 r3763701  
    166166                $check = get_option( self::CHECK_OPTION, array() );
    167167                return array(
    168                         'checked'   => is_array( $check ) && ! empty( $check['ok'] ),
    169                         'connected' => null !== $found,
    170                         'used'      => null !== $found && null !== $found['last_used'],
     168                        'checked'      => is_array( $check ) && ! empty( $check['ok'] ),
     169                        // A real check ran and failed (the only case step 1 may stay open on a connected site).
     170                        'check_failed' => is_array( $check ) && array_key_exists( 'ok', $check ) && empty( $check['ok'] ),
     171                        'connected'    => null !== $found,
     172                        'used'         => null !== $found && null !== $found['last_used'],
    171173                        // Connected for sure: AddSynapse confirmed this password (claim) or already used it. The wizard then shows every step done.
    172                         'claimed'   => $claimed,
    173                         'ready'     => null !== $found && ( $claimed || null !== $found['last_used'] ),
    174                         'detail'    => $found,
    175                         'log_count' => count( ApprovalLog::all() ),
     174                        'claimed'      => $claimed,
     175                        'ready'        => null !== $found && ( $claimed || null !== $found['last_used'] ),
     176                        'detail'       => $found,
     177                        'log_count'    => count( ApprovalLog::all() ),
    176178                );
    177179        }
  • addsynapse-store-assistant/trunk/package.json

    r3759648 r3763701  
    11{
    22  "name": "addsynapse-store-assistant",
    3   "version": "1.3.4",
     3  "version": "1.3.5",
    44  "private": true,
    55  "description": "Admin chat UI of the AddSynapse Store Assistant WordPress plugin.",
  • addsynapse-store-assistant/trunk/readme.txt

    r3759648 r3763701  
    77WC requires at least: 8.0
    88WC tested up to: 11.1
    9 Stable tag: 1.3.4
     9Stable tag: 1.3.5
    1010License: GPLv2 or later
    1111License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    172172== External services ==
    173173
    174 The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.
     174The plugin uses external services only for the features you turn on. Slash commands, previews, Approve and Undo in wp-admin run on your site and need no service. **Before an administrator saves an AI key (section 1 or 2), starts the Claude connection (section 5), creates an account (section 4) or allows anonymous usage info (section 6), the plugin makes no outgoing requests** (except the section 5 setup question) and schedules no recurring background tasks (only local one-off work you start yourself: applying a large approved change, or removing an image you uploaded in the chat and never used). The connectivity check of the wizard is a request from your site to itself. The links to the Store Assistant Pro page on addsynapse.com and to the plugin's reviews on WordPress.org are ordinary links your browser opens when you click them.
    175175
    176176= 1. Anthropic API — api.anthropic.com (only with your own Anthropic key) =
    … …  
    205205* AddSynapse privacy policy: https://addsynapse.com/privacy
    206206
    207 = 5. AddSynapse for Claude and other AI apps — addsynapse.com (only after you approve the connection) =
    208 
    209 Optional: use your site from Claude or ChatGPT (beta) (AddSynapse → Home → "Use your store from Claude or ChatGPT"). The setup steps only check your own site and show links and text to copy; they send nothing to AddSynapse. The "Add to Claude" link opens app.addsynapse.com with this site's address in the link (so AddSynapse knows which site to check when you sign up), which then opens Claude's connector page; your browser opens it only when you click it.
     207= 5. AddSynapse for Claude and other AI apps — addsynapse.com (after you approve the connection; one setup question before) =
     208
     209Optional: use your site from Claude or ChatGPT. "Connect to AddSynapse" opens app.addsynapse.com with this site's address in the link. While nothing is set up, for an administrator (after activation or on the setup screen, at most every 10 minutes), the plugin asks whether you started in Claude: `GET https://api.addsynapse.com/api/onboarding/pending?site=<site address>` (site address only). If yes, the browser continues there; nothing connects without Yes, connect.
    210210
    211211You start the connection from your AI chat. It opens this plugin's page on your site. For a logged-in administrator only, that page asks AddSynapse which account started the connection (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim/preview`: the one-time code and site address; answer: account name and masked email) and asks you to confirm it. Only if you press Connect does the plugin create a random connection key for AddSynapse, acting as you, and send it once from your server over HTTPS (`POST https://api.addsynapse.com/api/v1/assistant/relay-claim`: key, user name, site address, one-time code — never in a web address); Cancel creates nothing. The site keeps only a keyed hash of the key. A key AddSynapse does not use within 15 minutes is revoked. Sites connected with an earlier version keep their WordPress Application Password until they reconnect.
    … …  
    226226
    227227== Changelog ==
     228
     229= 1.3.5 =
     230* Improved: simpler setup. Step 2 is one «Connect to AddSynapse» button that links this site to your AddSynapse account; adding AddSynapse to Claude or ChatGPT moves to step 3, shown after the site is connected.
     231* Improved: started in Claude? Right after activation the plugin asks AddSynapse once (site address only, see External services) and goes straight to «Connect <site> to your AddSynapse account? Yes, connect», then «Done — go back to Claude». «Other options» keeps both ways one click away.
     232* Improved: setup has two steps (the connection check runs by itself) and Home opens with «You're all set!»: open Claude and three requests to try.
     233* Fixed: sites behind an HTTPS proxy (PHP sees plain HTTP) can connect; a failed request to AddSynapse now says what failed.
    228234
    229235= 1.3.4 =
    … …  
    307313== Upgrade Notice ==
    308314
     315= 1.3.5 =
     316Simpler setup: one «Connect to AddSynapse» button links the site to your account; started in Claude, you land right on «Yes, connect».
     317
    309318= 1.3.4 =
    310319Connect without WordPress Application Passwords, with a key only this plugin accepts. Coupons above 50% off are approved only in wp-admin. Includes 1.3.3: change your site's appearance from Claude or ChatGPT, with Approve and Undo.
  • addsynapse-store-assistant/trunk/uninstall.php

    r3759648 r3763701  
    8888                // 1.3.0: the welcome wizard (finished / skipped, chosen way).
    8989                'addsynapse_assistant_onboarding',
     90                // 1.3.5: Home's «You're all set!» panel (first shown, hidden).
     91                'addsynapse_assistant_allset',
    9092                // 1.3.0: the AddSynapse connection AddSynapse confirmed (uuid of the password, user, time — never the password).
    9193                'addsynapse_assistant_relay_claimed',
Note: See TracChangeset for help on using the changeset viewer.