Commit Graph
13 Commits
Author SHA1 Message Date
kristianwindandClaude Opus 5 cb58ffe26e 1.5.4: the text follows the rename, and a check keeps it that way
Kristian read the readme and found it still telling people to configure the
plugin under WooCommerce, for a page that moved to the Nolimit menu three
releases ago. Sweeping for it found seven stale places, and three of them are
notices THE PLUGIN SHOWS THE USER — the missing-WooCommerce notice, the
activation error and the incomplete-install warning all still called it
WooCommerce Email Discount Manager.

That is the mimir.guide failure exactly: the code moved, the text stood still,
and nothing failed. It was found by somebody reading, which is the only way it
could be found, and that is the argument for the check rather than for being
more careful next time.

ci/static.sh now asserts two things. The name in readme.txt's title matches the
Plugin Name header, because the header is what WordPress shows and everything
else has to agree with it. And no shipped PHP file contains OLD_PLUGIN_NAME,
which is now a value in ci/plugin.conf rather than a habit.

The changelog is deliberately exempt: an entry describes what was true for that
release, and rewriting it would be a different lie. Only text describing the
CURRENT state is checked.

Both halves proven red — a readme title that disagrees, and the old name put
back into one notice.

Not changed: the page stays under Nolimit rather than gaining a second entry
under WooCommerce. One menu is the family's shape, and a duplicate entry is
clutter rather than a fix for a readme that was simply wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 22:39:19 +02:00
kristianwindandClaude Opus 5 6a789349ec 1.5.3: shared-menu 1.2.2, and run the glyph style instead of reading it
678f1f03 is withdrawn. It hard-coded the icon box at 36x34, and WordPress
declares that parent three times with different sizes — 36x34 open, 35x30
folded, 34x30 under 960px — so a fixed number is correct in one state and 4px
too tall in the two a laptop is usually in. cache-manager measured where the
ink lands rather than where the box is and found it worse than that: the glyph
moved from 2.5px above centre to 2.5px below in the folded states, the same
error mirrored, in a state that had been nearly right. 1.2.2 removes the number
entirely with width/height at 100%.

1.5.2 is already published with 678f1f03, so this tree shipped the withdrawn
version. Checked before anything else, because a later candidate fatalled on
every admin page with "Unknown format specifier": that file was never taken
here, and the published 1.5.2 emits its stylesheet cleanly — verified by
running sprintf over its format string rather than reading it. So the shop is
cosmetically off in folded menus, not broken.

New assertion, and it is the one this round is really about: the style
function is INVOKED and its output captured, rather than the source being
read. The fatal that prompted it survived three sessions reading the file,
a clean php -l, a clean PHPCS, and two browsers agreeing about the CSS —
because every one of those was fed the stylesheet by hand rather than through
the printf that produces it. A literal per-cent must be doubled in a format
string, and nothing that looks at the file can see that.

Proven red with that exact fault rather than an arbitrary one: undoubling the
per-cent gives "ValueError: Unknown format specifier ';'".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 19:07:44 +02:00
kristianwindandClaude Opus 5 6114473be0 1.5.2: take shared-menu 1.2.1, which centres the glyph
Kristian reported the infinity sign sitting above centre in the admin menu on
the live shop. Security traced it to WordPress's own stylesheet rather than
adjusting it by eye: #adminmenu div.wp-menu-image is 36x34 and its :before
carries padding 7px 0, which is exactly 7 + 20 + 7 for a 20px dashicon at
line-height 1. The 1.2.0 rule set line-height 1.4 and inherited that padding,
so the pseudo-element was 42px tall inside a 34px parent.

1.2.1 reproduces the box, drops the padding and centres with flex — geometry
rather than a nudge, because U+221E sits on the maths axis instead of filling
its em box.

It is the same half-done abstraction as the escaping fix: the filter made the
character configurable and left the geometry tuned for one specific character.
Nothing could fail, because the only value anyone ever passed was the one it
was tuned for.

Verified before copying, including the part that makes the check meaningful:
Security predicted php -w WOULD differ this round, since the executable code
changed — so equal stripped hashes would have meant the wrong file. They
differ, and the diff is the version constant plus the CSS geometry, with
wpmgr_css_escape untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 18:52:51 +02:00
kristianwindandClaude Opus 5 526faf2c4c 1.5.1
The published 1.5.0 carries includes/shared-menu.php at f4862d5c — contract
1.0.0, the oldest of the six, in an artefact a shop downloads. It was built
before the family froze 1.1.0 and again 1.2.0, so the zip is older than the
repo in the one way that reaches a customer.

This release carries 9012fe98, so the menu shows the infinity glyph and the
glyph is emitted as CSS numeric escapes rather than through esc_attr().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 09:59:43 +02:00
kristianwindandClaude Opus 5 4c7731ae01 Assert the page really lands under the Nolimit menu, and bump to 1.5.0
Kristian's menu screenshot showed four plugins under Nolimit and not this one,
after a build that moved the page there had already merged. Two separate
problems behind that.

The version never moved. The rename and the menu change went out still calling
themselves 1.4.6 — the same number as the copy already installed. So two
different builds carried one version, which is the one thing a version number
exists to prevent, and nothing offered the new one as an update. Now 1.5.0.

And the tests could not have caught it if the code HAD been wrong. The only
menu assertion was that this plugin claims the label through
wpmgr_menu_title — which passes while the plugin is absent from the menu
entirely. The label and the membership are different facts, and the screenshot
showed exactly that pair: the menu said Nolimit, this plugin was not in it.

Three assertions now. The parent exists after wpmgr_ensure_menu(), this
plugin's page is among the children of WPMGR_MENU_SLUG, and — separately — the
main file lists includes/shared-menu.php in its is_admin() branch.

That third one is there because the first two are a reduced repro and say so:
the admin menu is only built in an admin request, and these run under WP-CLI
where is_admin() is false and class-admin.php is never included, so the test
has to require the files by hand. A by-hand require would pass even if the
plugin never loaded them in a real request. That is the half a reduced repro
always omits, so it is asserted on its own rather than assumed.

Checked while here, from the security session's report: no human_time_diff
anywhere in this plugin, so the half-translated "4 timer ago" seam does not
exist here. Nothing to fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 07:52:01 +02:00
kristianwindandClaude Opus 5 f14c51dcf2 Rename the repository to woo-discount-manager, keep the installed slug
Kristian named the five plugins as a family, and three repos disagreed with what
he calls them. His decision: rename the repo, never the installed slug.

MEASURED BEFORE DOING IT, because four published releases carry the old URL in
their Update URI header. A throwaway repo was created, renamed and deleted:

    old API path                    301 -> new
    old web path                    301 -> new
    old /releases/download/... path 301 -> new

and WordPress follows up to 5 redirects by default (class-wp-http.php:191), on
GET. Confirmed afterwards against the real thing: the old feed URL resolves to
v1.4.5 and the old download path still serves 48185 bytes.

So a shop on 1.4.2 keeps updating through the redirect, and 1.4.6 moves it onto
the new address directly.

PLUGIN_SLUG and PLUGIN_MAIN are deliberately untouched and now disagree with the
repository name. That is permanent: the installed directory has been
woocommerce-email-discount-manager since the first release and is on a live
shop, and changing it would not move that installation — it would put a SECOND
copy beside it and leave the old one active. ci/plugin.conf says so in the file,
because otherwise somebody will helpfully align them.

Also in, both for the family rather than for this plugin alone:

- docs/REUSE.md gains the hostname-keyed hook as a first-class section. It is
  the only bug we have found that is CAUSED by there being more than one plugin,
  so it is invisible to anyone reading a single repo — along with the same shape
  in a shared-*.php file copied into five plugins, which fatals with "Cannot
  redeclare" on the second activation without function_exists() guards.
- ci/static.sh fails on anything shaped like a credential in a tracked file.
  Suggested by the AI-manager port: a pushed key cannot be unpushed, only
  rotated. Deliberately narrow — sk-ant-, sk-, ghp_, gho_, github_pat_, AKIA,
  PRIVATE KEY blocks — because a broad entropy heuristic fires on minified
  assets and a check that cries wolf gets commented out.

Verified failing before being believed: a file containing sk-ant-api03-... makes
the step report the filename and the suite exit 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:18:50 +02:00
kristianwindandClaude Opus 5 cbad402656 1.4.5
Gets the cross-plugin fix onto a shop. Until this is installed, a plugin
updating from the same host can be offered this one's release as its own, and
the direction that breaks in is decided by load order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:56:27 +02:00
kristianwindandClaude Opus 5 db3658c422 1.4.4
The fifteen-minute cache is the only change here a shop can feel, and it is the
one Kristian hit: a release published and nothing shown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:49:04 +02:00
kristianwindandClaude Opus 5 056be0e917 Credit the plugin to its actual author, and 1.4.3
The header went out as "Author: WEDSMP" with a Plugin URI of example.com. That
is what wp-admin shows to whoever installs it, which is why it is worth a
release of its own rather than waiting for the next change.

Author is now Kristian Wind, with an Author URI pointing at where the source
lives, and the readme credits kristianwind rather than wedsmp.

ci/static.sh now fails on a placeholder in the header. It catches the generic
markers — example.com, TODO, yourname, an unfilled <...> — and it would have
caught the Plugin URI. It would NOT have caught "WEDSMP": a made-up vendor name
is indistinguishable from a real one to any check, so an empty field is warned
about and a wrong-but-plausible one cannot be.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:59:44 +02:00
kristianwindandClaude Opus 5 15f88db2c4 1.4.2
Puts the icon and the working Check again link in front of a shop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:24:57 +02:00
kristianwindandClaude Opus 5 0ac0e35787 1.4.1
The v1.4.0 zip that is already published contains the updater that trusts the
repository's own browser_download_url — the one that reports a plain-HTTP raw
IP. Anyone who installs 1.4.0 keeps that until an update arrives over exactly
the address it gets wrong. It works today, because that address happens to be
reachable, which is the reason to close it now rather than leave it working by
luck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:51:57 +02:00
kristianwindandClaude Opus 5 eca2851d35 Test against the newest WordPress and WooCommerce, and ship updates
Three things this repository could not do: prove the plugin still works on a
WordPress or WooCommerce released after it, get a fix onto a shop without
someone uploading a zip, and do either of those for the next plugin without
starting over.

ci/ is the test harness. ci/site-test.sh builds a throwaway WordPress +
WooCommerce in containers at any versions you name — including `nightly` and
`beta` — installs exactly the files the release zip will contain, and runs
tests/smoke.php inside it with wp eval-file. Real cart, real coupons, real
WC_Shipping_Rate objects: a mocked cart keeps passing after WooCommerce has
changed underneath it, which is the one failure worth catching here. WP_DEBUG
is on and any notice logged from this plugin's directory fails the build,
because that is the shape an upgrade actually breaks in — nothing fails, a
notice appears, and two releases later it is a fatal.

compat.yml runs that weekly against today's releases, the next WordPress and
the next WooCommerce, the oldest PHP the header promises and the newest PHP.
When it is green it opens a PR bumping "Tested up to" and "WC tested up to" —
only when green, because WooCommerce shows that second value to the shop owner
as an untested-plugin banner, and that banner is what they read before deciding
whether to press update.

includes/class-updater.php is the distribution half. It hooks
update_plugins_{hostname} for the host in the new "Update URI" header and reads
the Gitea release API, so WordPress handles the notice, the download, the
"Enable auto-updates" link and wp plugin update with no further code. Update
URI earns its place twice: since 5.8 a plugin that declares one is never asked
about on wordpress.org, so a .org plugin sharing this slug cannot be pushed
onto a shop running this one. The check sends nothing about the site and
overrides WordPress's default user agent, which carries the site's own URL.

release.yml tags, tests, builds, publishes — then reads the feed back with no
credentials. A private repository and a release with no zip attached both leave
a page that looks perfect to anyone logged in and an update no shop is ever
offered.

Measured before committing, all green: WP 7.1 + WC 11.1 on PHP 8.3 and 7.4,
WP 7.2-alpha on 8.3, and the beta cell falling back to stable on 8.4. No
deprecations from this plugin on any of them.

Three guards are there because the bug they catch is invisible without them:
the annotated tag is re-fetched before its message is read (checkout turns it
into a lightweight tag pointing at the merge commit), the two workflow roots
are kept in sync (.gitea/workflows hides .github/workflows entirely), and every
file the code loads must be in the release zip — which caught class-updater.php
being referenced and uncommitted while every local test passed.

docs/REUSE.md is the port to the next plugin: copy ci/, rewrite five lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 10:34:38 +02:00
kristianwind e4a40e53a0 Initial commit: WooCommerce Email Discount Manager plugin 2026-08-23 15:38:14 +02:00