== Changelog ==

= 2.3.0 =
* Fix: "Delete all data" (the Danger Zone button and the deactivation modal) left several things behind - the routine had drifted away from the plugin it cleans. **Uploaded files were the worst of it**: the destination was read from `accua_forms_file_data`, a name the plugin stopped writing long ago, so it always resolved to the default directory. A site with a configured upload path therefore had that default directory wiped while every file its visitors had submitted stayed on disk. The path now comes from the option the uploads really use (`accua_forms_default_file_field_data`), and the default directory is cleaned as well since files predating a custom path still live there. Because that makes the deletion actually follow an administrator-set path, it is now refused for any directory that is not plugin-owned - the uploads root, wp-content, wp-admin, the WordPress root or anything outside the install - so a destination like "wp-content/uploads" can never take the media library with it.
* Fix: four options survived "Delete all data": the captcha settings (which hold the reCAPTCHA and Cap **secret keys**), the analytics settings, the file-field settings and the recovery backup of the field list. The option list now covers everything the plugin writes, and keeps the names used by older versions so a long-lived install is cleaned out too. The cached dashboard statistics (a transient holding submission counts) and the per-user submissions-list preferences (per-page choice and hidden columns) are removed as well. Matching is deliberately narrow: an extension plugin's own options, tables and user meta are never touched.
* Improved: the reCAPTCHA v3 badge position setting says when its effect is visible. The badge only appears once the visitor starts filling the form, because no request is made to Google before that, and the CSS that positions or hides it is printed with the form - so a page cache keeps serving the previous position until it is emptied. Both are now stated under the setting; without that, changing the position looks like it does nothing.
* Fix: the values the plugin stores for its own use were offered as submission columns. The field columns of the submissions list come from `SELECT DISTINCT afsv_field_id`, which also returns internal rows - notably `_accua_download_token`, the token that authorizes a file download - so they appeared as "(removed)" columns in the list and in Screen Options, and their values went into the Excel export. Column keys with the reserved `__` prefix (which the Fields page refuses as a slug) or the internal `_accua_` prefix are now skipped, the same two prefixes the single submission page has always skipped.
* The DB 18 collation conversion is applied on every install run - every plugin activation as well as every version change - rather than only when the stored DB version is older. `dbDelta()` rewrites `afsv_field_id` back to the table's default (case-insensitive) collation whenever it runs, so a version-gated conversion would be lost again at the next activation, taking the case-sensitive slugs with it. The conversion returns early when the collation is already binary, so re-checking it costs one information_schema lookup.
* Fix (DB 19): forms carried over from a pre-2.0 installation saved submissions with **no field values at all**. The 1.x -> 2.0 conversion, unchanged since 2.0.0, rebuilt a form's comma-separated field list as instances that had no instance id and sat under numeric keys. Such a form renders perfectly (the render loop reads the field reference) and the submission is recorded, but every value is dropped, because the submission handler matches each posted field against the instance keys and finds nothing. The conversion now produces instances keyed by instance id, the way the form editor writes them, and DB 19 repairs the forms an earlier version already converted - saved forms and trashed ones alike, since a trashed form can be restored. A form that was saved normally is left untouched, and a field used twice in the same form keeps both instances.
* Feature: `uninstall.php`, honoring a new opt-in preference. The plugin had no uninstall handler at all - deleting it from the Plugins screen left every table and option behind, and only the Danger Zone button or the deactivation modal ever removed anything. Deleting a plugin is routinely a step in reinstalling or troubleshooting it, so the new `accua_forms_delete_data_on_uninstall` option ships **off** and the uninstaller then does nothing whatsoever. Ticking "Delete all Contact Forms data when the plugin is deleted" in the Danger Zone makes it run the same routine as the Danger Zone button: uploaded files at the configured destination (with the plugin-owned-directory guard), the three tables, every option including the captcha secret keys and the preference itself, the cron event, the transients and the per-user screen preferences - and nothing another plugin owns. Multisite is handled per site: the preference, the options and the tables all belong to a single site, so a network uninstall deletes the data of the sites that opted in and leaves the others exactly as they are. The deletion routine and its helpers live in `includes/data-deletion.php`, which defines functions and registers no hooks, because uninstall.php runs with the plugin not loaded and has to require what it uses itself.
* Fix: an unreadable field list is no longer replaced by the shipped defaults on upgrade (found on a real site while testing this release). If the `accua_forms_avail_fields` option cannot be unserialized, `get_option()` returns false, `accua_forms_install()` reads that as "no fields defined yet" and writes the default field set over it - discarding every field the site had defined, with their labels and options, and no way back. It only bites on an upgrade, because the install routine runs when the stored DB version changes, which is exactly when such a site meets it. The usual cause is a migration or search-and-replace tool that rewrote the option text (classically normalizing CRLF to LF) without recomputing the `s:<length>:` headers PHP validates: the data is all there, only the lengths disagree. The upgrade now recomputes those lengths and accepts the result only if it unserializes into something shaped like a field list; either way the untouched original is copied to `accua_forms_avail_fields_corrupt_backup` before anything is written, so nothing is lost even when the repair does not work. A healthy option is never touched: each declared length is trusted first and only shortened when it fails to land on the closing quote.
* Compatibility note for the DB 18 conversion: the binary collation is derived from the column's own charset (`latin1_bin`, `utf8mb3_bin`, `utf8mb4_bin` …), verified on real latin1 and legacy-utf8 installs, so a site that never moved to utf8mb4 converts correctly instead of failing on a hardcoded collation name. Only `afsv_field_id` changes; `afsv_value` and every other column keep theirs. Code that joins its own table against `afsv_field_id` keeps working without changes, whichever collation it uses on its side - a plugin that reads the live collation gains the case distinction, one that hardcodes a utf8mb4 collation simply keeps matching case-insensitively as before. A field slug still cannot be changed once the field exists: it is the key every form instance and every stored submission refers to, and anything else holding it - an extension, custom code, an external integration - could not be updated along with it.
* Fix: field slugs are now case-sensitive throughout, and two fields whose slugs differ only in case no longer collide (user-reported from a 2.2.32 site). Field definitions live in a PHP array, where keys are case-sensitive, so a site could legitimately hold both a `role` text field and a `Role` checkbox — but the submission values table stored `afsv_field_id` with the site's case-insensitive collation, and to MySQL the two were the same string. The visible symptom was that the submissions list and the Excel export offered a single column for the pair, because both are built from `SELECT DISTINCT afsv_field_id`. The unreported and more serious one: the table's `PRIMARY KEY (afsv_sub_id, afsv_field_id)` also treated them as one, so on every submission the second field's value was rejected as a duplicate key and silently lost. DB version 18 converts that one column to the binary collation of its own charset (an explicit ALTER, the way WordPress core changes collations; dbDelta does not compare them). Converting in this direction cannot fail on existing data: values that were equal under the old collation could never both exist. Only that column changes, so its indexes keep working and no query needs a per-query COLLATE.
* Fix: the Excel export ran the selected column keys through `sanitize_key()`, which lowercases. Column keys embed the field slug verbatim (`_field_{slug}`), so exporting a site with `role` and `Role` merged the two regardless of the database. The export now uses a case-preserving sanitizer (`accua_forms_sanitize_column_key()`) accepting the same characters a slug may contain.
* Change: newly created field slugs are lowercased, the way WordPress lowercases term slugs. Existing slugs are never touched — a site already holding mixed-case slugs keeps them working, and they remain editable — but a new field can no longer accidentally introduce a `role`/`Role` pair. The Fields page input lowercases as you type, so it always shows what will be stored.
* Change: reCAPTCHA v2 now defaults to "Reject with an error message" instead of the silent "Accept silently and mark as Spam" default introduced for both captcha types in 2.2.40 (user-reported). v2 shows the visitor a checkbox to solve, so a failed check is usually not a bot: it is a real person who did not tick it, whose token expired (Google keeps them valid for two minutes) or who retried with a token already consumed (they are single-use). Accepting those silently filed a genuine message under the Spam status and sent no notification email, with nothing on screen to tell the visitor. Google's guidance for a failed verification is to surface the error and let the visitor solve the challenge again, which is what this default now does — the AJAX handler already resets the widget after every response, so the retry carries a fresh token. reCAPTCHA v3 keeps the silent "mark as Spam" default: its score is computed invisibly, there is nothing for the visitor to solve, and a low score is best absorbed without bouncing anyone.
* Implementation: the two types now have separate site-wide defaults in the "Captcha defaults" settings box — "When the reCAPTCHA v2 check fails" (new `captcha_spam_action_v2` option, shipped default `reject`) and "When the reCAPTCHA v3 check fails" (the existing `captcha_spam_action`, shipped default `spam`) — because the settings page writes its select on every save, so a shared key would have masked the new v2 default on any site that had ever saved the settings page. `accua_forms_captcha_default_spam_action()` and `accua_forms_captcha_spam_action()` take the field type as a new argument (defaulting to the v3 resolution, so pre-2.3.0 callers are unaffected); the per-field "override" checkbox in the form editor is unchanged, and fields that already carry an explicit `spam_action` keep it.
* Fix: a form carrying both a v2 and a v3 field could apply the wrong action. The submission handler resolved one action per form by scanning for the first captcha field, which was harmless while both types shared a default but not once they differ. `AccuaForm_Validation_CaptchaSpam` now records the silent action of the validator that actually failed (`getSpamAction()`), and the handler follows that, so each captcha field's setting governs its own failures.
* Fix: the "Contact Form" block rendered nothing on WordPress 5.9 and 6.0, which the plugin still declares support for. `block.json` describes its dynamic rendering with the `render` property, honored only from WordPress 6.1, and the block was registered without a `render_callback` fallback — so on older versions it registered fine, appeared in the inserter, and produced an empty front end. `register_block_type()` is now called with an explicit `render_callback`; on 6.1+ the explicit argument wins over the metadata-derived one and both resolve to the same `block-editor/render.php`, so nothing changes on current WordPress.
* Public release consolidating the 2.2.33 - 2.2.47 development versions (the previously published version was 2.2.32). The readme.txt changelog entry for 2.3.0 summarizes all of them; the per-version detail stays in this file.
* Plugin Check (PCP) 2.0.0 final compliance pass: fixed the one finding on the shipped file set — the Fields page live preview printed the JSON-encoded "this is a preview" note into its inline script without an escaping annotation. The value is wp_json_encode() output of an already-escaped string, safe in a script context, now annotated as such (admin/fields-page.php). No functional changes.
* i18n: completed the one missing translation in the bundled catalogs (the "%d field deleted." plural, it_IT and es_ES) and regenerated POT/PO/MO. Both catalogs are now 100% translated.
* Fix: the two high-contrast blocks in frontend.css were written as `@media (prefers-contrast: high)`. `high` is not a value in Media Queries Level 5 (`no-preference | more | less | custom`), so the query never matched in Chrome or Firefox and the styles — a bolder floating label in the inline-label layout, 2px borders and a focus outline on the post-select control — had never applied for anyone browsing with a contrast preference. Now `@media (prefers-contrast: more), (prefers-contrast: high)`, keeping the legacy value for older WebKit. CSS version 205.
* Readme accuracy pass over the whole readme.txt, verified claim by claim against the code. Corrected: "20 available field types" (there are 24, and the list omitted Telephone, Date, Post select and Color picker); the "orange C icon" (the classic-editor button is the cyan Contact Forms logo, and the Gutenberg "Contact Form" block was not mentioned anywhere despite shipping since 2.0); a "Preview/Test Tab" that does not exist (the form editor has a live preview pane); the "Appearance" tab (labelled "Appearance and General"); an "Advanced Excel Export option" (there are two export buttons, and the frozen header row plus column filters apply to every export); Cloudflare Turnstile listed as if built in (it requires the free Simple Cloudflare Turnstile plugin); the spam-handling sentence, which read as if Spam, Trash and delete were all the default (only "mark as Spam" is); screenshot 1 calling Top Labels the default layout (the default is labels on the left); "ARIA labels" (labelling is native `label for`, the ARIA work is `aria-describedby`/`aria-invalid` and live regions); "all captcha fields" for Hide field title (Turnstile does not offer it); the reCAPTCHA v3 notice being printed "under the form" (it renders where the v3 field sits); "long values compress in every column" (URL and field columns); the dashboard "Go to:" links listing "each lead status" (only those in use); and the claim that extension field types show their own settings on the Fields page (they get the generic sections — that page fires no `accua_forms_field_settings` hook).
* Added the missing `Requires PHP: 7.4` line to the plugin header, which previously declared it only in readme.txt.
* Improved: the "Essential Columns" button tooltip explains where its list comes from (user-reported: the tooltip named the columns but read as a fixed, generic list). It already listed the columns actually configured on the site — the always-essential ones plus the flagged field columns — but nothing told the administrator that the field part is theirs to choose. A second line now says so, naming the option and the screen: "Choose which field columns are kept: edit the field on the Fields page and enable 'Show in essential columns'." Rendered as a real second line (title attributes honor newlines) and translated in it_IT and es_ES. The first line keeps its existing wording and translations.

= 2.2.47 =
* Feature: per-field "Show in essential columns" checkbox on the Fields page (add and edit form). `Accua_Forms_Submissions_List_Table::essential_columns()` is no longer a hardcoded list: Actions, ID, Form and Submitted are always essential, and a field column is included when its definition carries the new `essential_column` flag — the "Essential Columns" button and its tooltip follow automatically since both already consume `essential_columns()`. The previously hardcoded IP, Page, Referrer and Language main columns are no longer essential (diagnostic metadata, not what a submission list is scanned for). The flag is written explicitly (0/1) by `accua_forms_fields_filter_values()` — a missing key means "never saved on this version", which is what the upgrade migration keys on. The checkbox section is hidden for field types that never store a submission value (submit, html, the four captcha types); extension types get it like the other generic sections. It renders as the last option of the editor, behind an hr separator: it is an admin-list-only setting with no effect on how the field renders in forms, so it is kept visually apart from the settings above, which all shape the field itself.
* Feature: sensible defaults for the flag — fresh installs mark first_name, last_name, email and message (who wrote, and what they wrote); the first-visit default hidden columns of the submissions list (`default_hidden_columns` filter) now follow the same flag instead of hardcoding the email field.
* Upgrade: DB version 17. Existing installations get `essential_column = 1` on the email field only — exactly the field column of the old hardcoded list, so the button keeps showing the same field columns right after the update. Guarded by isset (the Fields page save always writes the key), so unchecking email later is never overridden by a later plugin activation run. Dev-stack note: bump the DB version constant last when editing a live-mounted install — the constant is compared on every init, so a request landing between the constant bump and the migration code burns the version with a no-op run (happened during development; recovered by resetting `accua_forms_db_version` and reloading).
* Fix: the Fields page live preview frame appeared oversized and shrank to the right size a moment later (user-reported). `resizePreview()` ran right after `document.write()`, measuring the written document before its stylesheets had loaded — unstyled form content is taller, so the frame grew immediately and only settled when the CSS applied and the ResizeObserver/timed re-measures caught up. The first resize (and the ResizeObserver setup) now waits for the frame's load event — bound on the iframe element, which survives `document.open()`, and before `doc.close()` so it cannot be missed — with a 1.5s fallback in case a subresource hangs; the loading overlay stays on until then; superseded refreshes clear the pending settle/re-measure timers, and the outgoing document's observer is disconnected before the write so its detached nodes cannot trigger a measure of the unstyled new document. The frame now goes from its previous height straight to the final one. Follow-up polish in the same release (user request: nothing in the admin may jump or resize abruptly): the frame starts compact (48px — below the 80px measure floor, so the first render can only grow it) instead of at an oversized 200px placeholder; every height change animates (0.25s ease, disabled under prefers-reduced-motion); the heavy white veil + large spinner were replaced by a small WordPress-size spinner with the frame content kept fully transparent while loading — the white wrapper shows through, and since the reveal happens at the frame's load event a document still loading its stylesheets can never flash unstyled (the veil used to mask that FOUC; removing it exposed the flash until the opacity gate restored the guarantee); and the preview document is written with `html { overflow: hidden }` so no scrollbar flashes while the frame is shorter than its content or animating — the resize logic restores scrolling only when the content exceeds the 700px height cap. The form editor preview keeps its existing veil + spinner treatment.
* Fix: the preview document is written with a `<!DOCTYPE html>` — without one the iframe rendered in Quirks Mode (jQuery is explicitly unsupported there and layout metrics differ; jQuery Migrate warned about it on every preview render).
* Improved: submitting the Fields page editor form cancels the pending debounced preview refresh and aborts an in-flight preview request — typing the label and clicking "Add new field" within the 700ms debounce window fired a refresh that raced the page unload for nothing. If the client-side validation blocks the submit, the next input schedules a fresh refresh as usual.
* Verification: submissions-list.spec.ts grew from 14 to 15 tests — an "Essential Columns button" describe seeds one flagged and one unflagged E2E field with stored values and asserts the click keeps ID/Actions/Submitted and the flagged field column while hiding the unflagged field, IP and Page (the admin's saved hidden-columns screen option is snapshotted and restored); the tooltip test now asserts the Email label and the smaller column set. fields-page.spec.ts grew from 29 to 30 — checkbox visibility per type (shown for textfield and unknown extension types, hidden for captcha/submit/html) and a full round trip: create with the flag → stored as 1 and present in essential_columns(), edit shows it pre-checked, uncheck → stored as 0 and absent. Full suite green. A Chromium automation gotcha surfaced by the new test is documented in the workspace testing skill: after any form-POST navigation the Playwright-bundled Chromium stops servicing requestAnimationFrame for that tab (reproduced on core options-general.php — no plugin code involved), so actionability-gated clicks on pages after a POST need force: true; not a plugin bug.
* i18n: 2 new strings translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated).
* Release housekeeping: 2.2.46 was not published to wordpress.org either (the public version is still 2.2.32), so the consolidated readme.txt entry is renamed to 2.2.47 and now includes the essential-columns feature and the preview sizing fix. CSS version 204, JS version 132, DB version 17.

= 2.2.46 =
* Fix: the submissions list Page and Referrer columns lost their compression — long URLs stretched the columns to the full URL width instead of collapsing behind the [+] expandable widget. Root cause: the 2.2.39 switch to content-based column sizing dropped the core 'fixed' table class, and without `table-layout: fixed` the CSS truncation on the cells (overflow + text-overflow) cannot constrain a table column, so `expandable-cells.js` never measured an overflowing cell and never built the `<details>` widget. The still-published 2.2.32 predates that change and truncates fine, which is how the regression surfaced (production compressed, dev not). The URL columns now carry an explicit `max-width: 15em` in admin.css — honored by auto table layout in all browsers — so truncation and the [+] widget work again while every other column keeps sizing to its content. The dashboard "Last 10 submissions" table was never affected (it kept the fixed class and a colgroup).
* Feature: "Expand All Rows" and "Collapse All Rows" buttons in the submissions list toolbar next to "Essential Columns" — icon-only buttons with the action name in the tooltip (`title`) and in `aria-label` for screen readers. No dashicon conveys "expand/collapse rows" (the editor-expand/contract pair reads as fullscreen), so the icons are two new plugin SVGs (`assets/img/expand-rows.svg` / `collapse-rows.svg`: a bulleted row list with vertical arrows pointing outward, respectively inward), applied via CSS `mask` with `background-color: currentColor` so they take the button text color in every state. The buttons are flex containers (`inline-flex` + `align-items: center`), so the icon is centered exactly regardless of the `.button` line-height/min-height metrics that differ between WordPress releases. They open/close every [+] expandable URL cell on the page at once (new `accuaToggleAllRows()` in submissions-list.js; the per-cell toggle listeners keep handling the row-height styles).
* Improved: the "Essential Columns" button explains itself — its tooltip lists the columns it keeps, with the same localized labels the table shows ("Keep only these columns visible: ID, Actions, Form, IP, Page, Referrer, Language, Submitted, Email"), built from `get_columns()` and skipping keys absent on the site. The column-key list moved to a single source of truth (`Accua_Forms_Submissions_List_Table::essential_columns()`), mirrored to `setEssentialColumns()` in submissions-list.js via `wp_localize_script` instead of being duplicated there.
* Fix: browser find-in-page (Ctrl+F) counted every match inside a compressed URL cell twice (user-reported; e.g. searching a word contained in a collapsed referrer). The [+] widget was a `<details>`/`<summary>` holding the text twice — a truncated preview in the summary plus the full link in the details body — and both Chrome and Firefox search collapsed `<details>` content (that is also what lets them auto-open one on a match), so both copies counted, collapsed or expanded. The widget is rebuilt around a single text copy: the link itself, CSS-truncated while collapsed (`.accua-expandable-text`), with a real `[+]`/`[−]` toggle `<button>` (aria-expanded synced) instead of the details element; the open state simply lifts the truncation and wraps (`overflow-wrap: anywhere`). Same fix applies to the dashboard "Last 10 submissions" URL columns (same script). The unreachable plain-text `<details>` branch in `truncate_long_value()` — both callers always pass a URL — is removed so the duplicated-text pattern is gone entirely. Expand/Collapse All Rows now drive the buttons (`accuaToggleAllRows()` clicks the toggles whose state differs). One behavior note: a find match can no longer auto-expand the cell (no native details), the browser just scrolls to the row.
* Improved: the Actions column is now the first column of the submissions list (right after the bulk-select checkbox) instead of the last — the quick actions are reachable without scrolling a wide table. The "Essential Columns" key list and its tooltip follow the new order.
* Feature: the [+] compression covers the field-value columns too — a long message no longer blows up the row height. Field cells are CSS-truncated to a single line (`max-width: 25em` + ellipsis) and `expandable-cells.js` builds the same single-copy [+]/[−] widget for any cell whose content overflows or contains line breaks (a multi-line value always gets the widget, since the collapsed one-line preview flattens its line breaks; expanding renders them as paragraphs again via `white-space: pre-line` in the open state). Short single-line values stay plain, and the Expand All Rows / Collapse All Rows buttons include these cells.
* Improved: line breaks typed by the visitor are preserved when a submitted value is displayed — as paragraphs on the single submission page (`white-space: pre-line` on the value cells; values remain esc_html'd, CSS is the only layer touched) and in the expanded state of the submissions-list widget above.
* Fix: the Fields page live preview frame could stay much taller than the field it shows. Neither `documentElement.scrollHeight` nor the body box can be trusted in an iframe (the frontend styles stretch the body to 100% height, and scrollHeight never reports less than the frame's own height), so the preview could grow but never shrink back. The height is now computed from the actual content — the lowest bottom edge among the body's children — and follows every later change through a ResizeObserver on those children: the frame grows the moment an inline validation error appears on blur and shrinks back when it clears (the timed re-measures remain as fallback). Height floor lowered from 160 to 80 px; a plain text field now previews at ~90 px instead of a frame ratcheted up to 700 px.
* Improved: the submissions list Actions column never wraps — `white-space: nowrap` on `.column-singlesub`, so with the content-based (auto) column sizing the column widens to keep the quick-action links (Open | Trash | Spam, and the Trash/Spam view variants) on a single row, like the dashboard Actions column already did.
* Demo content: the Playground blueprint covers more features with two new example pages, both linked from the demo menu and the Welcome page. "Website Restyling Brief" demonstrates the Color picker field (native color input; the hex value is rendered as a color swatch in the notification email and the submissions list) and a custom Submit button field with its own label replacing the auto-added button. "Member Registration" demonstrates the Password-and-confirmation field (two masked inputs that must match; the archive stores only a secure hash) and non-AJAX submission (Use AJAX off — classic full page reload with server-side validation). Some demo contact submissions now carry realistic long search-engine referrers so the Playground admin demos the [+] compression and the new buttons out of the box, and four sample Restyling Brief submissions carry color values. A few field definitions gained descriptions (shown in the admin Fields list). The welcome-page and navigation steps are now upserts (the welcome page moved to its own step), so replaying them updates an existing install instead of duplicating posts. A further additive step widens the edge-case coverage of the archive demo data: more long referrers from varied sources (social network share, redirector, newsletter platform, search engines) on workshop/job/quote/poll submissions, campaign-tagged long URIs on job and quote rows so the Page column compresses too, and three contact submissions with really long messages — a ~2,500-character wall of text, one containing a long unbroken URL, and a ~1,400-character multi-paragraph one — to exercise the Message column. Every URL in the demo data uses RFC 2606 reserved fake domains (`.example` TLD, `example.com`/`.net` subdomains) rather than real platforms — no Google/LinkedIn/Facebook/Bing/DuckDuckGo/Mailchimp hosts, and platform-branded tracking parameters neutralized (`gclid`/`li_fat_id` → `click_id`, `utm_source=google/linkedin` → `search_ads`/`social_network`) — so the demo can never link to an existing page or lend itself to impersonating those platforms. The E2E fixtures follow the same rule.
* Dev stack: the local installation was provisioned from a pre-2.2.44 blueprint — that is why the "Custom HTML Content" page and its menu entry were missing there — and was brought up to date by replaying the missing/changed blueprint steps (Custom HTML page, the two new pages, welcome + navigation updates, referrer backfill).
* Verification: submissions-list.spec.ts grew from 8 to 14 tests — long URI/referrer cells get the [+] widget while short ones stay plain links (fails against the unfixed CSS), a multi-line message collapses behind the widget, stays a single DOM text copy and expands to `pre-line` paragraphs while short messages stay plain, the Expand/Collapse All Rows buttons open and close every widget on the page, a compressed URL exists exactly once in the cell's DOM text (fails against the duplicated `<details>` markup — this is what find-in-page counts), the toggle icons measure vertically centered in their buttons (≤1px) and the Essential Columns tooltip lists the column labels, and the Actions column is the first data column and computes `nowrap` with all its links on one visual line; `createTestSubmission()` accepts optional uri/referrer metadata. Preview auto-height verified live: a text field previews at 87 px and grows to 110 px when the inline required error appears on blur. Full suite green. All 21 blueprint steps `php -l` clean; the two new demo forms exercised end-to-end on the dev stack (AJAX submit via the custom button; non-AJAX password mismatch error after reload + success round trip; hash-only password storage asserted in the DB).
* i18n: 3 new strings translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated — the regeneration also restored the 2.2.45 "Back to the fields list" string missing from the POT; its PO translations already existed).
* Release housekeeping: 2.2.45 was not published to wordpress.org either (the public version is still 2.2.32), so the consolidated readme.txt entry created for 2.2.45 is renamed to 2.2.46 and now includes the expand/collapse buttons, the find-in-page fix and the new demo pages. CSS version 203, JS version 131.

= 2.2.45 =
* Feature: live preview on the Fields admin page. A "Live preview" panel in the right column (above the fields list while adding, alone while editing — mirroring the form editor's preview placement) renders the field being configured — including unsaved edits — exactly as a form will show it. The new `accua_forms_field_preview` AJAX action runs the submitted settings through the same `accua_forms_fields_filter_values()` used on save, injects the resulting definition with an `option_accua_forms_avail_fields` filter (nothing is written to the database), fabricates a one-field draft through a `pre_transient_` filter and renders it with the real frontend pipeline (`AccuaForm` + `accua_forms_form_generate`) using the site's default form settings — so the preview is not a lookalike but the actual markup, CSS and JS the frontend produces, for every field type uniformly, including types registered by extension plugins through `accua_forms_field_types` (and unregistered types of deactivated extensions, kept renderable the same way the edit form keeps them selectable). The preview form suppresses the auto-added submit button (new `accua_forms_preview_suppress_auto_submit` filter in the generator — a submit-type field still previews its own button). The preview is also interactive: the field is marked required and the form uses AJAX mode on purpose, so the real client-side validation runs — leave the field empty (or enter an invalid email/phone) and click elsewhere to see the inline validation messages, including the custom required/format messages being edited on the page; the panel description explains that the field is mandatory in the preview only for demonstration, and a wrapper script guarantees the preview form never actually submits (a passing validation shows a "this is a preview" note instead). Fields with no visible output — hidden values, captcha fields whose placeholder is an HTML comment when keys are not configured — get an explanatory note instead of an empty box: comments are stripped and the remainder is checked for visible markup or text. Client side (fields-page.js): the panel refreshes with a 700 ms debounce while typing and immediately on select/date changes, POSTs the editor values with jQuery and writes the returned document into the iframe via document.write (no iframe navigation, so no browser history entries), aborts superseded requests, sizes the iframe to its content (160-700 px) and reuses the form editor's loading-spinner treatment.
* Improved: Fields page editing UX. The field slug is suggested from the label while adding a field (lowercased, accents folded, restricted to the allowed slug characters; the suggestion stops as soon as the slug is edited manually and resumes when the slug is emptied — same idea as the core permalink slug). The slug is also validated while typing, before anything is submitted: invalid characters, the reserved "__" prefix, over-70-characters and a slug already used by another field are flagged immediately with a red highlight and a message under the input (same wording as the saving validation), and the client-side submit check blocks saving with such a slug. "Add new field" / "Save changes" are now primary buttons, matching the core taxonomy screens (edit-tags.php styles Add New Category and Update as 'primary'); the Delete field button asks for confirmation (naming the slug in the dialog) and is styled with the core destructive button-link-delete class. When editing, the heading shows which field is open ("Edit field: my-field") and a "Back to the fields list" link is shown under the page title — above both columns, like the core term-edit screen — since the list table is hidden while editing; the editor heading and the "Live preview" heading are vertically aligned in both modes. Every help paragraph on the page is now associated to its input with aria-describedby.
* Improved: the Fields page preview panel only appears once the field has a label (there is nothing meaningful to preview before), and the colorpicker field previews as the native color input the frontend renders — its element used to pick the admin flavor inside the preview because admin-ajax is an admin context (new `accua_forms_preview_render_as_frontend` filter, forced on by the preview handler).
* Verification: php -l clean on the touched files; server-side smoke test of the preview render path via wp eval-file (select field: options parsed, default pre-selected, label present, no submit button). E2E: fields-page.spec.ts grew from 15 to 29 tests — a "live preview" describe (panel appears only once a label is entered and hides when it is cleared, no submit button, select options with the default pre-selected, multiselect box, html content, live label updates, hidden type showing the no-visible-output note, the required/custom-message validation demo, editing an existing field previews its saved settings immediately), a "preview type sweep" describe rendering every built-in field type in the preview in both add mode and edit mode (24 types each), and an "editing UX" describe (slug suggestion including accent folding, the manual-edit stop and the empty-slug resume; live slug validation for invalid characters, the reserved "__" prefix, over-length and collisions, with the client-side submit block; delete confirmation with dismiss keeping the field and accept deleting it; back link and slug in the heading). Full suite: 83 admin + 87 frontend tests green, 1 skipped (the optional real-Cap round trip).
* i18n: 10 new strings translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated); the client-side slug validation reuses the three existing server-side slug error strings.
* Release housekeeping: 2.2.44 was not published to wordpress.org either (the public version is still 2.2.32), so the consolidated readme.txt entry created for 2.2.44 is renamed to 2.2.45 and now includes the Fields page live preview and editing improvements. CSS version 199, JS version 128.

= 2.2.44 =
* Feature: the reCAPTCHA v3 minimum score is configurable. Google returns a 0.0 - 1.0 score for every submission and the plugin used to compare it against a hardcoded 0.5, changeable only through the `accua_forms_recaptcha3_score_threshold` filter. There is now a "Minimum score" field in the plugin settings (site-wide default, still 0.5) and a per-field override in the form editor, so a high-traffic public form can be stricter than an internal one. Resolution order is instance override → site-wide default → 0.5, and the filter still runs last, receiving the resolved value as its default — code that hooked it keeps winning. Stored as `score_threshold` on the field instance and `recaptcha_v3_score_threshold` in the `accua_forms_default_captcha_field_data` option; both are clamped to 0.0 - 1.0 and rounded to two decimals by `accua_forms_recaptcha3_clamp_score()`, and a non-numeric widget value is dropped rather than stored (storing 0 would silently disable the check). Like the secret and the spam action, the resolved threshold lives on the **validator** (`AccuaForm_Validation_Captcha3::$scoreThreshold`), not on the element, so it survives the encrypted-form serialization round trip.
* Feature: the score Google returned is recorded on the submission. Without it an administrator has no data to pick a threshold from — Google's own console shows a distribution, but not which of your submissions scored what. The validator keeps the score in a request-scoped registry (`AccuaForm_Validation_Captcha3::getScore()`, keyed by the same `accuaform_{fid}` flag key as the spam registry) and the submission handler writes it into the existing `afs_stats` JSON column as `recaptcha3_score` — no schema change. Recorded on pass and on fail alike, so submissions silently classified as Spam carry the score that got them there. Shown in the Details box of the single submission page and exposed as the `{__recaptcha3_score}` token for email messages (listed in the form editor's Tokens tab next to the other tracking tokens). The row is driven strictly by what the submission itself recorded, never by the fields the form currently has: submissions stored before the field existed show no row at all rather than an empty one, and removing the reCAPTCHA v3 field from a form later does not hide the scores its earlier submissions already recorded. A score of exactly `0` is displayed rather than treated as "no data" (`isset()` + `is_numeric()`, not a truthiness check), and a malformed `afs_stats` blob decodes to nothing without breaking the page.
* Feature: reCAPTCHA v3 badge placement — "Badge position" in the plugin settings: bottom right (Google's default), bottom left, or hidden. Site-wide rather than per field, because api.js injects a single floating badge shared by every reCAPTCHA on the page. The element prints the CSS once per page (the badge carries inline styles from Google, so the rules need `!important`); bottom left keeps the collapse/expand behaviour by animating a `transform` instead of Google's own `right`. Hiding the badge is allowed by Google only if the reCAPTCHA branding appears elsewhere in the user flow, so the `hidden` option makes the field render the required "This site is protected by reCAPTCHA…" notice (with the Privacy Policy and Terms of Service links) under the form; `.accua-forms-recaptcha3-notice` styles it in frontend.css. CSS version 198.
* Improved: every captcha field setting now follows the plugin's default-plus-override pattern. "When the spam check fails" and "Hide field title" gained site-wide defaults in a new "Captcha defaults" settings box (`captcha_spam_action`, `captcha_hide_title`), and their widget rows gained the standard "(override:)" checkbox used by every other field setting — the value is written to the field instance only when that box is ticked, otherwise the instance follows the site default. `accua_forms_captcha_spam_action()`, `accua_forms_captcha_form_spam_action()` and `accua_forms_captcha_hide_title()` fall back to the new `accua_forms_captcha_default_spam_action()` / `_default_hide_title()` instead of the hardcoded `spam` / hidden, which stay as the shipped defaults. Backward compatible: instances saved before 2.2.44 already carry explicit `spam_action` / `hide_title` values, so they read as overrides and behave exactly as before — only untouched instances start following the site default.
* Fix: on the Fields admin page, the "Custom HTML" field type offered no way to edit its HTML content. The content of an html field is its default value (the "Custom HTML content" textarea in the form editor edits the same key as a per-form override, and the frontend renders it directly), but the per-type section visibility introduced with the 2.2.5 Fields page rework left `html` out of the types that show the default-value section — before that rework every section was always visible and the content was simply entered under "Default value(s)". The section is now shown for html fields, relabeled "Custom HTML content" with its own help text to match the form editor. Further per-type coherence fixes from the same rework: post-select and multiple-post-checkboxes fields regained their default-value section, and for them the allowed-values section is relabeled "Additional query parameters" with the form editor's help text (for post fields that textarea holds query parameters, not key|label options); field types registered by extension plugins through the `accua_forms_field_types` filter (e.g. the Meeting Slot field) no longer render with every section hidden — unknown types now get the generic sections the form editor gives them (default value, custom required message); and the field description — displayed in the fields list for every field — is now always editable instead of being hidden for some types. No data migration involved: hidden sections still submitted their values, so existing definitions are untouched. JS version 127.
* Fix: editing the pre-installed "Turnstile" field on the Fields page corrupted it into a plain text field. Its `turnstile` type was missing from `accua_forms_fields_get_types()`, so the type dropdown fell back to its first option (Text Field) and saving stored the field with an "Invalid type" fallback of `textfield`; the fields list also showed the raw `turnstile` key instead of a label. The type is now registered as "Captcha (Turnstile)" next to the other captcha types: the Turnstile field can be edited and saved safely, is labeled properly in the list, and new fields of that type can be created.
* Demo content: the Playground blueprint (`blueprint.json`, replayed in the dev stack by `scripts/apply-blueprint.php`) now demonstrates the html field in all three flavors on a new "Custom HTML Content" demo page: a reusable "Privacy Notice" html field defined on the Fields page is used as-is by the new "Conference Registration (Custom HTML)" form and overridden per form by the new "Press Pass Request (Custom HTML override)" form, and both forms also carry a one-off Custom HTML widget banner. The page is linked from the demo navigation menu and the Welcome page.
* Verification for the Fields page fixes: new E2E spec `tests/e2e/admin/fields-page.spec.ts` (12 tests, form 9801): per-type section visibility (textfield, html, select, file, post-select, the captcha types, submit, plus an injected unknown type falling back to the generic sections — all label assertions read the localized strings from the page, so they are locale-proof); a full round trip creating an html field on the Fields page and asserting its content renders on the frontend with no per-form override; editing the html field showing the saved content and persisting an update; a Turnstile edit round trip keeping its type (fails against the unfixed code); and an "html field per-form override checkbox" describe driving the real form editor — with the override unchecked the widget shows the definition content and the frontend renders it, ticking the box and saving stores `default_value` on the field instance and the frontend renders the per-form content, unticking removes the key entirely so the frontend falls back to the definition, and editing the definition on the Fields page immediately updates forms without an override. Full admin suite: 62 tests green.
* Fix: editing a field whose type is not currently registered silently converted it to a plain text field. This is the general case of the Turnstile bug fixed above: with the providing extension plugin deactivated (e.g. Meetings for Contact Forms and its Meeting Slot type), or for a legacy type, the type dropdown fell back to its first option and saving stored `textfield` — a corruption that reactivating the extension did not undo. The edit form now offers the current type as a "%s (currently not registered)" option, and `accua_forms_fields_filter_values()` accepts an unchanged unregistered type on edit (a changed-to-invalid type is still rejected). Unregistered types get the same generic editor sections as extension types.
* Fix: the CSS Class and CSS ID settings on submit and hidden field instances had no effect. Buttons render inside the shared button group without the per-element wrapper the settings normally target, and the submit render case never received them — they are now applied to the `<button>` element itself; hidden inputs (which render without any wrapper) already used the CSS ID and now get the CSS Class too.
* Improved: captcha field widgets (reCAPTCHA v2/v3, Cap, Turnstile) in the form editor no longer offer the "Default value" and "Custom required message" rows — no captcha consumes either (their validators are set in the element constructors with their own messages). The widgets keep the settings that do work: spam action, hide title, minimum score, Required, CSS Class/ID, and the extension-settings hook still fires.
* Verification: Plugin Check (PCP) run on the dev stack — zero findings on the shipped file set (the only flagged files are dev-tree artifacts excluded from both release channels: old release zips, tests/, dotfiles, CLAUDE.md). `fields-page.spec.ts` grew to 15 tests: an unregistered-type edit round trip (type preserved, rename saved — fails against the unfixed code), CSS class/id of submit and hidden fields asserted in the frontend markup, and the captcha widget asserted to offer spam action/Required/CSS but no default-value or custom-required rows. Full suite: 156 tests green, 1 skipped (the optional real-Cap round trip).
* i18n: 19 new strings translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated).
* Verification: `php -l` clean on all touched files. E2E: `recaptcha3.spec.ts` grew from 26 to 39 tests. A "v3 minimum score" describe covers a stricter site-wide minimum rejecting a score that passes by default (0.9 against 0.95), a looser field override winning over a stricter site default, a stricter field override rejecting a score the site default accepts, and the `accua_forms_recaptcha3_score_threshold` filter still overriding both (through a temporary mu-plugin gated on an option, so a leftover file from a crashed run is inert). A "v3 recorded score" describe asserts the score lands in `afs_stats` for an accepted submission (0.9) and for a silently spam-flagged one (0.1), and that a v2-only form records nothing. A "v3 badge placement" describe asserts no CSS and no notice on the default, the repositioning CSS on bottom left, and the hiding CSS plus the notice with both Google links on hidden. Two helper tests assert the new resolvers (site defaults feeding instances that do not override, override precedence, clamping of out-of-range/non-numeric/null values, badge allowlist), and the editor-save tests assert that `spam_action`, `hide_title` and `score_threshold` are written only when their override box is ticked and dropped when it is not. Full suite of the touched specs (cap, turnstile, form-editor, recaptcha3): 66 tests green, 1 skipped (the optional real-Cap round trip).
* Release housekeeping: 2.2.43 was not published to wordpress.org either (the public version is still 2.2.32), so the consolidated readme.txt entry created for 2.2.43 is renamed to 2.2.44 and now includes the reCAPTCHA v3 configuration work.

= 2.2.43 =
* Fix: the Cloudflare Turnstile field was never reset after an AJAX submission, so a visitor who retried — typically after a validation error on another field — resubmitted a token Cloudflare had already consumed and got a "Turnstile verification failed" error they could not clear without reloading the page. Root cause: `classes/Element/Turnstile.php` printed a per-instance script block that reset the widget on the jQuery events `accua_form_submitted_{fid}` / `accua_form_error_{fid}`, but nothing has ever triggered those events (verified across this plugin, all the Cimatti extension plugins and the previous LocalWP tree) — the gap was documented in the 2.2.42 cleanup entry and is closed here. The reset now happens in the generated AJAX response handler in `AccuaForm.php`, next to the existing reCAPTCHA v2/v3 and Cap resets, and so covers both the success and the validation-error paths: every `.cf-turnstile` widget inside the submitted form is reset through `turnstile.reset('#'+containerId)`, so a retry always carries a fresh token. The dead listener block is removed from the element — which no longer emits any inline script — and the two events are now gone from the plugin entirely. JS version 126.
* Verification: new E2E spec `tests/e2e/frontend/turnstile.spec.ts` (3 tests, form 9851) built on Cloudflare's official testing keys — the dummy sitekey `1x00000000000000000000AA` auto-solves without interaction, and the dummy secrets make siteverify always pass (`1x0000000000000000000000000000000AA`) or always fail (`2x0000000000000000000000000000000AA`). The spec asserts that the widget renders through the Simple Cloudflare Turnstile plugin and no longer emits the dead listeners; that a successful AJAX submission resets the widget (asserted by spying on `turnstile.reset`); and the full retry flow — a rejected submission resets the widget, it re-solves into a fresh token, and resubmitting with verification passing again succeeds. Both reset tests fail against the unfixed code and pass with the fix. The spec installs (first run only) and activates the Simple Cloudflare Turnstile plugin, restoring the previous activation state and key pair afterwards. Full Playwright suite: 123 tests green, 1 skipped (the optional real-Cap round trip).

= 2.2.42 =
* Fix: in the form editor Messages tab, checking the "Customize" checkbox on the email override fields (To, Bcc, Subject, From name, From email) did not enable the associated text input, which stayed disabled and could not be edited. Root cause: the inline script that toggled the input's disabled state was removed in the "remove remaining inline scripts" refactor and its replacement in `assets/js/admin/form-settings.js` was scoped to the `#accua_tab_customise` panel id — a selector that never matched the Messages tab, and that since 2.1.0 matches nothing at all because the tab component renames panel ids at init (`accua_tab_*` → `accua_tabs-panel-*`). The Appearance tab was unaffected only because its show/hide behavior had meanwhile moved to pure CSS `:has()` rules. The dead block (including two leftover jQuery UI `#accua_tabs2` handlers) is replaced by a single delegated change handler on the Messages panel, selected via the stable `data-tab` attribute, that enables/disables the matching `.accua_form_value` input. JS version 124.
* Verification: new E2E spec `tests/e2e/admin/form-editor-messages.spec.ts` (4 tests, form 9811): all six override rows present; checking/unchecking each of the six checkboxes enables/disables its input (this test fails against the unfixed JS and passes with the fix); the on-screen success message radios toggle the TinyMCE wrapper and the default-message preview through all three states; and an override value round-trips through save + reload, then reverts to the disabled default state when unchecked and saved again. Full Playwright suite: 120 tests green (121 with the optional real-Cap round trip).
* Cleanup: removed code orphaned by earlier refactors, each verified unreferenced across the plugin, all Cimatti extension plugins and their themes before removal. PHP: the pre-2.0 "Forms submissions report" page pair `accua_forms_report_page()` / `accua_forms_report_page_head()` (unregistered from the menu long ago; queried the `cformssubmissions` table this plugin never creates), `accua_forms_filter_emails()` (last caller removed in 1.9.0), `accua_forms_single_submission_movetotrash()` (superseded by the GET-nonce trash flow with redirect), `_accua_forms_has_unsaved_draft()` (the unsaved-changes warning is client-side), and the theme-helper enqueue pair `accua_forms_theme_helper_page_styles()` / `_scripts()` (superseded by the settings page's own enqueue). Enqueues: `jquery-ui-selectable` (never used) and `jquery-ui-dialog` + `wp-jquery-ui-dialog` (only consumer was the dead token dialog) are no longer loaded on every admin page; the `jquery-ui-dialog` dependency was dropped from form-settings.js. JS: the token dialog block in form-settings.js (its `#dialog_token` / `.token_link` / `#accua_token` markup is rendered nowhere; the Tokens tab shows the token list inline) and the `#removing-widget` statements in form-fields.js (element from the core widgets screen that the form editor never had). CSS/markup: the always-hidden `.accua_form_save_settings_status` span and its rule, and admin.css rules for pre-2.1.0 Messages/Appearance markup (`.label_input`, `.label_container`, `.default_value`, `#dashboard_right_now`). Intentionally kept: the deprecated `accua_forms_recaptcha3_*` wrappers and `Validation/Captcha2.php` (documented BC), and the Turnstile element's `accua_form_submitted_{fid}` / `accua_form_error_{fid}` listeners (the events are never fired — tracked as a known gap: Turnstile is not reset after AJAX submissions). CSS version 197, JS version 125.
* Release housekeeping: 2.2.41 was never published to wordpress.org (the public version is still 2.2.32), so the consolidated readme.txt changelog entry created for 2.2.41 is renamed to 2.2.42 and now includes the Messages tab fix; the planned 2.2.41 release is superseded by 2.2.42.

= 2.2.41 =
* Plugin Check (PCP) 2.0.0 compliance pass on the release file set. Aligned the readme stable tag with the plugin version, and added justified phpcs annotations for two intentional patterns flagged by the checker: the explicit `suppress_filters` argument in the post-tree traversal helper `accua_forms_get_post_descendant_ids()` (get_posts()' own default, kept so multilingual filters cannot hide ancestors during structural traversal), and the dynamically built `IN ()` placeholder list in the submissions bulk spam action (the placeholder count is generated at runtime with `array_fill()`, which the static check cannot count). No functional changes.
* Public release housekeeping: readme.txt changelog consolidated into a single 2.2.41 entry covering all changes since 2.2.32, the previously published version; the detailed per-version history stays in this file.

= 2.2.40 =
* Feature: per-form spam action extended to reCAPTCHA v2, and the default for both v2 and v3 is now "Accept silently and mark as Spam". The "When the spam check fails" select now also appears on "Captcha (reCaptcha)" (v2) field instances: a submission whose verification fails can be accepted silently (normal success message, no notification emails) and marked with the Spam lead status, moved to Trash or deleted immediately, or rejected with a visible error so the visitor can retry the challenge — the pre-2.2.40 behavior, which existing fields keep only if "Reject with an error message" was saved explicitly. Failed checks now land in the submissions list Spam view (2.2.39) out of the box instead of bouncing visitors with an error.
* Implementation: new shared abstract `classes/Validation/CaptchaSpam.php` (`AccuaForm_Validation_CaptchaSpam`) holding the secret, the per-form flag key (`accuaform_{fid}`), the spam action and the request-scoped flag registry; `AccuaForm_Validation_Captcha3` and `AccuaForm_Validation_Captcha2b` now extend it, and every v2 failure path (missing/empty `g-recaptcha-response`, `WP_Error`, `success:false`) follows the configured action through the shared `failed()`. Generalized helpers `accua_forms_captcha_spam_action_options()` / `_spam_action()` (default `'spam'`) / `_form_spam_action()` (scans for `captcha` and `captcha_v3` refs); the `accua_forms_recaptcha3_*` names remain as deprecated wrappers. The submission handler checks the flag on the shared base class and fires the new `accua_forms_captcha_spam_submission` action plus the old `accua_forms_recaptcha3_spam_submission` name for backward compatibility. Validators serialized before the update (encrypted-form round trip) carry an empty spam action and keep rejecting, so cached pages don't change behavior mid-flight.
* Feature: "Hide field title" option on all captcha field instances (reCAPTCHA v2, reCAPTCHA v3, Cap), enabled by default — captcha fields usually need no visible heading, and the v3 field is entirely invisible. The title is not removed from the markup: the wrapper gets the `accua-captcha-title-hidden` class and the label is clipped to a screen-reader-only 1x1px box (CSS version 196), so assistive tech still announces it and the validation summary keeps resolving the field label for its error links (the summary would otherwise fall back to the humanized internal POST name, the exact bug fixed in 2.2.36). Stored as `hide_title` on the field instance, saved explicitly on every widget save; instances saved before 2.2.40 follow the hidden-by-default rule. Uncheck the option to show the title as before.
* i18n: two new strings and two reworded spam-action descriptions translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated).
* Verification: `php -l` clean on all touched files. E2E: `recaptcha3.spec.ts` grew to 26 tests (new forms 9842/9843; the dev-stub mu-plugin now also intercepts v2 siteverify for the neutral dev secret, so v2 success/failure verification paths run hermetically): a "v2 spam actions" describe covers the default (no `spam_action` saved → Spam), a posted-but-rejected token → Spam, trash, delete, a solved captcha with the silent default (stored normally, admin email delivered), and the non-AJAX POST path into Spam — each asserted against the database and Mailpit; a "hide captcha title" describe covers hidden-by-default on v2 and v3 (sr-only ≤1px box with the label still in the DOM), `hide_title = 0` showing the title, the inline-label layout (floating label clipped too), and the validation summary still resolving the sr-only label on a rejected captcha; the v3 describe gained a no-`spam_action`-saved → Spam default test; a helpers test asserts the resolution rules server-side (invalid values → `spam`, first captcha field wins, `hide_title` 0/1/missing); and an editor-save test drives the `accua-save-form-field` AJAX endpoint directly, asserting `hide_title` is stored explicitly (1 when checked, 0 when the checkbox is absent from the POST) and that a non-allowlisted `spam_action` value is not stored. `cap.spec.ts` asserts hidden-by-default and `hide_title = 0` for the Cap field. Reject-path forms pin `spam_action = 'reject'` explicitly. Full Playwright suite: 116 tests green (117 with the optional real-Cap round trip).

= 2.2.39 =
* Feature: spam management in the submissions list, modeled on the WordPress comments screen. Spam is the existing `-1` lead status (`afs_lead_status = -1`, orthogonal to the trash status), used both by the reCAPTCHA v3 silent "mark as Spam" action from 2.2.38 and by manual classification. The list now has an always-visible "Spam (n)" view (placed before Trash); the Active view and its count exclude spam, so spam-flagged submissions no longer clutter the default list. Row quick actions: "Spam" in normal views, "Not spam" / "Trash" / "Permanently delete" in the Spam view; matching bulk actions ("Mark as spam" / "Not spam"). "Not spam" restores the Undefined lead status (the previous lead status is not remembered — there is no history column). Trashed spam appears in the Trash view as before.
* Fix: submissions with the Spam lead status displayed as "Job Candidate" in the lead-status dropdown (list and single-submission pages), and the Spam quick-filter link above the list pointed to lead status 1. Root cause: `accua_forms_select_lead_status()` and the views builder passed lead statuses through `absint()`, collapsing `-1` onto `1`; both now cast with `(int)`. This made spam classification look unsupported even though the value was stored correctly.
* Improved: submissions list quick actions moved from the hover menu under the ID column to a dedicated always-visible "Actions" column (column key stays `singlesub`, preserving saved Screen Options preferences), now present in the Trash view too — "Open | Trash | Spam" (active), "Open | Not spam | Trash | Permanently delete" (spam), "Open | Restore | Permanently delete" (trash). Row-action labels use concise comments-screen wording via the "row action" translation context ("Trash" → it "Cestina", es "Papelera" — distinct from the "Trash"/"Cestino" view label). The core `fixed` table class is removed (`get_table_classes()` override), so column widths adapt to content instead of being evenly divided — noticeably better with the dynamic per-field columns.
* Feature: dashboard "Last 10 submissions" upgrades — sortable "Lead Status" column with the same AJAX quick-edit dropdown as the list (auto-flagged spam is visible at a glance), "Open | Trash | Spam / Not spam" quick actions (variable-width action last so rows stay visually aligned), vertically centered cells (`.widefat.accua-last-submissions td`, CSS version 195), and a "Go to:" `ul.subsubsub` row of quick links under the table (Active, each non-empty lead status, Spam, Trash — with counts) jumping to the corresponding submissions-list view.
* i18n: nine new strings translated in the bundled it_IT and es_ES catalogs (POT/PO/MO regenerated).
* Verification: manual E2E against seeded data (mark as spam → counts move Active→Spam, notice shown, dropdown shows Spam selected; not spam → restored to Undefined) and the Playwright submissions-list suite (8 tests) green after the actions-column change; E2E selectors are row-scoped href patterns and were unaffected.

= 2.2.38 =
* Feature: per-form spam action for reCAPTCHA v3. Since v3 classifies visitors silently with a score, showing an error to a low-score visitor is often undesirable: the new "When the spam check fails" setting on each "Captcha (reCAPTCHA v3)" field instance in the form editor now offers four choices. "Reject with an error message" (default, the pre-2.2.38 behavior and what existing forms keep doing), or three silent modes: "Accept silently and mark as Spam" (stored with the Spam lead status), "Accept silently and move to Trash" (stored trashed), and "Accept silently and delete immediately" (nothing stored, uploaded files removed via the existing `accua_forms_erase_submission()` erasure path). In all three silent modes the visitor sees the normal success message and confirmation flow, while the admin notification and autoreply emails are suppressed. All verification failures follow the configured action (low score, missing/expired token, action mismatch, failed siteverify call), so a silent form never blocks a real visitor whose browser could not fetch a token.
* Implementation: the setting is stored as `spam_action` on the captcha_v3 field instance (allowlisted in the widget save handler; helpers `accua_forms_recaptcha3_spam_action_options()` / `accua_forms_recaptcha3_spam_action()` / `accua_forms_recaptcha3_form_spam_action()` in `accua-forms.php`). The action travels to `AccuaForm_Validation_Captcha3` next to the secret and expected action (surviving the encrypted-form serialization round trip); on a failed check with a silent mode the validator returns valid and flags the reCAPTCHA action in a request-scoped static, read back in `accua_forms_form_submission_handler()` which sets `afs_lead_status = -1` (spam) or `afs_status = -1` (trash) at insert time, skips both `wp_mail()` calls, erases the stored rows in the delete mode, and fires the new `accua_forms_recaptcha3_spam_submission` action for extensions. No JS/CSS changes.
* Compatibility: fixed the PHP 8.4 "Implicitly marking parameter $properties as nullable is deprecated" warnings by converting the implicit nullable constructor signatures (`array $properties = null`) to the explicit `?array $properties = null` form in nine classes: `PFBC/OptionElement.php`, `PFBC/View/SideBySide.php`, `PFBC/View/Grid.php`, `PFBC/Element/YesNo.php`, `PFBC/Element/State.php`, `PFBC/Element/Country.php`, `classes/Element/Date.php`, `classes/Element/Captcha2.php` and `classes/Element/Captcha3.php`. No behavior change; the explicit form is required going forward because implicit nullable parameter types are deprecated since PHP 8.4 and scheduled for removal in PHP 9.
* Verification: `php -l` clean on all touched files; loading frontend pages containing reCAPTCHA v2, reCAPTCHA v3 and standard forms on PHP 8.4 appends no new deprecation entries to debug.log. The spam action is covered by 4 new E2E tests (`tests/e2e/frontend/recaptcha3.spec.ts`, form 9844): each silent mode is exercised with a low-score token and asserted against the database (Spam lead status / trashed / not stored) plus Mailpit (zero notification emails), and a good-score control asserts normal storage with the admin email delivered; the existing low-score-reject test covers the default. Full Playwright E2E suite: 102 tests pass.

= 2.2.36 =
* Feature: Google reCAPTCHA v3 support via the new `captcha_v3` field type, alongside the existing v2 checkbox field (`captcha`) which is unchanged. Field-based like Turnstile: drag the "Captcha (reCAPTCHA v3)" field into the forms that need it; existing forms are untouched. v3 uses its own key pair (configured in the new "reCAPTCHA v3 (invisible)" section of the reCaptcha settings postbox, stored as `recaptcha_v3_public_key` / `recaptcha_v3_private_key` in the existing `accua_forms_default_captcha_field_data` option via the backward-compatible defaults-union idiom). With no keys configured the field renders an HTML comment placeholder, same as the v2 no-keys behavior.
* Implementation: new `classes/Element/Captcha3.php` (renders a hidden `accua-forms-recaptcha3-response` input — deliberately distinct from v2's `g-recaptcha-response` so both field types can coexist on one page — plus a registration into the `accuaformRecaptcha3Queue` global, same gtag-style queue pattern as v2) and `classes/Validation/Captcha3.php` (server-side `siteverify` call checking `success`, that the returned `action` matches the per-form `accuaform_{fid}` action, and that `score` meets the threshold — default 0.5, tunable via the new `accua_forms_recaptcha3_score_threshold` filter; fail-closed on network errors like v2). The secret key and action live on the Validation object so they survive the encrypted-form serialization round trip (`Element::__sleep()` drops element properties).
* Frontend: new `assets/js/frontend/recaptcha3.js` (`recaptcha2.js` untouched). Google's `api.js` is loaded only on the visitor's first form interaction (same privacy stance as v2: no third-party request on page load) or on demand at submit time. Because v3 tokens are single-use and expire after ~2 minutes, they are fetched at submit time: on AJAX forms a gate in the generated submit handler (`AccuaForm.php`) cancels the submit, awaits `grecaptcha.execute()`, fills the hidden input and re-triggers; non-AJAX forms (which emit no submit JS at all) are covered by a delegated document-level submit handler in `recaptcha3.js` that resubmits natively after fetching the token. Used tokens are cleared after every AJAX response so retries fetch a fresh one; a failed `grecaptcha` still lets the submit proceed with an empty token (rejected server-side) instead of hanging or looping. If a v2 form on the same page already injected `api.js` in explicit-render mode, that same script is reused. The reCAPTCHA badge is shown (no hide option; hiding requires the ToS attribution text). DB version 15 (adds the `captcha_v3` field definition to existing installs), JS version 122.
* Fix: when the reCAPTCHA v2 server-side check failed, the frontend validation summary showed the humanized internal POST name ("Recaptcha Response Field") instead of the field's label, with a dead anchor link, and the inline error was silently dropped. Root cause: the v2 element renders only the widget container div (the widget POSTs `g-recaptcha-response`), so the error JS lookup by `[name="recaptcha_response_field"]` found nothing. The container div now carries a `data-accua-name` attribute with the element name and the error JS (`updateSummaryWithServerErrors()` in `AccuaForm.php` and both inline-error injectors in `classes/Error/Standard.php`) falls back to `[data-accua-name="..."]` when the name lookup is empty: the summary shows the field label, its link scrolls to the widget, and the inline error renders next to the widget. The `recaptcha_response_field` POST name is unchanged (backward-compatibility contract). Any future element that renders no named input can opt into the same mapping by setting `data-accua-name` on its wrapper.
* Verification: new E2E spec `tests/e2e/frontend/recaptcha3.spec.ts` (8 tests: lazy load privacy contract, good-score AJAX success, low-score rejection surfacing in the validation summary, grecaptcha-failure fail-closed path, non-AJAX POST path, empty-keys placeholder, a v2 lazy-load regression test, and a v2 server-error regression test asserting the summary shows the field label with a link anchored to the widget plus the inline error). Google publishes no official v3 test keys, so the client is stubbed (route-intercepted `api.js` / injected `grecaptcha`) and `siteverify` is stubbed by a dev-workspace mu-plugin keyed to a dev-only secret, with the score parsed from the token. Full suite: 91 tests pass.

= 2.2.35 =
* Fix: lost-update race in the form editor save flow (`assets/js/admin/form-settings.js`). The Save button dispatched the per-widget field saves (`accua-save-form-field`), the field-order save (`accua-form-fields-order`) and the settings save (`accua-save-form-settings`) as concurrent AJAX requests before publishing the draft. All of those handlers read-modify-write the same `accua_forms_draft_{fid}` transient, so two in-flight requests could each read the same draft snapshot and the later write discarded the earlier one's changes: a renamed title or a changed setting could silently revert while the button still showed the saved state. The requests now run strictly in sequence (each field widget -> field order -> settings -> `accua-publish-form-draft`) through a completion-callback chain, using the callbacks already exposed by `accuaWidgets.save()` and `accuaWidgets.saveOrder()`. JS version 121.
* Development environment: the private Playwright E2E suite was adapted to the new Docker workspace (target URL from Playwright `baseURL`, overridable via `CF_BASE_URL`; WP-CLI executed inside the WordPress container, overridable via `CF_WP_CLI`) and made self-sufficient: specs create the field definitions they depend on via the new `ensureAvailField()` helper (the `cv` file field, the `fav-post` post-select field, the `custom_required_message` on `last_name`) instead of relying on pre-existing rows in the development database, and clean them up afterwards.
* Verification: the full E2E suite (83 tests) passes against a freshly provisioned installation, including the three form-editor save-persistence tests that failed intermittently before the race fix.

= 2.2.34 =
* Cleanup: removed the dead reCAPTCHA v1 code (the v1 API was discontinued by Google in March 2018): `classes/Element/Captcha.php`, `classes/Validation/Captcha.php`, `PFBC/Element/Captcha.php`, `PFBC/Validation/Captcha.php`, `PFBC/Resources/recaptchalib.php` (Google's v1 PHP library) and `assets/js/frontend/recaptcha.js`. None of these were instantiated by the plugin: since the v1 shutdown the captcha render path emits an HTML comment for the v1 case. Also removed the leftovers: the `.accua_forms_show_recaptcha_button` click in the AJAX failure handler (`AccuaForm.php`), the `#recaptcha_table` rule in `assets/css/frontend.css`, the `instanceof AccuaForm_Element_Captcha` check in `classes/View/InlineLabel.php` and the unused "Show Captcha" translatable string (POT/PO/MO regenerated).
* Performance and decoupling: reCAPTCHA v2 loading rewritten. `classes/Element/Captcha2.php` now enqueues `assets/js/frontend/recaptcha2.js` via `wp_enqueue_script()` (footer, versioned, cacheable and minifiable by optimizers) and prints only the container div plus a one-line registration into the global `accuaformRecaptcha2Queue`, replacing roughly 35 lines of inline script per captcha field and two chained `jQuery.getScript()` calls (which bypass the browser cache with a timestamp parameter on every page view). `recaptcha2.js` drains the registration queue (correct regardless of whether the field markup is parsed before or after the file loads) and injects Google's `api.js` only on the visitor's first interaction with a form field, so no third-party request is made on page load, same as before.
* Backward compatibility: `accua_forms_show_recaptcha2()`, `accua_forms_onload_recaptcha2()`, `accua_forms_reload_recaptcha2()` and the `accuaform_recaptcha2_*` globals keep their names and signatures, so pages cached with the previous inline markup keep working against the new file (with a guard against injecting `api.js` twice in that scenario). The legacy `recaptcha_force_v1` option handling and the HTML comment placeholder rendered when no keys are configured are unchanged. The deprecated `AccuaForm_Validation_Captcha2` wrapper is kept (autoloaded only if third-party code instantiates it). The admin form preview prints the enqueued script through its existing `wp_print_footer_scripts()` call. JS version 120, CSS version 193.
* Verification: Playwright run against a page with two AJAX forms each containing a captcha field (Google test keys): zero Google requests on page load, exactly one `api.js` request after the first field interaction, both widgets rendered, captcha completed and the AJAX submit accepted by the server-side validation with no JS errors; with keys cleared the placeholder comment still renders without fatals. The form-submission E2E suite passes.

= 2.2.33 =
* Fix: race condition in the form editor live preview initialization (`assets/js/admin/form-settings.js`). The preview iframe `#accua_form_preview_area` is rendered server-side with its `src` already set, so on a fast response (warm cache, local server) its `load` event could fire before the jQuery ready handler attached the two `load` listeners. When that happened the auto-resize never ran (preview clipped at the 400px minimum height) and, worse, `previewReady` was never set, so `updateFullPreview()` returned early and all live style updates stayed dead until a layout reload. Both listeners are now extracted into named functions (`resizePreviewToContent()`, `handlePreviewLoaded()`) that also run immediately at init when the iframe is already loaded, detected via `contentDocument.readyState === 'complete'` with a non-empty body (the initial about:blank document also reports 'complete' but has an empty body). The `load` handlers remain in place, so preview reloads triggered by field edits and layout changes keep resizing as before.
* Fix: the auto-resize now sets the height on the preview wrapper (`#accua_form_preview_area_wrapper`) instead of the iframe when the jQuery UI resizable wrapper is active. Previously it wrote a pixel height directly on the iframe, overriding the `height: 100%` binding to the wrapper and detaching the iframe from manual wrapper resizing. When the resizable wrapper is not available, the iframe height is set directly as before. JS version 119.

= 2.2.32 =
* Security: the WordPress dashboard widget is now restricted to users with the `manage_options` capability, the same capability required by all plugin admin pages. Previously it was registered for anyone with `edit_posts` (editors, authors and contributors as well as administrators), exposing submission statistics to roles that cannot access the plugin. The render callback also returns silently when the capability check fails instead of calling `wp_die()`, which killed the whole dashboard page.
* Performance: the widget statistics (active forms, pages, submissions, distinct emails) are now computed by the new `accua_contact_forms_dashboard_get_stats()` helper and cached in the `accua_forms_dashboard_stats` transient for 5 minutes, so repeat dashboard loads run zero COUNT queries against the submissions tables (the distinct-emails COUNT scanned the whole `accua_forms_submissions_values` table on every dashboard load). A short TTL was chosen over invalidation hooks because submissions are written, trashed, restored and anonymized from several independent code paths. `get_plugin_data()` is now called with `$markup = false, $translate = false`, skipping markup filtering and header translation on each render.
* Coding conventions in the widget: `afsv_type LIKE 'autoreply_email'` replaced with `=` (no wildcard was involved), counts cast to int and displayed through `number_format_i18n()`, the Dashboard link built with `admin_url()` and escaped with `esc_url()` instead of a hardcoded relative `admin.php?page=...` URL, redundant untranslated `title` attribute removed, data fetching moved out of the table markup. The widget id (`accua_contact_forms_dashboard_widget_news`) and function names are unchanged so existing screen-option preferences and any `remove_action()` calls keep working.
* Code organization: the widget code (stats helper and render callback) moved from `contact-forms.php` to the new `admin/dashboard-widget.php`, included on demand from the `wp_dashboard_setup` callback after the capability check. Since `wp_dashboard_setup` only fires on the dashboard screen, the widget code is never loaded on the frontend, on other admin pages, or for users who cannot see the widget. Only the registration hook remains in the main plugin file.

= 2.2.31 =
* Fix: the sidebar admin menu icon no longer briefly flashes the brand blue (#15caff) before WordPress recolors it grey on load. `add_menu_page()` shipped the icon as a base64 SVG with the brand fill baked in; WordPress core `wp-admin/js/svg-painter.js` repaints admin-menu SVG icons to the active color scheme's icon color only after JavaScript runs, so the first server paint showed blue and the repaint caused a visible flash. The icon is now rendered server-side in the scheme's base icon color, so the first paint already matches svg-painter's result and there is no flash. Because color schemes register on `admin_init` (after `admin_menu`), the exact color is applied in an `admin_init` (priority 20) pass that rewrites the icon in the `$menu` global: `accua_forms_paint_menu_icon()` calls `accua_forms_admin_menu_icon()` / `accua_forms_admin_menu_icon_color()`, which resolve per active scheme (Default/modern -> #f3f1f1, Classic/fresh -> #a7aaad) and fall back to #a7aaad. The standalone brand icon (`assets/img/accua-contacts-forms.svg`) and the inline colored SVGs in the plugin's page headers are unchanged, so those contexts keep the colored logo. Also removed a stale rule in `assets/css/admin.css` that tried to force the menu icon to stay colored by targeting `.wp-menu-image img` (dead code: base64 SVG menu icons render as a `div` background-image, not an `img`). CSS version 192.

= 2.2.30 =
* Fix: restored the original frontend form DOM id format `accua-form___accua-form__{id}_{uniqid}`. Version 2.2.21 silently changed it to `accua-form_{id}_{uniqid}` (the internal `__accua-form__` prefix was stripped in `AccuaForm::__construct()` to match the then-new E2E selectors), breaking custom CSS/JS, analytics triggers and integrations that target the form element by id. The E2E selector helpers (`tests/helpers/selectors.ts`, now via a shared `formIdPrefix()`) were updated to the restored format instead. Anchor ids (`formSubmitSuccess-{id}` etc.) are unaffected: they are derived from the internal form id by `get_anchor_id()`, which strips the prefix independently of the DOM id.

= 2.2.29 =
* Fix: the reCAPTCHA v2 lazy-load trigger in `classes/Element/Captcha2.php` now uses a delegated `change` handler on `document` instead of binding directly to the elements matched when the first captcha's inline script executes during HTML parsing. With multiple forms on a page, the trigger script only runs once (guarded by `accuaform_recaptcha2_ajax_loaded`), so fields rendered after the first captcha (e.g. a second form in the footer) never got the handler: interacting with them did not load the reCAPTCHA API and the captcha stayed hidden until the first form was touched. The delegated binding also covers fields placed below the captcha within a single form and forms injected after page load.

= 2.2.28 =
* Feature: Multiple post checkboxes now honors a `post_type=` override in its "Additional query parameters" (validated against public post types), matching post-select; previously the render path set the query post_type unconditionally to the field dropdown, discarding the parameter. The value is admin-supplied (saved config, not a client request), so there is no tampering surface. The rest of the post-multicheckbox privacy model already matched post-select (server-rendered opt-in via `post_status=publish,private`, drafts never rendered, submit validated by the options allowlist) and needed no change.
* Tests: new E2E spec `tests/e2e/frontend/post-multicheckbox.spec.ts` covering private/draft hidden by default, private rendered+stored only with `post_status=publish,private`, the `post_type=` override, and forged-private-checkbox submit rejection.

= 2.2.27 =
* Feature: post-select / post-multicheckbox "Additional query parameters" now honor `post_status`, limited to publish/private via `accua_forms_filter_field_post_status()`. On the anonymous post-select AJAX endpoint (where extra_args is client-controlled), `post_status=private` is only honored when the received string matches a saved field configuration AND the request's resolved post type matches that configuration's post type (`accua_forms_extra_args_is_saved_config()`), or the user has `read_private_posts` (form editor preview). Binding the post type prevents lifting a private-enabled query string from one post type onto another. The selected-option prefetch, the PostSelect element render (`getAllowedPostStatuses()`), and submit-time validation accept the configured statuses accordingly. Draft/pending/future are never exposed.
* Fix: Post select / Multiple post checkboxes fields no longer drop child posts. `accua_get_pages()` applied `get_page_children()` to an already-paginated, publish-only result batch (because `hierarchical` defaults to 1), so any post whose parent was not in the same batch was silently removed: children of draft/private parents never appeared, searching for a child post returned nothing, `parent=`/`include=` filters could return empty lists, and AJAX "load more" pagination ended prematurely (`has_more` was computed on the filtered count).
* Fix: `child_of=` now returns all descendants (like core `get_pages()`) via the new `accua_forms_get_post_descendant_ids()` helper, instead of direct children only; `exclude_tree=` excludes the full subtree computed against the whole tree rather than the current batch. Both are resolved to explicit ID lists before querying so they compose correctly with pagination, search and `exclude=`.
* Fix: hierarchical (tree) ordering is now applied only when the result set is complete and title-sorted, and posts whose ancestors are unavailable are appended instead of dropped.
* Security: the `accua_forms_get_posts` AJAX endpoint (`selected=` prefetch) and the PostSelect element's selected-option rendering no longer disclose titles of draft/private/pending posts; both now require a publicly viewable post of the field's effective post type.
* Fix: post-select submitted values are validated against the field's configured post type (honoring the `post_type=` override in extra args, via `AccuaForm_Element_PostSelect::getEffectivePostType()`).
* Fix: post-multicheckbox `{__post_id_*}` / `{__post_url_*}` tokens used the whole value array instead of each item, producing "Array"/empty output.
* Fix: `AccuaForm_Element_PostSelect` lost its `post_type`/`extra_args` properties when the form instance was serialized into the submission transient (parent `OptionElement::__sleep()` whitelist), so submit-time validation compared against the defaults. Added a `__sleep()` override preserving the element's own properties.
* Fix: post-select.js dropped a search typed while the initial option load was still in flight (`isLoading` early return), leaving the unfiltered list; a stale slow response could also overwrite newer search results. Requests now carry a sequence token so the latest request always wins (JS version 118).

= 2.2.26 =
* Plugin Check (PCP) compliance fixes: `esc_sql()` applied to all `$wpdb->prefix` table name assignments in `includes/privacy.php`, with `phpcs:disable/enable` blocks for multi-line prepared queries; dashboard page `orderby`/`order` sort parameters sanitized with `wp_unslash()`/`sanitize_key()`, plus `phpcs:ignore` annotations for nonce-safe GET reads, an underscore-prefixed internal function name, and the block editor render variable.
* Removed stale build artifact zip files from the plugin directory.

= 2.2.25 =
* Fix: Translations no longer trigger a `_load_textdomain_just_in_time` notice on WordPress 6.7+. Form processing and the database-version check are now hooked to `init` (priorities 5 and 1 respectively) instead of `plugins_loaded`. This ensures translations are loaded after `after_setup_theme`, as WordPress requires since 6.7.
* Translations IT/ES: 49 previously untranslated strings added (lead statuses, border/title style options, form-editor labels, submission detail strings, notes UI, filter dropdowns).

= 2.2.24 =
* Fix: Trashing a submission from the single-submission page now redirects correctly. The previous helper function echoed HTML output before returning, causing `wp_safe_redirect()` to fail (headers already sent). The redirect path now calls the echo-free `accua_forms_trash_submission()` directly.

= 2.2.23 =
* Fix: Validation summary links now point to the field they reference (`href="#fieldId"` instead of `href="#"`), enabling native browser focus-on-click behaviour and fixing keyboard/screen-reader navigation.
* Fix: Email format inline error no longer appears twice when the field loses focus after a prior format error was already displayed. The blur handler now checks for an existing non-removing error element instead of the parent's CSS class, preventing a race condition with the required-blur handler.

= 2.2.22 =
* Fix: Non-AJAX form submission now sets the correct URL hash (#formSubmitSuccess-{id}) after page reload. Previously the form action defaulted to "#", causing the URL to show a bare "#". The form action is now set to the current page URL at construction time, and the post-submit DOMContentLoaded script always calls history.replaceState regardless of whether the anchor element exists.

= 2.2.21 =
* Fix: AJAX form submission no longer changes the page URL. The smooth-scroll helper previously called history.replaceState after each successful/invalid submit, updating the URL hash. This was unintended for AJAX mode where the page must not navigate.

= 2.2.20 =
* Admin: consolidated tab component into a single JS file; removed redundant script dependency.

= 2.2.19 =
* Translations IT: fixed "Submissions" noun → "Compilazioni" (was "Compilati"); updated "Forms submissions" and "Contact Forms - Submissions" accordingly.
* Translations ES: fixed missing accent "Envíos" (was "Envios"); unified "Unique submissions" / "Total submissions" / "Submissions from all forms" to use "envíos" consistently (was "presentaciones").

= 2.2.18 =
* PCP 2.0.0 compliance: raised minimum WordPress version to 5.9; fixed stable tag mismatch; added translators comments to printf calls; replaced parse_url() with wp_parse_url(); added phpcs:ignore for already-escaped helper output; removed redundant load_plugin_textdomain() call (WordPress auto-loads since 4.6).

= 2.2.17 =
* Fieldset group field: New "Border and Title" dropdown in the form editor with 6 style options: no border/no title, border only, border + inline legend, border + title above, border + title inside, no border + title inside.
* Fieldset group field: Group Title input is now always visible and appears first in the settings panel.
* Fieldset group field: Widget title bar displays the group title for both the Fieldset Begin and Fieldset End widgets; Fieldset End mirrors the preceding Fieldset Begin label.
* Frontend CSS: Fieldset border is now opt-in (removed unconditional `1px solid #ccc`). Existing fieldsets lose their border — users can re-enable it via the new dropdown. Resolves unexpected border appearing after upgrading from v1.9.x.
* New CSS helpers: `.accua-fieldset-border`, `.accua-fieldset-title-outer`, `.accua-fieldset-title-inner`.

= 2.2.16 =
* Dashboard: replaced ID column with an Actions column (Open / Move to Trash links); default sort changed to Submitted.
* Dashboard: Actions column is always non-wrapping; Move to Trash link is red; translations added (IT: Azioni / Sposta nel cestino, ES: Acciones / Mover a la papelera).
* Dashboard & Submissions list: URL columns (Page, Referrer) now use CSS-driven overflow detection instead of a fixed 80-character threshold. Only cells that truly overflow the rendered column width get the [+] expandable widget; short URLs are shown as plain links with no clutter.
* Submissions list: Referrer column no longer wraps for medium-length URLs.
* New JS: `assets/js/admin/expandable-cells.js` — measures natural link width via `Range.getBoundingClientRect()` and builds `<details>/<summary>` only when needed.

= 2.2.15 =
* Hidden field: the "CSS ID" setting now correctly sets the `id` attribute on the hidden `<input>` element itself (since hidden fields have no wrapper div).

= 2.2.14 =
* Frontend CSS: scoped `button:focus`, `input[type="checkbox"]:focus`, and `input[type="radio"]:focus` focus styles inside `.accua-form` to avoid polluting page-level button styles.

= 2.2.13 =
* **Date field**: Fixed validation error for custom date fields - now correctly extracts minDate/maxDate properties from form configuration.

= 2.2.12 =
* Submissions list: renamed "Review" column to "Open"; updated "Essential Columns" preset (ID, Open, Form, IP, Page, Referrer, Language, Submitted, Email).
* Submissions list: new users now see essential columns visible by default.
* Form editor: fixed widget Save button alignment (Cancella/Chiudi left, Salva right).

= 2.2.11 =
* Form editor: Added "Default Submit button" informational widget permanently at the bottom of the form area (non-draggable, non-sortable).
* Form editor: The widget auto-hides when a custom submit-type field is in the form, and reappears when removed.

= 2.2.10 =
* Single submission page: refactored to the WordPress-standard two-column postbox layout (`#poststuff`, `#post-body.columns-2`). Submitted fields and Details tables on one side; Submission, Lead status and Notes postboxes on the other. Form name shown as a link.
* Notes: author display name shown instead of email address.
* Removed toggletip, progress icons and custom submitbox styles; simplified `set-lead-status.js` and `single-submission.js`. CSS 185, JS 111.

= 2.2.9 =
* Submissions list: long page URLs and referrers collapse to a 2-line clamp with a `<details>` toggle that expands to the full clickable link; Page and Referrer columns are now clickable links.
* Dashboard last 10 submissions: same expandable pattern; column names aligned with the submissions list.
* Submissions list: export buttons moved below the description for better discoverability.
* Translation fix (IT): page title "Compilazioni" -> "Compilati".

= 2.2.8 =
* Single submission page: replaced the custom table with WordPress `widefat fixed striped` styling; human-readable field labels resolved from field definitions; internal structural fields hidden; line breaks preserved in text values; columns stack responsively at 782px.
* Dashboard last 10 submissions: rewritten with sortable columns, ID as primary column with row actions (View | Trash), and a single JOIN query replacing the previous N+1 pattern (30 queries per page load reduced to 1).
* Forms list: fixed the misaligned sort indicator on the Submissions column; removed stale inline CSS and float-based sorting hacks.
* Cleanup: removed dead `.accua_forms_trash` CSS and dead `del_sub_form` handler.

= 2.2.7 =
* **Refactor**: Extracted settings page into `admin/settings-page.php` (enqueue + render).
* **Refactor**: Extracted form list/add/edit pages into `admin/form-editor.php`.
* **Refactor**: Extracted privacy/GDPR/data-retention into `includes/privacy.php`.

= 2.2.6 =
* **Refactor**: Extracted fields page into `admin/fields-page.php` with separate JS file.
* **Refactor**: Moved submissions pages to `admin/` with separate JS files (`submissions-list.js`, `single-submission.js`).
* **Refactor**: Replaced all form editor inline `<script>` blocks with properly enqueued JS in `form-settings.js`.
* **Refactor**: Removed redundant color picker inline init (already handled by `form-settings.js`).
* **Refactor**: Removed dead Flot chart code from report page (library was removed in 2.1.0).
* **Cleanup**: Removed dead commented-out code throughout the codebase.

= 2.2.5 =
* **Fields page**: Replaced hand-crafted table with WordPress `WP_List_Table` (sortable columns, checkbox select, bulk delete).
* **Fields page**: Added "Forms" column showing how many forms use each field.
* **Fields page**: Client-side validation with accessible error notices and `wp.a11y.speak()` support.
* **Fields page**: Row actions (Edit | Delete) with nonce protection and confirmation dialog.

= 2.2.4 =
* **Form editor**: Appearance tab labels are now clickable (native `<label>` elements for accessibility).
* **Form editor**: Section headings use proper `<h2>` elements with consistent styling.
* **Form editor**: Consistent row heights for style options; color pickers hidden when row is unchecked.
* **Form editor**: Labels dropdown no longer overflows its container.
* **Form editor**: Responsive 2-column layout at viewports narrower than 1200px with preview below full-width.
* **Form editor**: Fixed flash of unstyled content (FOUC) on page load.
* **Form editor**: Added note under Labels dropdown explaining automatic responsive stacking at 500px.
* **i18n**: Italian and Spanish translations updated.

= 2.2.3 =
* **Form editor**: Added "+" button on each available field to add it to the form with a single click (appends as last field, scrolls into view, and opens settings).
* **Form editor**: Hidden the expand caret from available fields list (not needed there).
* **Form editor**: Long field names now wrap to multiple lines instead of being truncated with ellipsis. The "+" button and expand caret remain vertically centered and always clickable.
* **i18n**: Added Italian translations for new UI strings ("Campi disponibili", "Filtra campi…", "Aggiungi campo").

= 2.2.2 =
* **Form editor**: Added instant search filter in the Available Fields column — type to quickly find fields by name (substring match).

= 2.2.1 =
* **Form editor**: Redesigned Fields tab as a stable three-column CSS Grid layout (available fields, drop zone, live preview). The available fields column has its own scrollbar; the other columns use the main browser scroll. Fixes wrapping issues on high-zoom levels, small screens, and forms with many fields or long labels.
* **Form editor**: Each column now has its own heading ("Available Fields", "Drop fields here", "Preview") styled consistently and aligned at the same height.
* **Form editor**: Drop zone title moved outside the dashed border for clearer visual hierarchy.

= 2.2.0 =
* Tested up to WordPress 7.0.
* Added WordPress Playground blueprint (`blueprint.json`) for live preview on wordpress.org - creates five demo forms showcasing all field types: toplabel, sidebyside, inlinelabel layouts plus fieldsets, checkboxes, multi-select, post-select, HTML blocks, and hidden fields.
* **Email notifications**: Long URLs (e.g. Google Ads gclid parameters) no longer break print/PDF layout. Applied `table-layout: fixed` and `word-break: break-all` on URL placeholder links (`{__url}`, `{__review_submission_url}`, `{__referrer}`).
* **Email notifications**: Improved vertical spacing between submitted fields - label cells use `white-space: nowrap` with top-aligned padding, value cells get `overflow-wrap: break-word`.
* **Email notifications**: Fieldset labels now display correctly in the submitted data section instead of showing raw `__fieldset-begin-*` IDs.
* **Email notifications**: Fixed malformed `font-family` quotes in the `<body>` wrapper (now uses `&quot;` entities).
* **Single submission page**: Restructured from 3-column to 2-column (30%/70%) grid layout. Details and Lead Status postboxes are now stacked in the left column, submitted fields in the right.
* **Single submission page**: Renamed "Stats" postbox to "Details".
* **Single submission page**: Long URLs now wrap properly via `table-layout: fixed` and `word-break: break-word` on table cells.
* **Submissions list**: Added view links (Active, Trash, Lead Status) for quick filtering.
* **Submissions list**: Refactored filter controls (form, page, year, month, search) into `extra_tablenav()` using native `WP_List_Table` pattern.
* **Bugfix**: Fieldset (grouped fields) without a label no longer renders an empty `<legend>` element that caused a visual gap in the fieldset border.
* **Bugfix**: Essential Columns button - replaced individual checkbox AJAX calls with a single batch save, fixing a race condition that caused column settings to be lost.
* **Bugfix**: Fields page - fields stored in legacy format (`label` key instead of `name`/`id`) now display correctly, using the same fallback logic as front-end rendering.

= 2.1.4 =
* **Improvement**: Sidebyside (labels on left) layout now automatically switches to top labels when the form container is narrower than 500px. Uses CSS container queries to respond to the actual form width rather than viewport, so it works correctly across all themes, page builder columns, and sidebar widgets.

= 2.1.3 =
* **Bugfix**: Post select field (`post-select`) value was not saved to database and not included in notification emails. The submission handler relied on `$el->getOptions()` which returns empty for lazy-loaded AJAX fields. Fixed by resolving posts directly via `get_post()` with post type and publish status validation.

= 2.1.2 =
* **Form editor**: Added submission count with link to the submissions list, displayed inline with the page heading using the standard WordPress `page-title-action` pattern.
* **Form editor**: Title input now has proper spacing and sizing matching WordPress core post editor styling.
* **Form editor**: Google Ads and Tokens tabs now use WordPress postbox markup with `form-table` layout, consistent with Data Retention and Messages tabs.
* **Form editor**: Added unsaved changes warning - a `beforeunload` prompt prevents accidental navigation when the form has been modified.

= 2.1.0 =
* **Admin UI modernization** - Comprehensive redesign of the form editor and settings pages for a more coherent design language, aligned with native WordPress admin patterns.
* **Form editor**: Flat single-level tab bar replacing the previous nested two-level tab system. Six tabs: Fields, Appearance & General, Messages, Data Retention, Google Ads, Tokens.
* **Form editor**: CSS Grid-based layout for the field editor and live preview, replacing the old float-based 50/50 split.
* **Form editor**: Native WordPress title markup (`#titlewrap`) for the form title input.
* **Settings page**: Tab navigation with ARIA-compliant accessible tabs - Default Messages, Integrations, Privacy, Layout & Styling, Theme Helper, Danger Zone, Tokens.
* **Postbox structure**: All settings sections now use native WordPress `.postbox` + `.postbox-header` + `.inside` markup instead of custom wrappers.
* **Messages tab**: All four message postboxes displayed in a 2×2 grid layout. Radio buttons wrapped in `<fieldset>` with clickable `<label>` elements for better accessibility.
* **Messages tab**: TinyMCE editors now include a font family selector with 10 email-safe fonts (Arial, Arial Black, Comic Sans MS, Courier New, Georgia, Lucida Sans, Tahoma, Times New Roman, Trebuchet MS, Verdana). Link/unlink buttons added to the toolbar.
* **Default messages**: Default font changed from Lucida Sans to Arial for new forms. Removed overstylized wrappers (padding, background, border) from success and error message templates. Removed grey background from admin notification email template.
* **Default message preview**: Faithful rendering of email content - no longer strips inline styles.
* **CSS consolidation**: Merged `dashboard.css` into `admin.css`. Removed dead CSS selectors and legacy rules.
* **Bugfix**: Fixed broken CSS selectors caused by `accuaTabs` component renaming panel IDs - replaced `#accua_tab_*` selectors with `[data-tab="*"]` attribute selectors.
* **Bugfix**: Fixed Messages, Data Retention, and Tokens panels not properly contained within the tab system on the form editor page (unclosed `<div>` tags).

= 2.0.0-rc.3 =
* **Improvement**: Danger Zone moved to right column on Settings page, visible to administrators only
* **Improvement**: Bulk anonymize now shows a per-form preview with submission counts before confirming
* **Translation**: Complete Italian and Spanish translations (98% coverage)

= 2.0.0-rc.2 =
* **Bugfix**: Upgrade migration from 1.9.x - added data migration in `$old_db_version < 14` block: renames form `name` key to `title`, converts `fields` from CSV string to array, removes legacy `fieldnum` key.
* **Bugfix**: Submissions page - removed column header now uses translatable `%s (removed)` format instead of concatenated slug (e.g. "Email (removed)" instead of "emailrimosso").
* **Translation**: Updated Italian translations for removed column label.

= 2.0.0-rc.1 =
* Release candidate. See readme.txt for the full 2.0 feature summary.
* **PCP Compliance**: Fixed stable tag mismatch, added translators comments, replaced rmdir() with WP_Filesystem, added phpcs:ignore for shortcode output escaping.
* **Improvement**: Deactivation modal - anonymize now asks for confirmation before proceeding, "Just deactivate" button visually prominent.
* **Removed**: test-gdpr-deep.php from distribution.

= 2.0.0-beta.73 =
* **Bugfix**: Textdomain loading timing - moved `load_plugin_textdomain()` to `init` hook (was `plugins_loaded`), removed redundant call from `accua_form_init()` and `accua_forms_install()`. Fixes WordPress 6.7+ `_load_textdomain_just_in_time` notice.
* **Bugfix**: dbDelta `TEXT DEFAULT` warnings - removed `DEFAULT ''` from TEXT columns in CREATE TABLE statements. MySQL strict mode forbids default values on TEXT/BLOB columns.
* **Bugfix**: Deactivation "Delete all data" now properly cleans up - added `accua_forms_lastid` and `accua_form_api_keys` to deletion list, added transient guard to prevent `accua_forms_check_db_version_and_update()` from re-creating data during the deactivation redirect.
* **Bugfix**: Prevented double `accua_forms_install()` execution on activation (once from `plugins_loaded` version check + once from activation hook) via static guard.
* **Translation**: Added Italian translations for all Danger Zone and deactivation modal strings (25 new strings). Updated custom validation message strings.

= 2.0.0-beta.72 =
* **Bugfix**: File upload field - added `accept` attribute on the native `<input type="file">` so the browser file picker filters by allowed extensions (was only set on the JS wrapper via `data-accept`)
* **Bugfix**: File upload field - server-side extension validation is now case-insensitive (e.g. uploading `FILE.PDF` matches allowed extension `pdf`)
* **Improvement**: Extension override input now normalizes entries: strips leading dots and lowercases (`.PDF` → `pdf`)

= 2.0.0-beta.71 =
* **Improvement**: Data Retention tab in form editor - moved global default info to a description line below the override checkbox for clarity
* **Bugfix**: Danger Zone - fixed vertical alignment of the period dropdown (select) with adjacent input and button

= 2.0.0-beta.70 =
* **Feature**: Deactivation data cleanup modal on the Plugins page
 - Intercepts the "Deactivate" click and shows a modal with three options:
   - **Just deactivate** - keep all data, can reactivate later
   - **Anonymize all submissions** - replace personal data with placeholders, set IPs to 0.0.0.0
   - **Delete all data** - permanently remove forms, submissions, settings, and uploaded files (with extra confirmation)
 - Cancel button and overlay click to dismiss
* **Refactor**: Extracted `_accua_forms_delete_all_plugin_data()` helper to share deletion logic between Danger Zone and deactivation modal

= 2.0.0-beta.69 =
* **Feature**: Danger Zone section on settings page
 - Bulk anonymize submissions older than a configurable period (days/months/years) across all forms
 - Delete all Contact Forms data (settings, forms, submissions, uploaded files) with domain-name confirmation prompt
 - Both operations use AJAX with clear success/error feedback

= 2.0.0-beta.68 =
* **Bugfix**: Simplified IP anonymization - writes `0.0.0.0` directly instead of read-then-anonymize, eliminating an unnecessary DB query

= 2.0.0-beta.67 =
* **Feature**: All columns in the form list table are now sortable (From Email, From Name, Admin Email, BCC Email, Data Retention, Shortcode, PHP Code)
 - Retention column sorts by underlying seconds value for correct numeric ordering
 - Shortcode and PHP Code columns sort by form ID (since values are ID-derived)
 - Invalid orderby parameters safely fall back to ID sort

= 2.0.0-beta.66 =
* **Feature**: Per-form data retention tab in form editor - new "Data Retention" tab after Fields / Messages
 - Shows current global default summary
 - "Override default data retention" checkbox to enable per-form settings
 - Retention period (days/months/years) and mode (anonymize/delete) controls
* **Feature**: Data Retention column in form list - shows effective retention for each form
 - Displays per-form override or global default with human-readable format
* **Bugfix**: Fixed mixed-language dropdown in retention settings ("days, mesi, years" → properly translated)
* **Translations**: Italian translations for all data retention and GDPR privacy strings

= 2.0.0-beta.65 =
* **Bugfix**: Anonymized notes now show `[Anonymized]` instead of WordPress's generic `[deleted]` - consistent with field value anonymization

= 2.0.0-beta.64 =
* **Feature**: Submissions list table upgraded to full WP_List_Table native features
 - All columns sortable (SQL-level sorting with clickable headers)
 - Custom field columns sort via LEFT JOIN, with NULLs pushed to end regardless of direction
 - Row actions on ID column: View, Move to Trash / Restore, Permanently Delete (with confirmation dialog)
 - Per-page screen option: users can set items per page (default 100, saved per-user)
 - Removed fields separated into collapsible "Removed Fields" section in Screen Options
 - Primary column set to ID for WordPress responsive table support
 - Empty state message: "No submissions found."
* **Improvement**: Anonymized field values now show `[Anonymized]` instead of WordPress's generic `[deleted]` - clearer intent, properly translatable per-plugin
* **Translations**: Italian translations updated for all new strings

= 2.0.0-beta.63 =
* **Feature**: GDPR data anonymization and retention system
 - WordPress Privacy API integration: personal data exporter and eraser
 - Configurable data retention: global + per-form override (days/months/years), mode (anonymize or delete)
 - WP-Cron automated cleanup of expired submissions
 - Manual anonymization: single submission button (AJAX) + bulk action on submissions list
 - Privacy policy suggestion text via `wp_add_privacy_policy_content()`
 - Uses WordPress standard `wp_privacy_anonymize_data()` per field type
* **Architecture**: Anonymization stored as independent boolean (`afs_anonymized` column), orthogonal to trash status - anonymized submissions stay in active views and dashboard statistics
* **UI**: Single submission page redesigned to WordPress admin standards with 4-state action matrix (Anonymize/Trash/Restore depending on state)
* **CSS Version**: 148
* **DB Version**: 14

= 2.0.0-beta.62 =
* **Bugfix**: Fixed PHP 8.x "Undefined array key" warnings on the Submissions list page - added missing `isset` check for field `name` key in column headers, and added array bounds guard when parsing `_wp_http_referer` parameters

= 2.0.0-beta.61 =
* **Bugfix**: Sidebyside layout (labels on left) converted from flexbox to float-based layout for backward compatibility - custom JS using `.css('display', 'block')` to show hidden fields no longer breaks the label/field alignment
* **Bugfix**: Single checkboxes in sidebyside forms now left-aligned instead of indented at 25% margin - elements without a label div (`.pfbc-fieldwrap:first-child`) skip the label column offset
* **Bugfix**: Checkbox inputs (`.accuaform-fieldtype-checkbox`) now have consistent minimum size (`1rem`) and no left margin across all browsers and layouts
* **CSS Version**: 147

= 2.0.0-beta.60 =
* **Bugfix**: Fixed client-side validation treating "-" as empty in all field types - now only select dropdowns treat "-" and "Select..." as unselected; text inputs, textareas, and other fields accept "-" as a valid value
* **JS Version**: 93

= 2.0.0-beta.59 =
* **Updated Chart.js** from v3.5.0 to v4.5.1 - dashboard charts now use the latest Chart.js release
* **JS Version**: 92

= 2.0.0-beta.58 =
* **Custom validation messages**: Override default required and format validation messages at field definition and per-form level
 - Field-level overrides on the Fields page: set custom required message and custom format message for each field definition - applies everywhere that field is used
 - Per-form overrides in form editor: checkbox + text input to override at the individual form level (highest priority)
 - Priority chain: per-form instance → field definition → default i18n translation
 - Supports `%s` placeholder for the field name in custom messages
 - Custom messages bypass i18n translations - used as-is regardless of site language
 - Works on both client-side (inline errors, submit and blur) and server-side (AJAX) validation
 - Follows the draft system: per-form changes saved to draft until global Save button is clicked
* **Bugfix**: Phone field blur validation now uses custom format messages - previously only submit and server-side validation respected custom messages, blur showed the default translated message
* **Bugfix**: Phone field required error not shown after clearing a previously invalid value - error state was left inconsistent between phone format cleanup and required blur handler
* **JS Version**: 91

= 2.0.0-beta.57 =
* **Block editor support**: Added a native Gutenberg block for inserting contact forms
 - Search for "Contact Form" in the block inserter or type `/contact`
 - Select a form from the dropdown - live preview renders in the editor
 - Sidebar panel with form selector in "Form Settings"
 - No build tools required - works with plain JavaScript
 - Fully translatable - Italian translations included
* **Bugfix**: Fixed PHP warning "Undefined array key description" on the Fields admin page for fields without a description

= 2.0.0-beta.56 =
* **Insert Contact Form modal overhaul**: Rebuilt the classic editor "Insert Contact Form" TinyMCE button with a WordPress-native modal
 - Searchable form list with keyboard navigation, matching WP's own link dialog pattern
 - CSS/JS only loads on post editor screens (not every admin page)
 - All UI strings translatable via WPML/gettext - Italian translations included
 - Fixed Cancel/Insert button alignment with flexbox
 - Removed legacy iframe popup code and unused AJAX handler

= 2.0.0-beta.55 =
* **Dashboard: New "Monthly submissions by page" chart**: Added a second chart to the dashboard showing submissions and unique pages per month, with independent period control
* **Dashboard: Post type filtering**: Added content type filter to narrow dashboard statistics by post type (page, post, custom post types)
* **Dashboard: Improved content filter**: Page/content filter now shows post type labels, groups options by type, and dynamically filters based on selected content type
 - Moved page filter from the first chart area to the new second chart section
 - Post type and content dropdowns are linked: selecting a content type filters the content dropdown options

= 2.0.0-beta.54 =
* **Fix: File Download Rendered as Raw Text Instead of Downloading (improved)**: Improved the file download fix from beta.53 which was not fully effective on all server configurations
 - Added `header_remove()` to clear all pre-set HTTP headers (including `Content-Type: text/html` set by `admin-ajax.php`) before sending download headers
 - Added `nocache_headers()` to prevent browser/proxy caching of the download response
 - Changed `die('')` to `exit` to avoid extra bytes in the output stream

= 2.0.0-beta.53 =
* **Fix: File Download Rendered as Raw Text Instead of Downloading**: On servers with caching plugins, file attachments displayed raw binary in the browser instead of downloading - fixed by clearing output buffers before sending headers and using `FILEINFO_MIME_TYPE` instead of `FILEINFO_MIME` for clean MIME types
* **Improvement: Mask Password Values in Submissions List**: Password fields now show `••••` in the list table instead of the stored value

= 2.0.0-beta.52 =
* **Fix: AJAX Form Submission Stuck on "Sending" When Server Returns `submitted: false`**: The form permanently froze in loading state when the server did not recognize the submission (e.g. expired nonce from a cached page)
 - Added `else` branch for `response.submitted === false` in the AJAX response handler - shows error message, re-enables submit button, unlocks fields, and triggers fallback to direct POST after 3 failures
 - Fixed `postMessage` handler to accept responses where `buildID` is `null` (server rejection) as long as `jsuuid` matches, so error displays immediately instead of waiting for the polling timeout
 - Root cause on production: server-side page caching served stale WordPress nonces to non-logged-in visitors
* **Fix: Password-and-Confirm Field Ignored Label Override**: Used hardcoded "Password" label instead of user-configured custom label
* **Fix: Required Multiselect Validation Bypass**: Required `<select multiple>` fields were not flagged as invalid when empty - `[]` was incorrectly treated as truthy
* **JS Version**: 85

= 2.0.0-beta.51 =
* **Fix: Custom CSS Class/ID Not Rendered for Radio, Checkbox, Multicheckbox, Turnstile, and Captcha**: These field types were missing `$field_properties` in their constructor calls, causing custom CSS class and ID to be silently ignored
 - `accua-forms.php`: Added `$field_properties` to constructor calls for single checkbox, radio, multicheckbox/post-multicheckbox, turnstile, and captcha (v2)
* **Per-Option Custom ID and Class for Radio and Checkbox Fields**: Each individual radio/checkbox option now gets a unique ID and class derived from the field's custom CSS ID/class
 - Format: `{css_id}-option-N` / `{css_class}-option-N` (1-based)
 - `classes/Element/Radio.php`, `classes/Element/Checkbox.php`: Applied per-option ID and class to option wrapper divs

= 2.0.0-beta.50 =
* **Fix: Custom HTML Field CSS Class and ID Not Rendered**: Custom CSS class and ID set on Custom HTML fields were saved but never applied to the frontend HTML output
 - `Element/HTML.php`: Extended constructor to accept an optional `$properties` parameter for wrapper CSS class/ID
 - `accua-forms.php`: Pass `$field_properties` to `Element_HTML` constructor (same pattern as fieldset fix in beta.49)
* **Refresh Preview for Custom HTML Fields**: Added "Refresh Preview" link below the Custom HTML textarea in the form editor
 - Saves the field to draft and refreshes the preview iframe, allowing content changes to be previewed before publishing
* **Auto-Preview on Field Changes**: The form preview now updates automatically when any field option is changed in the editor
 - Checkboxes and selects trigger immediate preview refresh
 - Text inputs and textareas use an 800ms debounce delay to avoid excessive updates during typing
 - Uses silent save (saves to draft without replacing widget HTML) to preserve user's in-progress edits
* **JS Version**: 83

= 2.0.0-beta.49 =
* **Fix: Fieldset Custom CSS Class and ID Not Rendered**: Custom CSS class and ID set on fieldset (group) fields were saved but never applied to the HTML output
 - `accua-forms.php`: Pass `$field_properties` to `AccuaForm_Element_FieldsetBegin` constructor so `wrapperCssClass` and `wrapperCssId` are set on the element
 - `FieldsetBegin.php`: Merge wrapper CSS class into the `class` attribute and override `id` with wrapper CSS ID before rendering, so they appear directly on the `<fieldset>` tag

= 2.0.0-beta.48 =
* **Fix: Duplicate Validation Errors**: Fixed PFBC Element validation accumulating errors across calls instead of resetting them
 - `Element::isValid()` now clears the errors array before running validators, preventing duplicate messages
 - Removed redundant `Validation_Email` that was added both in the Email element constructor and in the form builder switch case
* **Extension Hooks for External Field Types**: Added five hooks for external plugins to register custom field types without modifying Contact Forms core
 - `accua_forms_field_types` (filter): Register custom field types in the editor dropdown
 - `accua_forms_render_field_element` (filter): Return custom Element objects for rendering
 - `accua_forms_field_settings` (action): Render extra settings HTML in field editor
 - `accua_forms_save_field_data` (filter): Modify field instance data on save
 - `accua_forms_enqueue_scripts` (action): Enqueue frontend JS/CSS when a form is rendered

= 2.0.0-beta.47 =
* **Fix: Prefix-Only Telephone Values Treated as Empty**: Values containing only a country prefix (e.g. "+39") are now consistently treated as empty across all validation layers
 - Required telephone fields with only a prefix now correctly show the "required field" error instead of silently submitting
 - Optional telephone fields with only a prefix submit without errors (treated as blank)
 - Client-side: Added `isTelephonePrefixOnly()` helper in `AccuaForm.php` used by both submit and blur required checks
 - Server-side: `Telephone.php` overrides `isValid()` to normalize prefix-only values to empty before PFBC validators run, so `Required` correctly rejects them
 - Prefix-only threshold: ≤4 digits starting with `+` (covers all international dialing codes)
* **JS Version**: 81

= 2.0.0-beta.46 =
* **Fix: Blurry text on Chromium browsers**: Removed unnecessary `will-change` CSS property from inline error messages that caused GPU-based text rendering, losing subpixel antialiasing. No visual or accessibility regressions - animation and content-flash prevention are handled by the keyframes themselves.
* **CSS Version**: 143

= 2.0.0-beta.45 =
* **Custom CSS Class & ID for Field Wrappers**: Added per-field CSS Class and CSS ID settings in the form editor
 - Each field now has "CSS Class" and "CSS ID" inputs in its settings panel
 - CSS Class supports multiple space-separated classes
 - CSS ID adds a unique HTML `id` attribute to the field wrapper `div.pfbc-element`
 - Available for all field types (except fieldset-end)
 - Works across all three form layouts: standard, side-by-side, and inline-label
 - Values are sanitized with `sanitize_html_class()` and escaped with `esc_attr()` on output
 - Follows the draft system: values are saved to draft until the global Save button is clicked
 - Fully translatable labels and help text (Italian translation included)
* **JS Version**: 78

= 2.0.0-beta.44 =
* **Removed all unnecessary `!important` from frontend CSS**: Improves theme compatibility and prevents forced styling conflicts (e.g., transparent backgrounds on select dropdowns overriding theme styles)
 - Removed 35 `!important` declarations; only 10 remain (visually-hidden file input, required for accessibility)
 - Fixed PHP inline styles: changed `background` shorthand to `background-color` to avoid resetting SVG caret on select elements
 - Removed forced `transparent` default for field backgrounds when no color is configured
 - All form inputs now use `background-color: inherit` for consistent appearance across any theme background
 - Increased CSS selector specificity for color picker fields instead of relying on `!important`
 - Removed PFBC jQuery `outerWidth()` calls that injected inline `style="width: Xpx"` on textboxes and textareas, conflicting with CSS `width: 100%`
* **CSS Version**: 142

= 2.0.0-beta.43 =
* **Restored URL Hash on Form Submission**: URL now updates with `#formSubmitSuccess-{formID}`, `#formSubmitInvalid-{formID}`, or `#formSubmitError-{formID}` after form submission (feature originally from v1.4.10, lost during v2.0.0 rewrite)
 - Uses `history.replaceState()` for clean URL update without page jump or browser history pollution
 - Works with smooth scrolling introduced in v2.0.0-beta.18 (no regression)
 - Covers all submission paths: client-side validation failure, AJAX success, AJAX server-side invalid, AJAX error
 - Non-AJAX fallback: inline script scrolls to result anchor and sets hash on page load
 - GA/gtag tracking unaffected (event-based, does not read URL fragments)
 - Bonus: GA4 now automatically captures `page_location` with hash in all subsequent events, enabling URL-based goal/funnel tracking
* **JS Version**: 77

= 2.0.0-beta.41 =
* **Preview Layout Fix**: Fixed form preview flashing wrong layout in form editor
 - Problem: Preview appeared correct for a moment, then switched to wrong layout (sidebyside) regardless of actual setting
 - Root Cause: `updatePreviewLayout()` ran after each iframe load and hardcoded `sidebyside` as fallback when dropdown was set to "default", ignoring the actual global default
 - Solution: Removed client-side layout class toggling from `updateFullPreview()` - the server already renders the correct layout; layout changes use `reloadPreviewWithLayout()` which rebuilds the iframe
 - Also fixed: Save handler now preserves layout in preview reload

= 2.0.0-beta.27 =
* **Critical Bug Fix**: AJAX form submission now correctly returns success/error messages
 - Problem: Messages were captured BEFORE email sending completed, resulting in empty response messages
 - Solution: Moved `getSubmittedMessages()` to AFTER `isValid()` and `wp_save()` calls
 - Impact: Error message "Oops! Something went wrong" now displays when `wp_mail()` fails; success message displays on success
* **Project Cleanup**: Moved documentation files to `docs/` subfolder
* **Git Hygiene**: Added `.gitignore` to exclude zip files and local-only tools

= 2.0.0-beta.25 =
* **Accessible Loading State**: Replaced hidden summary with visible loading state during AJAX submission
 - Shows spinner + text "Submitting your form, please wait..." during form submission
 - Uses `role="status"` with `aria-live="polite"` for screen reader announcement
 - `aria-busy="true"` indicates ongoing operation
 - Smooth CSS transitions (0.25s ease-out) between all states (loading → success/error)
 - Respects `prefers-reduced-motion` user preference (disables animations)
 - Removed old standalone throbbler spinner (now integrated in summary)
* **New CSS Class**: `.pfbc-validation-loading` with blue/neutral color scheme
* **CSS/JS Versions**: CSS 103, JS 45

= 2.0.0-beta.24 =
* **UX Fix**: Validation summary no longer shows premature "success" message during AJAX submission
 - Summary is hidden during server validation, only shows result after server response
 - Prevents confusing flash of "All fields correct" before server-side validation completes
* **JS Version**: 44

= 2.0.0-beta.23 =
* **Phone Validation E.164 Compliance**: Updated to follow ITU-T E.164 standard
 - Maximum 15 digits (E.164 compliant)
 - No minimum digit requirement (flexible for all countries)
 - Lenient formatting: spaces, dashes, dots, slashes, parentheses, plus sign
 - Rejects invalid characters (letters, special symbols like #, @, etc.)
* **Bug Fix**: Phone validation now correctly rejects text characters
 - Fixed: `+39 dasdsad` was incorrectly accepted as valid
 - Character validation now runs BEFORE digit count bypass
* **JS Version**: 43

= 2.0.0-beta.22 =
* **Phone Field A11y Improvement**: Placeholder-only approach (no prefilled default value)
 - Removed default value `+39 `, using placeholder only (better for screen readers)
* **Phone Validation Made More Lenient**: No minimum digit requirement
* **Frontend Performance Optimization**: Removed unused libphonenumber-min.js (171KB saved)
* **Simplified phone-validation.js**: Reduced from 166 lines to ~100 lines
* **JS Version**: 42

= 2.0.0-beta.21 =
* **Phone Validation Fix for Optional Fields**: Fixed validation failing when only country prefix entered
 - Treat values with ≤4 digits as "empty" (user hasn't entered actual number beyond prefix)
* **W3 Total Cache Compatibility**: Verified working with Page Cache, Minify, and Browser Cache
* **JS Version**: 41

= 2.0.0-beta.20 =
* **Critical Bug Fix**: Form no longer disappears after correcting validation errors
 - Fixed jsuuid transient caching issue in `AccuaForm.php`
* **Dead Code Cleanup**: Removed debug statements and commented-out legacy code

= 2.0.0-beta.19 =
* **i18n:** Fixed reCAPTCHA typo (was "reCATPCHA") in 5 validation files
* **i18n:** Fixed double space and "re-try" → "retry" in CAPTCHA error messages
* **i18n:** Replaced non-standard "identificative" with proper English "identifier" in form/field validation
* **i18n:** Fixed "it must contains" → "must contain" grammar error
* **i18n:** Improved "identificator" → "unique identifier" and "unchangeable" → "cannot be changed"
* **i18n:** Fixed "hyphen and underscores" → "hyphens, and underscores" (Oxford comma)
* **i18n:** Improved duplicate field error message clarity

= 2.0.0-beta.18 =
* **Fix:** Success messages now display correctly (was sometimes empty)
* **Fix:** Form submission no longer jumps around - smooth scrolling to messages
* **Fix:** Multiple forms on same page now handle messages independently
* **Fix:** Shows error message when email sending fails (wp_mail returns false)
* **UX:** Added CSS animation for success/error messages with reduced-motion support
* **Technical:** Changed static $submittedMessages to per-form array keyed by formId
* **Technical:** Added all three anchor elements (#formSubmitSuccess, #formSubmitInvalid, #formSubmitError)
* **Technical:** Updated CSS version 102, JS version 38

= 2.0.0-beta.17 =
* **Security:** Fixed null byte injection causing 500 server error on form submission
* **PHP 8:** Sanitize null bytes from user input to prevent mail() ValueError
* **Technical:** Added str_replace("\0", '', ...) sanitization during form value processing

= 2.0.0-beta.16 =
* **Debug:** Removed all debug statements (alert, console.log, error_log) for production release
* **Fix:** Server-side validation errors now properly update the validation summary area
* **UX:** Turnstile validation message changed to "Please verify you are not a robot."

= 2.0.0-beta.11 =
* **Accessibility:** Enhanced validation summary with clickable field links for easy navigation
* **UX:** Error summary now shows "Verifica i seguenti campi per continuare:" with list of invalid fields
* **UX:** Each field name in summary is a clickable link that scrolls to and focuses the field
* **UX:** Smooth scroll animation with focus after 500ms delay for better user experience
* **UX:** Green success state shows "Tutti i campi sono corretti. Pronto per l'invio!" when all fields valid
* **UX:** Summary only appears after first submit attempt (not on page load)
* **Typography:** Consistent font sizes - 0.9375rem header, 0.875rem list items
* **Translation:** Updated Italian and Spanish translations with new summary strings
* **Technical:** New JS functions: scrollToFieldAndFocus(), updateSummaryArea(), fieldErrorsList array
* **Technical:** New CSS classes: .pfbc-validation-summary, .pfbc-validation-error, .pfbc-validation-success
* **Technical:** Updated CSS version 95, JS version 36 for cache busting

= 2.0.0-beta.10 =
* **Feature:** Modern default file extensions for file upload fields
* **Feature:** Added docx, xlsx, pptx, odt, ods, odp, csv, gif, webp, svg, heic, rar, 7z, tar extensions
* **Removed:** Obsolete bz, bz2 archive formats from defaults

= 2.0.0-beta.9 =
* **UX:** Added prominent visible error message for unsupported file formats in drag & drop upload
* **UX:** Red error box with warning icon shows "⚠ filename: File type not allowed"
* **UX:** Dropzone border turns red on error, clears automatically when valid file uploaded
* **Design:** Neutral design update - removed border-radius from dropzone and file list items
* **Technical:** Updated CSS version 94, JS version 34 for cache busting

= 2.0.0-beta.8 =
* **NEW:** Accessible drag & drop file upload with modern UI
* **Accessibility:** Full WCAG 2.2 AA / European Accessibility Act compliance
* **Accessibility:** Keyboard navigation (Enter/Space to open file picker)
* **Accessibility:** ARIA attributes and screen reader announcements
* **UX:** Dashed border dropzone with folder emoji icon
* **UX:** File list with filename, size, and remove button
* **Technical:** Progressive enhancement with DataTransfer API

= 2.0.0-beta.7 =
* **UX:** Comprehensive CSS refinements for floating label form view
* **UX:** Balanced padding for proper vertical text centering
* **UX:** Custom SVG caret for dropdowns with cross-browser consistency
* **Accessibility:** Added font-family: inherit to date inputs

= 2.0.0-beta.6 =
* **Feature:** Added "Restore to Default" buttons for form messages 1-4 in Settings page
* **UX:** Restore buttons use native WordPress link styling for consistent admin UI
* **AJAX:** New `accua_forms_restore_default_message` endpoint for restoring default values
* **Technical:** Refactored default form data into reusable `accua_forms_get_default_form_data()` function
* **Technical:** Updated CSS version 73, JS version 30 for cache busting

= 2.0.0-beta.5 =
* **UX:** Save button now shows "Saving..." text during save operation instead of empty button
* **UX:** Removed redundant bottom "Salva le impostazioni" save button from form editor
* **PHP 8:** Fixed "Unsupported operand types: null + array" error when creating new forms
* **Translation:** Added "Saving..." string to translation files (.pot regenerated)
* **Technical:** Updated CSS version 69, JS version 25 for cache busting

= 2.0.0-beta.4 =
* **Accessibility:** Replaced jqColorPicker library with WordPress native wp-color-picker (Iris)
* **Accessibility:** Color picker now fully WCAG 2.2 AA compliant with keyboard navigation and screen reader support
* **Accessibility:** Full European Accessibility Act compliance for color selection fields
* **UX:** Fixed dropdown caret positioning - added proper right padding (2.5em) for visual clarity
* **UX:** WordPress color picker provides familiar interface for administrators and better mobile/touch support
* **Performance:** Removed external jqColorPicker dependency - uses WordPress core functionality
* **Technical:** Color picker callbacks ensure proper hex color format with # prefix
* **Technical:** Updated ColorPicker.php element class to use wpColorPicker() method
* **Technical:** Updated admin script enqueues to use wp-color-picker instead of jqColorPicker
* **Developer:** WordPress Iris color picker supports RTL languages and theme customization

= 2.0.0-beta.3 =
* **NEW:** Material Design "Inline Labels" layout option for forms
* **Accessibility:** Floating labels follow WCAG 2.2 Material Design guidelines
* **Accessibility:** Proper vertical centering and font sizing for readability
* **UX:** Consistent border styling across all field types (text inputs, textareas, dropdowns)
* **UX:** Firefox-compatible date field placeholder behavior (hides dd/mm/yyyy when empty)
* **UX:** Symmetric padding for better dropdown caret centering
* **Technical:** JavaScript date input value detection with .has-value class
* **Technical:** CSS fallbacks for cross-browser compatibility (WebKit + Firefox)
* **Translation:** Updated Italian and Spanish translation files
* Fixed: Border weight consistency - all fields now use 1px solid borders
* Fixed: Label vertical centering using CSS transform translateY(-50%)
* Fixed: Font size increased to 1rem for better readability

= 2.0.0-beta.2 =
* Fixed: Error message positioning on mandatory file upload fields
* Fixed: Error messages now appear after help text instead of overlapping it
* Improved: Correct visual order - Browse button → Help text → Error message
* Improved: Reduced excessive padding in file upload fields (1.2rem → 1rem)
* Technical: Updated client-side validation in AccuaForm.php (4 locations)
* Technical: Updated AJAX validation in Error/Standard.php (2 locations)

= 2.0.0-beta.1 =
* **NEW:** Cloudflare Turnstile Captcha field support (via Simple Cloudflare Turnstile plugin v1.35.0+)
* **MAJOR:** Complete file reorganization following WordPress plugin development best practices
* **MAJOR:** Comprehensive WCAG 2.2 and WAI-ARIA 1.2 accessibility improvements
* **Accessibility:** Added ARIA attributes (aria-required, aria-invalid, aria-describedby)
* **Accessibility:** Implemented real-time validation on blur/change events
* **Accessibility:** Auto-focus on first invalid field after form submission
* **Accessibility:** Inline error messages with role=alert and aria-live=polite
* **Accessibility:** Enhanced radio and checkbox field accessibility and error handling
* **Accessibility:** Added HTML5 required attributes for dual compliance
* **Accessibility:** Added accessible required indicators with aria-label
* **Accessibility:** Fixed duplicate id='dashboard_right_now' (13 instances removed)
* **UX:** Modernized error styling with clean compact design
* **UX:** Improved validation messages - clearer, more direct, field-specific
* **UX:** Removed unnecessary 'Attention:' and 'Error:' prefixes from messages
* **Translation:** Proper textdomain loading on plugins_loaded hook (WordPress best practice)
* **Translation:** Complete Italian translation with all new error messages
* **Translation:** Complete Spanish translation with all new error messages
* **Translation:** Removed incomplete German translation
* **Translation:** Generated new .pot template file
* **Files:** Moved all CSS files to `/assets/css/` with descriptive names (admin.css, dashboard.css, frontend.css)
* **Files:** Moved all JavaScript files to `/assets/js/admin/` and `/assets/js/frontend/`
* **Files:** Moved vendor libraries to `/assets/vendor/` (jqColorPicker, Chart.js, dragtable)
* **Files:** Moved all images to `/assets/img/`
* **Files:** Updated all asset references across 13 PHP files
* **Files:** Removed deprecated Flot charting library (27 files)
* **Files:** Added documentation (REORGANIZATION-2025.md, ASSET-LOCATION-REFERENCE.md)
* **Technical:** Updated asset version constants for cache busting (CSS v54, JS v15.0)
* **Technical:** Added PHP 7.4 minimum requirement
* **Technical:** CSS content fix to avoid charset declaration issues
* Fixed: Drag-and-drop functionality in form builder after reorganization
* Backward compatible with existing forms

= 1.9.14 =
* Added Cloudflare Turnstile field integration
* New field-based Turnstile implementation (drag-and-drop control)
* Works standalone or enhanced with Simple Cloudflare Turnstile plugin
* Backward compatible with existing forms

= 1.9.13 =
* Improved hostname detection for AJAX requests

= 1.9.12 =
* Removed "WordPress" from plugin name to comply with WordPress naming guidelines

= 1.9.11 =
* Improved dashboard widget access control for better security
* Enhanced permission checks and capability handling

= 1.9.10 =
* Tiny improvements for SEO and accessibility.

= 1.9.9 =
* Security improvements.
* Fixed several deprecation warnings when WP_DEBUG is enabled.

= 1.9.8 =
* Added support for Google Ads.
* To set a GADS conversion tracking code, go to Form Name > Appearance and General > fill the dedicated field with a GADS Conversion Code (es: AW-123456789/aaBBccDD)


= 1.9.7 =
* Enhance SQL query security following WordPress Best Practices
* Resolved path handling issues that prevented file uploads in Windows-based development environments like LocalWP
* Enhanced file upload security with proper MIME type validation
* Added download links for uploaded files on individual submission pages (previously, these links were only available on the list page)

= 1.9.6 =
* Fixed an issue that could cause PHP sessions to remain open, triggering WordPress Site Health warnings

= 1.9.5 =
* Minor fixes to previous version fix

= 1.9.4 =
* Fixed vulnerability - Missing Authorization to Unauthenticated Form Submission Download

= 1.9.3 =
* Matomo support for tracking field filled in and form submissions as events
* Fixed CSRF vulnerability

= 1.9.2 =
* Reverted an incorrect fix in the previous version

= 1.9.1 =
* Hardening for potential SQL injection vulnerabilities

= 1.9.0 =
* Checks for unfiltered_html capability and supports limited admin permissions in WordPress multisite configurations
* Filters the list of allowed upload file extensions according get_allowed_mime_types(), and check the maximum upload size with wp_max_upload_size()
* Default date fix
* Other minor fixes

= 1.8.0 =
* Using tinyColorPicker on frontend
* Fixed XSS vulnerability

= 1.7.0 =
* Lead status
* Minor fixes

= 1.6.1 =
* Fixed trashed form restore
* Fixed CSRF vulnerability

= 1.6.0 =
* IP masking option

= 1.5.10 =
* Fixed plugin icon

= 1.5.9 =
* Fixed adding more custom HTML fields and fieldsets

= 1.5.8 =
* Fixed CSRF vulnerabilities
* Fixed some JavaScript errors due to WordPress filters
* Fixed many minor bugs

= 1.5.7 =
* tested compatibility with WordPress 6.2.0

= 1.5.6 =
* Fixed CSRF vulnerability

= 1.5.5 =
* Fixed XSS vulnerabilities

= 1.5.4 =
* Graphical changes to default messages
* Fixed some minor bugs

= 1.5.3 =
* Removed unused code
* Fixed some minor issues

= 1.5.2 =
* Fixed critical error when trying to access the dashboard

= 1.5.1 =
* Renewed UI
* Fixing some minor bugs

= 1.4.14 =
* Lazy load reCAPTCHA

= 1.4.13 =
* added html attributes to improve SEO

= 1.4.12 =
* Tested compatibility with PHP up to 8.0 and WordPress up to 5.8.1
* reCAPTCHA loaded from recaptcha.net trying to improve accessibility from countries banning google.com domain
* Fixed minor XSS vulnerability reported on wpscan.com by Felipe Restrepo Rodriguez and Sebastian Cruz Cardona. Form title was not sanitized in every place it was used in the admin interface, however this is mitigated by the fact that only admin users with manage_options capability can edit it.

= 1.4.11 =
* avoid "headers already sent" warnings during WordPress cron

= 1.4.10 =
* Tested compatibility with PHP up to 7.4
* After submission, the fragment #formSubmitSuccess-formID is added to the URL, so the page is scrolled to the top of the message, and it's easier to track the submission with tools like Google Analytics
* fragments #formSubmitInvalid-formID and #formSubmitError-formID are added in case of invalid submission or error
* improved loading of reCAPTCHA
* removed unused resources from PFBC library
* colorPicker styles and javascripts now loads only if it's used
* other small bugfixes

= 1.4.9 =
* improved compatibility with Google Tag Manager to track field filled in and form submission

= 1.4.8 =
* fixed compatibility issues with php 7.2
* option to track field filled in and form submission as events on Google Analytics
* workaround to open/download attachments in submissions exported and opened with Microsoft Excel

= 1.4.7 =
* fixed compatibility issue with WordPress 4.8 that made show/hide buttons for field settings in the form editor invisible
* fixed compatibility issues with php 7.0 and 7.1
* fixed strict standards errors

= 1.4.6 =
* restored compatibility with PHP < 5.3
* fixed some strict standards errors

= 1.4.5 =
* Scaled reCAPTCHA 2 area for devices with less than 400px screen width
* More informations about senders and receivers of the form emails in the forms list page

= 1.4.4 =
* Changed text domain of translatable strings to match the plugin slug
* Bulk action to delete permanently trashed submissions

= 1.4.3 =
* Fixed table index length issue that prevented saving submission values of new user of Contact Forms with recent WordPress versions
* Added internationalization info

= 1.4.2 =
* Fixed table definition error that prevented saving submission of new users of Contact Forms 1.4.0

= 1.4.1 =
* Fixed undefined variable in accua-form-api.php on line 29

= 1.4.0 =
* Filter and export by year and month
* Added actions accua_forms_field_added, accua_forms_field_updated and accua_forms_field_deleted

= 1.3.9 =
* better support for reCAPTCHA allowing to enter site keys and use version 2
* fixed visualization bug of reCAPTCHA 1 with new WordPress themes
* fixed bug that prevented editing fields on the page after a form submission

= 1.3.8 =
* fixed incompatibility with WordPress 4.4 that prevented submissions export

= 1.3.7 =
* fixed incompatibility with WordPress 4.4 that caused a PHP error in every page that includes a form
* new token for select, checkbox and radio labels
* changed database table to allow referrers and urls longer than 255 characters 

= 1.3.6 =
* Replaced deprecated user level '10' with capability 'manage_options'

= 1.3.5 =
* fixed incompatibility of form editor with WordPress 4.3 and Chrome
* adding rules to robots.txt to allow /wp-admin/js/ and /wp-admin/css/ for styles and scripts included from that folders

= 1.3.4 =
* Password fields now saves the hash value of the password using wp_hash_password
* Field to set the emails "From:" name
* fixed CAPTCHA field incompatibility with CloudFlare RocketLoader and possibly other JavaScript optimizer
* fixed glitch in the "Form fields" area on the "Edit form" page with latest versions of WordPress

= 1.3.3 =
* improved checkboxes, select and radio definition to allow pre-selected options
* fixed PHP 5.5 incompatibility issue. Now the plugin works with PHP from version 5.2 to 5.5
* fixed default value for email, colorpicker and password fields
* allowed removal of elements by a filter after the form generation

= 1.3.2 =
* fixed visualization of color picker field
* workaround to have multiple forms with recaptcha on the same page

= 1.3.1 =
* fixed validation of required fields with multiple values
* show recipient of admin email in form list
* changes in submissions list generation and export to allow usage by other plugins

= 1.3 =
* Spanish translation by Maria Ramos of [WebHostingHub](http://www.webhostinghub.com/)
* Color picker field
* Possibility to insert raw tokens in html messages
* Disabled HTML5 validation of email fields, using JavaScript validation

= 1.2.1 =
* Fixed installation and upgrade process issues introduced in 1.2
* Users who installed 1.2 as their first version reported that submissions where not saved. Upgrading to this version will fix this issue

= 1.2 =
* Fieldsets
* Submissions trash and restore
* Fixed counting of active and deleted forms
* Fixed submission bug in Internet Explorer 8 and previous versions

= 1.1 =
* Added interface to set basic form styles (borders, colors, padding)
* Fixed captcha validation
* Added submission graph by form
* Screenshots removed from the package
* Other minor fixes