Skip to content

Release v5.4.1#151

Merged
parhumm merged 16 commits intomasterfrom
development
Mar 10, 2026
Merged

Release v5.4.1#151
parhumm merged 16 commits intomasterfrom
development

Conversation

@parhumm
Copy link
Copy Markdown
Contributor

@parhumm parhumm commented Mar 9, 2026

Release v5.4.1

Changelog

Summary by CodeRabbit

  • New Features

    • GDPR consent banner labels now translatable via WPML and Polylang
    • Consent translations automatically registered when saving settings
  • Bug Fixes

    • Fixed duplicate Accept/Deny buttons in banners with links
    • Charts now load in older browsers and handle missing data gracefully
    • Real URLs preserved in consent banner messages
    • GDPR Compliance Mode off is respected (banner hidden)
    • Refresh/reset behavior fixed so manual refresh resets countdown
  • Chores

    • Updated changelog and version to 5.4.1

parham tehrani and others added 12 commits March 9, 2026 11:54
Resolves #139 - Charts were crashing in older browsers (Firefox <121,
older Chrome/Safari/Edge) because the :has() pseudo-class is not
supported. Using querySelector with :has() throws errors in
unsupported browsers, causing chart rendering failures.

Changes:
- Replace querySelector(".inside:has(#chart)") with closest(".inside")
- Replace querySelector(".chart-wrap:has(#chart)") with closest()
- Add null checks before DOM manipulations to prevent TypeErrors
Resolves #140 - Consent banner was showing even when GDPR Compliance
Mode was disabled because the check only validated use_slimstat_banner
without checking gdpr_enabled.

Now the banner only renders when BOTH conditions are met:
- gdpr_enabled is 'on'
- use_slimstat_banner is 'on'
…or-crash

Fix: Replace CSS :has() with closest() for browser compatibility
…-regression

Fix: Gate consent banner on gdpr_enabled setting
#145)

- Strip anchor-only/empty-href links from custom banner message to
  prevent duplicate Accept/Deny controls alongside rendered buttons.
  Real URL links (e.g. privacy policy) are preserved.
- Register custom GDPR banner strings (message, accept/decline labels)
  with WPML (wpml_register_single_string) and Polylang (pll_register_string)
  on settings save, and apply translations at render time via
  wpml_translate_single_string with native Polylang fallback.
- Update banner message field description to clarify link requirements.
The previous preg_replace pattern only handled href="#" and href=""
but missed named anchors (href="#accept", href="#deny") and
whitespace variants (href=" # "). Replace with preg_replace_callback
that whitelists real URLs (http(s)://, /path) and strips everything
else, preserving inner text content.

Tested against 15 edge cases covering all known variants.
Fix: GDPR banner duplicate controls & WPML/Polylang i18n
Added version-gated migration for 5.4.1 to ensure 'use_separate_menu'
setting is properly set to 'on' for users upgrading from older versions
where the empty() check failed for the string 'no'.

Fixes #146
…ation

Fix admin bar migration bug for upgrading users
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 95082317-d358-4996-84d0-de31e0737d04

📥 Commits

Reviewing files that changed from the base of the PR and between fe5fdb0 and eceb20c.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • readme.txt

📝 Walkthrough

Walkthrough

Adds GDPR consent banner translation and registration hooks (WPML/Polylang), strips non‑URL anchors from banner messages, fixes duplicate banner buttons and chart-loading edge cases, adds a 5.4.1 upgrade migration for an admin menu setting, and updates plugin versioning, changelog, POT, and readme metadata.

Changes

Cohort / File(s) Summary
Version & Metadata
wp-slimstat.php, readme.txt, CHANGELOG.md, languages/wp-slimstat.pot
Bumped plugin/version constants to 5.4.1, updated changelog and readme stable tag, and refreshed POT metadata and string references.
GDPR banner logic & translations
src/Services/GDPRService.php, admin/config/index.php
Added translateString helper; register GDPR strings with WPML/Polylang on settings save; translate banner message and button texts at render time; strip anchor-only links from banner HTML.
Admin JS: charts & realtime
admin/assets/js/slimstat-chart.js, admin/assets/js/admin.js
Chart fetch now checks for required DOM elements and uses chart-specific show/hide logic; admin.js adds _lastManualRefreshTime, switches clearTimeout→clearInterval, and guards auto-refresh countdown after manual refresh.
Upgrade migration
admin/index.php
Added version-gated migration for 5.4.1 to ensure use_separate_menu is set to 'on' for older installs.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant AdminConfig as Admin Config (save)
    participant GDPRSvc as GDPRService
    participant WPML as WPML/Polylang
    participant Frontend as Browser (Banner)

    AdminConfig->>GDPRSvc: save settings (opt_out_message, accept/deny texts)
    GDPRSvc->>WPML: wpml_register_single_string (if available)
    GDPRSvc->>WPML: pll_register_string (if available)
    Frontend->>GDPRSvc: request banner HTML
    GDPRSvc->>WPML: fetch translated strings (translateString)
    GDPRSvc-->>Frontend: return banner HTML (anchors stripped, labels translated)
    Frontend->>Frontend: render banner
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through strings and tidy lines,
I taught banners tongues and pruned sneaky links,
I steadied charts and nudged a menu’s fate,
A tiny rabbit, version five‑four‑one—celebrate! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Release v5.4.1' directly and clearly summarizes the main objective of the pull request—to release version 5.4.1 of the plugin.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch development

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

}

// WPML registration
do_action('wpml_register_single_string', 'wp-slimstat', $name, $value);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only registers strings when the settings form is saved, but GDPRService::translateString() now runs on every banner render. On upgrade from 5.4.0, sites that already have custom opt_out_message / button labels configured will never hit this save path, so WPML/Polylang will keep returning the original strings until an admin re-saves Slimstat settings. That leaves #145 only partially fixed for existing multilingual installs. Consider registering these strings during plugin init / banner bootstrap as well, or adding a version-gated migration that registers any already-stored values once after upgrade.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
admin/assets/js/slimstat-chart.js (1)

100-152: ⚠️ Potential issue | 🟠 Major

Always restore the chart UI after failed refreshes.

Line 100 hides the wrapper before the request, but only the happy path at Line 152 shows it again. Any non-200 response, bad JSON, or result.success === false leaves the chart hidden and the loading state stuck until a full reload.

Suggested fix
         inside.appendChild(loadingIndicator);
-        chartWrap.style.display = "none";
+        var previousDisplay = chartWrap.style.display;
+        chartWrap.style.display = "none";
+
+        function resetLoadingState() {
+            if (loadingIndicator.parentNode === inside) {
+                inside.removeChild(loadingIndicator);
+            }
+            chartWrap.style.display = previousDisplay;
+        }

         var xhr = new XMLHttpRequest();
         xhr.open("POST", slimstat_chart_vars.ajax_url, true);
         xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

         xhr.onreadystatechange = function () {
             if (xhr.readyState === 4) {
                 if (xhr.status === 200) {
-                    var result = JSON.parse(xhr.responseText);
+                    var result;
+                    try {
+                        result = JSON.parse(xhr.responseText);
+                    } catch (e) {
+                        resetLoadingState();
+                        return;
+                    }
                     if (!result.success) {
+                        resetLoadingState();
                         console.error("AJAX error:", result.data.message);
                         return;
                     }
                     element.dataset.granularity = granularity;
@@
-                    inside.removeChild(loadingIndicator);
-                    chartWrap.style.display = "block";
+                    resetLoadingState();
                 } else {
                     console.error("XHR error:", xhr.statusText);
+                    resetLoadingState();
                 }
             }
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/assets/js/slimstat-chart.js` around lines 100 - 152, The AJAX handler
hides chartWrap before sending but only restores it on the successful branch;
update xhr.onreadystatechange (and the JSON parse) to ensure the
loadingIndicator is removed and chartWrap.style.display is set back to "block"
on all failure paths (non-200 status, JSON.parse errors, and when result.success
=== false). Specifically, wrap JSON.parse in try/catch inside
xhr.onreadystatechange, handle xhr.status !== 200 and result.success === false
by cleaning up (inside.removeChild(loadingIndicator) and chartWrap.style.display
= "block"), and consolidate final cleanup so both the happy path and any
early-return error cases restore the UI; reference chartWrap, loadingIndicator,
inside, xhr.onreadystatechange, and result in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@admin/assets/js/slimstat-chart.js`:
- Around line 87-89: The guard clause that checks element, chartCanvas, inside,
and chartWrap currently emits a console.warn with chartId; remove that
console.warn so the function simply returns when the required DOM pieces are
missing (i.e., keep the if (!element || !chartCanvas || !inside || !chartWrap) {
return; } behavior) to avoid introducing production console output in
slimstat-chart.js.

In `@admin/config/index.php`:
- Around line 941-960: The GDPR banner string registration currently done in the
settings save flow (the $gdpr_translatable loop that calls
do_action('wpml_register_single_string', ...) and pll_register_string(...)) must
be made idempotent and invoked during plugin init/upgrade as well so
pre-existing custom banner text is registered for WPML/Polylang; extract that
loop into a reusable function (e.g., register_gdpr_translatable_strings) and
call it from your upgrade/init sequence (or hook into admin_init/plugin_loaded)
in addition to the save handler, ensuring it skips empty values and safely
re-registers the same keys without side effects.

In `@admin/index.php`:
- Around line 735-740: The migration currently forces
wp_slimstat::$settings['use_separate_menu'] = 'on' for all sites < 5.4.1; change
it to only modify legacy/unset values: read
wp_slimstat::$settings['use_separate_menu'] and if the key is not set, is empty,
or equals the legacy 'no' value, then set it to 'on'; otherwise leave the
existing value untouched so existing user preferences are preserved (keep the
version_compare check and update behavior confined to the migration block).

In `@languages/wp-slimstat.pot`:
- Around line 5489-5495: Unify the negative consent label to a single key (use
"Decline") so translations aren’t split; update the POT entries in
languages/wp-slimstat.pot to remove msgid "Deny" and keep only msgid "Decline",
and then update all code references that emit the button label (e.g., in
src/Services/GDPRService.php and wp-slimstat.php where __('Deny') or similar is
used) to use __('Decline') (or the chosen translation helper) so every banner
path uses the same translation key.

In `@src/Services/GDPRService.php`:
- Around line 210-223: The callback used in preg_replace_callback is too
restrictive: inside the anonymous function (the one handling $matches and
setting $href) replace the current preg_match('#^(https?://|/)#i', $href) check
with a pattern that accepts any valid URL scheme plus protocol-relative and
root-relative URLs so mailto:, tel:, ftp:, etc. are preserved; e.g. test for a
scheme per RFC (like '^[a-z][a-z0-9+.\-]*:'), protocol-relative ('//') or
root-relative ('/'), and return $matches[0] when that test passes, otherwise
return $matches[2].

---

Outside diff comments:
In `@admin/assets/js/slimstat-chart.js`:
- Around line 100-152: The AJAX handler hides chartWrap before sending but only
restores it on the successful branch; update xhr.onreadystatechange (and the
JSON parse) to ensure the loadingIndicator is removed and
chartWrap.style.display is set back to "block" on all failure paths (non-200
status, JSON.parse errors, and when result.success === false). Specifically,
wrap JSON.parse in try/catch inside xhr.onreadystatechange, handle xhr.status
!== 200 and result.success === false by cleaning up
(inside.removeChild(loadingIndicator) and chartWrap.style.display = "block"),
and consolidate final cleanup so both the happy path and any early-return error
cases restore the UI; reference chartWrap, loadingIndicator, inside,
xhr.onreadystatechange, and result in your changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db1c7172-8ff2-494a-bab4-0f0f7263f145

📥 Commits

Reviewing files that changed from the base of the PR and between 9bb1059 and 85274be.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • admin/assets/js/slimstat-chart.js
  • admin/config/index.php
  • admin/index.php
  • languages/wp-slimstat.pot
  • readme.txt
  • src/Services/GDPRService.php
  • wp-slimstat.php

Comment on lines +87 to +89
if (!element || !chartCanvas || !inside || !chartWrap) {
console.warn("SlimStat: Could not find chart elements for chart " + chartId);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Please don't add new console output here.

Line 88 adds a production console.warn() for a guard path that already fails safely by returning early.

As per coding guidelines "Do not include var_dump(), console.log(), or debug code in shipped JavaScript; remove before release."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/assets/js/slimstat-chart.js` around lines 87 - 89, The guard clause
that checks element, chartCanvas, inside, and chartWrap currently emits a
console.warn with chartId; remove that console.warn so the function simply
returns when the required DOM pieces are missing (i.e., keep the if (!element ||
!chartCanvas || !inside || !chartWrap) { return; } behavior) to avoid
introducing production console output in slimstat-chart.js.

Comment on lines +941 to +960
// Register GDPR banner strings for WPML/Polylang translation
$gdpr_translatable = [
'opt_out_message' => wp_slimstat::$settings['opt_out_message'] ?? '',
'gdpr_accept_button_text' => wp_slimstat::$settings['gdpr_accept_button_text'] ?? '',
'gdpr_decline_button_text' => wp_slimstat::$settings['gdpr_decline_button_text'] ?? '',
];

foreach ($gdpr_translatable as $name => $value) {
if (empty($value)) {
continue;
}

// WPML registration
do_action('wpml_register_single_string', 'wp-slimstat', $name, $value);

// Native Polylang registration
if (function_exists('pll_register_string')) {
pll_register_string($name, $value, 'wp-slimstat', ($name === 'opt_out_message'));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Register existing banner strings outside the save flow.

This loop only runs after a settings save. On upgraded sites that already have custom banner text, GDPRService will try to translate these names before WPML/Polylang has ever seen them, so the new translation feature looks broken until someone manually re-saves the settings page. Please make this registration idempotent and call it during upgrade/init as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/config/index.php` around lines 941 - 960, The GDPR banner string
registration currently done in the settings save flow (the $gdpr_translatable
loop that calls do_action('wpml_register_single_string', ...) and
pll_register_string(...)) must be made idempotent and invoked during plugin
init/upgrade as well so pre-existing custom banner text is registered for
WPML/Polylang; extract that loop into a reusable function (e.g.,
register_gdpr_translatable_strings) and call it from your upgrade/init sequence
(or hook into admin_init/plugin_loaded) in addition to the save handler,
ensuring it skips empty values and safely re-registers the same keys without
side effects.

Comment on lines +735 to +740
// --- Updates for version 5.4.1 ---
// Fix admin bar migration: empty('no') returned false in 5.4.0, missing users with legacy 'no' value
// Safe because this runs once (version bumps to 5.4.1 after), users who disable later are already on 5.4.1+
if (version_compare(wp_slimstat::$settings['version'], '5.4.1', '<')) {
wp_slimstat::$settings['use_separate_menu'] = 'on';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This migration overwrites existing menu preferences.

Line 739 forces use_separate_menu to 'on' for every site upgrading from < 5.4.1, including installs where the admin already chose the other menu mode. That makes the upgrade change behavior even when no migration is needed. Please gate this to unset/legacy values only.

Suggested guard
 if (version_compare(wp_slimstat::$settings['version'], '5.4.1', '<')) {
-    wp_slimstat::$settings['use_separate_menu'] = 'on';
+    if (
+        ! array_key_exists('use_separate_menu', wp_slimstat::$settings) ||
+        '' === wp_slimstat::$settings['use_separate_menu']
+    ) {
+        wp_slimstat::$settings['use_separate_menu'] = 'on';
+    }
 }

Based on learnings: Applies to **/*.php : Migrate options/globals on upgrade; do not rename or remove without a migration path

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/index.php` around lines 735 - 740, The migration currently forces
wp_slimstat::$settings['use_separate_menu'] = 'on' for all sites < 5.4.1; change
it to only modify legacy/unset values: read
wp_slimstat::$settings['use_separate_menu'] and if the key is not set, is empty,
or equals the legacy 'no' value, then set it to 'on'; otherwise leave the
existing value untouched so existing user preferences are preserved (keep the
version_compare check and update behavior confined to the migration block).

Comment on lines +5489 to 5495
#: src/Services/GDPRService.php:227
#: wp-slimstat.php:821
msgid "Accept"
msgstr ""

#: src/Services/GDPRService.php:187
#: src/Services/GDPRService.php:230
msgid "Deny"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use one default decline label across all banner paths.

These entries now expose both Deny and Decline for the same negative consent action. That splits translations into two keys, so the new WPML/Polylang support can still miss one render path and show inconsistent button text.

Also applies to: 5892-5894

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@languages/wp-slimstat.pot` around lines 5489 - 5495, Unify the negative
consent label to a single key (use "Decline") so translations aren’t split;
update the POT entries in languages/wp-slimstat.pot to remove msgid "Deny" and
keep only msgid "Decline", and then update all code references that emit the
button label (e.g., in src/Services/GDPRService.php and wp-slimstat.php where
__('Deny') or similar is used) to use __('Decline') (or the chosen translation
helper) so every banner path uses the same translation key.

Comment on lines +210 to +223
// Strip links that don't point to real URLs (legacy accept/deny controls)
// Keeps: https://..., http://..., /path, //protocol-relative
// Strips: #, #fragment, empty, whitespace-only — preserves inner text
$message = preg_replace_callback(
'/<a\s[^>]*href\s*=\s*["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is',
function ($matches) {
$href = trim($matches[1]);
if (preg_match('#^(https?://|/)#i', $href)) {
return $matches[0]; // Real URL — keep the link
}
return $matches[2]; // Not a real URL — keep text, strip tag
},
$message
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't strip valid non-HTTP links from the banner.

After wp_kses() has already sanitized the anchor, the callback on Line 217 still removes safe schemes like mailto: and tel: because it only whitelists http(s) and slash-prefixed URLs. That means legitimate consent/privacy/contact links can still disappear.

Suggested fix
 		$message = preg_replace_callback(
 			'/<a\s[^>]*href\s*=\s*["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is',
 			function ($matches) {
 				$href = trim($matches[1]);
-				if (preg_match('#^(https?://|/)#i', $href)) {
+				$scheme = wp_parse_url($href, PHP_URL_SCHEME);
+
+				if (
+					0 === strpos($href, '/') ||
+					(is_string($scheme) && in_array(strtolower($scheme), wp_allowed_protocols(), true))
+				) {
 					return $matches[0]; // Real URL — keep the link
 				}
 				return $matches[2]; // Not a real URL — keep text, strip tag
 			},
 			$message
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Services/GDPRService.php` around lines 210 - 223, The callback used in
preg_replace_callback is too restrictive: inside the anonymous function (the one
handling $matches and setting $href) replace the current
preg_match('#^(https?://|/)#i', $href) check with a pattern that accepts any
valid URL scheme plus protocol-relative and root-relative URLs so mailto:, tel:,
ftp:, etc. are preserved; e.g. test for a scheme per RFC (like
'^[a-z][a-z0-9+.\-]*:'), protocol-relative ('//') or root-relative ('/'), and
return $matches[0] when that test passes, otherwise return $matches[2].

parham tehrani and others added 2 commits March 9, 2026 20:10
- Fix clearTimeout to clearInterval for interval handle
- Add _lastManualRefreshTime to track manual refresh events
- Show 0:00 for 2 seconds after manual refresh to provide visual feedback
- Replace unused _refresh_timer with functional _lastManualRefreshTime

Fixes #153

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix refresh button not resetting countdown timer (#153)
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@admin/assets/js/admin.js`:
- Around line 1470-1477: The early return when timeSinceManualRefresh < 2000
prevents dispatching the minute pulse event and delays real-time updates; before
returning in that conditional (inside the block that checks
SlimStatAdmin._lastManualRefreshTime) ensure you still dispatch the minute pulse
(the same event emitted elsewhere, e.g. 'slimstat:minute_pulse' or by calling
the existing minute pulse emitter) and then reset lastTriggerMinute = -1 and
return; reference SlimStatAdmin._lastManualRefreshTime and lastTriggerMinute to
locate the block and add the dispatch so the minute pulse always fires even
after a manual refresh.
- Around line 1449-1451: Currently the code sets
SlimStatAdmin._lastManualRefreshTime inside the refresh path when id ==
"slim_p7_02", which makes auto-refreshes appear manual; remove that assignment
from refresh_report() and instead set SlimStatAdmin._lastManualRefreshTime at
the actual manual trigger site(s). Concretely, either (a) delete the block that
sets _lastManualRefreshTime in refresh_report()/where id=="slim_p7_02", and add
a line that sets SlimStatAdmin._lastManualRefreshTime = Date.now() in the manual
refresh handler(s) that call refresh_report("slim_p7_02"), or (b) change
refresh_report(id, {manual: true}) to accept a manual flag and only set
_lastManualRefreshTime when manual===true, updating manual callers to pass that
flag; reference: id "slim_p7_02", function refresh_report, and property
SlimStatAdmin._lastManualRefreshTime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcaedb89-4ef8-4542-9432-7a3ddd774e38

📥 Commits

Reviewing files that changed from the base of the PR and between 85274be and fe5fdb0.

📒 Files selected for processing (1)
  • admin/assets/js/admin.js

Comment on lines 1449 to 1451
if (id == "slim_p7_02") {
SlimStatAdmin._refresh_timer = SlimStatAdminParams.refresh_interval;
SlimStatAdmin._lastManualRefreshTime = Date.now();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Track manual refreshes at the trigger site, not in refresh_report().

refresh_report("slim_p7_02") is also used by the minute-pulse auto-refresh path, so setting _lastManualRefreshTime here makes automatic refreshes look manual and shows the new 0:00 state after non-manual reloads too.

Suggested direction
 jQuery(document).on("click", "[id^=slim_] .refresh", function (e) {
     e.preventDefault();

     var id = jQuery(this).parents(".postbox").attr("id");
+    if (id == "slim_p7_02") {
+        SlimStatAdmin._lastManualRefreshTime = Date.now();
+    }

     // Is this a pagination link?
     if (typeof jQuery(this).attr("href").split("?")[1] == "string") {
         ...
     }

     refresh = SlimStatAdmin.refresh_report(id);
     refresh();
 });
-                        if (id == "slim_p7_02") {
-                            SlimStatAdmin._lastManualRefreshTime = Date.now();
-                        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/assets/js/admin.js` around lines 1449 - 1451, Currently the code sets
SlimStatAdmin._lastManualRefreshTime inside the refresh path when id ==
"slim_p7_02", which makes auto-refreshes appear manual; remove that assignment
from refresh_report() and instead set SlimStatAdmin._lastManualRefreshTime at
the actual manual trigger site(s). Concretely, either (a) delete the block that
sets _lastManualRefreshTime in refresh_report()/where id=="slim_p7_02", and add
a line that sets SlimStatAdmin._lastManualRefreshTime = Date.now() in the manual
refresh handler(s) that call refresh_report("slim_p7_02"), or (b) change
refresh_report(id, {manual: true}) to accept a manual flag and only set
_lastManualRefreshTime when manual===true, updating manual callers to pass that
flag; reference: id "slim_p7_02", function refresh_report, and property
SlimStatAdmin._lastManualRefreshTime.

Comment on lines +1470 to +1477
// Check if a manual refresh happened recently (within 2 seconds)
var timeSinceManualRefresh = Date.now() - SlimStatAdmin._lastManualRefreshTime;
if (timeSinceManualRefresh < 2000) {
jQuery(".refresh-timer").html("0:00");
// Reset the trigger minute to sync with the wall clock after manual refresh
lastTriggerMinute = -1;
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't return before dispatching the minute pulse.

If a refresh finishes during second 0, this early return skips slimstat:minute_pulse for the entire minute. That delays both the real-time auto-refresh and the online-visitors update until the next minute.

Suggested fix
         function slimstat_sync_and_countdown() {
             var now = new Date();
             var currentSeconds = now.getSeconds();
             var currentMinute = now.getMinutes();
 
+            if (currentSeconds === 0 && lastTriggerMinute !== currentMinute) {
+                lastTriggerMinute = currentMinute;
+                window.dispatchEvent(new CustomEvent("slimstat:minute_pulse"));
+            }
+
             var timeSinceManualRefresh = Date.now() - SlimStatAdmin._lastManualRefreshTime;
             if (timeSinceManualRefresh < 2000) {
                 jQuery(".refresh-timer").html("0:00");
-                lastTriggerMinute = -1;
                 return;
             }
-
-            if (currentSeconds === 0 && lastTriggerMinute !== currentMinute) {
-                lastTriggerMinute = currentMinute;
-                window.dispatchEvent(new CustomEvent("slimstat:minute_pulse"));
-            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/assets/js/admin.js` around lines 1470 - 1477, The early return when
timeSinceManualRefresh < 2000 prevents dispatching the minute pulse event and
delays real-time updates; before returning in that conditional (inside the block
that checks SlimStatAdmin._lastManualRefreshTime) ensure you still dispatch the
minute pulse (the same event emitted elsewhere, e.g. 'slimstat:minute_pulse' or
by calling the existing minute pulse emitter) and then reset lastTriggerMinute =
-1 and return; reference SlimStatAdmin._lastManualRefreshTime and
lastTriggerMinute to locate the block and add the dispatch so the minute pulse
always fires even after a manual refresh.

@parhumm parhumm merged commit 1f33c56 into master Mar 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants