Repository navigation
Feat: show a live count next to counter checklist tasks - #1218
Conversation
Resolves #1097 and #467. Counter tasks (excerpt/title/word count, tag and category count, taxonomy counts, internal/external link count) only showed pass/fail, giving no sense of how close the content was to the configured range. This adds a small live badge, e.g. "(42 / 60)", next to the task label, updating on the same tic/content-change cycle each counter already uses. Two shared helpers in meta-box.js: - format_live_count(count, min, max): "count / max" when a max is configured, "count (min N)" for a minimum-only rule, else just the count. - update_live_count(id, text): updates the badge in the metabox DOM and appends it to ppChecklists.requirements[id].label (caching the original label first, so repeated updates replace rather than stack the badge). Wired into all 9 counter-type tasks across both the classic editor and Gutenberg code paths (11 call sites): tags, categories, hierarchical and non-hierarchical taxonomies, excerpt and title character counts, word count, and internal/external link counts. image_alt_count is intentionally NOT covered: it validates each image's alt text length separately (Array.every()), so there is no single number to show. No changes needed in gutenberg-panel.jsx: the sidebar already renders requirement labels via dangerouslySetInnerHTML and re-syncs whenever 'pp-checklists.requirements-updated' fires, which every tic handler already triggers after a status check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two issues caught in review before opening the PR:
- format_live_count()'s min-only branch returned an already-
parenthesized string ("42 (min 50)"), which update_live_count()
then wrapped in another pair of parens for both the label and the
DOM badge, producing "(42 (min 50))" for every min-only counter task
(a common configuration - filled_excerpt, title_count, and most of
the count-type tasks in this plugin are typically configured with
only a minimum). Changed to a bare "42, min 50" so the single
outer wrap in update_live_count produces clean text.
- The live badge is appended into req.label, which
gutenberg-panel.jsx's normalizeLabelForSort() also uses to build the
alphabetical-sort key when "Checklist task sort order" is set to
Alphabetical. Since the badge's digits change as the user types, two
counter tasks that happen to share a label (e.g. after using the
per-task label rename feature) could visibly swap positions in the
sidebar on every keystroke. normalizeLabelForSort() now strips the
.pp-checklists-live-count element before extracting the sort text.
Rebuilt gutenberg-panel.min.js via webpack (NODE_ENV=development, to
match how the checked-in bundle is already built - production mode
produces a fully minified single line that would otherwise replace the
whole file). gutenberg-warning.min.js is untouched, matching its
unmodified .jsx source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Merge-order note: this PR and #1217 (internal-links fix) both insert a new method at the same spot in It's mechanical to resolve — the two additions don't call each other or share state, so it's just a manual splice keeping both methods — but flagging it now so it isn't a surprise during merge. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23aaddabdc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| req.pp_base_label = req.label; | ||
| } | ||
|
|
||
| req.label = req.pp_base_label + ' <span class="pp-checklists-live-count">(' + displayText + ')</span>'; |
There was a problem hiding this comment.
Update Gutenberg labels without mutating existing state
In Gutenberg, updateRequirements() stores the original ppChecklists.requirements objects directly in component state. Mutating req.label here therefore also changes this.state.requirements before the requirements-updated hook calculates prevHash; the previous and next hashes match and no setState() occurs. For counters updated by the periodic TIC handlers, such as title, excerpt, tags, and categories, the sidebar can retain the previous count until an unrelated React render. Build a new requirement object or otherwise perform an immutable state update so the hook reliably renders the new badge.
Useful? React with 👍 / 👎.
| var min = parseInt(config.value[0]), | ||
| max = parseInt(config.value[1]); | ||
|
|
||
| PP_Checklists.update_live_count(requirementId, PP_Checklists.format_live_count(count, min, max)); |
There was a problem hiding this comment.
Initialize counter caches so zero values render
When a post initially has zero words, this call is skipped because lastCount starts at 0 and the equality guard returns before reaching it. The same initial-zero suppression affects Gutenberg internal/external links and Classic word/external-link counts, leaving those tasks without any badge until the count first becomes nonzero. Initialize the caches to an unset sentinel or force the first refresh so the promised current value includes zero.
Useful? React with 👍 / 👎.
| if (max_value > 0) { | ||
| return count + ' / ' + max_value; |
There was a problem hiding this comment.
Treat a lower maximum as a minimum-only rule
For supported configurations where min_value > max_value > 0, validation and the generated requirement label intentionally treat the rule as minimum-only, but this formatter takes the max_value > 0 branch and displays values such as 42 / 20 for a minimum of 50. That badge contradicts both the task label and pass/fail behavior; mirror check_valid_quantity() by preferring the minimum-only representation when the maximum is below the minimum.
Useful? React with 👍 / 👎.
| * @return {void} | ||
| */ | ||
| update_live_count: function (requirementId, displayText) { | ||
| var req = (typeof ppChecklists !== 'undefined') ? ppChecklists.requirements[requirementId] : null; |
There was a problem hiding this comment.
Wire live counts into the Yoast counter tasks
When the Yoast integration is enabled, Focus_Keyword and Meta_Description are Base_counter requirements with single character counts, but their handlers in modules/yoastseo/assets/js/meta-box.js still only trigger status updates and never call this new helper. Those configured counter tasks therefore remain pass/fail-only while all core counters show a badge, so invoke update_live_count() from both Yoast handlers as well.
Useful? React with 👍 / 👎.
| if (min_value > 0) { | ||
| return count + ', min ' + min_value; |
There was a problem hiding this comment.
Localize the minimum-count marker
For every minimum-only counter, this newly visible user-facing text is hard-coded as English. Consequently translated editor screens show an otherwise localized requirement followed by (42, min 50). Supply the marker through the existing localized script data or a WordPress i18n call so the badge follows the active locale.
Useful? React with 👍 / 👎.
Review: tested on a fresh SandyWP sandbox (WP 7.1.3, PHP 8.3), block editor and Classic Editor
Verdict: Not yet. The badges work, but several Codex findings reproduce, including the Gutenberg one. Setup
Recordings
Classic editor, Classic editor, this PR (real wp-admin, new post): Block editor, this PR, after the title and content edits (badges correct once the sidebar re-renders): Block editor, this PR, 8 s after changing the title to 36 chars: the badge still says (2 / 60). What blocks merging
Notes: the Yoast focus keyword and meta description counters don't get badges (Codex P2). That's fine as a follow-up. Reviewed on a throwaway SandyWP sandbox; nothing was pushed to this branch. |
Resolve the meta-box.js conflict with #1217 by keeping both format_live_count()/update_live_count() and get_internal_link_hosts()/ normalize_link_hosts(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…slatable - Gutenberg sidebar: keep copies of the requirement objects in state and re-read ppChecklists.requirements on requirements-updated, so in-place label/status changes from meta-box.js produce a new hash and re-render. The title badge no longer stays stale while typing. - Start the word/internal/external link count caches at null so a count of 0 renders a badge on new posts. - format_live_count(): a max lower than the min is ignored, mirroring check_valid_quantity(), so min 50 / max 20 shows "42, min 50". - Badge formats are localized (live_count_max, live_count_min) and escaped before going into the label HTML. - Show the badge for the Yoast focus keyword and meta description counters. - Rebuild gutenberg-panel.min.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on one line The pre-publish panel registered its update-failed-requirements handler under the 'publishpress/checklists' namespace, which the sidebar panel removes when it unmounts, so the panel never rendered. Give it its own namespace and register it on mount. Rebuild gutenberg-warning.min.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Update: review findings fixed, tested on fresh SandyWP sandboxes (WP 7.1.3, PHP 8.3)Verdict: Ready to merge. Every finding from the earlier review and all 5 Codex comments are fixed. Badges now update as you type in the block editor sidebar, the pre-publish panel and the Classic Editor metabox. No console errors and no PHP notices from the plugin. What changed (pushed to this branch, no force-push)
How I testedTwo fresh sandboxes (not cloned from customer data):
Driven with real key presses (Playwright + Chromium):
Evidence
Test siteOpen a copy of the test site: each click starts a fresh copy, signed in to wp-admin as
Open question from the PR description (badge always on vs. opt-in setting) is still open for a product decision. It doesn't block merging. |
Brings in #1218 (live count badges). Its updateRequirements() change stores { ...req } copies, so a custom task toggled in the block editor Checklists panel now re-renders immediately instead of waiting for the next unrelated change. Conflicts: kept both the a11y strings and the live count strings in checklists.php; rebuilt gutenberg-panel.min.js and gutenberg-warning.min.js with webpack --mode development. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Shows the current value of counter checklist tasks in the editor, updating live as the author types.
Resolves #1097 and #467.
Why
Counter tasks only reported pass or fail. A writer could see that the excerpt was too short, with no idea by how much. #467 put it well: writers "are finding it difficult to estimate how much they've written" — and explicitly asked for it beyond the excerpt.
How
Adds
PP_Checklists.update_live_count(), which renders the value as a small badge in two places:ppChecklists.requirements, which is what the Gutenberg sidebar renders (no source change needed there — it already renders labels viadangerouslySetInnerHTMLand re-syncs wheneverpp-checklists.requirements-updatedfires, which every counter's tic handler already triggers)The original label is cached as
pp_base_label, so repeated updates replace the badge rather than stacking it.Coverage
Wired into every counter task that has a single meaningful value, in both editors: excerpt/title character counts, word count, tag/category counts, hierarchical and non-hierarchical taxonomy counts, and internal/external link counts.
image_alt_countis deliberately excluded: it validates each image's alt text separately (Array.every()), so there is no single number to display.Open question for reviewers
The badge is currently always on. If you'd rather it were opt-in, it would be easy to gate behind a setting in the Appearance section — say the word and I'll add it.
Testing
(42 (min 50))— double-parenthesized — now renders as(42, min 50)).normalizeLabelForSort), so two counter tasks sharing a label (via the label-rename setting) no longer swap position on every keystroke as their live counts change.gutenberg-panel.min.jsvia webpack in development mode, matching how the checked-in bundle is already built.Not yet exercised in a live WordPress install — the Gutenberg sidebar path in particular deserves a manual look before merge.