From 226b50642af0895948fbaa623a9b7180399a63b6 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 13 May 2026 19:15:48 +0000
Subject: [PATCH] =Queue fixes

---
 assets/css/nav.min.css                                            |    2 
 inc/managers/queue/Processor.php                                  |   43 +--
 activate.php                                                      |    1 
 inc/managers/ScriptLoader.php                                     |    2 
 inc/meta/Meta.php                                                 |    8 
 inc/integrations/PostMark.php                                     |    4 
 inc/managers/queue/Locker.php                                     |   42 +--
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php |    1 
 inc/admin/SEOAdmin.php                                            |    6 
 inc/helpers/all.php                                               |    2 
 inc/managers/queue/Storage.php                                    |  442 +++++++++++++++++++--------------------
 inc/managers/queue/Queue.php                                      |   85 -------
 inc/rest/routes/FormRoutes.php                                    |    1 
 13 files changed, 267 insertions(+), 372 deletions(-)

diff --git a/activate.php b/activate.php
index 65ec6a9..c76a65d 100644
--- a/activate.php
+++ b/activate.php
@@ -30,7 +30,6 @@
     do_action(BASE.'activation');
 	error_log('Action done!');
 	error_log('Checking custom tables...');
-	Queue::defineTables();
 	CustomTable::ensureTables();
 	error_log('Dashboard is setup: '.print_r(JVB()->dashboard(), true));
 
diff --git a/assets/css/nav.min.css b/assets/css/nav.min.css
index de0c4f6..65848db 100644
--- a/assets/css/nav.min.css
+++ b/assets/css/nav.min.css
@@ -1 +1 @@
-nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav li{width:100%;--justify:center;max-inline-size:none;padding:0;list-style:none}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid var(--base);color:var(--contrast)}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:var(--action-0);color:var(--action-contrast)}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base-rgb),var(--op-6));border:1px solid var(--base-50)}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid var(--action-0);outline-offset:1px}nav.always{--dir:column;--justify:flex-end;position:fixed;bottom:0;right:0;width:var(--btn);z-index:var(--z-10)}nav.always.open{width:100vw;height:100vh;padding-bottom:var(--btn_);background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.always>ul{z-index:1;--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100%;position:relative;right:-300vw;width:100vw;padding:1rem 0 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{--wrap:wrap;--dir:row;height:max-content;--justify:flex-start;background-color:rgba(var(--base-rgb),var(--op-6))}nav.always a{padding:1rem;--justify:center;max-width:calc(100% - var(--btn))}nav.always .has-submenu>a{z-index:var(--z-3)}nav.always .has-submenu>button{width:var(--btn)}nav.always .submenu{position:relative;padding-right:4rem;top:0;border:1px solid var(--action-0);background-color:rgba(var(--contrast-rgb),var(--op-1))}nav.always .submenu li{background-color:rgba(var(--base-rgb),var(--op-3))}nav.always>.toggle{position:fixed;bottom:0;right:0;width:var(--btn);height:var(--btn);background-color:var(--base);color:var(--contrast);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);transition:width var(--trans-base)}nav.always>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always.open>.toggle{width:100%;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:1000000}nav.always.open>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always button .icon-x,nav.always.open button .icon-list{display:none}nav.always button .icon-list,nav.always.open button .icon-x{display:block;--w:32px}@media (min-width:768px){nav.always>ul{padding-top:var(--btn)}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:var(--contrast-200);padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:var(--contrast);text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed{position:fixed;left:0;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom{bottom:0;width:calc(100% - var(--btn))}nav.fixed ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.fixed li{flex:1}nav.fixed a{--gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base-rgb),var(--op-45));max-width:none}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base-rgb),var(--op-6));color:var(--action-contrast)}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:var(--action-0);color:var(--action-contrast)}nav.letters,nav.letters a,nav.letters li,nav.letters ul{height:var(--chip)}nav.letters li{max-width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:calc(100% - var(--btn_));flex-shrink:1;max-width:none}nav.index li:first-of-type{flex-shrink:1;transform:scaleX(1);order:9999;width:var(--btn);height:var(--btn);max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index a{border-bottom:4px solid transparent}nav.index .active a{border-color:var(--action-0);color:var(--contrast)}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:flex-end;padding:0 2rem 0 0;background-color:transparent}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}nav.condensed{--gap:0 .25rem;width:100%}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:var(--contrast);border-color:var(--action-0)}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:stretch;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x}.always ul.socials{width:100%}ul.socials a{padding:.5rem;max-width:none}ul.socials .icon{margin:0}nav.tabs{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:var(--action-0);color:var(--action-contrast);border-color:var(--base)}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color:var(--action-0)}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:center;padding:0 .5rem;background-color:var(--base);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}
\ No newline at end of file
+nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;font-family:var(--heading)}nav,nav a,nav li,nav ol,nav ul{display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;padding:0;margin:0}nav li{width:100%;--justify:center;max-inline-size:none;padding:0;list-style:none}nav a,nav button{--justify:center;width:100%;white-space:nowrap;text-transform:uppercase;border-radius:0;background-color:transparent;text-decoration:none}nav a{padding:var(--padding)}nav .toggle{aspect-ratio:1;border:1px solid var(--base);color:var(--contrast)}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:var(--action-0);color:var(--action-contrast)}.toggle .icon-caret-down{transform:rotate(0);transition:transform var(--trans-base)}.open>.row>.toggle .icon-caret-down,.open>.toggle .icon-caret-down{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;right:0;max-height:0;transform:scaleY(0);transform-origin:top;width:100%;min-width:max-content;background-color:rgba(var(--base-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),var(--op-3));transition:max-height var(--trans-base),transform var(--trans-base);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base-rgb),var(--op-6));border:1px solid var(--base-50)}.submenu a{height:var(--chipchip)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:1px solid var(--action-0);outline-offset:1px}nav.always{--dir:column;--justify:flex-end;position:fixed;bottom:0;right:0;width:var(--btn);z-index:var(--z-10)}nav.always.open{width:100vw;height:100vh;padding-bottom:var(--btn_);background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.always>ul{z-index:1;--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;max-height:100%;position:relative;right:-300vw;width:100vw;padding:var(--btn) 0 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{--wrap:wrap;--dir:row;height:max-content;--justify:flex-start;background-color:rgba(var(--base-rgb),var(--op-6))}nav.always a{padding:1rem;--justify:center;max-width:calc(100% - var(--btn))}nav.always .has-submenu>a{z-index:var(--z-3)}nav.always .has-submenu>button{width:var(--btn)}nav.always .submenu{position:relative;padding-right:4rem;top:0;border:1px solid var(--action-0);background-color:rgba(var(--contrast-rgb),var(--op-1))}nav.always .submenu li{background-color:rgba(var(--base-rgb),var(--op-3))}nav.always>.toggle{position:fixed;bottom:0;right:0;width:var(--btn);height:var(--btn);background-color:var(--base);color:var(--contrast);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);transition:width var(--trans-base)}nav.always>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always.open>.toggle{width:100%;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:1000000}nav.always.open>.toggle:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always button .icon-x,nav.always.open button .icon-list{display:none}nav.always button .icon-list,nav.always.open button .icon-x{display:block;--w:32px}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-5)}nav#breadcrumbs ol{height:max-content;--wrap:wrap}nav#breadcrumbs li{width:max-content;height:var(--chip);--wrap:nowrap}nav#breadcrumbs li::after{content:'/';color:var(--contrast-200);padding:0 .25rem}nav#breadcrumbs li:last-of-type::after{display:none}nav#breadcrumbs a{height:var(--chip)}nav#breadcrumbs a,nav#breadcrumbs span{padding:0 .125rem;color:var(--contrast);text-transform:none}nav#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed{position:fixed;left:0;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom{bottom:0;width:calc(100% - var(--btn))}nav.fixed ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.always.fixed>ul{padding-top:var(--btn)}nav.fixed li{flex:1}nav.fixed a{--gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:100vw;z-index:var(--z-5);background-color:rgba(var(--base-rgb),var(--op-45));max-width:none}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}nav.on-this-page button{order:3;padding:0 1rem;width:max-content;aspect-ratio:unset}nav.on-this-page.open button{order:0}nav.on-this-page ul{width:100%;--gap:0}nav.on-this-page a{padding:0}nav.on-this-page .active a{background-color:rgba(var(--base-rgb),var(--op-6));color:var(--action-contrast)}nav.on-this-page #back-to-top span{display:none}nav.on-this-page .active a{background-color:var(--action-0);color:var(--action-contrast)}nav.letters,nav.letters a,nav.letters li,nav.letters ul{height:var(--chip)}nav.letters li{max-width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:space-between;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}nav.index ul{width:100%}nav.index li{flex-shrink:0;transform:scaleX(0);max-width:0;overflow:hidden}nav.index li.active,nav.index li.adj{transform:scaleX(1);width:calc(100% - var(--btn_));flex-shrink:1;max-width:none}nav.index li:first-of-type{flex-shrink:1;transform:scaleX(1);order:9999;width:var(--btn);height:var(--btn);max-width:none}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}nav.index a{border-bottom:4px solid transparent}nav.index .active a{border-color:var(--action-0);color:var(--contrast)}nav.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;--align:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}nav.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}nav.index.open a{--justify:flex-end;padding:0 2rem 0 0;background-color:transparent}nav.condensed,nav.condensed a,nav.condensed li,nav.condensed ul{height:max-content;width:max-content;--wrap:wrap;min-height:var(--chip)}nav.condensed{--gap:0 .25rem;width:100%}nav.condensed li+li::before{content:'·';padding:0 .25em}nav.condensed a{font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav button:focus{background-color:transparent;color:var(--contrast);border-color:var(--action-0)}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:stretch;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x}.always ul.socials{width:100%}ul.socials a{padding:.5rem;max-width:none}ul.socials .icon{margin:0}nav.tabs{padding-bottom:2px;touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}nav.tabs button.active{cursor:default}nav.tabs button{font-family:var(--heading);font-size:var(--txt-x-small);border-bottom:4px solid transparent}nav.tabs button.active,nav.tabs button.active:hover{background-color:var(--action-0);color:var(--action-contrast);border-color:var(--base)}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color:var(--action-0)}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}.wp-site-blocks>header,body>header{--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:center;padding:0 .5rem;background-color:var(--base);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}.dashboard-nav{width:100%}nav.filters{--dir:row;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}nav.term-navigation:has([hidden]){display:none}
\ No newline at end of file
diff --git a/inc/admin/SEOAdmin.php b/inc/admin/SEOAdmin.php
index 45e215f..007e453 100644
--- a/inc/admin/SEOAdmin.php
+++ b/inc/admin/SEOAdmin.php
@@ -145,7 +145,7 @@
 
 	public function addDashboardSection(string $content, string $page):string
 	{
-		if ($page !== 'jvb-seo') {
+		if ($page !== 'SEO') {
 			return $content;
 		}
 		ob_start();
@@ -203,6 +203,10 @@
 			$this->renderStyles();
 		}
 	}
+		private function renderStyles(): void
+		{
+			jvbInlineStyles('forms');
+		}
 
 	public function renderProperty(string $property, ?string $value, mixed $class):void
 	{
diff --git a/inc/helpers/all.php b/inc/helpers/all.php
index 9d59203..67d79fe 100644
--- a/inc/helpers/all.php
+++ b/inc/helpers/all.php
@@ -54,6 +54,8 @@
 //    delete_option(BASE.'do_these_once');
     //Ensure we have the option starting with BASE
     $option = jvbCheckBase($option);
+//	delete_option($option);
+//	delete_option(BASE.'do_these_once');
     $options = get_option(BASE.'do_these_once', []);
 //    delete_option($option);
     if (!array_key_exists($option, $options)) {// Prevent concurrent runs
diff --git a/inc/integrations/PostMark.php b/inc/integrations/PostMark.php
index 9882d6c..7ac31e6 100644
--- a/inc/integrations/PostMark.php
+++ b/inc/integrations/PostMark.php
@@ -166,10 +166,12 @@
 		$result = $this->sendEmail($payload);
 
 		if ($result === true) {
+			error_log('================================ Email sent! ================================');
 			// Prevent default wp_mail from sending
 			add_filter('pre_wp_mail', '__return_true');
 			do_action('postmark_email_sent', $args, $payload);
 		} else {
+			error_log('=-======================[POSTMARK]Something went wrong... ================================');
 			// Log failure but allow fallback to default mail
 			do_action('postmark_email_failed', $args, $result);
 
@@ -263,7 +265,7 @@
 		}
 		try {
 			$response = $this->postRequest('email', $payload);
-
+			error_log('================================ POSTMARK RESPONSE: ================================'.print_r($response, true));
 			if (is_wp_error($response)) {
 				return $response;
 			}
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
index 6965d2e..f6db0b9 100644
--- a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
@@ -2,3 +2,4 @@
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/City.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/State.php');
diff --git a/inc/managers/ScriptLoader.php b/inc/managers/ScriptLoader.php
index 90de1d6..dc66aaa 100644
--- a/inc/managers/ScriptLoader.php
+++ b/inc/managers/ScriptLoader.php
@@ -2,7 +2,7 @@
 add_action('init', 'jvbRegisterScripts', 5);
 
 function jvbRegisterScripts() {
-	$version = '1.1.5';
+	$version = '1.1.55';
 	$strategy = [
 		'strategy'	=> 'defer',
 		'in_footer'	=> true
diff --git a/inc/managers/queue/Locker.php b/inc/managers/queue/Locker.php
index a048942..4d4b5d6 100644
--- a/inc/managers/queue/Locker.php
+++ b/inc/managers/queue/Locker.php
@@ -1,43 +1,24 @@
 <?php
 namespace JVBase\managers\queue;
-use wpdb;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
-
 class Locker
 {
 	private string $lockKey;
 	private int $timeout;
-	protected wpdb $wpdb;
 	private ?string $token = null;
 
-	public function __construct(string $key = 'queue', int $timeout = 0)
+	public function __construct(string $key = 'queue', int $timeout = 60)
 	{
 		$this->lockKey = BASE . $key . '_lock';
 		$this->timeout = $timeout;
-		global $wpdb;
-		$this->wpdb = $wpdb;
 	}
 
-	/**
-	 * Execute callback with lock, auto-release after
-	 */
 	public function withLock(callable $callback): void
 	{
-		$acquired = $this->wpdb->get_var(
-			$this->wpdb->prepare(
-				'SELECT GET_LOCK(%s, %d)',
-				$this->lockKey,
-				$this->timeout
-			)
-		);
-
-		if ((int) $acquired !== 1) {
-			// Lock already held — just exit quietly
-			return;
-		}
+		if (!$this->acquire()) return;
 
 		try {
 			$callback();
@@ -46,13 +27,18 @@
 		}
 	}
 
-	public function unlock():void
+	private function acquire(): bool
 	{
-		$this->wpdb->get_var(
-			$this->wpdb->prepare(
-				'SELECT RELEASE_LOCK(%s)',
-				$this->lockKey
-			)
-		);
+		$this->token = bin2hex(random_bytes(8));
+		return (bool) wp_cache_add($this->lockKey, $this->token, 'locks', $this->timeout);
+	}
+
+	public function unlock(): void
+	{
+		$current = wp_cache_get($this->lockKey, 'locks');
+		if ($current === $this->token) {
+			wp_cache_delete($this->lockKey, 'locks');
+		}
+		$this->token = null;
 	}
 }
diff --git a/inc/managers/queue/Processor.php b/inc/managers/queue/Processor.php
index ccc4ddb..f54fadd 100644
--- a/inc/managers/queue/Processor.php
+++ b/inc/managers/queue/Processor.php
@@ -14,48 +14,39 @@
 
 	public function run(): void
 	{
-		if (get_transient(BASE.'queue_running')) {
-			return;
-		}
-		set_transient(BASE.'queue_running', true, 60);
 		if (!$this->hasAdequateResources()) {
 			error_log('[Processor] Insufficient resources to start processing');
 			return;
 		}
 
-		$ops = $this->storage->fetchRunnable();
-		if (empty($ops)) {
-			return;
-		}
-		foreach ($ops as $op) {
-			if ($op->state === 'completed') {
-				return;
+		$op = null;
+		$this->storage->withTransaction(function() use (&$op) {
+			$candidates = $this->storage->fetchRunnable();
+			foreach ($candidates as $candidate) {
+				if ($candidate->state === 'completed') continue;
+				if (!$this->dependenciesSatisfied($candidate)) continue;
+				if ($this->storage->markProcessing($candidate->id)) {
+					$op = $candidate;
+					break;
+				}
 			}
-			if (!$this->dependenciesSatisfied($op)) {
-				continue;
-			}
-			if (!$this->storage->markProcessing($op->id)) {
-				continue;
-			}
-			$this->processOne($op);
-			usleep(10000);
-		}
+		});
+
+		if (!$op) return;
+
+		$this->processOne($op);
+		usleep(10000);
 
 		$this->storage->invalidateQueueCache();
 	}
 
+
 	private function processOne(Operation $op): void
 	{
-		if (get_transient(BASE.$op->id)) {
-			return;
-		}
-		set_transient(BASE.$op->id, true, 500);
 		$progress = new Progress($op);
-
 		$executor = $this->registry->getExecutor($op->type) ?? $this->defaultExecutor;
 		$op->startedAt = current_time('mysql');
 		$op->state = 'processing';
-
 		$this->storage->saveProgress($op);
 
 		try {
diff --git a/inc/managers/queue/Queue.php b/inc/managers/queue/Queue.php
index 95b7afb..89a8ceb 100644
--- a/inc/managers/queue/Queue.php
+++ b/inc/managers/queue/Queue.php
@@ -19,7 +19,6 @@
 
 	public function __construct()
 	{
-		$this->defineTables();
 		$this->storage = new Storage();
 		$this->registry = new TypeRegistry();
 		$this->locker = new Locker();
@@ -29,6 +28,7 @@
 
 		add_action('jvb_process_queue', [$this, 'checkQueue']);
 		add_action('jvb_queue_maintenance', [$this, 'maintenance']);
+		add_action('jvb_daily_snapshot', [$this->storage, 'snapshotDaily']);
 
 		if (!wp_next_scheduled('jvb_process_queue')) {
 			wp_schedule_event(time(), 'every-minute', 'jvb_process_queue');
@@ -36,89 +36,16 @@
 		if (!wp_next_scheduled('jvb_queue_maintenance')) {
 			wp_schedule_event(time(), 'hourly', 'jvb_queue_maintenance');
 		}
+		if (!wp_next_scheduled('jvb_daily_snapshot')) {
+			// Schedule for next 3am
+			$next3am = strtotime('tomorrow 3am', current_time('timestamp'));
+			wp_schedule_event($next3am, 'daily', 'jvb_daily_snapshot');
+		}
 
 		jvb_register_do_once('queue_admin_action_registered', [$this, 'registerAdminAction']);
 		add_filter(BASE.'admin_action_filter', [$this, 'adminActionFilter'], 10, 3);
 	}
 
-	public static function defineTables():void
-	{
-		$queue = CustomTable::for('_operation_queue');
-		$queue->setColumns([
-			'id'			=> 'VARCHAR(64) NOT NULL',
-			'type'			=> 'VARCHAR(50) NOT NULL',
-			'user_id'		=> $queue->getUserIDType().' NOT NULL',
-
-			'request_data'	=> 'JSON NOT NULL CHECK (JSON_VALID(request_data))',
-
-			'total_items'	=> 'INT(11) NOT NULL DEFAULT 1',
-			'processed_items'	=> 'INT(11) DEFAULT 0',
-			'failed_items'	=> 'JSON',
-
-			'priority'		=> 'ENUM(\'high\',\'normal\',\'low\') DEFAULT \'normal\'',
-			'state'			=> 'ENUM(\'pending\', \'scheduled\', \'processing\', \'completed\') DEFAULT \'pending\'',
-			'outcome'		=> 'ENUM(\'pending\', \'success\',\'partial\',\'merged\',\'failed\',\'failed_permanent\') DEFAULT \'pending\'',
-
-			'retries'		=> 'INT(11) DEFAULT 0',
-			'last_error_hash'=> 'CHAR(32) DEFAULT NULL',
-			'error_message'	=> 'TEXT',
-
-			'scheduled_at'	=> 'DATETIME DEFAULT NULL',
-			'started_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
-			'completed_at'	=> 'DATETIME DEFAULT NULL',
-
-			'metadata'		=> 'JSON DEFAULT NULL',
-			'result'		=> 'JSON',
-			'dependencies'	=> 'JSON',
-			'merged_into'	=> 'VARCHAR(64) DEFAULT NULL',
-
-			'user_dismissed'=> 'tinyint(1) DEFAULT 0',
-			'created_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
-			'updated_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
-		]);
-
-		$queue->setKeys([
-			['key' => 'PRIMARY', 'value' => '(`id`)'],
-			'`idx_run_queue` (`state`, `priority`, `scheduled_at`)',
-			'`idx_user_ops` (`user_id`, `state`)',
-			'`idx_user_type_pending` (`user_id`, `type`, `state`)',
-			'`idx_completed_at` (`completed_at`)',
-			'`idx_processing_stuck` (`state`, `started_at`)'
-		]);
-
-		$queue->defineTable();
-
-		$stats = CustomTable::for('stats__operation_queue');
-		$stats->setColumns([
-			'id'	=> 'BIGINT unsigned AUTO_INCREMENT',
-			'date'	=> 'DATE NOT NULL',
-			'type'	=> 'VARCHAR(50) NOT NULL',
-
-			'total_operations'		=> 'INT NOT NULL DEFAULT 0',
-			'successful_operations'	=> 'INT NOT NULL DEFAULT 0',
-			'partial_operations'	=> 'INT NOT NULL DEFAULT 0',
-			'failed_operations'		=> 'INT NOT NULL DEFAULT 0',
-			'failed_permanent_operations'=> 'INT NOT NULL DEFAULT 0',
-			'total_items_processed'	=> 'INT NOT NULL DEFAULT 0',
-
-			'average_duration'		=> 'FLOAT DEFAULT NULL',
-			'max_duration'			=> 'INT DEFAULT NULL',
-			'peak_queue_size'		=> 'INT NOT NULL DEFAULT 0',
-			'peak_memory_usage'		=> 'INT DEFAULT NULL',
-			'peak_cpu_usage'		=> 'FLOAT DEFAULT NULL',
-
-			'created_at'			=> 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
-		]);
-
-		$stats->setKeys([
-			['key' => 'PRIMARY', 'value' => '(`id`)'],
-			['key' => 'UNIQUE', 'value' => '(`date`, `type`)'],
-			'`date_idx` (`date`)',
-			'`type_idx` (`type`)'
-		]);
-		$stats->defineTable();
-	}
-
 	/**
 	 * Access type registry for registering operation configs
 	 */
diff --git a/inc/managers/queue/Storage.php b/inc/managers/queue/Storage.php
index 40426e6..2a5d540 100644
--- a/inc/managers/queue/Storage.php
+++ b/inc/managers/queue/Storage.php
@@ -5,48 +5,39 @@
 }
 
 use JVBase\managers\Cache;
+use JVBase\managers\CustomTable;
 use LogicException;
 
 class Storage
 {
-	private \wpdb $wpdb;
-	private string $table;
+	private CustomTable $table;
+	private CustomTable $stats;
 	private Cache $cache;
 
 	private const CACHE_QUEUE_INFO = 'queue_info';
 
 	public function __construct()
 	{
-		global $wpdb;
-		$this->wpdb = $wpdb;
-		$this->table = $wpdb->prefix . BASE . '_operation_queue';
+		$this->defineTables();
 		$this->cache = Cache::for('queue', DAY_IN_SECONDS);
 	}
 
 	public function hasProcessingOperations(): bool
 	{
-		return (bool) $this->wpdb->get_var(
-			"SELECT 1 FROM {$this->table} WHERE state = 'processing' LIMIT 1"
-		);
+		return (bool) $this->table->queryVar("SELECT 1 FROM {table} WHERE state = 'processing' LIMIT 1");
 	}
 
 	public function fetchRunnable(int $offset = 0): array
 	{
 		$now = current_time('mysql');
-
-		$rows = $this->wpdb->get_results(
-			$this->wpdb->prepare("
-            SELECT *
-            FROM {$this->table}
-            WHERE state IN ('pending', 'scheduled')
-              AND scheduled_at <= %s
-            ORDER BY
-              FIELD(priority, 'high', 'normal', 'low'),
-              scheduled_at
-            LIMIT 10 OFFSET %d
-            FOR UPDATE SKIP LOCKED
-        ", $now, $offset)
-		);
+		$rows = $this->table->queryResults("
+        SELECT * FROM {table}
+        WHERE state IN ('pending', 'scheduled')
+          AND scheduled_at <= %s
+        ORDER BY FIELD(priority, 'high', 'normal', 'low'), scheduled_at
+        LIMIT 10 OFFSET %d
+        FOR UPDATE SKIP LOCKED
+    ", [$now, $offset]);
 
 		$total = count($rows);
 		foreach ($rows as $row) {
@@ -88,26 +79,22 @@
 	public function markProcessing(string $id): bool
 	{
 		$now = current_time('mysql');
-
-		$affected = $this->wpdb->query($this->wpdb->prepare("
-            UPDATE {$this->table}
-            SET state = 'processing', started_at = %s, updated_at = %s
-            WHERE id = %s AND state IN ('pending', 'scheduled')
-        ", $now, $now, $id));
+		$affected = $this->table->query("
+        UPDATE {table}
+        SET state = 'processing', started_at = %s, updated_at = %s
+        WHERE id = %s AND state IN ('pending', 'scheduled')
+    ", [$now, $now, $id]);
 
 		if ($affected > 0) {
 			$op = $this->find($id);
-			if ($op) {
-				$this->invalidateUser($op->userId);
-			}
+			if ($op) $this->invalidateUser($op->userId);
 		}
-
 		return $affected > 0;
 	}
 
 	public function save(Operation $op): bool
 	{
-		$data = [
+		$result = $this->table->update([
 			'request_data'    => json_encode($op->requestData),
 			'total_items'     => $op->totalItems,
 			'processed_items' => $op->processedItems,
@@ -118,23 +105,17 @@
 			'retries'         => $op->retries,
 			'last_error_hash' => $op->lastErrorHash,
 			'error_message'   => $op->errorMessage,
-			'scheduled_at'    => $op->scheduledAt,
-			'started_at'      => $op->startedAt,
 			'completed_at'    => $op->completedAt,
 			'metadata'        => json_encode($op->metadata),
 			'result'          => $op->result ? json_encode($op->result) : null,
 			'dependencies'    => json_encode($op->dependencies),
 			'merged_into'     => $op->merged_into,
 			'user_dismissed'  => $op->userDismissed ? 1 : 0,
-			'updated_at'      => current_time('mysql'),
-		];
+		],
+			['id' => $op->id]
+		);
 
-		$result = $this->wpdb->update($this->table, $data, ['id' => $op->id]);
-
-		if ($result !== false) {
-			$this->invalidateUser($op->userId);
-		}
-
+		if ($result !== false) $this->invalidateUser($op->userId);
 		return $result !== false;
 	}
 
@@ -143,35 +124,17 @@
 	 * @param Operation $op
 	 * @return bool
 	 */
-	public function saveProgress(Operation $op): bool {
-		global $wpdb;
-
-		$table = $this->table;
-
-		$data = [
+	public function saveProgress(Operation $op): bool
+	{
+		$result = $this->table->update([
 			'processed_items' => $op->processedItems ?? 0,
 			'failed_items'    => $op->failedItems ? json_encode($op->failedItems) : null,
-			'metadata'        => ($op->metadata) ? json_encode($op->metadata) : null,
-			'result'          => ($op->result) ? json_encode($op->result) :null,
-			'updated_at'      => current_time('mysql'),
-		];
+			'metadata'        => $op->metadata ? json_encode($op->metadata) : null,
+			'result'          => $op->result ? json_encode($op->result) : null,
+		], ['id' => $op->id, 'state' => 'processing']); // state guard preserved
 
-		// IMPORTANT: never touch terminal state
-		$where = [
-			'id'    => $op->id,
-			'state' => 'processing',
-		];
-
-		$updated = $wpdb->update($table, $data, $where);
-
-		if ($updated === false) {
-			error_log('[Storage::saveProgress] DB error: ' . $wpdb->last_error);
-			return false;
-		}
-
-
+		if ($result === false) return false;
 		$this->invalidateUser($op->userId);
-
 		return true;
 	}
 
@@ -180,50 +143,32 @@
 	 * @param Operation $op
 	 * @return bool
 	 */
-	public function saveFinal(Operation $op): bool {
-		global $wpdb;
-
-		if (($op->state?? null) !== 'completed') {
+	public function saveFinal(Operation $op): bool
+	{
+		if ($op->state !== 'completed') {
 			throw new LogicException('saveFinal called without completed state');
 		}
 
-		$table = $this->table;
+		$result = $this->table->update([
+			'state'           => 'completed',
+			'outcome'         => $op->outcome ?? 'success',
+			'processed_items' => $op->processedItems ?? 0,
+			'failed_items'    => $op->failedItems ? json_encode($op->failedItems) : null,
+			'result'          => isset($op->result) ? wp_json_encode($op->result) : null,
+			'completed_at'    => $op->completedAt ?? current_time('mysql'),
+		], ['id' => $op->id, 'state' => 'processing']); // hard guard preserved
 
-		$data = [
-			'state'          => 'completed',
-			'outcome'        => $op->outcome?? 'success',
-			'processed_items'=> $op->processedItems ?? 0,
-			'failed_items' 	 => $op->failedItems ? json_encode($op->failedItems) : null,
-			'result'         => isset($op->result) ? wp_json_encode($op->result) : null,
-			'completed_at'   => $op->completedAt ?? current_time('mysql'),
-			'updated_at'     => current_time('mysql'),
-		];
+		if ($result === 0) return true; // already completed, not an error
+		if ($result === false) return false;
 
-		// HARD GUARD: cannot overwrite completed
-		$where = [
-			'id'    => $op->id,
-			'state' => 'processing',
-		];
-
-		$updated = $wpdb->update($table, $data, $where);
-
-		if ($updated === 0) {
-			return true;
-		}
-
-		if ($updated === false) {
-			error_log('[Storage::saveFinal] DB error: ' . $wpdb->last_error);
-			return false;
-		}
 		$this->invalidateQueueCache();
 		$this->invalidateUser($op->userId);
-
 		return true;
 	}
 
 	public function insert(Operation $op): bool
 	{
-		$result = $this->wpdb->insert($this->table, [
+		$result = $this->table->insert([
 			'id'              => $op->id,
 			'type'            => $op->type,
 			'user_id'         => $op->userId,
@@ -235,18 +180,10 @@
 			'state'           => $op->state,
 			'outcome'         => $op->outcome,
 			'retries'         => 0,
-			'last_error_hash' => null,
-			'error_message'   => null,
 			'scheduled_at'    => $op->scheduledAt ?? current_time('mysql'),
-			'started_at'      => null,
-			'completed_at'    => null,
 			'metadata'        => json_encode($op->metadata),
-			'result'          => null,
 			'dependencies'    => json_encode($op->dependencies),
-			'user_dismissed'  => 0,
 			'merged_into'  	  => null,
-			'created_at'      => current_time('mysql'),
-			'updated_at'      => current_time('mysql'),
 		]);
 
 		if ($result) {
@@ -258,36 +195,25 @@
 
 	public function find(string $id): ?Operation
 	{
-		$row = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT * FROM {$this->table} WHERE id = %s",
-			$id
-		));
-
+		$row = $this->table->get(['id' => $id]);
 		return $row ? $this->rowToOperation($row) : null;
 	}
 
 	public function findMergeable(string $type, int $userId, array $criteria = []): ?Operation
 	{
-		$sql = "SELECT * FROM {$this->table}
-            WHERE type = %s AND user_id = %d AND state IN ('pending', 'scheduled')";
+		$sql = "SELECT * FROM {table} WHERE type = %s AND user_id = %d AND state IN ('pending', 'scheduled')";
 		$params = [$type, $userId];
 
 		foreach ($criteria as $key => $value) {
-			if ($value === null) {
-				continue;
-			}
+			if ($value === null) continue;
 			$sql .= " AND JSON_UNQUOTE(JSON_EXTRACT(request_data, %s)) = %s";
 			$params[] = '$.' . $key;
 			$params[] = (string) $value;
 		}
-
 		$sql .= " ORDER BY created_at DESC LIMIT 1";
 
-		$row = $this->wpdb->get_row($this->wpdb->prepare($sql, ...$params));
-
-		$this->invalidateUser($userId);
-
-		return $row ? $this->rowToOperation($row) : null;
+		$rows = $this->table->queryResults($sql, $params);
+		return !empty($rows) ? $this->rowToOperation($rows[0]) : null;
 	}
 
 	public function getUserOperations(int $userId, array $filters = []): array
@@ -328,14 +254,12 @@
 		// Order by state priority, then created_at
 		$orderBy = $filters['order_by'] ?? "FIELD(state, 'processing', 'pending', 'scheduled', 'completed'), created_at DESC";
 
-		$limit = $filters['limit'] ?? 50;
-		$params[] = $limit;
+		$params[] = $filters['limit'] ?? 50;
 
-		$rows = $this->wpdb->get_results($this->wpdb->prepare(
-			"SELECT * FROM {$this->table} WHERE " . implode(' AND ', $where) .
-			" ORDER BY {$orderBy} LIMIT %d",
-			...$params
-		));
+		$rows = $this->table->queryResults(
+			"SELECT * FROM {table} WHERE " . implode(' AND ', $where) . " ORDER BY {$orderBy} LIMIT %d",
+			$params
+		);
 
 		return array_map([$this, 'rowToOperation'], $rows ?: []);
 	}
@@ -343,19 +267,17 @@
 	public function getQueueInfo(): array
 	{
 		$cached = $this->cache->get(self::CACHE_QUEUE_INFO);
-		if ($cached !== false) {
-			return $cached;
-		}
+		if ($cached !== false) return $cached;
 
 		$now = current_time('mysql');
-		$row = $this->wpdb->get_row($this->wpdb->prepare("
-            SELECT
-                COUNT(*) as total,
-                SUM(IF(state IN ('pending', 'processing'), 1, 0)) as active,
-                SUM(IF(state = 'scheduled' AND scheduled_at <= %s, 1, 0)) as ready_scheduled
-            FROM {$this->table}
-            WHERE state IN ('pending', 'processing', 'scheduled')
-        ", $now));
+		$row = $this->table->queryResults("
+        SELECT COUNT(*) as total,
+            SUM(IF(state IN ('pending', 'processing'), 1, 0)) as active,
+            SUM(IF(state = 'scheduled' AND scheduled_at <= %s, 1, 0)) as ready_scheduled
+        FROM {table}
+        WHERE state IN ('pending', 'processing', 'scheduled')
+    ", [$now]);
+		$row = $row[0] ?? null;
 
 		$info = [
 			'total'     => (int) ($row->total ?? 0),
@@ -399,33 +321,28 @@
 	public function getQueueStatus(): array
 	{
 		$now = current_time('mysql');
+		$rows = $this->table->queryResults("
+        SELECT state, COUNT(*) as count,
+            SUM(IF(state = 'scheduled' AND scheduled_at <= %s, 1, 0)) as ready
+        FROM {table} GROUP BY state
+    ", [$now]);
 
-		$rows = $this->wpdb->get_results($this->wpdb->prepare("
-            SELECT
-                state,
-                COUNT(*) as count,
-                SUM(IF(state = 'scheduled' AND scheduled_at <= %s, 1, 0)) as ready
-            FROM {$this->table}
-            GROUP BY state
-        ", $now), OBJECT_K);
-
+		$indexed = array_column($rows, null, 'state');
 		return [
-			'pending'         => (int) ($rows['pending']->count ?? 0),
-			'scheduled'       => (int) ($rows['scheduled']->count ?? 0),
-			'scheduled_ready' => (int) ($rows['scheduled']->ready ?? 0),
-			'processing'      => (int) ($rows['processing']->count ?? 0),
-			'completed'       => (int) ($rows['completed']->count ?? 0),
+			'pending'         => (int) ($indexed['pending']->count ?? 0),
+			'scheduled'       => (int) ($indexed['scheduled']->count ?? 0),
+			'scheduled_ready' => (int) ($indexed['scheduled']->ready ?? 0),
+			'processing'      => (int) ($indexed['processing']->count ?? 0),
+			'completed'       => (int) ($indexed['completed']->count ?? 0),
 		];
 	}
 
 	public function getUserStats(int $userId): array
 	{
-		$rows = $this->wpdb->get_results($this->wpdb->prepare("
-            SELECT state, outcome, COUNT(*) as count
-            FROM {$this->table}
-            WHERE user_id = %d
-            GROUP BY state, outcome
-        ", $userId));
+		$rows = $this->table->queryResults(
+			"SELECT state, outcome, COUNT(*) as count FROM {table} WHERE user_id = %d GROUP BY state, outcome",
+			[$userId]
+		);
 
 		$stats = [
 			'pending' => 0,
@@ -458,13 +375,7 @@
 
 	public function dismiss(string $id): bool
 	{
-		$result = $this->wpdb->update(
-			$this->table,
-			['user_dismissed' => 1, 'updated_at' => current_time('mysql')],
-			['id' => $id]
-		);
-
-		return $result !== false;
+		return $this->table->update(['user_dismissed' => 1], ['id' => $id]) !== false;
 	}
 
 	/**
@@ -473,14 +384,8 @@
 	public function delete(string $id): bool
 	{
 		$op = $this->find($id);
-		$userId = $op?->userId;
-
-		$result = $this->wpdb->delete($this->table, ['id' => $id]);
-
-		if ($result && $userId) {
-			$this->invalidateUser($userId);
-		}
-
+		$result = $this->table->delete(['id' => $id]);
+		if ($result && $op) $this->invalidateUser($op->userId);
 		return $result !== false;
 	}
 
@@ -493,21 +398,16 @@
 	{
 		Cache::for($userId.'_queue')->flush();
 	}
-	public function getLastError(): string
-	{
-		return $this->wpdb->last_error;
-	}
 
 	public function withTransaction(callable $callback): mixed
 	{
-		$this->wpdb->query('START TRANSACTION');
-
+		$this->table->startTransaction();
 		try {
 			$result = $callback();
-			$this->wpdb->query('COMMIT');
+			$this->table->commit();
 			return $result;
 		} catch (\Throwable $e) {
-			$this->wpdb->query('ROLLBACK');
+			$this->table->rollback();
 			error_log('[Storage] Transaction rolled back: ' . $e->getMessage());
 			throw $e;
 		}
@@ -518,36 +418,19 @@
 		return $this->withTransaction(function () use ($stuckMinutes) {
 			$cutoff = date('Y-m-d H:i:s', strtotime("-{$stuckMinutes} minutes"));
 
-			// Get IDs first for logging
-			$stuckIds = $this->wpdb->get_col($this->wpdb->prepare("
-            SELECT id FROM {$this->table}
-            WHERE state = 'processing'
-              AND started_at < %s
-            FOR UPDATE
-        ", $cutoff));
+			$stuckIds = $this->table->queryResults(
+				"SELECT id FROM {table} WHERE state = 'processing' AND started_at < %s FOR UPDATE",
+				[$cutoff]
+			);
+			$stuckIds = array_column($stuckIds, 'id');
 
-			if (empty($stuckIds)) {
-				return 0;
-			}
+			if (empty($stuckIds)) return 0;
 
-			// Reset them
-			$affected = $this->wpdb->query($this->wpdb->prepare("
-            UPDATE {$this->table}
-            SET state = 'scheduled',
-                scheduled_at = %s,
-                retries = retries + 1,
-                updated_at = %s
-            WHERE id IN (" . implode(',', array_fill(0, count($stuckIds), '%s')) . ")
-        ",
-				date('Y-m-d H:i:s', time() + 60),
-				current_time('mysql'),
-				...$stuckIds
-			));
-
-			// Optional: Log to audit table
-			// $this->logStuckReset($stuckIds);
-
-			return (int) $affected;
+			$placeholders = implode(',', array_fill(0, count($stuckIds), '%s'));
+			return (int) $this->table->query(
+				"UPDATE {table} SET state = 'scheduled', scheduled_at = %s, retries = retries + 1, updated_at = %s WHERE id IN ({$placeholders})",
+				array_merge([date('Y-m-d H:i:s', time() + 60), current_time('mysql')], $stuckIds)
+			);
 		});
 	}
 
@@ -557,23 +440,124 @@
 	public function replaceDependency(string $fromId, string $toId): int
 	{
 		return $this->withTransaction(function () use ($fromId, $toId) {
-
-			// Only affect pending/scheduled operations
-			$affected = $this->wpdb->query($this->wpdb->prepare("
-            UPDATE {$this->table}
-            SET dependencies = REPLACE(dependencies, %s, %s),
-                updated_at = %s
-            WHERE state IN ('pending', 'scheduled')
-              AND dependencies LIKE %s
-        ",
-				'"' . $fromId . '"',
-				'"' . $toId . '"',
-				current_time('mysql'),
-				'%"' . $fromId . '"%'
-			));
-
-			return (int) $affected;
+			return (int) $this->table->query(
+				"UPDATE {table} SET dependencies = REPLACE(dependencies, %s, %s), updated_at = %s WHERE state IN ('pending', 'scheduled') AND dependencies LIKE %s",
+				['"'.$fromId.'"', '"'.$toId.'"', current_time('mysql'), '%"'.$fromId.'"%']
+			);
 		});
 	}
 
+	public function defineTables():void
+	{
+		$queue = CustomTable::for('_operation_queue');
+		$queue->setColumns([
+			'id'			=> 'VARCHAR(64) NOT NULL',
+			'type'			=> 'VARCHAR(50) NOT NULL',
+			'user_id'		=> $queue->getUserIDType().' NOT NULL',
+
+			'request_data'	=> 'JSON NOT NULL CHECK (JSON_VALID(request_data))',
+
+			'total_items'	=> 'INT(11) NOT NULL DEFAULT 1',
+			'processed_items'	=> 'INT(11) DEFAULT 0',
+			'failed_items'	=> 'JSON',
+
+			'priority'		=> 'ENUM(\'high\',\'normal\',\'low\') DEFAULT \'normal\'',
+			'state'			=> 'ENUM(\'pending\', \'scheduled\', \'processing\', \'completed\') DEFAULT \'pending\'',
+			'outcome'		=> 'ENUM(\'pending\', \'success\',\'partial\',\'merged\',\'failed\',\'failed_permanent\') DEFAULT \'pending\'',
+
+			'retries'		=> 'INT(11) DEFAULT 0',
+			'last_error_hash'=> 'CHAR(32) DEFAULT NULL',
+			'error_message'	=> 'TEXT',
+
+			'scheduled_at'	=> 'DATETIME DEFAULT NULL',
+			'started_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+			'completed_at'	=> 'DATETIME DEFAULT NULL',
+
+			'metadata'		=> 'JSON DEFAULT NULL',
+			'result'		=> 'JSON',
+			'dependencies'	=> 'JSON',
+			'merged_into'	=> 'VARCHAR(64) DEFAULT NULL',
+
+			'user_dismissed'=> 'tinyint(1) DEFAULT 0',
+			'created_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+			'updated_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+		]);
+
+		$queue->setKeys([
+			['key' => 'PRIMARY', 'value' => '(`id`)'],
+			'`idx_run_queue` (`state`, `priority`, `scheduled_at`)',
+			'`idx_user_ops` (`user_id`, `state`)',
+			'`idx_user_type_pending` (`user_id`, `type`, `state`)',
+			'`idx_completed_at` (`completed_at`)',
+			'`idx_processing_stuck` (`state`, `started_at`)'
+		]);
+
+		$queue->defineTable();
+		$this->table = $queue;
+
+		$stats = CustomTable::for('stats__operation_queue');
+		$stats->setColumns([
+			'id'            => 'BIGINT unsigned AUTO_INCREMENT',
+			'date'          => 'DATE NOT NULL',
+			'type'          => 'VARCHAR(50) NOT NULL',
+
+			// Only store what can't be queried from the main table later
+			'peak_queue_size'   => 'INT NOT NULL DEFAULT 0',
+			'peak_memory_bytes' => 'BIGINT DEFAULT NULL',
+
+			// Snapshot totals for post-purge historical view
+			'total_operations'          => 'INT NOT NULL DEFAULT 0',
+			'successful_operations'     => 'INT NOT NULL DEFAULT 0',
+			'failed_permanent_operations' => 'INT NOT NULL DEFAULT 0',
+			'total_items_processed'     => 'INT NOT NULL DEFAULT 0',
+
+			'created_at'    => 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
+			'updated_at'    => 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
+		]);
+
+		$stats->setKeys([
+			['key' => 'PRIMARY', 'value' => '(`id`)'],
+			['key' => 'UNIQUE', 'value' => '(`date`, `type`)'],
+			'`date_idx` (`date`)',
+			'`type_idx` (`type`)'
+		]);
+		$stats->defineTable();
+
+		$this->stats = $stats;
+	}
+
+	public function snapshotDaily(): void
+	{
+		$today = current_time('Y-m-d');
+		$types = $this->table->queryResults(
+			"SELECT DISTINCT type FROM {table} WHERE DATE(completed_at) = %s",
+			[$today]
+		);
+
+		foreach ($types as $row) {
+			$stats = $this->table->queryResults("
+            SELECT
+                COUNT(*) as total,
+                SUM(outcome = 'success') as successful,
+                SUM(outcome = 'failed_permanent') as failed_permanent,
+                SUM(processed_items) as items_processed
+            FROM {table}
+            WHERE type = %s AND DATE(completed_at) = %s
+        ", [$row->type, $today]);
+
+			$s = $stats[0] ?? null;
+			if (!$s) continue;
+
+			$this->stats->table->query("
+            INSERT INTO {table} (date, type, total_operations, successful_operations, failed_permanent_operations, total_items_processed)
+            VALUES (%s, %s, %d, %d, %d, %d)
+            ON DUPLICATE KEY UPDATE
+                total_operations = VALUES(total_operations),
+                successful_operations = VALUES(successful_operations),
+                failed_permanent_operations = VALUES(failed_permanent_operations),
+                total_items_processed = VALUES(total_items_processed),
+                updated_at = NOW()
+        ", [$today, $row->type, $s->total, $s->successful, $s->failed_permanent, $s->items_processed]);
+		}
+	}
 }
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index a6594a7..fcf262a 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -17,9 +17,9 @@
 	 */
 	protected string $type;
 	/**
-	 * @var string the full slug, with BASE
+	 * @var ?string the full slug, with BASE
 	 */
-	protected string $slug;
+	protected ?string $slug;
 
 	protected string $contentType;
 	protected Item $item;
@@ -27,7 +27,7 @@
 	protected Validator $validator;
 	protected Sanitizer $sanitizer;
 	protected array $fields;
-	protected WP_Post|WP_Term|WP_User|null $wpObject;
+	protected WP_Post|WP_Term|WP_User|false|null $wpObject;
 	protected int|string $ID;
 	protected MetaTypeManager $typeManager;
 	protected static array $instances = ['post' => [],'term' => [], 'user'=>[],'options'=>[]];
@@ -116,7 +116,7 @@
 
 
 
-		$registrar = Registrar::getInstance($this->slug);
+		$registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false;
 		$fields = $registrar ? $registrar->getFields() : [];
 		$meta = match($type) {
 			'post'	=> get_post_meta($id),
diff --git a/inc/rest/routes/FormRoutes.php b/inc/rest/routes/FormRoutes.php
index e57163d..1cf8b23 100644
--- a/inc/rest/routes/FormRoutes.php
+++ b/inc/rest/routes/FormRoutes.php
@@ -165,7 +165,6 @@
 		} catch (\Exception $e) {
 			return new WP_Error('validation_failed', 'Data validation error: ' . $e->getMessage());
 		}
-
 		if (array_key_exists('success', $processed_data) && $processed_data['success'] === false) {
 			return $processed_data;
 		}

--
Gitblit v1.10.0