🩹 QuizWizz v1.10.0 — Merge-Ready Patch Spec

Do not use as a patch source

These 2026-07-24 diffs no longer apply to live source; current status is in Program Masterplan. Current handoff: 2026-08-27 canonical handoff.

Provenance

These fixes were authored + verified on the wrdp bench, which has no QuizWizz source repo. Per BUILDING.md (locked, signed, SLSA-provenance release pipeline) and SOURCE-PROVENANCE.md, bench edits are development artifacts only — never a shippable release. The release owner MUST re-apply these diffs against the canonical source and rebuild through the signed pipeline. All four are additive/deletion-only and were smoke-verified live (see the Roast and Frontend audit for evidence).

Summary

FixSeverityFilesNature
FE-1 primary button invisible🔴 a11yassets/css/qwizz-finetune.css+4 lines (append)
FE-8 unstyled loading state🟡 UXassets/css/qwizz-player.css, includes/Frontend/QuizEmbed.php+6 CSS lines, +1 class attr
BE-3 dead allow() wrapper🟠 correctnessincludes/RateLimiter.php−8 lines (delete)
BE-7 dead QUIZWIZZ_VER alias🟡 hygienequizwizz.php−2 lines (delete)

FE-1 — restore primary-button fill

Root cause: qwizz-finetune.css is enqueued after qwizz-builder.css (includes/Frontend/Shortcode.php:143) and re-declares a bare global .qwizz-btn--primary{background:var(--qe-violet)}. --qe-violet is scoped only to .qwizz-editor, so for any primary button outside the editor the var() is invalid → transparent background wins by source order → white text on a transparent fill (invisible). Fix co-locates a higher-specificity corrective rule at the end of the same file (it loads only when the leak can occur).

--- a/assets/css/qwizz-finetune.css
+++ b/assets/css/qwizz-finetune.css
@@ -308,3 +308,7 @@
 		font-size: 21px;
 	}
 }
+
+/* FE-1: restore primary-button fill where editor-scoped var(--qe-violet) would leak (invalid outside .qwizz-editor). */
+.qwizz-builder .qwizz-btn--primary,
+.qwizz-player .qwizz-btn--primary{background:linear-gradient(135deg,var(--qw-v),var(--qw-v2))!important;border:0!important;color:#fff!important}

Optional cleaner alternative (root-cause scope instead of override): change finetune.css’s two bare rules from .qwizz-btn--primary… to .qwizz-editor .qwizz-btn--primary… so they can never leak. Equivalent result; the append is preferred for a built artifact because it avoids editing the minified line.


FE-8 — visible loading state (spinner)

.qwizz-player__loading was styled only as {padding:42px;text-align:center} (no spinner); the class is otherwise referenced only in qwizz-player.js. Fix adds a reduced-motion-safe spinner and applies the class to the server-rendered initial loader so the pre-hydration flash matches. Failure/retry already exist (qwizz-player.js renderFailure() + Retry-After) and are untouched.

--- a/assets/css/qwizz-player.css
+++ b/assets/css/qwizz-player.css
@@ -58,3 +58,9 @@
 
 /* Keep every question compact enough to read as a single decision. */
 .qwizz-player{max-width:740px;margin-block:clamp(20px,4vw,48px)}...
+
+/* FE-8: visible loading state with reduced-motion-safe spinner. */
+.qwizz-player__loading{display:grid;place-items:center;gap:14px;padding:clamp(40px,8vw,72px) 24px;color:var(--qw-muted);font-size:.95rem;text-align:center}
+.qwizz-player__loading::before{content:"";width:34px;height:34px;border-radius:50%;border:3px solid var(--qw-line);border-top-color:var(--qw-v);animation:qwizz-player-spin .8s linear infinite}
+@keyframes qwizz-player-spin{to{transform:rotate(360deg)}}
+@media(prefers-reduced-motion:reduce){.qwizz-player__loading::before{animation:none}}
--- a/includes/Frontend/QuizEmbed.php
+++ b/includes/Frontend/QuizEmbed.php
@@ -36 +36 @@ public static function render( int $quiz_id ): string {
-		return '<div ' . implode( ' ', $serialized ) . '><div class="qwizz-player__card"><p>' . esc_html__( 'Loading quiz…', 'quizwizz' ) . '</p></div><div class="qwizz-player__live" role="status" aria-live="polite" aria-atomic="true"></div></div>';
+		return '<div ' . implode( ' ', $serialized ) . '><div class="qwizz-player__card"><p class="qwizz-player__loading">' . esc_html__( 'Loading quiz…', 'quizwizz' ) . '</p></div><div class="qwizz-player__live" role="status" aria-live="polite" aria-atomic="true"></div></div>';

BE-3 — delete dead RateLimiter::allow()

Zero callers plugin-wide; the wrapper returns true on storage error (the footgun its own doc comment warns about). consume() is the real API and stays.

--- a/includes/RateLimiter.php
+++ b/includes/RateLimiter.php
@@ -108,15 +108,7 @@
 		delete_option( 'qwizz_rate_limit_last_error' );
 		return true;
 	}
-
-	/**
-	 * Compatibility wrapper. New code should use consume() so storage failures
-	 * remain distinguishable from actual throttling.
-	 */
-	public static function allow( string $scope, int $limit, int $window = HOUR_IN_SECONDS ): bool {
-		return true === self::consume( $scope, $limit, $window );
-	}
-
 	/** @return bool|WP_Error */
 	private static function increment( string $bucket, int $limit, int $window ) {

BE-7 — delete dead QUIZWIZZ_VER alias

Used only at its own definition. QUIZWIZZ_VERSION (line 22) is the real constant and stays.

--- a/quizwizz.php
+++ b/quizwizz.php
@@ -20,8 +20,6 @@
 define( 'QUIZWIZZ_URL', plugin_dir_url( __FILE__ ) );
 define( 'QUIZWIZZ_VERSION', '1.10.0' );
-// Backward-compatible internal alias retained for older integrations.
-define( 'QUIZWIZZ_VER', QUIZWIZZ_VERSION );
 
 require QUIZWIZZ_PATH . 'vendor/autoload.php';

Verification performed on bench

  • FE-1: live builder page — CTA “Online spielen” computed backgroundImage: linear-gradient(135deg, rgb(109,40,217), rgb(91,…)), width 286px (was rgba(0,0,0,0) / border:0 none, invisible).
  • FE-8: served qwizz-player.css rendered — .qwizz-player__loading::before = 34px, border-top-color: rgb(109,40,217) (--qw-v), border-radius:50%, animation-name: qwizz-player-spin.
  • BE-3/BE-7: wp plugin deactivate/activate quizwizz → Success (no fatal); defined("QUIZWIZZ_VER")→false, defined("QUIZWIZZ_VERSION")→true; method_exists(RateLimiter,"allow")→false, consume→true; rate-limit REST endpoint still HTTP 200.

Deferred to release owner (not executed)

  • BE-1 prune vendor/mpdf locale/font payload; BE-2 lower PHP floor to 8.1 + syntax gate; BE-4/5 GenerateController DI refactor; BE-6 collapse AI settle/release duplication; BE-8 WP compat CI + real “Tested up to”; redundant random_compat. Studio design-shell work (FE-3-chrome, FE-6 settings rhythm) is a separate design pass.