Pilot on washanyanya.ru exposed that Telegram Bot API rejects tel:
URLs in inline_keyboard with HTTP 400 "Wrong port number specified
in the URL". Queue was stuck on 5 items retrying the same failure
4 times each.
- MessageFormatter: always returns reply_markup = null. The phone
number is already rendered in the '📱 ...' line, and mobile
Telegram clients linkify it automatically.
- TelegramClient::is_permanent: treat all 4xx except 429 as permanent
client-side errors, so future payload-shape bugs surface in the
dashboard widget immediately instead of burning five retry cycles.
- MessageFormatterTest updated: the old test asserting tel: in
reply_markup is replaced with one asserting reply_markup is null
and the phone still appears in the text body.
- Spec §9 + §19 updated, §19's open question closed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flamingo's '_fields' meta stores only the list of field NAMES with
null values; the actual values live in separate per-field meta keys
prefixed with '_field_'. The old extract_fields() returned the
null-valued '_fields' array on the first branch and never fell
through to the '_field_*' collector, so every retro Telegram message
showed '(не указан)' / '(пусто)' for every slot.
Also collect '_from_name' / '_from_email' (set by Akismet) as
convenience mappings to 'your-name' / 'your-email' so the formatter's
standard slots are populated for most submissions even when the form
uses custom field names like text-211 / name_user.
Pilot on washanyanya.ru caught this (retro delivered empty skeletons).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dispatcher::register_hooks() runs on plugins_loaded, which is AFTER
register_activation_hook fires. The custom recurrence 'cf7stg_every_minute'
was therefore unknown to wp_schedule_event during activation, and the
'cf7stg_dispatch' event silently failed to schedule — queue items never
got dispatched.
Pilot caught this on washanyanya.ru (23 items queued, 0 sent, 0 failed —
no cron hook firing at all).
Adding the filter directly inside Activator::schedule_events() makes the
custom schedule available in the same request as the wp_schedule_event
call, regardless of bootstrap order.
After updating the plugin, deactivate + reactivate is required to
re-trigger Activator::activate() so events get (re)scheduled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>