diff --git a/.distignore b/.distignore index c16c6d6..b6b674d 100644 --- a/.distignore +++ b/.distignore @@ -18,6 +18,7 @@ vendor/* # AI guidance and dev docs .claude/* +.vibe/* AGENTS.md CLAUDE.md README.md diff --git a/.gitattributes b/.gitattributes index 6aedfaa..88ef7df 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,7 @@ .gitignore export-ignore .distignore export-ignore .claude export-ignore +.vibe export-ignore .DS_Store export-ignore .vscode export-ignore .idea export-ignore diff --git a/.vibe/config.toml b/.vibe/config.toml new file mode 100644 index 0000000..6d57383 --- /dev/null +++ b/.vibe/config.toml @@ -0,0 +1,186 @@ +active_model = "mistral-medium-3.5" +vim_keybindings = false +disable_welcome_banner_animation = false +displayed_workdir = "" +auto_compact_threshold = 200000 +context_warnings = false +textual_theme = "catppuccin-mocha" +instructions = "" +system_prompt_id = "vibe" +include_commit_signature = true +include_model_info = true +include_project_context = true +include_prompt_detail = true +enable_update_checks = true +api_timeout = 720.0 +tool_paths = [] +mcp_servers = [] +enabled_tools = [] +disabled_tools = [] + +[[providers]] +name = "mistral" +api_base = "https://api.mistral.ai/v1" +api_key_env_var = "MISTRAL_API_KEY" +api_style = "openai" +backend = "mistral" + +[[providers]] +name = "llamacpp" +api_base = "http://127.0.0.1:8080/v1" +api_key_env_var = "" +api_style = "openai" +backend = "generic" + +[[models]] +name = "mistral-vibe-cli-latest" +provider = "mistral" +alias = "mistral-medium-3.5" +temperature = 1.0 +input_price = 1.5 +output_price = 7.5 +thinking = "high" + +[[models]] +name = "devstral-small-latest" +provider = "mistral" +alias = "devstral-small" +temperature = 0.2 +input_price = 0.1 +output_price = 0.3 + +[[models]] +name = "devstral" +provider = "llamacpp" +alias = "local" +temperature = 0.2 +input_price = 0.0 +output_price = 0.0 + +[project_context] +max_chars = 40000 +default_commit_count = 5 +max_doc_bytes = 32768 +truncation_buffer = 1000 +max_depth = 3 +max_files = 1000 +max_dirs_per_level = 20 +timeout_seconds = 2.0 + +[session_logging] +save_dir = "/Users/jasperfrumau/.vibe/logs/session" +session_prefix = "session" +enabled = true + +[tools.search_replace] +permission = "ask" +allowlist = [] +denylist = [] +max_content_size = 100000 +create_backup = false +fuzzy_threshold = 0.9 + +[tools.bash] +permission = "ask" +allowlist = [ + "cat", + "echo", + "file", + "find", + "git diff", + "git log", + "git status", + "head", + "ls", + "pwd", + "stat", + "tail", + "tree", + "uname", + "wc", + "which", + "whoami", +] +denylist = [ + "gdb", + "pdb", + "passwd", + "nano", + "vim", + "vi", + "emacs", + "bash -i", + "sh -i", + "zsh -i", + "fish -i", + "dash -i", + "screen", + "tmux", +] +max_output_bytes = 16000 +default_timeout = 30 +denylist_standalone = [ + "ipython", + "bash", + "sh", + "nohup", + "vi", + "vim", + "emacs", + "nano", + "su", +] + +[tools.grep] +permission = "always" +allowlist = [] +denylist = [] +max_output_bytes = 64000 +default_max_matches = 100 +default_timeout = 60 +exclude_patterns = [ + ".venv/", + "venv/", + ".env/", + "env/", + "node_modules/", + ".git/", + "__pycache__/", + ".pytest_cache/", + ".mypy_cache/", + ".tox/", + ".nox/", + ".coverage/", + "htmlcov/", + "dist/", + "build/", + ".idea/", + ".vscode/", + "*.egg-info", + "*.pyc", + "*.pyo", + "*.pyd", + ".DS_Store", + "Thumbs.db", +] +codeignore_file = ".vibeignore" + +[tools.read_file] +permission = "always" +allowlist = [] +denylist = [] +max_read_bytes = 64000 +max_state_history = 10 + +[tools.todo] +permission = "always" +allowlist = [] +denylist = [] +max_todos = 100 + +[tools.write_file] +permission = "ask" +allowlist = [] +denylist = [] +max_write_bytes = 64000 +create_parent_dirs = true diff --git a/.vibe/prompts/vibe.md b/.vibe/prompts/vibe.md new file mode 100644 index 0000000..360d039 --- /dev/null +++ b/.vibe/prompts/vibe.md @@ -0,0 +1,63 @@ +# Custom Instructions for Vibe + +## Primary Directive +Always follow the rules and conventions in the project's `CLAUDE.md` file. This is your authoritative source for all project-specific guidance. + +## Project Structure + +**warder-cookie-consent** is a WordPress plugin wrapping the [vanilla-cookieconsent](https://github.com/orestbida/cookieconsent) v3 library. + +Key files: +- `warder-cookie-consent.php` — all PHP logic (~851 lines), single-file plugin +- `src/index.js` — JS entry point, maps settings to vanilla-cookieconsent config +- `dist/cookieconsent.bundle.js` — compiled output (do not edit directly) +- `assets/js/warder-admin.js` — admin page JS (AJAX save, UI interactions) + +## Before Responding +1. **Review CLAUDE.md** for: + - Architecture and data flow + - PHP function naming conventions (`warder_` prefix) + - Versioning rules (four files must be updated together) + - Build commands + +2. **Check Current Context**: + - Confirm the current branch and git status + - Review recent commits for relevant changes + +3. **Adhere to Key Principles**: + - All PHP functions use the `warder_` prefix; options stored under `warder_options` in `wp_options` + - Settings are deep-merged with defaults via `warder_get_merged_options()` — never read raw from DB + - JS is bundled via webpack: run `npx webpack` after editing `src/index.js` + - `.vibe/` directories are untracked and should remain so + - PHP requires 8.0+; no test suite exists + +## Build Commands + +```bash +npm install # Install JS dependencies +npx webpack # Build dist/cookieconsent.bundle.js +npx webpack --watch # Rebuild on file change +``` + +## Versioning (bump all four together) + +- `warder-cookie-consent.php` — `Version:` header + `WARDER_VERSION` constant +- `readme.txt` — `Stable tag:` + changelog entry +- `CHANGELOG.md` — new version heading +- `package.json` — `version` field + +Do **not** add a `version` field to `composer.json`. + +## Git Commits + +- Do not mention "Claude", "Claude Code", or AI tools in commit messages +- Do not include "Co-Authored-By: Mistral Vibe" or any AI attribution +- Use atomic commits; stage files individually or in logical groups with specific messages +- Follow standard conventional commit format (`feat:`, `fix:`, `docs:`, `refactor:`, etc.) + +## Response Guidelines +- Be concise and technical +- Reference specific files and line numbers +- Use markdown formatting for code and structure +- Prioritize verification over assumptions +- When unsure, ask for clarification before acting diff --git a/CHANGELOG.md b/CHANGELOG.md index 97118a9..5445266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to Warder Cookie Consent are documented here. +## [1.5.2] - 2026-05-28 + +### Added +- AJAX save for the settings page via `wp_ajax_warder_save_settings` (`warder_ajax_save_settings()`). Submit is intercepted in `assets/js/admin.js`, the form serializes through `$.post( ajaxurl, ... )`, and the response is shown in a dismissible `.warder-ajax-notice` injected before the form — no `options.php` redirect, no scroll-to-top +- Admin JS extracted from the inline `wp_add_inline_script()` heredoc into `assets/js/admin.js`, enqueued via `wp_enqueue_script( 'warder-admin' )` with `wp_localize_script` providing `warderAdmin.ajaxurl`, `warderAdmin.save`, and `warderAdmin.saving` +- `?warder_notice=saved` redirect-after-POST in `warder_handle_admin_actions()` so add/delete-category and add/delete-cookie actions show a success notice on the resulting page + +### Fixed +- Setup welcome notice in `warder_admin_notices()` now self-suppresses once `warder_options_last_updated` is set, so it stops appearing on every admin page after configuration (WordPress.org guideline 11) +- Add Cookie form was nested inside `#warder-main-settings-form` and used the HTML5 `form="..."` attribute to target a hidden form elsewhere in the DOM. Browsers handle `display:none` target forms inconsistently, which dropped the `is_regex` checkbox state and caused the AJAX save handler to intercept Add Cookie submissions. Each Add Cookie form is now rendered as a self-contained `
` of the main settings form +- AJAX save notice is now scrolled into view via `$( 'html, body' ).animate( { scrollTop: $notice.offset().top - 50 }, 300 )` so it is visible regardless of the scroll position at submit time +- Show-add-cookie button scrolls the now-revealed container into view on open + +### Changed +- Dropped the `:not([form])` filters on the change-highlight selector and submit-button lookup in `assets/js/admin.js`, and removed the JS `.after()` reposition of add-cookie containers. These were workarounds for the old nested-form layout and are no longer needed now that PHP renders the containers outside the main form + ## [1.5.1] - 2026-05-28 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index c5db96d..ff4d467 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,7 @@ There are no tests. PHP requires 8.0+. To install as a Composer dependency: ```bash -composer require imagewize/simple-cookie-consent +composer require imagewize/warder-cookie-consent ``` ## Architecture @@ -26,23 +26,23 @@ composer require imagewize/simple-cookie-consent ### Data Flow ``` -WordPress DB (scc_options key) - → scc_enqueue_scripts() fetches and localizes +WordPress DB (warder_options key) + → warder_enqueue_scripts() fetches and localizes → window.sccSettings in browser → createConfigFromSettings() in src/index.js → CookieConsent.run(config) ``` -### PHP Layer (`simple-cookie-consent.php`) +### PHP Layer (`warder-cookie-consent.php`) -All plugin logic is in this single file (~552 lines): +All plugin logic is in this single file (~851 lines): -- **`scc_get_default_options()`** — defines the canonical default settings structure -- **`scc_get_merged_options()`** — retrieves DB options and deep-merges with defaults; always returns a complete settings object -- **`scc_validate_options()`** — sanitizes/validates before saving to `scc_options` in `wp_options` -- **`scc_enqueue_scripts()`** — enqueues `dist/cookieconsent.bundle.js` and localizes it as `window.sccSettings` -- **`scc_settings_page()`** — renders the admin UI at Settings > Cookie Consent -- Settings are versioned via `scc_options_last_updated` timestamp for cache busting +- **`warder_get_default_options()`** — defines the canonical default settings structure +- **`warder_get_merged_options()`** — retrieves DB options and deep-merges with defaults; always returns a complete settings object +- **`warder_validate_options()`** — sanitizes/validates before saving to `warder_options` in `wp_options` +- **`warder_enqueue_scripts()`** — enqueues `dist/cookieconsent.bundle.js` and localizes it as `window.sccSettings` +- **`warder_render_options_page()`** — renders the admin UI at Settings > Cookie Consent +- Settings are versioned via `warder_options_last_updated` timestamp for cache busting ### JS Layer (`src/index.js` → `dist/cookieconsent.bundle.js`) @@ -59,12 +59,24 @@ Scripts are blocked until consent is given via `data-cookiecategory` HTML attrib ### Settings Structure ```php -scc_options = [ - 'general' => [ position, language, ... ], - 'texts' => [ banner title/description, accept/reject button labels, ... ], - 'categories' => [ - 'necessary' => [ enabled, readonly, cookies[] ], - 'analytics' => [ enabled, cookies[] ], +warder_options = [ + 'enabled' => bool, + 'current_lang' => string, + 'autoclear_cookies' => bool, + 'page_scripts' => bool, + 'show_preferences_toggle' => bool, + 'preferences_toggle_position'=> string, + 'title' => string, + 'description' => string, + 'primary_btn_text' => string, + 'primary_btn_role' => string, + 'secondary_btn_text' => string, + 'secondary_btn_role' => string, + 'privacy_policy_url' => string, + 'cookie_categories' => [ + 'necessary' => [ title, description, enabled, readonly, cookies[] ], + 'analytics' => [ title, description, enabled, cookies[] ], + ... // user-defined categories ], ] ``` @@ -75,7 +87,7 @@ Each `cookies` entry supports `name` (exact string or `/regex/` pattern) and `do The `Version:` header in `warder-cookie-consent.php` is the canonical version (this is what WordPress.org reads). When bumping the version, update all of these together: -- `warder-cookie-consent.php` — `Version:` header +- `warder-cookie-consent.php` — `Version:` header and `WARDER_VERSION` constant - `readme.txt` — `Stable tag:` plus new `== Changelog ==` and `== Upgrade Notice ==` entries - `CHANGELOG.md` — new version heading - `package.json` — `version` field (kept in sync even though this package is not published to npm) diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..37ce4d1 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,46 @@ +/* global warderAdmin */ +jQuery( document ).ready( function( $ ) { + + $( '.show-add-cookie-form' ).on( 'click', function() { + var categoryId = $( this ).data( 'category' ); + var $container = $( '#warder-add-cookie-container-' + categoryId ); + $container.show(); + $( 'html, body' ).animate( { scrollTop: $container.offset().top - 50 }, 300 ); + } ); + + $( '.cancel-add-cookie' ).on( 'click', function( e ) { + e.preventDefault(); + $( this ).closest( '.warder-add-cookie-form-container' ).hide(); + } ); + + $( '#warder-main-settings-form input, #warder-main-settings-form textarea, #warder-main-settings-form select' ).on( 'change', function() { + $( this ).css( 'background-color', '#ffffdd' ); + } ); + + $( '#warder-main-settings-form' ).on( 'submit', function( e ) { + e.preventDefault(); + + var form = $( this ); + var submitBtn = form.find( 'input[type="submit"]' ); + + submitBtn.prop( 'disabled', true ).val( warderAdmin.saving ); + + $.post( + warderAdmin.ajaxurl, + form.serialize() + '&action=warder_save_settings', + function( response ) { + $( '.warder-ajax-notice' ).remove(); + var cls = response.success ? 'notice-success' : 'notice-error'; + var $notice = $( '' + response.data.message + '