Ravanix — full changelog
=========================

The most recent versions are kept in readme.txt (Changelog section);
this file holds the complete history.

= 2.8.0 =
* New in the free version: composite factors -- combine several dimensions into a total or an average and interpret it on its raw score (Composite Factors tab in the test editor). Composites made here keep working, with the same data, if you add Ravanix Pro, which adds weighted, standardized and formula-based composites with norms
* New in the free version: a CSV export of one test's results (participant, date, raw score and percentage of every dimension and composite). Spreadsheet formulas in participant-supplied text are neutralized
* New in the free version: the [ravanix_my_results] shortcode -- a logged-in participant's own past results, each with scores, levels and descriptions
* New: a "Print this result" button on the participant's result, on My Results and on the admin result screen; only the result is printed
* The Pro versions of these features replace the free ones automatically when Ravanix Pro is active (no duplicate menus, buttons or shortcodes). If Pro is later deactivated, a composite that uses a Pro-only method is listed as needing Pro and is not scored; it is never silently turned into a plain sum
* Improved: the "Upgrade to Pro" comparison table reflects the new split

= 2.7.0 =
* New: database tables for Ravanix Pro's AI-assisted interpretation (an AI profile per test and the stored drafts). Drafts are deleted with their result, with their test and when a participant's identifying data is erased
* Improved: the "Upgrade to Pro" comparison table now lists everything each edition contains today
* Training questionnaire: a new full-feature training test (Persian and English) is available as an import file, see the plugin's documentation

= 2.6.2 =
* Fixed (important): results saved with versions 2.4.0 to 2.6.1 were stored with a wrong submission date (shown as 0001/11/30), the per-question timing was stored as "0", and the consent timestamp was lost. Cause: the list of column formats in the results insert was misaligned after a column was added in 2.4.0. Each column is now written together with its own format. Scores, answers and version stamps were not affected. The true date of the affected results cannot be recovered, so they are shown with a dash, left out of date-based ordering in exports (blank date), and a notice on the results list tells you how many there are. Update as soon as possible
* Fixed: on phones the two cards on the Import/Export screen (and other two-column screens) stayed side by side in narrow columns; they now stack. The file picker no longer overflows its card, and the list of tests to export is aligned and wraps cleanly
* Fixed: the test title in the trend view sat against the top edge of its panel
* Improved: the export buttons in the results list are grouped into "Scores export" and "Research export" with a short description each, and the names no longer repeat (the score exports now say "for scores", the research ones "for the dataset")

= 2.6.1 =
* Code quality: resolved the Plugin Check findings (unslashing of the posted per-question timing data, documentation of the integer-placeholder IN lists used for cleanup and privacy queries). No change in behaviour

= 2.6.0 =
* New: database support for advanced composite scoring (member weights, formula, scale range, transformation), cross-dimension validity rules and their flags on results, and for composites that cannot be computed for a participant (stored as unavailable, never as zero). Created automatically on update
* Unchanged for existing tests: the version snapshot includes the new scoring facts only when a test actually uses them, so tests that do not will not get a new version because of this update

= 2.5.0 =
* New: database tables for report templates and their versions (created automatically on update). They are used by Ravanix Pro's Report Builder and are removed together with a test, and by the uninstaller
* Fixed: the report fonts are now loaded from the plugin that ships them

= 2.4.0 =
* New (Ravanix Pro): Response Quality 2.0. Flags are now structured (a code plus data) and the text is produced in the viewer's language; every flag is a reason to look at a result, never a finding that it is invalid
* New: per-question answer timing (milliseconds after the form appeared, offsets only, kept in memory in the browser and sent with the submission). It is collected only while an add-on that analyses it is active, validated by the server, and never used to block or invalidate anything
* Improved: straight-lining now considers every dimension an item is keyed to (not only its primary one) and needs more identical answers on scales with fewer options, since chance alone produces them far more easily on a yes/no item than on a 7-point item
* New flags: long identical runs across the test, one answer chosen for almost all Likert items, extreme (endpoint) responding, disagreement between forward- and reverse-keyed items of a dimension, and skip-logic coverage (items hidden by branching)
* Changed: the CSV export gains two columns at the end (flag codes, median time between answers); existing column positions are unchanged

= 2.3.0 =
* New: the Personal Data Exporter and Eraser now also cover guest participants, matched by the e-mail address they entered in the "Email address" participant field (previously only registered users were reachable). The export now includes the participant fields, the name given by a guest, and the stored IP address
* New: Settings > Privacy and data retention: store the full, an anonymized, or no IP address; clear stored IP addresses after N days; delete unsubmitted Save & Resume drafts after N days (default 90 -- previously they were kept forever); discard browser autosave older than N days (default 30)
* New: browser autosave can be switched off per test (Test settings > Browser autosave) for sensitive instruments or shared computers; included in JSON export/import
* New: suggested text for the site's privacy policy (Settings > Privacy > Policy Guide)
* Changed: guest tokens are stored as a SHA-256 hash instead of the raw cookie value, and the cookie value is format-validated. Existing rows keep working (raw values are still recognised, hashed ones can never be replayed as a cookie)
* Fixed: the eraser also clears the stored IP address and per-question timing

= 2.2.0 =
* New: composite-factor scores are now stored with each result (new table, created automatically). Previously they were recomputed live from the test's current composites and norms, so editing a composite or one of its norms silently changed what an old result said. Results saved before 2.2.0 have no stored composite rows and keep using the live computation
* Improved: test-version snapshots (schema 2) now also record dimension/composite names, interpretation text and colour, option identity and full norm provenance (norm set, source, version, population, country, language, sample size, year, methodology). Expect one new version per test after the first result saved following the update -- the snapshot format changed, the test did not
* Improved: the clinician report and the Personal Data Exporter read question wording from the version snapshot of that result, not from the test's current definition
* Changed: "Percentile rank" is now labelled "Estimated percentile" everywhere -- it is a normal-theory estimate from the Z-score, not an empirical percentile rank
* Changed: Z, T and the estimated percentile are computed from the unrounded value and rounded only for storage (T and percentile used to be derived from an already-rounded Z)
* Fixed: when branching (skip logic) left some items of a dimension unanswered, the partial raw score was still compared with the norm's full-scale mean/SD. Standardised scores for such a dimension (or a composite containing it) are now unavailable; sites that accept this can return true from the new ravanix_allow_norm_for_partial_dimension filter
* Fixed: saving a result is now all-or-nothing -- a failed insert is reported to the participant instead of continuing with a half-saved result

= 2.1.1 =
* Fixed (security / data integrity): "Assign questions to dimension", bulk question import and per-option dimensions did not verify that the chosen dimension belongs to the same test, so a posted dimension ID from another test could be attached. Child records can also no longer be created under a test ID that does not exist, and question_type is limited to the known types
* Fixed: deleting a test left its norms, validity rules, branching conditions, composites (with their interpretations and norms), saved drafts and version snapshots behind as orphan rows. All of them are removed now, and the uninstaller drops the three tables it had been missing
* Fixed: a T-score-based interpretation with no applicable norm compared the raw score against ranges written for T-scores. It is now reported as unavailable. Validity rules based on T-scores are likewise not evaluated without a T-score
* Fixed: participant age was converted with intval(), so "abc" became 0 and could match an age-restricted norm. Age is now a whole number of completed years between 1 and 120, validated on submission, in norm groups and in JSON import; anything else is treated as unknown
* Fixed: the "minimum completion time" check and the response-quality analysis trusted a browser-supplied elapsed time, and omitting it skipped the check entirely. The form now carries a server-signed render-time token; the server caps the reported time with its own measurement and no longer accepts a missing value
* Fixed: norm groups are validated when saved or imported (SD greater than 0, min age not above max age, valid ranges for sample size and year); interpretation ranges with min above max are rejected
* Fixed: when several equally specific norm groups overlap, the winner is now chosen deterministically (sort order, then ID) instead of depending on the PHP version

= 2.1.0 =
* New: validity scales (Ravanix Pro) now support multiple rules per scale, combined with AND or OR, and five operators (>=, <=, equals, between, absolute value) instead of a single fixed ">= threshold" -- existing single-threshold scales keep working exactly as before, and quietly migrate to the new format the next time they're saved
* Fixed: composite/dimension norm provenance metadata (source, population, country, sample size, etc. -- added in 2.0.0) was never included in the JSON test import/export format, so it was silently lost on export/re-import; now carried through like every other norm field

= 2.0.1 =
* New: Personal Data Exporter (Tools -> Export Personal Data), completing the pair with the existing Personal Data Eraser -- a registered user's own questionnaire data (which test, when, raw per-item answers with the exact question wording they were shown, dimension scores, consent record, completion time, and any validity/response-quality flags) can now be included in a WordPress data export request. Like the Eraser, this can only act on a request tied to a registered user's email address; a guest submission has no account to look the request up by
* Security/quality audit: reviewed every SQL query, output-escaping site, and new database table introduced this development cycle for injection, XSS, and object-ownership issues -- no new findings

= 2.0.0 =
* Fixed: the participant results list (admin) rendered unreadably on narrow mobile screens on RTL sites -- WordPress core's own default responsive list-table behavior (a floated label next to each cell's value) can position the two on top of each other on an RTL admin screen instead of side by side. This table now has its own explicit, direction-safe mobile layout (a bordered "card" per row, each field's label and value in their own flex row) instead of relying on that core default
* Fixed: the dimension/composite score tables on the individual result page overflowed horizontally off the edge of the screen on mobile instead of scrolling within the page; now wrapped in the same horizontal-scroll container already used elsewhere in the admin (e.g. the Dimensions/Questions/Norms tables)
* This release is versioned 2.0 to mark a major cumulative milestone rather than a single change -- see 1.4.0 through 1.4.8 below for the full detail of everything it includes: Test Versioning (immutable per-test definition snapshots, with results traceable to exactly which version scored them), AND/OR Conditional Logic with six comparison operators and dependency-aware question randomization, consent provenance (versioned/timestamped per result), response-quality analysis (Ravanix Pro), full norm provenance metadata (norm set, source, population, country, sample size, collection year, methodology -- Ravanix Pro), a trend-chart metric selector (raw/percentage/Z/T/percentile -- Ravanix Pro), and a systematic object-ownership/security hardening pass across every admin save-and-delete handler

= 1.4.8 =
* Fixed (important): a Conditional Logic condition checked against an *unanswered* source question could incorrectly evaluate as if that question's first listed answer option had been selected (a jQuery .val() quirk on a set of same-name radio buttons with none checked), causing a dependent question to show or hide incorrectly from the very start of the test -- most visibly, a question the participant never realized was relevant could end up required, blocking submission with no obvious explanation. Verified fixed with an automated browser test covering single-page, multi-page, equals/not-equals conditions
* Hardened: a malformed Conditional Logic condition can now never break the rest of the page's scripting (including the submit button itself) -- it's skipped with a console warning instead

= 1.4.7 =
* New: results_scores table gained a norm_metadata_json column (schema-only in Lite; populated by Ravanix Pro 1.2.11's new norm provenance fields) so a dimension score stays linked to exactly which norm reference it was scored against, even if that norm is later edited

= 1.4.6 =
* New: total completion time (elapsed_ms, already collected for the existing minimum-completion-time anti-spam check) is now stored on every result and shown on the result page -- lays the groundwork for Ravanix Pro's response-quality analysis

= 1.4.5 =
* New: Conditional Logic now supports multiple conditions per question, combined with AND or OR, and four extra comparison operators (is not / greater than / less than / greater-or-equal / less-or-equal) alongside the original "equals" -- previously a question could only depend on exactly one other question's answer being equal to a fixed value. Existing single-condition questions keep working exactly as before, and quietly migrate to the new format the next time they're saved
* Fixed: when a test had both question randomization and Conditional Logic turned on, the random order could place a dependent question before the question it depends on, making it impossible to satisfy that condition; randomization is now dependency-aware -- a question can never be placed before another question its Conditional Logic depends on, directly or transitively -- while everything with no such relationship still shuffles freely

= 1.4.4 =
* New: Test Versioning -- an immutable snapshot of a test's scoring-relevant definition (dimensions, questions, answer options, weights, reverse keys, interpretations, and -- with Ravanix Pro -- norms, composites, branching) is now recorded the first time a result is submitted against it, and again whenever that definition changes; every result is stamped with exactly which version scored it. This is the foundation the roadmap's later Test Battery/Organizations work depends on, and immediately shows up on the result page: if a questionnaire has since been edited, older results now say so explicitly instead of silently implying they're still comparable to a fresh attempt. Existing results predating this update show as "not recorded" rather than a guessed version
* New: the research export columns (CSV, SPSS syntax/data, R script) now include the definition version each result was scored against

= 1.4.3 =
* New: consent provenance -- each result now records consent_version (a short content hash of the exact consent wording shown at submission time) and consented_at alongside the existing consent_agreed flag, so a later reviewer can tell not just whether a participant agreed, but to precisely which wording and when; shown on the result page when consent was required
* Fixed: object-ownership checks on every dimension/question/interpretation admin save-and-delete handler (and, in Ravanix Pro, norm/composite handlers) -- a posted child ID is now always verified against its claimed parent test before being written to, instead of being trusted on its own
* Fixed: deleting a dimension left orphaned rows behind in the norms table and the Pro multi-dimension-scoring link table; both are now cleaned up along with interpretations
* Fixed: a hardcoded SVG icon in the questionnaire-list "Titles only" layout was flagged by Plugin Check for missing output escaping; now passed through wp_kses() with a scoped allowlist
* Fixed (Ravanix Pro): norm matching could select an age- or gender-specific norm group even when the participant's age or gender wasn't collected, silently applying the wrong reference population's Z/T/percentile; a demographic-specific norm group is now only ever matched when that same demographic datum is actually known for the participant
* Fixed (Ravanix Pro): the SPSS/R data CSV, .sps syntax file, and .R script downloads didn't include a UTF-8 BOM, so Persian/non-Latin test, dimension, and participant-field text could display as garbled characters when opened in Excel or a plain text editor on Windows
* Fixed (Ravanix Pro): a few numeric fields in the JSON test-import path (validity_threshold, and min_age/max_age on both dimension and composite norms) weren't cast to a number like every sibling field, so a malformed import file could store a non-numeric value in a numeric column
* Housekeeping: the .pot translation template is now fully in sync with the source (every translatable string has a matching entry, and a few strings that were present with a stray escaped-apostrophe artifact and a stale line reference are now correct)

= 1.4.2 =
* New: Ravanix_Shortcodes::render_test_list() accepts two new $atts -- 'limit' (0, default, means no LIMIT clause; otherwise intval()'d and passed through $wpdb->prepare()) and 'order' ('newest', default, ORDER BY id DESC; or 'random', ORDER BY RAND() -- chosen from this fixed pair of hardcoded SQL fragments, never built from the raw attribute string, so there's nothing for it to inject). Applies before the layout branch, so render_test_titles() (added in 1.4.1) benefits automatically along with the grid/list card layouts
* Changed: class-ravanix-block.php's attributes array and render() gained matching limit/order entries (same defaults), passed through to render_test_list() unchanged; assets/js/ravanix-block.js's editor gained a RangeControl ("Maximum number to show", 0-24, help text explaining 0 = all) and a SelectControl ("Order": Newest first / Random), both shown for mode === 'list' regardless of which of the three layouts is selected, unlike the layout-specific controls

= 1.4.1 =
* New: 'titles' is now a valid value for [ravanix_test_list layout="..."] (and the matching Gutenberg block option, "Titles only") -- Ravanix_Shortcodes::render_test_list() branches to a new render_test_titles() for it: a plain <ul> of just each published test's title (linked to its custom-post-type page if one exists, plain text otherwise -- deliberately no image, excerpt, button, or the admin-only "no dedicated page yet" hint the card layouts show, to stay minimal), each marked with Ravanix_Shortcodes::get_titles_layout_icon() (a small inline SVG, currentColor stroke, same convention as the meta-row icons in templates/frontend-test.php) instead of a bullet
* New: .rs-test-list-titles / .rs-test-titles(-icon) in ravanix-frontend.css -- icon color from --md-primary (derives from the admin's brand_color), logical properties throughout, focus-visible on the title links
* Changed: assets/js/ravanix-block.js's Layout control has a third "Titles only" option; the column-count RangeControl and the image/excerpt ToggleControls are hidden while it's selected, since none of them apply to this layout

= 1.3.10 =
* Fixed: Ravanix_Admin::enqueue_assets() compared $hook against a hardcoded 'ravanix_page_ravanix-settings' guess to decide whether to load ravanix-settings.css/.js and call wp_enqueue_editor(). WordPress actually builds a top-level page's hook-name component from sanitize_title() of add_menu_page()'s *menu title* argument (translatable), not the literal $menu_slug -- so on a site with a Persian translation for the string "Ravanix" active, the real hook looked like "{sanitized-persian-title}_page_ravanix-settings", the hardcoded comparison never matched, and this page's CSS/JS simply never loaded (confirmed via a reporting user's own console: document.styleSheets/document.scripts both came back empty for 'ravanix-settings', and document.body.className contained the Persian translation UTF-8-byte-encoded). Ravanix_Admin now has a $settings_hook property, set from add_submenu_page()'s own return value for the Settings page in register_menu() (which always runs before enqueue_assets(), same object instance), and compared against instead of any guessed string
* Note: the broader `strpos( $hook, 'ravanix' ) === false` gate (used here and in both of Ravanix Pro's admin_enqueue_scripts callbacks) was never affected by this -- only the translated *parent* portion of the hook changes; every submenu's own slug (e.g. 'ravanix-settings') stays the literal, untranslated string passed to add_submenu_page(), so a substring check for "ravanix" still matches regardless

= 1.3.9 =
* Fixed: assets/js/ravanix-settings.js's initEditorsForTab() now wraps its wp.editor.initialize() call in try/catch, and the DOMContentLoaded handler wraps tab-restoration and the "copy shortcode" button setup in their own try/catch blocks too, with tab-button click listeners attached before any of that (so they're registered even if everything after them throws). Root cause on the reporting site: a Content Security Policy blocking eval() broke TinyMCE's RTL/directionality language pack (only loaded for an RTL admin locale), and without isolation that uncaught exception prevented the rest of this file's initialization -- tabs appeared as plain, unstyled, non-functional buttons with every section stacked below them, on RTL admin locales only
* Changed: assets/css/ravanix-settings.css's toggle-switch RTL flip now also matches body.rtl (the class WordPress core adds to <body> for an RTL admin locale), not just this page's own [dir="rtl"] wrapper attribute -- consistent with the convention core and other plugins' admin CSS already use

= 1.3.8 =
* New: admin/views/settings.php rebuilt around 4 tabs (data-tab="general|notifications|users|tools", pure client-side show/hide -- see assets/js/ravanix-settings.js) with card-grouped sections (assets/css/ravanix-settings.css, enqueued only on this page). Every existing field kept its exact name/id/value handling; only the surrounding markup changed
* New: Ravanix_Admin::enqueue_assets() calls wp_enqueue_editor() and enqueues ravanix-settings.css/.js, conditionally on the 'ravanix_page_ravanix-settings' hook only
* Changed: the three rich-text fields on this page (consent_text, notify_admin_body, notify_participant_body) are plain <textarea> elements in PHP; ravanix-settings.js calls wp.editor.initialize() on each one itself, exactly when its tab becomes the active one (including on page load, for whichever tab that happens to be) -- not through wp_editor() directly, since TinyMCE does not reliably size itself the first time it initializes inside a display:none container, which every inactive tab is. The two notification-enabled toggles no longer hide their own subject/recipient/message fields via JS for the same reason (nested hidden containers around a lazily-initialized editor); they're always visible now, matching the same choice already made for Ravanix Pro's per-test notification overrides
* Changed: Ravanix_Admin_Handlers::save_settings()'s redirect now carries active_tab from the submitted form back through the URL, so ravanix-settings.js can restore the tab the admin was on when they saved, instead of always landing back on "General"
* Improved: the roles matrix (Roles & Permissions tab) is wrapped in .rs-table-scroll-x, since wp_roles() is dynamic -- a site with WooCommerce or a membership plugin can have far more than the handful of default roles this table's width was originally sized around

= 1.3.7 =
* Fixed: Ravanix_Roles::ensure_roles() only granted the ravanix_manager role 'read' plus its Ravanix-specific capabilities; added 'edit_posts' too, since that's the capability WooCommerce's own WC_Admin (and similar plugins) check to decide whether to redirect a user away from wp-admin to the frontend account page as a "mere customer". ensure_roles() now also re-applies the full capability set to the role every time it runs (not just at role-creation time), so a site whose ravanix_manager role was already created under 1.3.6 gets this fix automatically on upgrade, without needing to delete and recreate the role
* Changed: notify_admin_body/notify_participant_body (Settings → Email notifications) render via wp_editor() (Classic Editor) instead of a plain <textarea>, and are sanitized with wp_kses_post() instead of sanitize_textarea_field() on save, matching the existing consent_text pattern
* Changed: Ravanix_Notifications::send() now sends with a 'Content-Type: text/html' header and passes the body through wpautop() first, instead of sending as plain text
* Changed: Ravanix_Notifications::build_placeholders()/send_admin_notification() now esc_html() (test_title, site_name, submitted_at, participant_name) or esc_url() (admin_result_link) every value before it's substituted into a template, since the template is now HTML -- participant_name in particular can come directly from a participant's own submitted "full name" field
* Changed: default_admin_body()/default_participant_body() rewritten as HTML (<p>/<br>) instead of plain text with \n separators, so they display correctly the first time an admin opens the rich-text editor

= 1.3.6 =
* New: Ravanix_Roles (includes/class-ravanix-roles.php) -- 6 capabilities (ravanix_manage_tests, ravanix_view_results, ravanix_delete_results, ravanix_export_results, ravanix_import_export_tests, ravanix_manage_settings) plus one auto-managed umbrella capability (ravanix_access, controls only whether the top-level admin menu is visible; recomputed whenever the matrix below changes a role's other capabilities). Administrator gets all of them via ensure_roles(), called from Ravanix_Activator::activate() (idempotent, so a future version adding a new capability reaches existing sites on their next upgrade too, no reactivation needed)
* New: a 'user_has_cap' filter grants every Ravanix capability to any user who already has manage_options -- this is what actually guarantees no lockout, including for WordPress's own internal current_user_can() calls (e.g. deciding admin menu visibility) that this plugin has no other way to wrap
* New: ready-made "ravanix_manager" role (Settings → Roles & Permissions describes it as "Ravanix Manager"), granted ravanix_manage_tests + ravanix_view_results + ravanix_access by ensure_roles()
* New: Settings → Roles & Permissions -- a checkbox matrix (6 capabilities x every real registered WP role from wp_roles(), Administrator shown fixed/disabled) saved via Ravanix_Roles::save_matrix(), submitted through the same form/nonce as the rest of Settings
* Changed: admin menu registration (class-ravanix-admin.php) and Ravanix_Admin_Handlers::check() (now takes an optional $capability parameter, defaulting to ravanix_manage_tests) use the new capabilities instead of a flat manage_options; ravanix_delete_result and the results-list bulk action require ravanix_delete_results specifically, ravanix_save_settings requires ravanix_manage_settings
* Changed: admin/views/results-list.php only renders the bulk-select checkboxes and each row's "Delete" link for a user who actually has ravanix_delete_results (previously: unconditionally, then blocked server-side on submit)
* Changed: uninstall.php (opt-in erase path only) now also removes every Ravanix capability from every role and deletes the ravanix_manager role, reimplemented directly against $wpdb/core role functions rather than requiring class-ravanix-roles.php, matching this file's existing standalone-execution design

= 1.3.5 =
* New: two extension points in the email-notification system for Ravanix Pro to use -- 'ravanix_notification_settings' (per-test recipient/subject/body/on-off overrides, applied in Ravanix_Notifications::maybe_send() just before it acts on the site-wide settings) and 'ravanix_notification_placeholders' (extra {tokens}, applied in build_placeholders(); Pro uses this for its opt-in {scores_summary}). Neither filter has a callback added to it by Lite itself
* New: 8 new nullable/defaulted columns on ravanix_tests (notify_admin_mode, notify_admin_emails_override, notify_admin_subject_override, notify_admin_body_override, notify_admin_condition, notify_participant_mode, notify_participant_subject_override, notify_participant_body_override) -- inert unless Ravanix Pro 1.2.4+ is active and a test's mode is changed from the 'default' every column defaults to, so existing tests keep behaving exactly as before this update

= 1.3.4 =
* New: Ravanix_Notifications (includes/class-ravanix-notifications.php), hooked to a new 'ravanix_result_finalized' action fired in Ravanix_Ajax::submit_test() right after Ravanix_Scoring::save_result() returns -- deliberately later than and separate from 'ravanix_after_save_result' (fired *inside* save_result(), which Pro uses to attach T-scores/composite factors), so notification behavior never depends on hook-priority ordering against Pro
* New: 7 new Settings fields (notify_admin_enabled/emails/subject/body, notify_participant_enabled/subject/body) under a new "Email notifications" section; all off by default

= 1.3.3 =
* Improved: .rs-columns gap reduced from 30px to 15px, and a new @media (min-width: 783px) and (max-width: 1100px) breakpoint gives the form column 45% (was a flat 40% at every width down to the 782px stack point) -- reuses wp-admin's own two menu breakpoints (960px icon-collapse, 782px mobile toggle) rather than introducing unrelated ones

= 1.3.2 =
* Improved: Ravanix_DB::get_full_test() now loads dimensions/questions/options/interpretations/norms/composites with a fixed, small number of batched "WHERE id IN (...)" queries instead of one query per parent row, and caches the assembled structure (object cache, with a transient fallback on sites without an external object cache); the cache is versioned by the test's own updated_at and automatically invalidated whenever a dimension, question, interpretation, norm, or composite belonging to it is saved or deleted
* Improved: activation/upgrade no longer always re-syncs every existing test with the custom post type and flushes rewrite rules synchronously in the same request; above 20 existing tests, this now runs once in the background (Action Scheduler if available, otherwise wp-cron) to avoid a PHP timeout on large sites, matching the same deferred pattern already used by the "catch-up sync" when CPT display is newly enabled in Settings
* Security: guest participant tokens (ravanix_guest_token cookie) are now generated with random_bytes(), PHP's CSPRNG, instead of wp_generate_password(); previously-issued tokens are unaffected and keep working
* Fixed: an interpretation range's admin-chosen level_color is drawn as a background behind fixed white badge text in several places (frontend result page, admin result view, Ravanix Pro's PDF/CSV/trend views); a light color (or the previous raw '#999999' fallback, itself only ~2.85:1) could fail WCAG AA contrast. It now passes through the same ensure_min_contrast_with_white() check already used for the brand color, applied where the score is computed rather than to the admin's saved setting
* Fixed: same layout problem as the .rs-table-scroll-x fix below, but in the two-column "Add New.../list" layout itself (shared by Dimensions, Questions, Interpretation Ranges, and Ravanix Pro's Norms/Composite Factors): Ravanix_DB::get_full_test()'s .rs-col-form was flex: 0 1 288px next to .rs-col-list's flex: 1, which stayed a small fixed width on wide screens while the list column claimed everything else. .rs-columns is now display:grid with grid-template-columns: 40% 60%, holding that ratio at any width above the existing single mobile breakpoint (which still stacks to one column)
* Fixed: .rs-table-scroll-x (overflow-x:auto safety net, previously only on the Dimensions tab) is now also on the Questions and Interpretation Ranges tables, so a table wider than its 60% column scrolls internally instead of overflowing the page
* Fixed: two WCAG AA contrast failures on the "Upgrade to Pro" admin screen — the button-hero CTA's white-on-gold text (~2.2:1) and the ✓/✕ comparison-table icons (~1.6–2.6:1)
* Fixed: a database error while saving a test (admin_post_ravanix_save_test) showed the raw $wpdb->last_error text to the admin; it now shows a generic message and logs the real error under WP_DEBUG only

= 1.3.1 =
* Fixed: the "Dimensions of this test" / "Questions in this test" tables (and the same layout on Interpretation Ranges, and Ravanix Pro's Norms and Composite Factors tabs) needed horizontal scrolling on many screens; the "Add New..." form column next to them is now 20% narrower and no longer expands to steal their space, so those tables fit without scrolling on far more screen sizes

= 1.3.0 =
* New: the plain "Number of questions" text on a test's intro is now a 3-item table (question count, estimated completion time, total participant count so far), each with its own icon
* Improved: the "Start Test" and "Submit answers" buttons are now noticeably larger and more prominent, as the two most significant actions on the page
* Fixed: "My Results", the questionnaire archive, and the single-questionnaire page never declared their own background color -- only their text color adapted to dark mode, while the background stayed whatever the surrounding theme/page provided (usually light), which could make table cells and other content in those specific views hard to read on a device with dark mode active. (The 1.2.3 fix for this same class of bug covered the test-taking page itself, but missed these other three views.)
* Fixed: the Dimensions tab's table (Name, Code, Number of questions, Quick-assign, Actions) could overflow the page horizontally; the quick-assign field is narrower now, and the table has a safety-net horizontal scroll of its own rather than the whole admin page scrolling

= 1.2.3 =
* Fixed: tables (My Results, composite scores) could become unreadable -- dark background with equally-dark text, or the reverse -- on a device/browser with a "force dark mode for web content" setting (common on some mobile browsers), because the page never told the browser it already handles dark mode itself; color-scheme: light dark is now declared, and table cells also set their text color explicitly rather than relying only on inheritance

= 1.2.2 =
* Fixed: enabling "Display as a custom post type" in Settings only ever created the linked post for a test the next time that specific test was individually opened and re-saved -- a test created (or already published) before the setting was turned on never got synced automatically. Turning the setting on now catches up every published test in that same request.

= 1.2.1 =
* Fixed: an admin-chosen brand color that was too light could make white button text (and the color's own use as question-number/link text) fail WCAG AA contrast; a real WCAG relative-luminance check now automatically darkens a color only as much as actually needed, computed fresh on every page load -- the saved setting itself is never changed
* Fixed: in dark mode, the default color scheme's button text measured only ~2.6:1 contrast against its own (deliberately lightened) button background, well under the 4.5:1 minimum; both the default and any custom brand color now get a dark-mode text color chosen by actually measuring contrast rather than a fixed color blend

= 1.2.0 =
* New: the front-end test-taking and results pages (and Ravanix Pro's My Results dashboard, which shares this stylesheet) now use a Material Design 3-inspired visual system -- MD3 color roles, shape scale, simplified type scale, and CSS-only state layers on buttons/answer selections
* New: a "Brand color" setting (Ravanix Settings -> Branding) lets the admin pick one accent color; every other color in the front-end palette is derived from it automatically via the browser's own color-mix() function, no extra library or build step
* New: automatic light/dark appearance, following the visitor's OS/browser preference (prefers-color-scheme)
* This is a CSS-only redesign: no HTML/JS structure changed, so no database migration and no impact on existing shortcodes, custom post type pages, or stored results

= 1.1.5 =
* Fixed (HIGH): the server accepted any numeric value for a submitted answer without checking it was actually one of that question's real choices for every question type (Likert-5/7, Yes/No, custom multiple-choice, and Pro's forced-choice questions), so a tampered request could submit e.g. "999" for a 5-point Likert item and corrupt that dimension's raw score/percentage
* Fixed: a "Powered by Ravanix" link was always shown on every result page with no way to turn it off (a WordPress.org Guideline 10 violation); it is now off by default, with an opt-in "Show branding" toggle in Ravanix Settings
* Fixed: a question's "show only if" (skip logic) source question can no longer be set to one that comes later in display order, which could never actually be satisfied; this is now enforced when saving a question
* Fixed: readme.txt incorrectly described text direction (RTL/LTR) as a per-test setting; it actually follows the site's language (WordPress's own is_rtl()) site-wide, not per test
* Improved: restoring a saved draft no longer builds a jQuery selector directly from the saved value, so an unusual/tampered browser-local draft can no longer break the whole restore -- values are now compared via JS equality instead
* Improved: reworded an internal code comment away from the word "tracking" to more accurately describe its actual, narrow purpose (guest-session identification and anti-abuse controls)
* Added: a Privacy section in readme.txt, documenting exactly what's stored for a guest or logged-in participant and why

= 1.1.4 =
* Fixed: resuming a saved draft (Save & Resume) could skip the informed-consent step entirely, since the "Resume where you left off" button never checked whether consent had been agreed to
* Fixed: comparing a server-saved draft against the browser's local one used the wrong timezone (a site-local timestamp was read back as if it were already UTC), which could make a genuinely older draft look newer than it was, or vice versa
* Fixed: deleting a question that another question's "show only if" (skip logic) condition depended on left that other question permanently, silently hidden with no way to ever satisfy its condition again; the reference is now cleared automatically when the depended-on question is deleted
* Fixed: an already-defined but never-actually-used debounced autosave function is now correctly wired up to the per-answer autosave, avoiding redundant localStorage writes on rapid successive clicks

= 1.1.3 =
* Fixed: several responsive/mobile layout bugs in the admin panel — a fixed-width form column that didn't shrink to fit narrow screens (Dimensions, Interpretation Ranges, and the question edit form all share this layout), the bulk-import row's dropdowns/inputs overflowing the viewport, WordPress core's own fixed-width .regular-text/.large-text classes not fitting phone-width screens, and two custom admin tables missing the data-colname/toggle-row markup WordPress's own responsive table CSS needs (Participant info fields, and Participant Results, which had the same bug though it wasn't reported)
* Improved: consistent spacing/alignment for checkbox-plus-label pairs across every admin form (e.g. the "Randomize order" checkboxes)
* Improved: the built-in sample questionnaire ("Sample: Mood Screening Scale") now demonstrates every feature in this version of the plugin — branching/skip logic, a custom per-test informed-consent notice, Save & Resume, all four question types (Likert-5, Likert-7, Yes/No, custom multiple-choice), importance weighting, pagination, ranked results, and randomized answer-option order — instead of only the original two Likert-5 dimensions

= 1.1.2 =
* Improved: readme.txt rewritten to current WP.org guidelines — Tags trimmed to the current limit of 5 relevant, non-generic terms (RTL dropped as redundant), and version history split into changelog.txt to keep this file under the recommended size
* Fixed: Ravanix Pro's Plugin URI no longer duplicates this plugin's own, per official guidance against reusing a URI across a free/paid pair

= 1.1.1 =
* Fixed: Plugin Check warnings introduced by 1.1.0's Save & Resume feature — a misplaced phpcs suppression comment on the wrong line in the draft lookup query, missing sniff codes for the new $wpdb->delete()/->replace() calls, and an unsanitized read of the consent_agreed field
* Fixed: a stale, incorrect header comment in class-ravanix-ajax.php that described a nonce-checking pattern from a different file, not this one

= 1.1.0 =
* New: informed consent — a site-wide default notice (Ravanix Settings) with a per-test override or opt-out, shown collapsed before "Start Test" and enforced both client- and server-side
* New: Save & Resume — every participant's answers are always autosaved in their browser; the admin can additionally enable a visible "Save my progress" button per test, which also lets a logged-in participant resume on a different device
* New: branching / skip logic — a question can be set to only appear if a specific earlier question was answered a specific way
* Fixed: with skip logic, a question hidden by its branch condition is no longer submitted or included in that question's dimension's score, consistent with how the scoring engine already treats any other unanswered question

= 1.0.14 =
* Fixed: Plugin Check warnings on uninstall.php — the phpcs suppression comments were targeting the wrong sniff codes (WordPress.DB.PreparedSQL.NotPrepared instead of the actually-firing WordPress.DB.PreparedSQL.InterpolatedNotPrepared and WordPress.DB.DirectDatabaseQuery.SchemaChange) and two loop variables lacked the plugin's prefix
* Fixed: the same missing InterpolatedNotPrepared suppression on the CREATE TABLE statements in the activator (audited the entire codebase; no other occurrences found)

= 1.0.13 =
* Fixed: on left-to-right sites, the "Feature" column header on the Upgrade page's comparison table was right-aligned instead of left-aligned (audited every other table for the same issue; none found)
* Improved: the test title field on the "Add/Edit Test" screen is now noticeably wider than the slug field below it
* Improved: rounded corners applied consistently to text inputs, textareas, and dropdowns across every Ravanix admin screen

= 1.0.12 =
* New: an opt-in "Delete data on uninstall" setting (Settings -> Danger zone, off by default). Only when enabled does removing the plugin permanently delete its questionnaires, results, and settings; otherwise everything is left in place for a future reinstall
* New: integrates with WordPress's own Tools -> Erase Personal Data screen, so a request for a specific logged-in user anonymizes their identifying details while keeping their anonymous dimension scores for aggregate statistics
* Fixed: Plugin URI now points to the plugin's own page instead of the Pro add-on's page

= 1.0.11 =
* Removed the RSS feed calls to psykey.ir on the Settings page (no external requests remain)
* Fixed a missing/doubled separator between the "View profile", "PDF" (Pro), and "Delete" row actions on the "Participant Results" screen
* Tested up to WordPress 7.1

= 1.0.2 =
* New: an optional "Show results ranked from highest to lowest" setting per test, for questionnaires where relative ranking between dimensions matters more than each one's absolute level (e.g. strengths, interests, dominant traits)
* New: dimensions without an interpretation range now show their own general description instead of a blank "not defined" message
* New: test display direction (RTL/LTR) is now detected automatically from the site's language, removing a manual per-test setting
* New: the "All Tests" admin screen now follows WordPress's native list-table conventions (status filter, hover row actions, mobile-responsive layout)

= 1.0.1 =
* Full English translation of the admin interface and all plugin strings; the source .pot file was regenerated accordingly
* Fixed a bug where a participant's gender was matched against a translated display label instead of a stable internal value, which could silently break gender-based norm matching on non-default languages
* Various WordPress coding-standards and documentation clean-ups

= 1.0.0 =
* Initial public release of Ravanix
