From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh

---
 base/_setup.php                                                                       |    6 
 inc/managers/CustomTable.php                                                          |   94 
 inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/index.php                    |    2 
 inc/integrations/SyncToPost.php                                                       |    3 
 inc/ui/index.php                                                                      |    3 
 inc/blocks/SummaryBlock.php                                                           |   12 
 inc/managers/Emails.php                                                               |    5 
 jvb.php                                                                               |  402 +-
 inc/helpers/formatting.php                                                            |    2 
 inc/admin/ContentTaxonomy.php                                                         |    2 
 inc/integrations/_Base.php                                                            |   12 
 inc/managers/OperationQueue.php                                                       |   40 
 inc/managers/DashboardManager.php                                                     |   45 
 inc/rest/routes/ContentRoutes.php                                                     |   38 
 inc/managers/SEO/render/Thing/Intangible/Reservation/index.php                        |    2 
 inc/registrar/index.php                                                               |    2 
 inc/rest/routes/FeedRoutes.php                                                        |   77 
 inc/registrar/fields/index.php                                                        |    2 
 inc/rest/routes/ApprovalRoutes.php                                                    |   66 
 inc/integrations/services/CloudflareOld.php                                           |    2 
 inc/managers/_setup.php                                                               |   27 
 inc/integrations/_setup.php                                                           |   73 
 inc/managers/SEO/render/Thing/Action/index.php                                        |    2 
 inc/managers/SEO/render/Traits/_Properties/index.php                                  |    2 
 inc/managers/SEO/render/Traits/Organization/_setup.php                                |    3 
 inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php                         |    3 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php                   |    3 
 inc/managers/SEO/render/Thing/Person/_setup.php                                       |    3 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/index.php    |    2 
 inc/rest/routes/ShopRoutes.php                                                        |   23 
 inc/integrations/services/Facebook.php                                                |  354 ++
 inc/rest/routes/NewsRoutes.php                                                        |    4 
 inc/managers/queue/executors/IntegrationExecutor.php                                  |    2 
 inc/registrar/config/Management.php                                                   |    5 
 inc/admin/SEOAdmin.php                                                                |    9 
 inc/helpers/breadcrumbs.php                                                           |    4 
 inc/managers/Responses.php                                                            |  413 ++
 inc/managers/Notifications/index.php                                                  |    2 
 inc/managers/SEO/render/Thing/CreativeWork/Article/index.php                          |    2 
 inc/meta/index.php                                                                    |    3 
 inc/managers/SEO/render/index.php                                                     |    2 
 inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php                          |    3 
 inc/managers/SEO/render/Thing/Person/index.php                                        |    2 
 inc/managers/SEO/render/Thing/Intangible/Quantity/index.php                           |    2 
 inc/managers/SEO/render/Thing/Properties/_setup.php                                   |    3 
 inc/registrar/Labels.php                                                              |    4 
 inc/integrations/services/Cloudflare.php                                              |    5 
 inc/managers/SEO/render/Thing/CreativeWork/_setup.php                                 |    3 
 inc/rest/routes/ContentTermsRoutes.php                                                |   28 
 inc/blocks/FAQBlock.php                                                               |   10 
 inc/managers/SEO/_edmonotonink.php                                                    |    4 
 inc/managers/SEO/render/Thing/MedicalEntity/_setup.php                                |    3 
 inc/rest/routes/UploadRoutes.php                                                      |  539 ---
 inc/managers/SEO/SEOAdminPage.php                                                     |    3 
 inc/managers/SEO/render/Thing/Properties/index.php                                    |    2 
 inc/managers/SEO/render/Thing/Intangible/ItemList/index.php                           |    2 
 inc/managers/UploadManager.php                                                        |  651 ++-
 inc/blocks/GlossaryBlock.php                                                          |    2 
 inc/integrations/services/Helcim.php                                                  |    2 
 inc/integrations/Admin.php                                                            |    2 
 inc/integrations/OAuth.php                                                            |   12 
 inc/managers/SEO/render/Traits/index.php                                              |    2 
 base/Login.php                                                                        |    3 
 inc/admin/_setup.php                                                                  |    4 
 inc/managers/queue/executors/UploadExecutor.php                                       |  181 
 inc/managers/Dashboard/Dashboard.php                                                  |   35 
 inc/managers/SEO/render/Thing/Event/index.php                                         |    2 
 inc/rest/routes/Invitations.php                                                       |   17 
 inc/rest/routes/SettingsRoutes.php                                                    |    5 
 inc/managers/SEO/render/Thing/Place/index.php                                         |    2 
 base/options.php                                                                      |    3 
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php                     |    3 
 src/drawer-menu/render.php                                                            |    4 
 inc/integrations/SyncFromPost.php                                                     |    3 
 inc/integrations/services/GoogleMyBusinessOld.php                                     |    3 
 inc/rest/Rest.php                                                                     |   10 
 inc/rest/routes/MagicLinkRoutes.php                                                   |   14 
 inc/managers/SEO/render/Thing/CreativeWork/index.php                                  |    2 
 inc/registrar/config/Breadcrumbs.php                                                  |    2 
 inc/managers/VerifyEntryManager.php                                                   |   15 
 inc/managers/CacheBack.php                                                            |  684 ++++
 inc/managers/SEO/render/Thing/Intangible/Rating/index.php                             |    2 
 assets/index.php                                                                      |    3 
 inc/integrations/services/Instagram.php                                               |    2 
 inc/rest/routes/UserRoutes.php                                                        |    5 
 inc/integrations/services/HelcimOld.php                                               |    2 
 inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php                          |    3 
 assets/css/index.php                                                                  |    3 
 inc/rest/routes/NotificationsRoutes.php                                               |    6 
 inc/registrar/config/Integration.php                                                  |    9 
 base/users.php                                                                        |    3 
 inc/meta/_setup.php                                                                   |    4 
 inc/managers/SEO/render/Thing/_setup.php                                              |    4 
 inc/blocks/CustomBlocks.php                                                           |   22 
 build/drawer-menu/index.php                                                           |    2 
 inc/rest/routes/CRUDRoutes.php                                                        |    4 
 inc/registrar/config/seo/Schema.php                                                   |   20 
 inc/rest/routes/IntegrationsSquareRoutes.php                                          |   10 
 inc/blocks/TimelineBlock.php                                                          |    3 
 inc/managers/SEO/_setup.php                                                           |    3 
 inc/managers/queue/executors/ContentExecutor.php                                      |    4 
 inc/managers/TaxonomyRelationships.php                                                |   13 
 templates/dashboard/sections/notifications.php                                        |    5 
 inc/integrations/services/index.php                                                   |    2 
 inc/importers/JaneAppSalesImporter.php                                                |    6 
 inc/managers/IconsManager.php                                                         |   13 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php |    3 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/index.php  |    2 
 inc/integrations/services/SquareOld.php                                               |   10 
 inc/rest/routes/index.php                                                             |    3 
 base/Membership.php                                                                   |    4 
 src/gmbreviews/render.php                                                             |    4 
 inc/integrations/services/Umami.php                                                   |  210 +
 inc/managers/SEO/render/Thing/MedicalEntity/index.php                                 |    2 
 inc/managers/SEO/render/Thing/Action/_setup.php                                       |    3 
 inc/integrations/services/GoogleMyBusiness.php                                        |   15 
 inc/rest/routes/FavouritesRoutes.php                                                  |  254 -
 inc/integrations/services/PostMark.php                                                |  301 +
 inc/integrations/SyncToUser.php                                                       |    2 
 assets/css/admin/index.php                                                            |    3 
 inc/managers/queue/Queue.php                                                          |   13 
 inc/helpers/members.php                                                               |   15 
 inc/rest/routes/QueueRoutes.php                                                       |   48 
 inc/Instance.php                                                                      |    3 
 base/content.php                                                                      |    3 
 inc/managers/Notifications/Content.php                                                |    2 
 inc/registrar/Users.php                                                               |    5 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/index.php                    |    2 
 inc/managers/MagicLinks.php                                                           |   70 
 inc/managers/queue/executors/ContentTermExecutor.php                                  |    8 
 inc/managers/SEO/render/Traits/ThingSchema.php                                        |    3 
 inc/managers/SEO/render/Thing/Organization/_setup.php                                 |    3 
 inc/registrar/fields/Calendar.php                                                     |    5 
 inc/managers/SEO/render/Thing/Intangible/index.php                                    |    2 
 inc/integrations/SyncHelpers.php                                                      |   15 
 inc/blocks/FormBlock.php                                                              |   10 
 inc/managers/SEO/render/Traits/_Helpers/_setup.php                                    |    3 
 inc/managers/Favourites.php                                                           |  118 
 inc/managers/queue/mergers/UploadMerger.php                                           |    4 
 src/glossary/render.php                                                               |    4 
 assets/css/icons.css                                                                  |    2 
 inc/managers/SEO/render/Traits/Place/_setup.php                                       |    3 
 src/simple-calendar/render.php                                                        |    4 
 inc/managers/SEO/render/Traits/_Helpers/index.php                                     |    2 
 inc/registrar/config/seo/Helpers.php                                                  |    3 
 base/email.php                                                                        |    3 
 inc/managers/SEO/render/SchemaOutput.php                                              |   31 
 inc/managers/Cache.php                                                                |  613 +--
 inc/rest/routes/TermRoutes.php                                                        |  313 -
 base/colours.php                                                                      |    4 
 inc/meta/Form.php                                                                     |    7 
 inc/managers/SEO/render/Thing/CreativeWork/Comment/_setup.php                         |    3 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/index.php                      |    2 
 inc/registrar/config/seo/Resolver.php                                                 |    4 
 inc/managers/SEO/render/Thing/Product/index.php                                       |    2 
 inc/registrar/_setup.php                                                              |    4 
 inc/ui/Checkout.php                                                                   |    8 
 inc/helpers/crud.php                                                                  |    8 
 inc/rest/routes/ResponseRoutes.php                                                    |  402 --
 src/faq/render.php                                                                    |    5 
 JVBase.php                                                                            |  342 --
 inc/blocks/DrawerMenuBlock.php                                                        |    7 
 src/video/render.php                                                                  |    4 
 inc/managers/SEO/render/Thing/Product/_setup.php                                      |    3 
 base/Site.php                                                                         |    2 
 inc/integrations/services/Square.php                                                  |   48 
 inc/integrations/Integrations.php                                                     |    7 
 inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php                   |    3 
 inc/rest/routes/SEORoutes.php                                                         |   91 
 base/SchemaHelper.php                                                                 |   14 
 inc/managers/Referrals.php                                                            |  791 ++--
 assets/js/admin/index.php                                                             |    3 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage/index.php           |    2 
 base/taxonomies.php                                                                   |    3 
 inc/registrar/helpers/index.php                                                       |    2 
 inc/managers/SEO/render/Traits/Organization/index.php                                 |    2 
 inc/managers/SEO/render/_setup.php                                                    |    3 
 inc/integrations/services/GoogleMaps.php                                              |    2 
 inc/registrar/config/TrackChanges.php                                                 |    5 
 inc/managers/queue/executors/InvitationExecutor.php                                   |    6 
 inc/registrar/config/seo/_setup.php                                                   |    3 
 activate.php                                                                          |   12 
 inc/ui/_setup.php                                                                     |    4 
 inc/managers/SEO/render/Thing/Event/_setup.php                                        |    3 
 inc/meta/Meta.php                                                                     |  124 
 inc/utility/helpers.php                                                               |    4 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php                   |    3 
 inc/rest/routes/ErrorRoutes.php                                                       |    5 
 inc/rest/routes/ReferralRoutes.php                                                    |  178 
 inc/blocks/SimpleCalendarBlock.php                                                    |    5 
 inc/managers/SEO/render/Thing/Intangible/Brand/index.php                              |    2 
 inc/managers/queue/Storage.php                                                        |    2 
 inc/registrar/config/Responses.php                                                    |    5 
 inc/rest/_setup.php                                                                   |    7 
 inc/managers/SEO/render/Thing/Intangible/_setup.php                                   |    3 
 inc/helpers/directory.php                                                             |    5 
 inc/managers/Login.php                                                                |   42 
 inc/managers/queue/TypeRegistry.php                                                   |    2 
 checks.php                                                                            |   30 
 inc/registrar/helpers/AddIntegrationFields.php                                        |    9 
 inc/integrations/SyncToTerm.php                                                       |    2 
 inc/registrar/config/Verification.php                                                 |    5 
 inc/managers/ScriptLoader.php                                                         |  209 +
 inc/utility/setup.php                                                                 |    4 
 inc/admin/Integrations.php                                                            |   24 
 inc/rest/routes/LoginRoutes.php                                                       |   13 
 assets/phosphor-icons/bold/index.php                                                  |    2 
 inc/integrations/UserConnection.php                                                   |    2 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/index.php                        |    2 
 inc/managers/ImageGenerator.php                                                       |    5 
 inc/managers/InvitationsManager.php                                                   |   33 
 inc/rest/routes/VoteRoutes.php                                                        |   18 
 inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php                               |    2 
 inc/managers/Roles.php                                                                |    5 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/index.php                    |    2 
 inc/rest/index.php                                                                    |    3 
 inc/integrations/services/UmamiOld.php                                                |    2 
 inc/registrar/config/index.php                                                        |    2 
 inc/integrations/SyncFromTerm.php                                                     |    3 
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/index.php                      |    2 
 inc/blocks/GMBReviewBlock.php                                                         |   25 
 inc/managers/Dashboard/DashboardPage.php                                              |    6 
 inc/managers/CRUDManager.php                                                          |    8 
 inc/rest/routes/OptionsRoutes.php                                                     |    4 
 inc/managers/Dashboard/index.php                                                      |    2 
 inc/rest/routes/ImporterRoutes.php                                                    |    2 
 src/timeline/render.php                                                               |    4 
 inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php                      |    3 
 inc/managers/Dashboard/_setup.php                                                     |    2 
 inc/managers/NewsRelationships.php                                                    |    5 
 inc/managers/NotificationManager.php                                                  |   10 
 inc/managers/Notifications/EmailDigests.php                                           |   25 
 inc/managers/SEO/render/Traits/_Properties/_setup.php                                 |    3 
 inc/helpers/time.php                                                                  |    5 
 inc/integrations/ServiceMap.php                                                       |   35 
 inc/managers/SEO/render/DataType/_setup.php                                           |    3 
 inc/rest/RegisterRoutes.php                                                           |    4 
 inc/managers/DirectoryManager.php                                                     |   15 
 inc/managers/SEO/render/Thing/Organization/index.php                                  |    2 
 inc/rest/routes/IntegrationsHelcimRoutes.php                                          |   48 
 inc/managers/SEO/render/Thing/CreativeWork/Comment/index.php                          |    2 
 inc/rest/routes/FormRoutes.php                                                        |   29 
 inc/blocks/FeedBlock.php                                                              |   11 
 inc/managers/SEO/BreadcrumbManager.php                                                |   73 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php                     |    3 
 inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php                       |    3 
 inc/managers/KarmaManager.php                                                         |  118 
 inc/registrar/Registrar.php                                                           |   62 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php   |    3 
 inc/managers/UserTermsManager.php                                                     |    9 
 inc/registrar/config/seo/index.php                                                    |    2 
 inc/forms/PostSelector.php                                                            |    5 
 inc/managers/SEO/render/Traits/Place/index.php                                        |    2 
 inc/helpers/renderFields.php                                                          |   17 
 inc/managers/queue/Progress.php                                                       |    4 
 inc/helpers/dashboard.php                                                             |    2 
 inc/managers/Errors.php                                                               |   63 
 inc/managers/Approvals.php                                                            |   14 
 inc/integrations/SyncFromUser.php                                                     |    3 
 inc/blocks/MenuBlock.php                                                              |  130 
 src/menu/render.php                                                                   |    4 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/index.php                          |    2 
 inc/rest/routes/IntegrationsRoutes.php                                                |   35 
 inc/managers/AdminPages.php                                                           |    6 
 inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php                             |    3 
 inc/managers/SEO/render/Thing/Intangible/ContactPoint/index.php                       |    2 
 inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php                            |    3 
 /dev/null                                                                             |   64 
 inc/rest/RestRouteManager.php                                                         |    7 
 inc/managers/SEO/render/Thing/Place/_setup.php                                        |    3 
 inc/rest/routes/AdminRoutes.php                                                       |   28 
 inc/managers/SEO/render/Traits/_setup.php                                             |    3 
 inc/importers/_setup.php                                                              |    4 
 inc/managers/SEO/render/DataType/index.php                                            |    2 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php                       |    4 
 inc/managers/queue/mergers/DefaultMerger.php                                          |    4 
 276 files changed, 5,594 insertions(+), 4,236 deletions(-)

diff --git a/JVBase.php b/JVBase.php
index 36f94ca..ebbcbc5 100644
--- a/JVBase.php
+++ b/JVBase.php
@@ -5,20 +5,14 @@
 use JVBase\base\SchemaHelper;
 use JVBase\base\Site;
 use JVBase\blocks\CustomBlocks;
-use JVBase\managers\AdminPages;
-use JVBase\managers\ApprovalManager;
-use JVBase\managers\Dashboard\DashboardManager;
+use JVBase\managers\Approvals;
 use JVBase\managers\DirectoryManager;
-use JVBase\managers\EmailManager;
-use JVBase\managers\ErrorHandler;
-use JVBase\managers\FavouritesManager;
+use JVBase\managers\Favourites;
 use JVBase\managers\InvitationsManager;
-use JVBase\managers\LoginManager;
-use JVBase\managers\MagicLinkManager;
+use JVBase\managers\Login;
 use JVBase\managers\NotificationManager;
 use JVBase\managers\queue\Queue;
-use JVBase\managers\ReferralManager;
-use JVBase\managers\RoleManager;
+use JVBase\managers\Referrals;
 use JVBase\managers\SEO\render\SchemaOutput;
 use JVBase\managers\TaxonomyRelationships;
 use JVBase\managers\UserTermsManager;
@@ -31,15 +25,11 @@
 use JVBase\rest\routes\FavouritesRoutes;
 use JVBase\rest\routes\FeedRoutes;
 use JVBase\rest\routes\FormRoutes;
-use JVBase\rest\routes\IntegrationsHelcimRoutes;
-use JVBase\rest\routes\IntegrationsRoutes;
-use JVBase\rest\routes\IntegrationsSquareRoutes;
 use JVBase\rest\routes\Invitations;
 use JVBase\rest\routes\LoginRoutes;
 use JVBase\rest\routes\NewsRoutes;
 use JVBase\rest\routes\NotificationsRoutes;
 use JVBase\rest\routes\OptionsRoutes;
-use JVBase\rest\routes\QueueRoutes;
 use JVBase\rest\routes\ReferralRoutes;
 use JVBase\rest\routes\ResponseRoutes;
 use JVBase\rest\routes\SEORoutes;
@@ -59,23 +49,11 @@
 	protected array $managers = [];
 	protected array $content = [];
 	protected array $taxonomies = [];
-	protected array $integrations = [];
 	protected array $blocks = [];
 	protected array $routes = [];
 	protected CustomBlocks $customBlocks;
 
-	protected array $serviceMap = [
-		'maps' => 'JVBase\integrations\services\GoogleMaps',
-		'square' => 'JVBase\integrations\services\Square',
-		'facebook' => 'JVBase\integrations\services\Facebook',
-		'helcim' => 'JVBase\integrations\services\Helcim',
-		'instagram' => 'JVBase\integrations\services\Instagram',
-		'bluesky' => 'JVBase\integrations\services\BlueSky',
-		'gmb' => 'JVBase\integrations\services\GoogleMyBusiness',
-		'cloudflare' => 'JVBase\integrations\services\Cloudflare',
-		'umami' => 'JVBase\integrations\services\Umami',
-		'postmark' => 'JVBase\integrations\services\PostMark',
-	];
+
 
 	public static function getInstance(): JVB
 	{
@@ -88,177 +66,6 @@
 
 	private function __construct()
 	{
-		$this->customBlocks = new CustomBlocks();
-		$this->managers = [
-			'errors' 	=> new ErrorHandler(),
-			'queue' 	=> new Queue(),
-//            'dash'    => new DashboardManager(),
-			'roles' 	=> new RoleManager(),
-//            'forms'   => new FormManager(),
-//			'schema' 	=> new SchemaOutputManager(),
-			'admin' 	=> new AdminPages(),
-			'seoAdmin' 	=> new SEOAdmin(),
-			'seo'		=> new SchemaOutput(),
-			'schemaHelper' => new SchemaHelper(),
-//			'uploads'	=> new UploadManager(),
-//			'userTerms' => new UserTermsManager(),
-			'email' 	=> new EmailManager(),
-//			'terms'		=> new TaxonomyRelationships(),
-		];
-
-        if (Site::has('feed_block')) {
-            $this->managers['terms'] = new TaxonomyRelationships();
-        }
-
-        if (Site::hasAll(['dashboard', 'membership', 'feed_block'])) {
-            $this->managers['userTerms'] = new UserTermsManager();
-        }
-
-		$this->routes = [
-			'login' => new LoginRoutes(),
-			'integrations' => new IntegrationsRoutes(),
-			'seo' => new SEORoutes(),
-			'queue' => new QueueRoutes(),
-			'settings' => new SettingsRoutes(),
-			'upload' => new UploadRoutes(),
-			'forms' => new FormRoutes(),
-			'user'	=> new UserRoutes()
-		];
-
-		if (Site::has('magic_link')) {
-//			$this->routes['magicLink'] = new MagicLinkRoutes();
-			$this->managers['magicLink'] = new MagicLinkManager();
-		}
-		if (Site::has('referrals')) {
-			$this->managers['referral'] = new ReferralManager();
-			$this->routes['referral'] = new ReferralRoutes();
-		}
-
-
-		if (Site::hasIntegration('square')) {
-			$this->routes['square'] = new IntegrationsSquareRoutes();
-		}
-		if (Site::hasIntegration('helcim')) {
-			$this->routes['helcim'] = new IntegrationsHelcimRoutes();
-		}
-
-		if (Site::has('feed_block')) {
-			$this->routes['feed'] = new FeedRoutes();
-		}
-        $membership = Site::membership();
-		if ($membership && $membership->has('notifications')) {
-			$this->managers['notifications'] = new NotificationManager();
-			$this->routes['notifications'] = new NotificationsRoutes();
-		}
-		if (!empty(Registrar::withFeature('approve_new'))) {
-			$this->managers['approvals'] = new ApprovalManager();
-		}
-		if (Site::has('feed_block') || Site::has('dashboard')) {
-			$this->routes['term'] = new TermRoutes();
-		}
-
-		if (Site::has('is_directory')) {
-			$this->managers['directory'] = new DirectoryManager();
-		}
-
-		if (Site::has('dashboard')) {
-			$this->routes['error'] = new ErrorRoutes();
-            $this->routes['admin']  = new AdminRoutes();
-			$this->routes['content'] = new ContentRoutes();
-//            $this->routes['bio']    = new BioRoutes();
-//			$this->routes['shop'] = new ShopRoutes();
-			$this->routes['contentTax'] = new ContentTermsRoutes();
-			$this->routes['options'] = new OptionsRoutes();
-		}
-
-		if (Site::has('favourites')) {
-			$this->managers['favourites'] = new FavouritesManager();
-			$this->routes['favourites'] = new FavouritesRoutes();
-		}
-
-		if ($membership && $membership->has('forum')) {
-			$this->routes['news'] = new NewsRoutes();
-		}
-		if ($membership && $membership->has('invitable')) {
-			$this->managers['invitations'] = new InvitationsManager();
-		}
-		if (!empty(Registrar::withFeature('has_responses'))) {
-			$this->routes['comments'] = new ResponseRoutes();
-		}
-		if (!empty(Registrar::withFeature('karma'))) {
-			$this->routes['vote'] = new VoteRoutes();
-		}
-		if (!empty(Registrar::withFeature('karma'))
-			|| ($membership && $membership->has('member_verified')) ||
-            ($membership && $membership->has('term_approval'))) {
-			$this->routes['approvals'] = new ApprovalRoutes();
-		}
-		if ($membership && $membership->has('can_invite')) {
-			$this->routes['invites'] = new Invitations();
-		}
-
-
-		if (Site::has('dashboard')) {
-			$this->managers['dash'] = new DashboardManager();
-		}
-
-		$this->setupIntegrations();
-
-		add_action('wp_footer', [$this, 'additionalActions']);
-//        $this->managers['notifications'] = new NotificationManager();
-		// Register activation hook
-		register_activation_hook(JVB_DIR . '/jvb.php', [$this, 'activate']);
-	}
-
-
-	protected function setupIntegrations(): void
-	{
-        foreach(array_keys(Site::getIntegrations()) as $integration) {
-            $this->integrations[$integration] = $this->serviceMap[$integration]::getInstance();
-        }
-	}
-
-	public function registeredContent(): array
-	{
-		return array_merge(array_keys($this->content), array_keys($this->taxonomies));
-	}
-
-	public function favourites(): FavouritesManager|false
-	{
-		return $this->managers['favourites'] ?? false;
-	}
-
-	public function dashboard(): DashboardManager|false
-	{
-		return $this->managers['dash'] ?? false;
-	}
-
-	public function directories(): DirectoryManager|false
-	{
-		return $this->managers['directory'] ?? false;
-	}
-
-	public function error(): ErrorHandler
-	{
-		return $this->managers['errors'];
-	}
-
-	public function file()
-	{
-		return $this->managers['file'];
-	}
-
-	public function queue(): Queue
-	{
-		return $this->managers['queue'];
-	}
-//    public function forms()
-//    {
-//        return $this->managers['forms'];
-//    }
-	public function notification(): NotificationManager|false
-	{
-		return $this->managers['notifications'] ?? false;
 	}
 
 	public function routes($route): mixed
@@ -269,149 +76,20 @@
 		return false;
 	}
 
-	public function roles(): RoleManager
-	{
-		return $this->managers['roles'];
-	}
-
-	public function admin()
-	{
-		return $this->managers['admin'];
-	}
-
-	public function seoAdmin()
-	{
-		return $this->managers['seoAdmin'];
-	}
 
 	public function getFields($type): array
 	{
 		return Registrar::getFieldsFor($type)??[];
 	}
 
+	/**
+	 * @deprecated Should use the Registrar::getInstance
+	 * @param $type
+	 * @return mixed
+	 */
 	public function getContent($type): mixed
 	{
 		return $this->content[$type] ?? $this->taxonomies[$type] ?? $this->blocks[$type] ?? null;
 	}
 
-	public function connect(string $service, ?int $userID = null): mixed
-	{
-//		if ($userID) {
-//			if (!$this->userCanConnect($service, $userID)) {
-//				return null;
-//			}
-//
-//			if (!array_key_exists($service, $this->integrations)) {
-//				return null;
-//			}
-//
-//			return $this->serviceMap[$service]::getInstance($userID);
-//		}
-		$instance = $this->integrations[$service]??null;
-		if ($instance) {
-			$instance->switchUser($userID);
-		}
-		return $instance;
-	}
-
-	public function userCanConnect(string $service, int $userID): bool
-	{
-		$role = jvbUserRole($userID);
-		$registrar = Registrar::getInstance($role);
-		return user_can($userID, 'manage_options') || $registrar->hasIntegration($service);
-	}
-
-	public function getAvailableServices(bool $keys = true): array
-	{
-
-		return ($keys) ? array_keys($this->integrations) : $this->integrations;
-	}
-
-	public function activate(): void
-	{
-		// Activate roles - will be properly initialized after post types are registered
-		$this->roles()->activate();
-	}
-
-	public function addRoute($slug, $class): void
-	{
-		$this->routes[$slug] = $class;
-	}
-
-	public function email(): EmailManager
-	{
-		return $this->managers['email'];
-	}
-
-	public function referrals(): ReferralManager|false
-	{
-		return $this->managers['referral'] ?? false;
-	}
-
-	public function magicLink(): MagicLinkManager|false
-	{
-		return $this->managers['magicLink'] ?? false;
-	}
-
-	public function invitations(): InvitationsManager|false
-	{
-		return $this->managers['invitations'] ?? false;
-	}
-
-	public function additionalActions():void
-	{
-		if (LoginManager::isLogin()) {
-			return;
-		}
-		$extras = apply_filters('jvbAdditionalActions', []);
-		$extras = array_filter($extras, function ($extra) {
-			return is_array($extra) && array_key_exists('button', $extra) && array_key_exists('content', $extra);
-		});
-		if (empty ($extras)) {
-			return;
-		}
-		$buttons = array_map(function ($extra) {
-			return $extra['button'];
-		}, $extras);
-		$contents = array_map(function($extra) {
-			return $extra['content'];
-		}, $extras);
-
-		if (!empty ($buttons)) {
-			?>
-			<section class="main-actions">
-				<div class="buttons col">
-					<?= implode('', $buttons); ?>
-				</div>
-				<div class="actions">
-					<?= implode('', $contents); ?>
-				</div>
-			</section>
-			<?php
-		}
-	}
-
-	public function seo():SchemaOutput
-	{
-		return $this->managers['seo'];
-	}
-
-	public function blocks():CustomBlocks|bool
-	{
-		return $this->customBlocks??false;
-	}
-	public function schemaHelper():SchemaHelper
-	{
-		return $this->managers['schemaHelper'];
-	}
-
-	public function termRelationships():TaxonomyRelationships
-	{
-		return $this->managers['terms'];
-	}
-
-	public function approvals ():ApprovalManager|false
-	{
-		return $this->managers['approvals']??false;
-	}
 }
diff --git a/activate.php b/activate.php
index 3c960d7..0773a61 100644
--- a/activate.php
+++ b/activate.php
@@ -6,8 +6,8 @@
 use JVBase\managers\DashboardManager;
 use JVBase\managers\DirectoryManager;
 use JVBase\managers\queue\Queue;
-use JVBase\managers\ReferralManager;
-use JVBase\managers\RoleManager;
+use JVBase\managers\Referrals;
+use JVBase\managers\Roles;
 use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
@@ -42,7 +42,7 @@
     jvbSchedules();
 	error_log('Schedules done!');
 
-	RoleManager::activate();
+	Roles::activate();
 
 	jvbRegisterAdminPages();
 //	if (array_key_exists('integrations', JVB_SITE) && array_key_exists('umami', JVB_SITE['integrations']) && JVB_SITE['integrations']['umami']=== true) {
@@ -59,7 +59,7 @@
 
 function jvbAddAdminCaps()
 {
-	$roleManager = new RoleManager();
+	$roleManager = new Roles();
 
 
 	$role = get_role('administrator');
@@ -102,7 +102,7 @@
 		return;
 	}
 
-	$roleManager = new RoleManager();
+	$roleManager = new Roles();
 	$users = get_users(['role' => 'administrator']);
 
 	foreach (array_merge(Registrar::getRegistered('post'), Registrar::withFeature('is_content', 'term')) as $slug) {
@@ -269,7 +269,7 @@
 function jvbRegisterAdminPages():void
 {
 	if (Site::has('referrals')){
-		ReferralManager::addSubpage();
+		Referrals::addSubpage();
 	}
 //	SEOAdminPage::addSubpage();
 }
diff --git a/assets/css/admin/index.php b/assets/css/admin/index.php
index 4f2541d..6220032 100644
--- a/assets/css/admin/index.php
+++ b/assets/css/admin/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/assets/css/icons.css b/assets/css/icons.css
index 6751bac..834f0ae 100644
--- a/assets/css/icons.css
+++ b/assets/css/icons.css
@@ -1 +1 @@
-.icon-google-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjIsMTI4YTk0LDk0LDAsMSwxLTIxLjQ5LTU5LjgyLDYsNiwwLDEsMS05LjI1LDcuNjRBODIsODIsMCwxLDAsMjA5Ljc4LDEzNEgxMjhhNiw2LDAsMCwxLDAtMTJoODhBNiw2LDAsMCwxLDIyMiwxMjhaIi8+PC9zdmc+');}.icon-apple-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTkuNCwxNjcuODRDMjAxLjcxLDE1NS42OSwxOTgsMTM1LjEyLDE5OCwxMjBjMC0xOC40MiwxMy44Ni0zNC4yOSwyMi4xMi00Mi4xMmE2LDYsMCwwLDAsMC04LjcxQzIwOCw1Ny43LDE4Ny4wNyw1MCwxNjgsNTBhNzAuMjMsNzAuMjMsMCwwLDAtNDAsMTIuNTUsNjkuNiw2OS42LDAsMCwwLTg5LjMxLDguMDhBNzIuNjMsNzIuNjMsMCwwLDAsMTgsMTIzLjM1YTEyNS4xMSwxMjUuMTEsMCwwLDAsMzkuNTMsODguMzNBMzcuODUsMzcuODUsMCwwLDAsODMuNiwyMjJoODcuN0EzNy44MywzNy44MywwLDAsMCwxOTksMjEwLjA3YTEyMi42LDEyMi42LDAsMCwwLDE3LjU0LTI0LjJjNi41NS0xMiw1Ljc3LTEzLjc1LDUtMTUuNDhBNi4wNyw2LjA3LDAsMCwwLDIxOS40LDE2Ny44NFptLTI5LjIzLDM0QTI1LjgyLDI1LjgyLDAsMCwxLDE3MS4zLDIxMEg4My42QTI1Ljg1LDI1Ljg1LDAsMCwxLDY1Ljc4LDIwMywxMTMuMjEsMTEzLjIxLDAsMCwxLDMwLDEyM2E2MC41NSw2MC41NSwwLDAsMSwxNy4yMS00NEE1Ni44Miw1Ni44MiwwLDAsMSw4OCw2MmguODFhNTcuMzUsNTcuMzUsMCwwLDEsMzUuNDQsMTIuNzEsNiw2LDAsMCwwLDcuNSwwQTU3LjM5LDU3LjM5LDAsMCwxLDE2OCw2MmMxMy44OSwwLDI4LjgxLDQuNjgsMzkuMTEsMTItOS40NCwxMC4xNC0yMS4xLDI2LjU5LTIxLjEsNDYsMCwyMy43OCw3LjgxLDQyLjYsMjIuNjYsNTQuNzdBMTA3LjMzLDEwNy4zMywwLDAsMSwxOTAuMTcsMjAxLjg5Wm0tNjAtMTcxLjM5QTM4LDM4LDAsMCwxLDE2NywyaDFhNiw2LDAsMCwxLDAsMTJoLTFhMjYsMjYsMCwwLDAtMjUuMTgsMTkuNSw2LDYsMCwxLDEtMTEuNjItM1oiLz48L3N2Zz4=');}.icon-check-circle{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzIuMjQsOTkuNzZhNiw2LDAsMCwxLDAsOC40OGwtNTYsNTZhNiw2LDAsMCwxLTguNDgsMGwtMjQtMjRhNiw2LDAsMCwxLDguNDgtOC40OEwxMTIsMTUxLjUxbDUxLjc2LTUxLjc1QTYsNiwwLDAsMSwxNzIuMjQsOTkuNzZaTTIzMCwxMjhBMTAyLDEwMiwwLDEsMSwxMjgsMjYsMTAyLjEyLDEwMi4xMiwwLDAsMSwyMzAsMTI4Wm0tMTIsMGE5MCw5MCwwLDEsMC05MCw5MEE5MC4xLDkwLjEsMCwwLDAsMjE4LDEyOFoiLz48L3N2Zz4=');}.icon-cloud-slash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik01Mi40NCwzNkE2LDYsMCwwLDAsNDMuNTYsNDRsNDAuMTgsNDQuMmMtLjQ1Ljg3LS45LDEuNzUtMS4zMiwyLjY0QTYyLDYyLDAsMSwwLDcyLDIxNGg4OGE4NS4yMyw4NS4yMywwLDAsMCwzMi4zNS02LjNMMjAzLjU2LDIyMGE2LDYsMCwwLDAsOC44OC04LjA4Wk0xNjAsMjAySDcyYTUwLDUwLDAsMSwxLDUuOS05OS42NEE4Ni4yNSw4Ni4yNSwwLDAsMCw3NCwxMjhhNiw2LDAsMCwwLDEyLDAsNzMuOTIsNzMuOTIsMCwwLDEsNi40NC0zMC4ybDkxLjIyLDEwMC4zNEE3My42NSw3My42NSwwLDAsMSwxNjAsMjAyWm04Ni03NGE4NS44NSw4NS44NSwwLDAsMS0yMS44NSw1Ny4yNyw2LDYsMCwwLDEtNC40NywyLDYsNiwwLDAsMS00LjQ3LTEwLDc0LDc0LDAsMCwwLTk5LTEwOC45Miw2LDYsMCwxLDEtNy4xMS05LjY3QTg2LDg2LDAsMCwxLDI0NiwxMjhaIi8+PC9zdmc+');}.icon-exclamation-mark{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNDIsMjAwYTE0LDE0LDAsMSwxLTE0LTE0QTE0LDE0LDAsMCwxLDE0MiwyMDBabS0xNC00MmE2LDYsMCwwLDAsNi02VjQ4YTYsNiwwLDAsMC0xMiwwVjE1MkE2LDYsMCwwLDAsMTI4LDE1OFoiLz48L3N2Zz4=');}.icon-cloud-arrow-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yNDYsMTI4YTg1LjI3LDg1LjI3LDAsMCwxLTE3LjIsNTEuNiw2LDYsMCwxLDEtOS42LTcuMkE3NCw3NCwwLDEsMCw4NiwxMjhhNiw2LDAsMCwxLTEyLDAsODUuNTQsODUuNTQsMCwwLDEsMy45MS0yNS42NEE1MC42OCw1MC42OCwwLDAsMCw3MiwxMDJhNTAsNTAsMCwwLDAsMCwxMDBIOTZhNiw2LDAsMCwxLDAsMTJINzJBNjIsNjIsMCwxLDEsODIuNDMsOTAuODgsODYsODYsMCwwLDEsMjQ2LDEyOFptLTY2LjI0LDQzLjc2TDE1OCwxOTMuNTFWMTI4YTYsNiwwLDAsMC0xMiwwdjY1LjUxbC0yMS43Ni0yMS43NWE2LDYsMCwwLDAtOC40OCw4LjQ4bDMyLDMyYTYsNiwwLDAsMCw4LjQ4LDBsMzItMzJhNiw2LDAsMCwwLTguNDgtOC40OFoiLz48L3N2Zz4=');}.icon-caret-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTIuMjQsMTAwLjI0bC04MCw4MGE2LDYsMCwwLDEtOC40OCwwbC04MC04MGE2LDYsMCwwLDEsOC40OC04LjQ4TDEyOCwxNjcuNTFsNzUuNzYtNzUuNzVhNiw2LDAsMCwxLDguNDgsOC40OFoiLz48L3N2Zz4=');}.icon-cloud-arrow-up{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xODguMjQsMTY0LjI0YTYsNiwwLDAsMS04LjQ4LDBMMTU4LDE0Mi40OVYyMDhhNiw2LDAsMCwxLTEyLDBWMTQyLjQ5bC0yMS43NiwyMS43NWE2LDYsMCwwLDEtOC40OC04LjQ4bDMyLTMyYTYsNiwwLDAsMSw4LjQ4LDBsMzIsMzJBNiw2LDAsMCwxLDE4OC4yNCwxNjQuMjRaTTE2MCw0MkE4Ni4xLDg2LjEsMCwwLDAsODIuNDMsOTAuODgsNjIsNjIsMCwxLDAsNzIsMjE0aDQwYTYsNiwwLDAsMCwwLTEySDcyYTUwLDUwLDAsMCwxLDAtMTAwLDUwLjY4LDUwLjY4LDAsMCwxLDUuOTEuMzZBODUuNTQsODUuNTQsMCwwLDAsNzQsMTI4YTYsNiwwLDAsMCwxMiwwLDc0LDc0LDAsMSwxLDEwMy42LDY3Ljg1LDYsNiwwLDAsMCw0LjgsMTFBODYsODYsMCwwLDAsMTYwLDQyWiIvPjwvc3ZnPg==');}.icon-cloud-check{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDJBODYuMTEsODYuMTEsMCwwLDAsODIuNDMsOTAuODgsNjIsNjIsMCwxLDAsNzIsMjE0aDg4YTg2LDg2LDAsMCwwLDAtMTcyWm0wLDE2MEg3MmE1MCw1MCwwLDAsMSwwLTEwMCw1MC42Nyw1MC42NywwLDAsMSw1LjkxLjM1QTg1LjYxLDg1LjYxLDAsMCwwLDc0LDEyOGE2LDYsMCwwLDAsMTIsMCw3NCw3NCwwLDEsMSw3NCw3NFptMzYuMjQtOTQuMjRhNiw2LDAsMCwxLDAsOC40OGwtNDgsNDhhNiw2LDAsMCwxLTguNDgsMGwtMjQtMjRhNiw2LDAsMCwxLDguNDgtOC40OEwxNDQsMTUxLjUxbDQzLjc2LTQzLjc1QTYsNiwwLDAsMSwxOTYuMjQsMTA3Ljc2WiIvPjwvc3ZnPg==');}.icon-cloud-warning{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDJBODYuMTEsODYuMTEsMCwwLDAsODIuNDMsOTAuODgsNjIsNjIsMCwxLDAsNzIsMjE0aDg4YTg2LDg2LDAsMCwwLDAtMTcyWm0wLDE2MEg3MmE1MCw1MCwwLDAsMSwwLTEwMCw1MC42Nyw1MC42NywwLDAsMSw1LjkxLjM1QTg1LjYxLDg1LjYxLDAsMCwwLDc0LDEyOGE2LDYsMCwwLDAsMTIsMCw3NCw3NCwwLDEsMSw3NCw3NFptLTYtNzRWODhhNiw2LDAsMCwxLDEyLDB2NDBhNiw2LDAsMCwxLTEyLDBabTE2LDM2YTEwLDEwLDAsMSwxLTEwLTEwQTEwLDEwLDAsMCwxLDE3MCwxNjRaIi8+PC9zdmc+');}.icon-syncing{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iY3VycmVudENvbG9yIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggaWQ9InJlZnJlc2giIGQ9Ik0xNjAuMDQ3IDEyMi44NzVhMzAuNzg0IDMwLjc4NCAwIDAgMC0yMS43NSA4Ljc5N2MtMi44NDIgMy4wMDMtLjQ2NyA0Ljk3MSAxLjMxMiAzLjE1NiAxMS4wNDMtMTAuNzg2IDI4LjcxLTEwLjY4IDM5LjYyNS4yMzRsNy4yMDMgNy4yMDRoLTEyLjg3NWMtMy4zNDcuMDA4LTMuMTY1IDMuODc1IDAgMy44NzVoMTYuMTFjMi4wNjIgMCAyLjU0LTEuNDE4IDIuNTYyLTQuOTdsLjA5NC0xNC45MjFjLjAyLTMuMjktMy40MzctMy4xNjUtMy40MzcgMHYxMi44NmwtNy4yMDMtNy4xODhhMzAuNzY4IDMwLjc2OCAwIDAgMC0yMS42NDEtOS4wNDd6bS0yOS41OTQgMzkuNzk3Yy0yLjA2MiAwLTIuNTI0IDEuNDAyLTIuNTQ3IDQuOTUzbC0uMDk0IDE0LjkyMmMtLjAyIDMuMjkgMy40MjIgMy4xNjQgMy40MjIgMHYtMTIuODZsNy4yMDMgNy4yMDRjMTEuOTU2IDExLjk1NSAzMS4zMTIgMTIuMDY0IDQzLjQwNy4yNSAyLjg0Mi0zLjAwMy40NTEtNC45ODgtMS4zMjgtMy4xNzItMTEuMDQzIDEwLjc4Ni0yOC43MSAxMC42OC0zOS42MjUtLjIzNWwtNy4xODgtNy4yMDNoMTIuODZjMy4zNDctLjAwOCAzLjE2NS0zLjg2IDAtMy44NmgtMTYuMTF6Ii8+PHBhdGggZD0iTTE2MCA0NGE4NC4xMSA4NC4xMSAwIDAgMC03Ni40MSA0OS4xMkE2MC43MSA2MC43MSAwIDAgMCA3MiA5MmE2MCA2MCAwIDAgMCAwIDEyMGg4OGE4NCA4NCAwIDAgMCAwLTE2OFptMCAxNjBINzJhNTIgNTIgMCAxIDEgOC41NS0xMDMuM0E4My42NiA4My42NiAwIDAgMCA3NiAxMjhhNCA0IDAgMCAwIDggMCA3NiA3NiAwIDEgMSA3NiA3NloiLz48L3N2Zz4=');}.icon-cloud-x{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDJBODYuMTEsODYuMTEsMCwwLDAsODIuNDMsOTAuODgsNjIsNjIsMCwxLDAsNzIsMjE0aDg4YTg2LDg2LDAsMCwwLDAtMTcyWm0wLDE2MEg3MmE1MCw1MCwwLDAsMSwwLTEwMCw1MC42Nyw1MC42NywwLDAsMSw1LjkxLjM1QTg1LjYxLDg1LjYxLDAsMCwwLDc0LDEyOGE2LDYsMCwwLDAsMTIsMCw3NCw3NCwwLDEsMSw3NCw3NFptMjguMjQtODUuNzZMMTY4LjQ4LDEzNmwxOS43NiwxOS43NmE2LDYsMCwxLDEtOC40OCw4LjQ4TDE2MCwxNDQuNDhsLTE5Ljc2LDE5Ljc2YTYsNiwwLDAsMS04LjQ4LTguNDhMMTUxLjUyLDEzNmwtMTkuNzYtMTkuNzZhNiw2LDAsMCwxLDguNDgtOC40OEwxNjAsMTI3LjUybDE5Ljc2LTE5Ljc2YTYsNiwwLDAsMSw4LjQ4LDguNDhaIi8+PC9zdmc+');}.icon-arrows-clockwise{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjIsNDhWOTZhNiw2LDAsMCwxLTYsNkgxNjhhNiw2LDAsMCwxLDAtMTJoMzMuNTJMMTgzLjQ3LDcyYTgxLjUxLDgxLjUxLDAsMCwwLTU3LjUzLTI0aC0uNDZBODEuNSw4MS41LDAsMCwwLDY4LjE5LDcxLjI4YTYsNiwwLDEsMS04LjM4LTguNTgsOTMuMzgsOTMuMzgsMCwwLDEsNjUuNjctMjYuNzZIMTI2YTkzLjQ1LDkzLjQ1LDAsMCwxLDY2LDI3LjUzbDE4LDE4VjQ4YTYsNiwwLDAsMSwxMiwwWk0xODcuODEsMTg0LjcyYTgxLjUsODEuNSwwLDAsMS01Ny4yOSwyMy4zNGgtLjQ2YTgxLjUxLDgxLjUxLDAsMCwxLTU3LjUzLTI0TDU0LjQ4LDE2Nkg4OGE2LDYsMCwwLDAsMC0xMkg0MGE2LDYsMCwwLDAtNiw2djQ4YTYsNiwwLDAsMCwxMiwwVjE3NC40OGwxOCwxOC4wNWE5My40NSw5My40NSwwLDAsMCw2NiwyNy41M2guNTJhOTMuMzgsOTMuMzgsMCwwLDAsNjUuNjctMjYuNzYsNiw2LDAsMSwwLTguMzgtOC41OFoiLz48L3N2Zz4=');}.icon-share-fat{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzYuMjQsMTA3Ljc2bC04MC04MEE2LDYsMCwwLDAsMTQ2LDMyVjc0LjJjLTU0LjQ4LDMuNTktMTIwLjM5LDU1LTEyNy45MywxMjAuNjZhMTAsMTAsMCwwLDAsMTcuMjMsOGgwQzQ2LjU2LDE5MC44NSw4NywxNTIuNiwxNDYsMTUwLjEzVjE5MmE2LDYsMCwwLDAsMTAuMjQsNC4yNGw4MC04MEE2LDYsMCwwLDAsMjM2LjI0LDEwNy43NlpNMTU4LDE3Ny41MlYxNDRhNiw2LDAsMCwwLTYtNmMtMjcuNzMsMC01NC43Niw3LjI1LTgwLjMyLDIxLjU1YTE5My4zOCwxOTMuMzgsMCwwLDAtNDAuODEsMzAuNjVjNC43LTI2LjU2LDIwLjE2LTUyLDQ0LTcyLjI3Qzk4LjQ3LDk3Ljk0LDEyNy4yOSw4NiwxNTIsODZhNiw2LDAsMCwwLDYtNlY0Ni40OUwyMjMuNTEsMTEyWiIvPjwvc3ZnPg==');}.icon-trash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTYsNTBIMTc0VjQwYTIyLDIyLDAsMCwwLTIyLTIySDEwNEEyMiwyMiwwLDAsMCw4Miw0MFY1MEg0MGE2LDYsMCwwLDAsMCwxMkg1MFYyMDhhMTQsMTQsMCwwLDAsMTQsMTRIMTkyYTE0LDE0LDAsMCwwLDE0LTE0VjYyaDEwYTYsNiwwLDAsMCwwLTEyWk05NCw0MGExMCwxMCwwLDAsMSwxMC0xMGg0OGExMCwxMCwwLDAsMSwxMCwxMFY1MEg5NFpNMTk0LDIwOGEyLDIsMCwwLDEtMiwySDY0YTIsMiwwLDAsMS0yLTJWNjJIMTk0Wk0xMTAsMTA0djY0YTYsNiwwLDAsMS0xMiwwVjEwNGE2LDYsMCwwLDEsMTIsMFptNDgsMHY2NGE2LDYsMCwwLDEtMTIsMFYxMDRhNiw2LDAsMCwxLDEyLDBaIi8+PC9zdmc+');}.icon-star{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzcuMjgsOTcuODdBMTQuMTgsMTQuMTgsMCwwLDAsMjI0Ljc2LDg4bC02MC4yNS00Ljg3LTIzLjIyLTU2LjJhMTQuMzcsMTQuMzcsMCwwLDAtMjYuNTgsMEw5MS40OSw4My4xMSwzMS4yNCw4OGExNC4xOCwxNC4xOCwwLDAsMC0xMi41Miw5Ljg5QTE0LjQzLDE0LjQzLDAsMCwwLDIzLDExMy4zMkw2OSwxNTIuOTNsLTE0LDU5LjI1YTE0LjQsMTQuNCwwLDAsMCw1LjU5LDE1LDE0LjEsMTQuMSwwLDAsMCwxNS45MS42TDEyOCwxOTYuMTJsNTEuNTgsMzEuNzFhMTQuMSwxNC4xLDAsMCwwLDE1LjkxLS42LDE0LjQsMTQuNCwwLDAsMCw1LjU5LTE1bC0xNC01OS4yNUwyMzMsMTEzLjMyQTE0LjQzLDE0LjQzLDAsMCwwLDIzNy4yOCw5Ny44N1ptLTEyLjE0LDYuMzctNDguNjksNDJhNiw2LDAsMCwwLTEuOTIsNS45MmwxNC44OCw2Mi43OWEyLjM1LDIuMzUsMCwwLDEtLjk1LDIuNTcsMi4yNCwyLjI0LDAsMCwxLTIuNi4xTDEzMS4xNCwxODRhNiw2LDAsMCwwLTYuMjgsMEw3MC4xNCwyMTcuNjFhMi4yNCwyLjI0LDAsMCwxLTIuNi0uMSwyLjM1LDIuMzUsMCwwLDEtMS0yLjU3bDE0Ljg4LTYyLjc5YTYsNiwwLDAsMC0xLjkyLTUuOTJsLTQ4LjY5LTQyYTIuMzcsMi4zNywwLDAsMS0uNzMtMi42NSwyLjI4LDIuMjgsMCwwLDEsMi4wNy0xLjY1bDYzLjkyLTUuMTZhNiw2LDAsMCwwLDUuMDYtMy42OWwyNC42My01OS42YTIuMzUsMi4zNSwwLDAsMSw0LjM4LDBsMjQuNjMsNTkuNmE2LDYsMCwwLDAsNS4wNiwzLjY5bDYzLjkyLDUuMTZhMi4yOCwyLjI4LDAsMCwxLDIuMDcsMS42NUEyLjM3LDIuMzcsMCwwLDEsMjI1LjE0LDEwNC4yNFoiLz48L3N2Zz4=');}.icon-alphabetical{--icon:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTgzLjc4IDE4NC4wNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNTkuNTg2IDY5Ljc0MmMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAyMjktMC41Mjk1OCAxLjk0OTNzMC4xNzkwMyAxLjU5MzcgMC41Mjk1OCAxLjk5NDRjMC4zNzU1OCAwLjM3NTU4IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGg3LjAxOTdsLTEyLjQyOCAzNC4zNjZoLTIuMTA3Yy0wLjg1MTMgMC0xLjQ2MSAwLjE5NjU2LTEuODM2NiAwLjU5NzE4LTAuMzUwNTQgMC4zNzU1OC0wLjUyOTU3IDEuMDM0MS0wLjUyOTU3IDEuOTYwNiAwIDAuOTI2NDQgMC4xNzkwMyAxLjU4MjUgMC41Mjk1NyAxLjk4MyAwLjM3NTU4IDAuMzc1NTkgMC45ODUyOSAwLjU2MzM4IDEuODM2NiAwLjU2MzM4aDEyLjU1MmMwLjg1MTMgMCAxLjQ1MjItMC4xODc3OSAxLjgwMjgtMC41NjMzOCAwLjM3NTU4LTAuNDAwNjIgMC41NjMzNy0xLjA1NjYgMC41NjMzNy0xLjk4MyAwLTAuOTI2NDUtMC4xODc3OS0xLjU4NS0wLjU2MzM3LTEuOTYwNi0wLjM1MDU0LTAuNDAwNjItMC45NTE0Ny0wLjU5NzE4LTEuODAyOC0wLjU5NzE4aC00LjU1MjFsMy4xMjExLTguOTM0OWgxOC4yMmwzLjA3NiA4LjkzNDloLTUuMDcwNGMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAzNDEtMC41Mjk1OCAxLjk2MDYgMCAwLjkyNjQ0IDAuMTc5MDMgMS41ODI1IDAuNTI5NTggMS45ODMgMC4zNzU1OCAwLjM3NTU5IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGgxMy4yOTZjMC44NTEzIDAgMS40NTIyLTAuMTg3NzkgMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzctMS4wNTY2IDAuNTYzMzctMS45ODMgMC0wLjkyNjQ1LTAuMTg3NzktMS41ODUtMC41NjMzNy0xLjk2MDYtMC4zNTA1NC0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOGgtMi4yODczbC0xMy4yNjItMzcuMDM2Yy0wLjMwMDQ3LTAuODUxMy0wLjc1OTk0LTEuNDYxLTEuMzg1OS0xLjgzNjYtMC42MDA5My0wLjQwMDYyLTEuNDA5Ny0wLjU5NzE4LTIuNDExMy0wLjU5NzE4em00NC4xNDYgMGMtMC44NTEzIDAtMS40NzIzIDAuMTk2NTYtMS44NDc4IDAuNTk3MTgtMC4zNTA1NSAwLjM3NTU4LTAuNTE4MyAxLjAyMjktMC41MTgzIDEuOTQ5M3YxMS45MWMwIDAuODc2MzMgMC4yMDUzMiAxLjUwNjEgMC42MzA5OCAxLjg4MTcgMC40MjU2NiAwLjM3NTU4IDEuMTU5MyAwLjU2MzM3IDIuMTg1OSAwLjU2MzM3czEuNzQ5LTAuMTg3NzkgMi4xNzQ3LTAuNTYzMzdjMC40MjU2OS0wLjM3NTU4IDAuNjQyMjYtMS4wMDUzIDAuNjQyMjYtMS44ODE3di05LjM1MTdoMTguODUxbC0yNC43NTQgMzUuMzAxYy0wLjM1MDU0IDAuNTI1ODItMC41MTgzMSAxLjA3MTctMC41MTgzMSAxLjYyMjYgMCAwLjkyNjQ1IDAuMTY3NzcgMS41ODI1IDAuNTE4MzEgMS45ODMxIDAuMzc1NTggMC4zNzU1OCAwLjk5NjU0IDAuNTYzMzggMS44NDc4IDAuNTYzMzhoMjguNzY2YzAuODUxMyAwIDEuNDUyMi0wLjE4NzggMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzgtMS4wNTY2IDAuNTYzMzgtMS45ODMxdi0xMi42NjVjMC0wLjg3NjMzLTAuMjE2NTgtMS40OTQ4LTAuNjQyMjUtMS44NzA0LTAuNDI1NjYtMC4zNzU1OC0xLjE0OC0wLjU2MzM4LTIuMTc0Ny0wLjU2MzM4LTEuMDI2NiAwLTEuNzQ5IDAuMTg3NzktMi4xNzQ3IDAuNTYzMzgtMC40MjU2NiAwLjM3NTU4LTAuNjQyMjQgMC45OTQwMi0wLjY0MjI0IDEuODcwNHYxMC4xMDdoLTE5Ljk3OGwyNC45MDEtMzUuNDU5YzAuMjUwMzktMC4zNTA1NCAwLjM3MTgzLTAuODM4ODMgMC4zNzE4My0xLjQ2NDggMC0wLjkyNjQ1LTAuMTg3OC0xLjU3MzctMC41NjMzOC0xLjk0OTMtMC4zNTA1NS0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOHptLTMxLjc1MiA1LjEwNDJoMC43MDk4NWw2Ljk4NTkgMjAuMzE1aC0xNC43MTZ6bS0zNy43MjMtNDkuMTgzYy00LjczNDIgMC04LjYzMTMgMy44OTctOC42MzEzIDguNjMxM3YxMTUuNDdjMCA0LjczNDIgMy44OTcgOC42MzEzIDguNjMxMyA4LjYzMTNoMTE1LjI2YzQuNzM0MiAwIDguNjQyMS0zLjg5NyA4LjY0MjEtOC42MzEzdi0xMTUuNDdjMC00LjczNDItMy45MDgyLTguNjMxMy04LjY0MjEtOC42MzEzem0wIDUuNzI0aDExNS4yNmMxLjY1OCAwIDIuOTA3IDEuMjQ5MSAyLjkwNyAyLjkwNzF2MTE1LjQ3YzAgMS42NTgtMS4yNDkxIDIuOTA3LTIuOTA3IDIuOTA3aC0xMTUuMjZjLTEuNjU4IDAtMi44OTU4LTEuMjQ5MS0yLjg5NTgtMi45MDd2LTExNS40N2MwLTEuNjU4IDEuMjM3OC0yLjkwNzEgMi44OTU4LTIuOTA3MXoiIGZpbGw9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIuNzIxMTQiLz48L3N2Zz4=');}.icon-sun-dim{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xMjIsNDBWMzJhNiw2LDAsMCwxLDEyLDB2OGE2LDYsMCwwLDEtMTIsMFptNjgsODhhNjIsNjIsMCwxLDEtNjItNjJBNjIuMDcsNjIuMDcsMCwwLDEsMTkwLDEyOFptLTEyLDBhNTAsNTAsMCwxLDAtNTAsNTBBNTAuMDYsNTAuMDYsMCwwLDAsMTc4LDEyOFpNNTkuNzYsNjguMjRhNiw2LDAsMSwwLDguNDgtOC40OGwtOC04YTYsNiwwLDAsMC04LjQ4LDguNDhabTAsMTE5LjUyLTgsOGE2LDYsMCwxLDAsOC40OCw4LjQ4bDgtOGE2LDYsMCwxLDAtOC40OC04LjQ4Wm0xMzYtMTM2LTgsOGE2LDYsMCwxLDAsOC40OCw4LjQ4bDgtOGE2LDYsMCwwLDAtOC40OC04LjQ4Wm0uNDgsMTM2YTYsNiwwLDAsMC04LjQ4LDguNDhsOCw4YTYsNiwwLDAsMCw4LjQ4LTguNDhaTTQwLDEyMkgzMmE2LDYsMCwwLDAsMCwxMmg4YTYsNiwwLDAsMCwwLTEyWm04OCw4OGE2LDYsMCwwLDAtNiw2djhhNiw2LDAsMCwwLDEyLDB2LThBNiw2LDAsMCwwLDEyOCwyMTBabTk2LTg4aC04YTYsNiwwLDAsMCwwLDEyaDhhNiw2LDAsMCwwLDAtMTJaIi8+PC9zdmc+');}.icon-moon{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzIuMTMsMTQzLjY0YTYsNiwwLDAsMC02LTEuNDlBOTAuMDcsOTAuMDcsMCwwLDEsMTEzLjg2LDI5Ljg1YTYsNiwwLDAsMC03LjQ5LTcuNDhBMTAyLjg4LDEwMi44OCwwLDAsMCw1NC40OCw1OC42OCwxMDIsMTAyLDAsMCwwLDE5Ny4zMiwyMDEuNTJhMTAyLjg4LDEwMi44OCwwLDAsMCwzNi4zMS01MS44OUE2LDYsMCwwLDAsMjMyLjEzLDE0My42NFptLTQyLDQ4LjI5YTkwLDkwLDAsMCwxLTEyNi0xMjZBOTAuOSw5MC45LDAsMCwxLDk5LjY1LDM3LjY2LDEwMi4wNiwxMDIuMDYsMCwwLDAsMjE4LjM0LDE1Ni4zNSw5MC45LDkwLjksMCwwLDEsMTkwLjEsMTkxLjkzWiIvPjwvc3ZnPg==');}.icon-house{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTcuOSwxMTAuMWwtODAtODBhMTQsMTQsMCwwLDAtMTkuOCwwbC04MCw4MEExMy45MiwxMy45MiwwLDAsMCwzNCwxMjB2OTZhNiw2LDAsMCwwLDYsNmg2NGE2LDYsMCwwLDAsNi02VjE1OGgzNnY1OGE2LDYsMCwwLDAsNiw2aDY0YTYsNiwwLDAsMCw2LTZWMTIwQTEzLjkyLDEzLjkyLDAsMCwwLDIxNy45LDExMC4xWk0yMTAsMjEwSDE1OFYxNTJhNiw2LDAsMCwwLTYtNkgxMDRhNiw2LDAsMCwwLTYsNnY1OEg0NlYxMjBhMiwyLDAsMCwxLC41OC0xLjQybDgwLTgwYTIsMiwwLDAsMSwyLjg0LDBsODAsODBBMiwyLDAsMCwxLDIxMCwxMjBaIi8+PC9zdmc+');}.icon-star-half-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMCwyNC41LDE3LjgyTDEyOCwxOTguNDlsNTAuNTMsMzEuMDhBMTYuNCwxNi40LDAsMCwwLDIwMywyMTEuNzVsLTEzLjc2LTU4LjA3LDQ1LTM4LjgzQTE2LjQzLDE2LjQzLDAsMCwwLDIzOS4xOCw5Ny4yNlptLTE1LjM0LDUuNDctNDguNyw0MmE4LDgsMCwwLDAtMi41Niw3LjkxbDE0Ljg4LDYyLjhhLjM3LjM3LDAsMCwxLS4xNy40OGMtLjE4LjE0LS4yMy4xMS0uMzgsMGwtNTQuNzItMzMuNjVBOCw4LDAsMCwwLDEyOCwxODEuMVYzMmMuMjQsMCwuMjcuMDguMzUuMjZMMTUzLDkxLjg2YTgsOCwwLDAsMCw2Ljc1LDQuOTJsNjMuOTEsNS4xNmMuMTYsMCwuMjUsMCwuMzQuMjlTMjI0LDEwMi42MywyMjMuODQsMTAyLjczWiIvPjwvc3ZnPg==');}.icon-star-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzQuMjksMTE0Ljg1bC00NSwzOC44M0wyMDMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMS0yNC41LDE3LjgyTDEyOCwxOTguNDksNzcuNDcsMjI5LjU3QTE2LjQsMTYuNCwwLDAsMSw1MywyMTEuNzVsMTMuNzYtNTguMDctNDUtMzguODNBMTYuNDYsMTYuNDYsMCwwLDEsMzEuMDgsODZsNTktNC43NiwyMi43Ni01NS4wOGExNi4zNiwxNi4zNiwwLDAsMSwzMC4yNywwbDIyLjc1LDU1LjA4LDU5LDQuNzZhMTYuNDYsMTYuNDYsMCwwLDEsOS4zNywyOC44NloiLz48L3N2Zz4=');}
\ No newline at end of file
+.icon-google-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsMTI4YTk2LDk2LDAsMSwxLTIxLjk1LTYxLjA5LDgsOCwwLDEsMS0xMi4zMywxMC4xOEE4MCw4MCwwLDEsMCwyMDcuNiwxMzZIMTI4YTgsOCwwLDAsMSwwLTE2aDg4QTgsOCwwLDAsMSwyMjQsMTI4WiIvPjwvc3ZnPg==');}.icon-apple-logo{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjMuMywxNjkuNTlhOC4wNyw4LjA3LDAsMCwwLTIuOC0zLjRDMjAzLjUzLDE1NC41MywyMDAsMTM0LjY0LDIwMCwxMjBjMC0xNy42NywxMy40Ny0zMy4wNiwyMS41LTQwLjY3YTgsOCwwLDAsMCwwLTExLjYyQzIwOC44Miw1NS43NCwxODcuODIsNDgsMTY4LDQ4YTcyLjIsNzIuMiwwLDAsMC00MCwxMi4xMyw3MS41Niw3MS41NiwwLDAsMC05MC43MSw5LjA5QTc0LjYzLDc0LjYzLDAsMCwwLDE2LDEyMy40YTEyNy4wNiwxMjcuMDYsMCwwLDAsNDAuMTQsODkuNzNBMzkuOCwzOS44LDAsMCwwLDgzLjU5LDIyNGg4Ny42OGEzOS44NCwzOS44NCwwLDAsMCwyOS4xMi0xMi41NywxMjUsMTI1LDAsMCwwLDE3LjgyLTI0LjZDMjI1LjIzLDE3NCwyMjQuMzMsMTcyLDIyMy4zLDE2OS41OVptLTM0LjYzLDMwLjk0YTIzLjc2LDIzLjc2LDAsMCwxLTE3LjQsNy40N0g4My41OWEyMy44MiwyMy44MiwwLDAsMS0xNi40NC02LjUxQTExMS4xNCwxMTEuMTQsMCwwLDEsMzIsMTIzLDU4LjUsNTguNSwwLDAsMSw0OC42NSw4MC40Nyw1NC44MSw1NC44MSwwLDAsMSw4OCw2NGguNzhBNTUuNDUsNTUuNDUsMCwwLDEsMTIzLDc2LjI4YTgsOCwwLDAsMCwxMCwwQTU1LjQ0LDU1LjQ0LDAsMCwxLDE2OCw2NGE3MC42NCw3MC42NCwwLDAsMSwzNiwxMC4zNWMtMTMsMTQuNTItMjAsMzAuNDctMjAsNDUuNjUsMCwyMy43Nyw3LjY0LDQyLjczLDIyLjE4LDU1LjNBMTA1LjgyLDEwNS44MiwwLDAsMSwxODguNjcsMjAwLjUzWk0xMjguMjMsMzBBNDAsNDAsMCwwLDEsMTY3LDBoMWE4LDgsMCwwLDEsMCwxNmgtMWEyNCwyNCwwLDAsMC0yMy4yNCwxOCw4LDgsMCwxLDEtMTUuNS00WiIvPjwvc3ZnPg==');}.icon-check-circle{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzMuNjYsOTguMzRhOCw4LDAsMCwxLDAsMTEuMzJsLTU2LDU2YTgsOCwwLDAsMS0xMS4zMiwwbC0yNC0yNGE4LDgsMCwwLDEsMTEuMzItMTEuMzJMMTEyLDE0OC42OWw1MC4zNC01MC4zNUE4LDgsMCwwLDEsMTczLjY2LDk4LjM0Wk0yMzIsMTI4QTEwNCwxMDQsMCwxLDEsMTI4LDI0LDEwNC4xMSwxMDQuMTEsMCwwLDEsMjMyLDEyOFptLTE2LDBhODgsODgsMCwxLDAtODgsODhBODguMSw4OC4xLDAsMCwwLDIxNiwxMjhaIi8+PC9zdmc+');}details.all-filters summary::after,.icon-faders-horizontal{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzYsODBhOCw4LDAsMCwxLDgtOGgzMmE4LDgsMCwwLDEsMCwxNkgxODRBOCw4LDAsMCwxLDE3Niw4MFpNNDAsODhIMTQ0djE2YTgsOCwwLDAsMCwxNiwwVjU2YTgsOCwwLDAsMC0xNiwwVjcySDQwYTgsOCwwLDAsMCwwLDE2Wm0xNzYsODBIMTIwYTgsOCwwLDAsMCwwLDE2aDk2YTgsOCwwLDAsMCwwLTE2Wk04OCwxNDRhOCw4LDAsMCwwLTgsOHYxNkg0MGE4LDgsMCwwLDAsMCwxNkg4MHYxNmE4LDgsMCwwLDAsMTYsMFYxNTJBOCw4LDAsMCwwLDg4LDE0NFoiLz48L3N2Zz4=');}.icon-cloud-slash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik01My45MiwzNC42MkE4LDgsMCwxLDAsNDIuMDgsNDUuMzhMODEuMzIsODguNTVsLS4wNi4xMkE2NSw2NSwwLDAsMCw3Miw4OGE2NCw2NCwwLDAsMCwwLDEyOGg4OGE4Ny4zNCw4Ny4zNCwwLDAsMCwzMS44LTUuOTNsMTAuMjgsMTEuMzFhOCw4LDAsMSwwLDExLjg0LTEwLjc2Wk0xNjAsMjAwSDcyYTQ4LDQ4LDAsMCwxLDAtOTZjMS4xLDAsMi4yLDAsMy4zLjEyQTg4LjQsODguNCwwLDAsMCw3MiwxMjhhOCw4LDAsMCwwLDE2LDAsNzIuMjUsNzIuMjUsMCwwLDEsNS4wNi0yNi41NGw4Nyw5NS43QTcxLjY2LDcxLjY2LDAsMCwxLDE2MCwyMDBabTg4LTcyYTg3Ljg5LDg3Ljg5LDAsMCwxLTIyLjM1LDU4LjYxQTgsOCwwLDAsMSwyMTMuNzEsMTc2LDcyLDcyLDAsMCwwLDExNy4zNyw3MGE4LDgsMCwwLDEtOS40OC0xMi44OUE4OCw4OCwwLDAsMSwyNDgsMTI4WiIvPjwvc3ZnPg==');}.icon-exclamation-mark{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNDQsMjAwYTE2LDE2LDAsMSwxLTE2LTE2QTE2LDE2LDAsMCwxLDE0NCwyMDBabS0xNi00MGE4LDgsMCwwLDAsOC04VjQ4YTgsOCwwLDAsMC0xNiwwVjE1MkE4LDgsMCwwLDAsMTI4LDE2MFoiLz48L3N2Zz4=');}.icon-cloud-arrow-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yNDgsMTI4YTg3LjM0LDg3LjM0LDAsMCwxLTE3LjYsNTIuODEsOCw4LDAsMSwxLTEyLjgtOS42MkE3MS4zNCw3MS4zNCwwLDAsMCwyMzIsMTI4YTcyLDcyLDAsMCwwLTE0NCwwLDgsOCwwLDAsMS0xNiwwLDg4LDg4LDAsMCwxLDMuMjktMjMuODhDNzQuMiwxMDQsNzMuMSwxMDQsNzIsMTA0YTQ4LDQ4LDAsMCwwLDAsOTZIOTZhOCw4LDAsMCwxLDAsMTZINzJBNjQsNjQsMCwxLDEsODEuMjksODguNjgsODgsODgsMCwwLDEsMjQ4LDEyOFptLTY5LjY2LDQyLjM0TDE2MCwxODguNjlWMTI4YTgsOCwwLDAsMC0xNiwwdjYwLjY5bC0xOC4zNC0xOC4zNWE4LDgsMCwwLDAtMTEuMzIsMTEuMzJsMzIsMzJhOCw4LDAsMCwwLDExLjMyLDBsMzItMzJhOCw4LDAsMCwwLTExLjMyLTExLjMyWiIvPjwvc3ZnPg==');}details summary::after,.icon-caret-down{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTMuNjYsMTAxLjY2bC04MCw4MGE4LDgsMCwwLDEtMTEuMzIsMGwtODAtODBBOCw4LDAsMCwxLDUzLjY2LDkwLjM0TDEyOCwxNjQuNjlsNzQuMzQtNzQuMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-cloud-arrow-up{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNzguMzQsMTY1LjY2LDE2MCwxNDcuMzFWMjA4YTgsOCwwLDAsMS0xNiwwVjE0Ny4zMWwtMTguMzQsMTguMzVhOCw4LDAsMCwxLTExLjMyLTExLjMybDMyLTMyYTgsOCwwLDAsMSwxMS4zMiwwbDMyLDMyYTgsOCwwLDAsMS0xMS4zMiwxMS4zMlpNMTYwLDQwQTg4LjA4LDg4LjA4LDAsMCwwLDgxLjI5LDg4LjY4LDY0LDY0LDAsMSwwLDcyLDIxNmg0MGE4LDgsMCwwLDAsMC0xNkg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTJBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDEwMC44LDY2LDgsOCwwLDAsMCwzLjIsMTUuMzQsNy45LDcuOSwwLDAsMCwzLjItLjY4QTg4LDg4LDAsMCwwLDE2MCw0MFoiLz48L3N2Zz4=');}.icon-cloud-check{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0zNy42Ni05My42NmE4LDgsMCwwLDEsMCwxMS4zMmwtNDgsNDhhOCw4LDAsMCwxLTExLjMyLDBsLTI0LTI0YTgsOCwwLDAsMSwxMS4zMi0xMS4zMkwxNDQsMTQ4LjY5bDQyLjM0LTQyLjM1QTgsOCwwLDAsMSwxOTcuNjYsMTA2LjM0WiIvPjwvc3ZnPg==');}.icon-cloud-warning{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0tOC03MlY4OGE4LDgsMCwwLDEsMTYsMHY0MGE4LDgsMCwwLDEtMTYsMFptMjAsMzZhMTIsMTIsMCwxLDEtMTItMTJBMTIsMTIsMCwwLDEsMTcyLDE2NFoiLz48L3N2Zz4=');}.icon-syncing{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0iY3VycmVudENvbG9yIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggaWQ9InJlZnJlc2giIGQ9Ik0xNjAuMDQ3IDEyMi44NzVhMzAuNzg0IDMwLjc4NCAwIDAgMC0yMS43NSA4Ljc5N2MtMi44NDIgMy4wMDMtLjQ2NyA0Ljk3MSAxLjMxMiAzLjE1NiAxMS4wNDMtMTAuNzg2IDI4LjcxLTEwLjY4IDM5LjYyNS4yMzRsNy4yMDMgNy4yMDRoLTEyLjg3NWMtMy4zNDcuMDA4LTMuMTY1IDMuODc1IDAgMy44NzVoMTYuMTFjMi4wNjIgMCAyLjU0LTEuNDE4IDIuNTYyLTQuOTdsLjA5NC0xNC45MjFjLjAyLTMuMjktMy40MzctMy4xNjUtMy40MzcgMHYxMi44NmwtNy4yMDMtNy4xODhhMzAuNzY4IDMwLjc2OCAwIDAgMC0yMS42NDEtOS4wNDd6bS0yOS41OTQgMzkuNzk3Yy0yLjA2MiAwLTIuNTI0IDEuNDAyLTIuNTQ3IDQuOTUzbC0uMDk0IDE0LjkyMmMtLjAyIDMuMjkgMy40MjIgMy4xNjQgMy40MjIgMHYtMTIuODZsNy4yMDMgNy4yMDRjMTEuOTU2IDExLjk1NSAzMS4zMTIgMTIuMDY0IDQzLjQwNy4yNSAyLjg0Mi0zLjAwMy40NTEtNC45ODgtMS4zMjgtMy4xNzItMTEuMDQzIDEwLjc4Ni0yOC43MSAxMC42OC0zOS42MjUtLjIzNWwtNy4xODgtNy4yMDNoMTIuODZjMy4zNDctLjAwOCAzLjE2NS0zLjg2IDAtMy44NmgtMTYuMTF6Ii8+PHBhdGggZD0iTTE2MCA0NGE4NC4xMSA4NC4xMSAwIDAgMC03Ni40MSA0OS4xMkE2MC43MSA2MC43MSAwIDAgMCA3MiA5MmE2MCA2MCAwIDAgMCAwIDEyMGg4OGE4NCA4NCAwIDAgMCAwLTE2OFptMCAxNjBINzJhNTIgNTIgMCAxIDEgOC41NS0xMDMuM0E4My42NiA4My42NiAwIDAgMCA3NiAxMjhhNCA0IDAgMCAwIDggMCA3NiA3NiAwIDEgMSA3NiA3NloiLz48L3N2Zz4=');}.icon-cloud-x{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0xNjAsNDBBODguMDksODguMDksMCwwLDAsODEuMjksODguNjcsNjQsNjQsMCwxLDAsNzIsMjE2aDg4YTg4LDg4LDAsMCwwLDAtMTc2Wm0wLDE2MEg3MmE0OCw0OCwwLDAsMSwwLTk2YzEuMSwwLDIuMiwwLDMuMjkuMTFBODgsODgsMCwwLDAsNzIsMTI4YTgsOCwwLDAsMCwxNiwwLDcyLDcyLDAsMSwxLDcyLDcyWm0yOS42Ni04Mi4zNEwxNzEuMzEsMTM2bDE4LjM1LDE4LjM0YTgsOCwwLDAsMS0xMS4zMiwxMS4zMkwxNjAsMTQ3LjMxbC0xOC4zNCwxOC4zNWE4LDgsMCwwLDEtMTEuMzItMTEuMzJMMTQ4LjY5LDEzNmwtMTguMzUtMTguMzRhOCw4LDAsMCwxLDExLjMyLTExLjMyTDE2MCwxMjQuNjlsMTguMzQtMTguMzVhOCw4LDAsMCwxLDExLjMyLDExLjMyWiIvPjwvc3ZnPg==');}.icon-arrows-clockwise{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMjQsNDhWOTZhOCw4LDAsMCwxLTgsOEgxNjhhOCw4LDAsMCwxLDAtMTZoMjguNjlMMTgyLjA2LDczLjM3YTc5LjU2LDc5LjU2LDAsMCwwLTU2LjEzLTIzLjQzaC0uNDVBNzkuNTIsNzkuNTIsMCwwLDAsNjkuNTksNzIuNzEsOCw4LDAsMCwxLDU4LjQxLDYxLjI3YTk2LDk2LDAsMCwxLDEzNSwuNzlMMjA4LDc2LjY5VjQ4YTgsOCwwLDAsMSwxNiwwWk0xODYuNDEsMTgzLjI5YTgwLDgwLDAsMCwxLTExMi40Ny0uNjZMNTkuMzEsMTY4SDg4YTgsOCwwLDAsMCwwLTE2SDQwYTgsOCwwLDAsMC04LDh2NDhhOCw4LDAsMCwwLDE2LDBWMTc5LjMxbDE0LjYzLDE0LjYzQTk1LjQzLDk1LjQzLDAsMCwwLDEzMCwyMjIuMDZoLjUzYTk1LjM2LDk1LjM2LDAsMCwwLDY3LjA3LTI3LjMzLDgsOCwwLDAsMC0xMS4xOC0xMS40NFoiLz48L3N2Zz4=');}.icon-share-fat{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzcuNjYsMTA2LjM1bC04MC04MEE4LDgsMCwwLDAsMTQ0LDMyVjcyLjM1Yy0yNS45NCwyLjIyLTU0LjU5LDE0LjkyLTc4LjE2LDM0LjkxLTI4LjM4LDI0LjA4LTQ2LjA1LDU1LjExLTQ5Ljc2LDg3LjM3YTEyLDEyLDAsMCwwLDIwLjY4LDkuNThoMGMxMS0xMS43MSw1MC4xNC00OC43NCwxMDcuMjQtNTJWMTkyYTgsOCwwLDAsMCwxMy42Niw1LjY1bDgwLTgwQTgsOCwwLDAsMCwyMzcuNjYsMTA2LjM1Wk0xNjAsMTcyLjY5VjE0NGE4LDgsMCwwLDAtOC04Yy0yOC4wOCwwLTU1LjQzLDcuMzMtODEuMjksMjEuOGExOTYuMTcsMTk2LjE3LDAsMCwwLTM2LjU3LDI2LjUyYzUuOC0yMy44NCwyMC40Mi00Ni41MSw0Mi4wNS02NC44NkM5OS40MSw5OS43NywxMjcuNzUsODgsMTUyLDg4YTgsOCwwLDAsMCw4LThWNTEuMzJMMjIwLjY5LDExMloiLz48L3N2Zz4=');}.icon-trash{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMTYsNDhIMTc2VjQwYTI0LDI0LDAsMCwwLTI0LTI0SDEwNEEyNCwyNCwwLDAsMCw4MCw0MHY4SDQwYTgsOCwwLDAsMCwwLDE2aDhWMjA4YTE2LDE2LDAsMCwwLDE2LDE2SDE5MmExNiwxNiwwLDAsMCwxNi0xNlY2NGg4YTgsOCwwLDAsMCwwLTE2Wk05Niw0MGE4LDgsMCwwLDEsOC04aDQ4YTgsOCwwLDAsMSw4LDh2OEg5NlptOTYsMTY4SDY0VjY0SDE5MlpNMTEyLDEwNHY2NGE4LDgsMCwwLDEtMTYsMFYxMDRhOCw4LDAsMCwxLDE2LDBabTQ4LDB2NjRhOCw4LDAsMCwxLTE2LDBWMTA0YTgsOCwwLDAsMSwxNiwwWiIvPjwvc3ZnPg==');}.icon-star{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjM4LDE2LjM4LDAsMCwwLDI0LjUsMTcuODJMMTI4LDE5OC40OWw1MC41MywzMS4wOEExNi40LDE2LjQsMCwwLDAsMjAzLDIxMS43NWwtMTMuNzYtNTguMDcsNDUtMzguODNBMTYuNDMsMTYuNDMsMCwwLDAsMjM5LjE4LDk3LjI2Wm0tMTUuMzQsNS40Ny00OC43LDQyYTgsOCwwLDAsMC0yLjU2LDcuOTFsMTQuODgsNjIuOGEuMzcuMzcsMCwwLDEtLjE3LjQ4Yy0uMTguMTQtLjIzLjExLS4zOCwwbC01NC43Mi0zMy42NWE4LDgsMCwwLDAtOC4zOCwwTDY5LjA5LDIxNS45NGMtLjE1LjA5LS4xOS4xMi0uMzgsMGEuMzcuMzcsMCwwLDEtLjE3LS40OGwxNC44OC02Mi44YTgsOCwwLDAsMC0yLjU2LTcuOTFsLTQ4LjctNDJjLS4xMi0uMS0uMjMtLjE5LS4xMy0uNXMuMTgtLjI3LjMzLS4yOWw2My45Mi01LjE2QTgsOCwwLDAsMCwxMDMsOTEuODZsMjQuNjItNTkuNjFjLjA4LS4xNy4xMS0uMjUuMzUtLjI1cy4yNy4wOC4zNS4yNUwxNTMsOTEuODZhOCw4LDAsMCwwLDYuNzUsNC45Mmw2My45Miw1LjE2Yy4xNSwwLC4yNCwwLC4zMy4yOVMyMjQsMTAyLjYzLDIyMy44NCwxMDIuNzNaIi8+PC9zdmc+');}.icon-alphabetical{--icon:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9ImN1cnJlbnRDb2xvciIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTgzLjc4IDE4NC4wNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNTkuNTg2IDY5Ljc0MmMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAyMjktMC41Mjk1OCAxLjk0OTNzMC4xNzkwMyAxLjU5MzcgMC41Mjk1OCAxLjk5NDRjMC4zNzU1OCAwLjM3NTU4IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGg3LjAxOTdsLTEyLjQyOCAzNC4zNjZoLTIuMTA3Yy0wLjg1MTMgMC0xLjQ2MSAwLjE5NjU2LTEuODM2NiAwLjU5NzE4LTAuMzUwNTQgMC4zNzU1OC0wLjUyOTU3IDEuMDM0MS0wLjUyOTU3IDEuOTYwNiAwIDAuOTI2NDQgMC4xNzkwMyAxLjU4MjUgMC41Mjk1NyAxLjk4MyAwLjM3NTU4IDAuMzc1NTkgMC45ODUyOSAwLjU2MzM4IDEuODM2NiAwLjU2MzM4aDEyLjU1MmMwLjg1MTMgMCAxLjQ1MjItMC4xODc3OSAxLjgwMjgtMC41NjMzOCAwLjM3NTU4LTAuNDAwNjIgMC41NjMzNy0xLjA1NjYgMC41NjMzNy0xLjk4MyAwLTAuOTI2NDUtMC4xODc3OS0xLjU4NS0wLjU2MzM3LTEuOTYwNi0wLjM1MDU0LTAuNDAwNjItMC45NTE0Ny0wLjU5NzE4LTEuODAyOC0wLjU5NzE4aC00LjU1MjFsMy4xMjExLTguOTM0OWgxOC4yMmwzLjA3NiA4LjkzNDloLTUuMDcwNGMtMC44NTEzIDAtMS40NjEgMC4xOTY1Ni0xLjgzNjYgMC41OTcxOC0wLjM1MDU0IDAuMzc1NTgtMC41Mjk1OCAxLjAzNDEtMC41Mjk1OCAxLjk2MDYgMCAwLjkyNjQ0IDAuMTc5MDMgMS41ODI1IDAuNTI5NTggMS45ODMgMC4zNzU1OCAwLjM3NTU5IDAuOTg1MjkgMC41NjMzOCAxLjgzNjYgMC41NjMzOGgxMy4yOTZjMC44NTEzIDAgMS40NTIyLTAuMTg3NzkgMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzctMS4wNTY2IDAuNTYzMzctMS45ODMgMC0wLjkyNjQ1LTAuMTg3NzktMS41ODUtMC41NjMzNy0xLjk2MDYtMC4zNTA1NC0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOGgtMi4yODczbC0xMy4yNjItMzcuMDM2Yy0wLjMwMDQ3LTAuODUxMy0wLjc1OTk0LTEuNDYxLTEuMzg1OS0xLjgzNjYtMC42MDA5My0wLjQwMDYyLTEuNDA5Ny0wLjU5NzE4LTIuNDExMy0wLjU5NzE4em00NC4xNDYgMGMtMC44NTEzIDAtMS40NzIzIDAuMTk2NTYtMS44NDc4IDAuNTk3MTgtMC4zNTA1NSAwLjM3NTU4LTAuNTE4MyAxLjAyMjktMC41MTgzIDEuOTQ5M3YxMS45MWMwIDAuODc2MzMgMC4yMDUzMiAxLjUwNjEgMC42MzA5OCAxLjg4MTcgMC40MjU2NiAwLjM3NTU4IDEuMTU5MyAwLjU2MzM3IDIuMTg1OSAwLjU2MzM3czEuNzQ5LTAuMTg3NzkgMi4xNzQ3LTAuNTYzMzdjMC40MjU2OS0wLjM3NTU4IDAuNjQyMjYtMS4wMDUzIDAuNjQyMjYtMS44ODE3di05LjM1MTdoMTguODUxbC0yNC43NTQgMzUuMzAxYy0wLjM1MDU0IDAuNTI1ODItMC41MTgzMSAxLjA3MTctMC41MTgzMSAxLjYyMjYgMCAwLjkyNjQ1IDAuMTY3NzcgMS41ODI1IDAuNTE4MzEgMS45ODMxIDAuMzc1NTggMC4zNzU1OCAwLjk5NjU0IDAuNTYzMzggMS44NDc4IDAuNTYzMzhoMjguNzY2YzAuODUxMyAwIDEuNDUyMi0wLjE4NzggMS44MDI4LTAuNTYzMzggMC4zNzU1OC0wLjQwMDYyIDAuNTYzMzgtMS4wNTY2IDAuNTYzMzgtMS45ODMxdi0xMi42NjVjMC0wLjg3NjMzLTAuMjE2NTgtMS40OTQ4LTAuNjQyMjUtMS44NzA0LTAuNDI1NjYtMC4zNzU1OC0xLjE0OC0wLjU2MzM4LTIuMTc0Ny0wLjU2MzM4LTEuMDI2NiAwLTEuNzQ5IDAuMTg3NzktMi4xNzQ3IDAuNTYzMzgtMC40MjU2NiAwLjM3NTU4LTAuNjQyMjQgMC45OTQwMi0wLjY0MjI0IDEuODcwNHYxMC4xMDdoLTE5Ljk3OGwyNC45MDEtMzUuNDU5YzAuMjUwMzktMC4zNTA1NCAwLjM3MTgzLTAuODM4ODMgMC4zNzE4My0xLjQ2NDggMC0wLjkyNjQ1LTAuMTg3OC0xLjU3MzctMC41NjMzOC0xLjk0OTMtMC4zNTA1NS0wLjQwMDYyLTAuOTUxNDctMC41OTcxOC0xLjgwMjgtMC41OTcxOHptLTMxLjc1MiA1LjEwNDJoMC43MDk4NWw2Ljk4NTkgMjAuMzE1aC0xNC43MTZ6bS0zNy43MjMtNDkuMTgzYy00LjczNDIgMC04LjYzMTMgMy44OTctOC42MzEzIDguNjMxM3YxMTUuNDdjMCA0LjczNDIgMy44OTcgOC42MzEzIDguNjMxMyA4LjYzMTNoMTE1LjI2YzQuNzM0MiAwIDguNjQyMS0zLjg5NyA4LjY0MjEtOC42MzEzdi0xMTUuNDdjMC00LjczNDItMy45MDgyLTguNjMxMy04LjY0MjEtOC42MzEzem0wIDUuNzI0aDExNS4yNmMxLjY1OCAwIDIuOTA3IDEuMjQ5MSAyLjkwNyAyLjkwNzF2MTE1LjQ3YzAgMS42NTgtMS4yNDkxIDIuOTA3LTIuOTA3IDIuOTA3aC0xMTUuMjZjLTEuNjU4IDAtMi44OTU4LTEuMjQ5MS0yLjg5NTgtMi45MDd2LTExNS40N2MwLTEuNjU4IDEuMjM3OC0yLjkwNzEgMi44OTU4LTIuOTA3MXoiIGZpbGw9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIuNzIxMTQiLz48L3N2Zz4=');}.icon-star-half-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzkuMTgsOTcuMjZBMTYuMzgsMTYuMzgsMCwwLDAsMjI0LjkyLDg2bC01OS00Ljc2TDE0My4xNCwyNi4xNWExNi4zNiwxNi4zNiwwLDAsMC0zMC4yNywwTDkwLjExLDgxLjIzLDMxLjA4LDg2YTE2LjQ2LDE2LjQ2LDAsMCwwLTkuMzcsMjguODZsNDUsMzguODNMNTMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMCwyNC41LDE3LjgyTDEyOCwxOTguNDlsNTAuNTMsMzEuMDhBMTYuNCwxNi40LDAsMCwwLDIwMywyMTEuNzVsLTEzLjc2LTU4LjA3LDQ1LTM4LjgzQTE2LjQzLDE2LjQzLDAsMCwwLDIzOS4xOCw5Ny4yNlptLTE1LjM0LDUuNDctNDguNyw0MmE4LDgsMCwwLDAtMi41Niw3LjkxbDE0Ljg4LDYyLjhhLjM3LjM3LDAsMCwxLS4xNy40OGMtLjE4LjE0LS4yMy4xMS0uMzgsMGwtNTQuNzItMzMuNjVBOCw4LDAsMCwwLDEyOCwxODEuMVYzMmMuMjQsMCwuMjcuMDguMzUuMjZMMTUzLDkxLjg2YTgsOCwwLDAsMCw2Ljc1LDQuOTJsNjMuOTEsNS4xNmMuMTYsMCwuMjUsMCwuMzQuMjlTMjI0LDEwMi42MywyMjMuODQsMTAyLjczWiIvPjwvc3ZnPg==');}.icon-star-fi{--icon:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0yMzQuMjksMTE0Ljg1bC00NSwzOC44M0wyMDMsMjExLjc1YTE2LjQsMTYuNCwwLDAsMS0yNC41LDE3LjgyTDEyOCwxOTguNDksNzcuNDcsMjI5LjU3QTE2LjQsMTYuNCwwLDAsMSw1MywyMTEuNzVsMTMuNzYtNTguMDctNDUtMzguODNBMTYuNDYsMTYuNDYsMCwwLDEsMzEuMDgsODZsNTktNC43NiwyMi43Ni01NS4wOGExNi4zNiwxNi4zNiwwLDAsMSwzMC4yNywwbDIyLjc1LDU1LjA4LDU5LDQuNzZhMTYuNDYsMTYuNDYsMCwwLDEsOS4zNywyOC44NloiLz48L3N2Zz4=');}
\ No newline at end of file
diff --git a/assets/css/index.php b/assets/css/index.php
index 4f2541d..6220032 100644
--- a/assets/css/index.php
+++ b/assets/css/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/assets/index.php b/assets/index.php
index 4f2541d..6220032 100644
--- a/assets/index.php
+++ b/assets/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/assets/js/admin/index.php b/assets/js/admin/index.php
index 4f2541d..6220032 100644
--- a/assets/js/admin/index.php
+++ b/assets/js/admin/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/assets/phosphor-icons/bold/index.php b/assets/phosphor-icons/bold/index.php
index e69de29..6220032 100644
--- a/assets/phosphor-icons/bold/index.php
+++ b/assets/phosphor-icons/bold/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/base/Login.php b/base/Login.php
index 312cf47..9c7186f 100644
--- a/base/Login.php
+++ b/base/Login.php
@@ -1,5 +1,8 @@
 <?php
 namespace JVBase\base;
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * Custom settings for the login page can be set here
  */
diff --git a/base/Membership.php b/base/Membership.php
index dd57219..78c71c7 100644
--- a/base/Membership.php
+++ b/base/Membership.php
@@ -2,6 +2,10 @@
 namespace JVBase\base;
 use JVBase\registrar\Registrar;
 
+if (!defined('ABSPATH')) {
+	exit;
+}
+
 /**
  * JVB_MEMBERSHIP defines the structure of the directory
  *         Options include:
diff --git a/base/SchemaHelper.php b/base/SchemaHelper.php
index 75e619a..a3ea785 100644
--- a/base/SchemaHelper.php
+++ b/base/SchemaHelper.php
@@ -1,18 +1,22 @@
 <?php
 namespace JVBase\base;
+use JVBase\Instance;
 use JVBase\meta\Meta;
 use JVBase\registrar\config\seo\Resolver;
 use JVBase\registrar\Registrar;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 class SchemaHelper
 {
+	use Instance;
 	protected static array $allowedTypes;
 	protected static array $allowedFormats = ['schema', 'archive', 'meta', 'reference'];
 	protected static array $schemas = [];
 	protected static array $metas = [];
 	protected static array $archives = [];
 	protected static array $references = [];
-	public function __construct()
+	protected function __construct()
 	{
 		self::$allowedTypes = array_merge(['website', 'organization'], Registrar::getRegistered());
 	}
@@ -299,4 +303,10 @@
 		}
 		return $class;
 	}
+
+	public static function reset(string $type, string $format):bool
+	{
+		$default = self::getDefault($type, $format);
+		return self::update($type, $format, $default);
+	}
 }
diff --git a/base/Site.php b/base/Site.php
index f6ecad0..b535550 100644
--- a/base/Site.php
+++ b/base/Site.php
@@ -177,7 +177,7 @@
 	public static function hasIntegration(string $integration):bool
 	{
 		$integration = strtolower($integration);
-		return array_key_exists($integration, self::$integrations);
+		return array_key_exists($integration, self::$integrations) && self::$integrations[$integration] === true;
 	}
 
 	public static function hasAnyIntegration(array $integrations = []):bool
diff --git a/base/_setup.php b/base/_setup.php
index 544b7e0..f4170bd 100644
--- a/base/_setup.php
+++ b/base/_setup.php
@@ -1,12 +1,16 @@
 <?php
 
+if (!defined('ABSPATH')) {
+	exit;
+}
+
 use JVBase\base\Site;
 
 require(JVB_DIR.'/base/Site.php');
 
 
 add_action('plugins_loaded', 'jvbDefineSite', 1);
-function jvbDefineSite() {
+function jvbDefineSite():void {
 	Site::getInstance();
 }
 //require(JVB_DIR.'/base/content.php');
diff --git a/base/colours.php b/base/colours.php
index 17d89de..d74baf9 100644
--- a/base/colours.php
+++ b/base/colours.php
@@ -1,5 +1,7 @@
 <?php
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 $defaults = [
 	'action-0'		=> '#ff0080',
 	'action-50'		=> '#ff2492',
diff --git a/base/content.php b/base/content.php
index 215c697..10178d7 100644
--- a/base/content.php
+++ b/base/content.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * JVB_CONTENT: Base post type definitions
  *     Structure:  slug=> [$options]
diff --git a/base/email.php b/base/email.php
index 4a52d35..20821bf 100644
--- a/base/email.php
+++ b/base/email.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 $name = get_bloginfo('name');
 $desc = get_bloginfo('description');
 $defaults = [
diff --git a/base/options.php b/base/options.php
index 3cc8583..ed3fa19 100644
--- a/base/options.php
+++ b/base/options.php
@@ -4,6 +4,9 @@
 use JVBase\meta\Form;
 use JVBase\meta\Meta;
 use JVBase\registrar\Fields;
+if (!defined('ABSPATH')) {
+	exit;
+}
 
 /**
  * Custom options for the site can be set here. They will need to be handled by the child plugin, but you can make use of the Meta.php this way
diff --git a/base/taxonomies.php b/base/taxonomies.php
index 02d3c6e..01bc272 100644
--- a/base/taxonomies.php
+++ b/base/taxonomies.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * JVB_TAXONOMY : defines the custom taxonomies
  *  structure: slug => [$options]
diff --git a/base/users.php b/base/users.php
index 02efdb4..2b06309 100644
--- a/base/users.php
+++ b/base/users.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * JVB_USER -> defines user roles. Mainly for Dashboard/Directory sites
  *      Structure: slug => $options
diff --git a/build/drawer-menu/index.php b/build/drawer-menu/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/build/drawer-menu/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/checks.php b/checks.php
index 25e4f80..242bf4c 100644
--- a/checks.php
+++ b/checks.php
@@ -43,20 +43,24 @@
 		function() use($taxonomy, $termID) {
 			$taxonomy = jvbCheckBase($taxonomy);
 			$tax = get_taxonomy($taxonomy);
-			$query = new WP_Query([
-				'post_type'	=> $tax->object_type,
-				'posts_per_page' => 1,
-				'fields' => 'ids',
-				'tax_query'	=> [
-					[
-						'taxonomy' => $taxonomy,
-						'terms'	=> $termID
+			if ($tax) {
+				$query = new WP_Query([
+					'post_type'	=> $tax->object_type,
+					'posts_per_page' => 1,
+					'fields' => 'ids',
+					'tax_query'	=> [
+						[
+							'taxonomy' => $taxonomy,
+							'terms'	=> $termID
+						]
 					]
-				]
-			]);
-			$result = ($query->have_posts()) ? 'true' : 'false';
-			wp_reset_postdata();
-			return $result;
+				]);
+				$result = ($query->have_posts()) ? 'true' : 'false';
+				wp_reset_postdata();
+				return $result;
+
+			}
+			return 'false';
 		}
 	);
 }
diff --git a/inc/integrations/Instance.php b/inc/Instance.php
similarity index 87%
rename from inc/integrations/Instance.php
rename to inc/Instance.php
index 28d5e66..c489724 100644
--- a/inc/integrations/Instance.php
+++ b/inc/Instance.php
@@ -1,6 +1,5 @@
 <?php
-namespace JVBase\integrations;
-
+namespace JVBase;
 if (!defined('ABSPATH')) {
 	exit;
 }
diff --git a/inc/admin/ContentTaxonomy.php b/inc/admin/ContentTaxonomy.php
index 0863a1e..7c53c85 100644
--- a/inc/admin/ContentTaxonomy.php
+++ b/inc/admin/ContentTaxonomy.php
@@ -358,7 +358,7 @@
         $results['total'] = count($terms);
         $results['messages'][] = "Found {$results['total']} terms to process";
 
-        JVB()->queue()->queueOperation(
+        Queue::getInstance()->queueOperation(
             'sync_content_taxonomy_tables',
             0,
             [
diff --git a/inc/admin/Integrations.php b/inc/admin/Integrations.php
index e190ac2..4c59c50 100644
--- a/inc/admin/Integrations.php
+++ b/inc/admin/Integrations.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\admin;
 
+use JVBase\base\Site;
+use JVBase\integrations\ServiceMap;
 use JVBase\managers\IconsManager;
 use JVBase\registrar\Registrar;
 
@@ -10,6 +12,8 @@
 
 class Integrations
 {
+	use ServiceMap;
+
 	public function __construct()
 	{
 		// Hook into WordPress admin
@@ -34,17 +38,15 @@
 		);
 
 		// Add submenu pages for configured integrations
-		$integrations = JVB()->getAvailableServices(false);
-
-		foreach($integrations as $service => $integration) {
-//			if ($integration->isSetUp() && !empty($integration->enabledContentTypes())) {
-			if (!empty($integration->enabledContentTypes())) {
+		foreach ($this->getAvailableServices() as $service) {
+			$integration = $this->getService($service);
+			if ($integration->isSetUp() && method_exists($integration, 'getAllowedContent') && !empty($integration->getAllowedContent())) {
 				add_submenu_page(
 					'jvb-integrations',
 					$integration->getTitle() . ' Defaults',
 					$integration->getTitle(),
 					'manage_options',
-					'jvb-integration-'.$service,
+					'jvb-integration-'.$integration->getServiceName(),
 					[$integration, 'renderDefaults']
 				);
 			}
@@ -121,11 +123,13 @@
 
 				<div class="item-grid integrations">
 					<?php
-					$integrations = JVB()->getAvailableServices(false);
+					foreach ($this->getAvailableServices() as $service) {
+						$integration = $this->getService($service);
+						if ($integration->isSetUp() && method_exists($integration, 'renderConnection')) {
+							$integration->renderConnection();
+						}
+					}
 
-					foreach ($integrations as $service => $integration):
-						$integration->renderConnection();
-					endforeach;
 					?>
 				</div>
 			</div>
diff --git a/inc/admin/SEOAdmin.php b/inc/admin/SEOAdmin.php
index ddf9d75..61d7215 100644
--- a/inc/admin/SEOAdmin.php
+++ b/inc/admin/SEOAdmin.php
@@ -2,6 +2,8 @@
 namespace JVBase\admin;
 
 use JVBase\base\SchemaHelper;
+use JVBase\Instance;
+use JVBase\managers\AdminPages;
 use JVBase\meta\Form;
 use JVBase\registrar\Registrar;
 use JVBase\ui\Tabs;
@@ -11,6 +13,7 @@
 }
 
 class SEOAdmin {
+	use Instance;
 	protected string $admin_page;
 	protected array $types = [
 		'JVBase\managers\SEO\render\Thing' 									=> 'Thing',
@@ -105,7 +108,7 @@
 
 	protected array $checks;
 
-	public function __construct()
+	protected function __construct()
 	{
 		$this->setChecks();
 		add_action(BASE.'dashboard_page_seo', [$this, 'addDashboardSection'], 10);
@@ -134,7 +137,7 @@
 
 	public function addAdminMenu():void
 	{
-		$main = JVB()->admin()->getMainConfig();
+		$main = AdminPages::getInstance()->getMainConfig();
 		$this->admin_page = add_submenu_page(
 			$main['menu_slug'],
 			'SEO',
@@ -358,7 +361,7 @@
 			];
 		}
 
-		$success = JVB()->schemaHelper()->updateSchema($action, $data);
+		$success = SchemaHelper::getInstance()->updateSchema($action, $data);
 		return $success ? [
 			'jvb_notice'	=> 'success',
 			'jvb_message'	=> 'Saved changes successfully'
diff --git a/inc/admin/_setup.php b/inc/admin/_setup.php
index 31d7b6c..ef0bc77 100644
--- a/inc/admin/_setup.php
+++ b/inc/admin/_setup.php
@@ -1,5 +1,7 @@
 <?php
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 use JVBase\registrar\Registrar;
 
 require(JVB_DIR . '/inc/admin/Integrations.php');
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index aa3cd91..2612fbd 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -1,14 +1,12 @@
 <?php
 namespace JVBase\blocks;
 
-use DateTime;
 use DOMDocument;
+use JVBase\Instance;
 use JVBase\managers\Cache;
-use JVBase\managers\LoginManager;
+use JVBase\managers\Login;
 use JVBase\managers\SEO\BreadcrumbManager;
-use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\VideoObject;
 use JVBase\utility\Image;
-use JVBase\utility\Video;
 use WP_Block;
 use WP_Query;
 
@@ -18,6 +16,7 @@
 
 class CustomBlocks
 {
+	use Instance;
     protected Cache $cache;
 
 	protected static ?WP_Query $currentLoop = null;
@@ -31,11 +30,10 @@
 	protected static array $pendingClass = [];
 	protected static bool $renderGallery = false;
 
-    public function __construct()
+    private function __construct()
     {
-        $this->cache = Cache::for('blocks', WEEK_IN_SECONDS);
-		$this->cache->connect('post')->connect('taxonomy');
-		$this->cache->flush();
+//        $this->cache = Cache::for('blocks', null,WEEK_IN_SECONDS);
+
 		add_filter('pre_render_block', [$this, 'prerender'], 10, 3);
 		add_filter('render_block', [$this, 'render'], 10, 2);
 
@@ -113,6 +111,9 @@
 	protected function checkMethods(?string $content, array $block, ?WP_Block $parent = null, bool $isPrerender = false):?string
 	{
 		$blockName = $this->sanitizeBlockName($block);
+		if (str_starts_with($blockName, 'woocommerce')) {
+			return $content;
+		}
 
 		$base = ($isPrerender) ? 'prerender_' : 'render_';
 		$method = $base.$blockName;
@@ -135,6 +136,7 @@
 			//Ignore for both
 			$base = [
 				'core/null',
+				'core/pattern',
 				'core/list-item',
 				'core/post-content',
 				'jvb/drawer-menu',
@@ -820,8 +822,8 @@
 		}
 
 		if (array_key_exists('displayLoginAsForm', $attrs) && $attrs['displayLoginAsForm']) {
-			LoginManager::getInstance()->setAction($action);
-			return LoginManager::getInstance()->renderLoginForm($action, $redirect, '<h2>Login</h2>');
+			Login::getInstance()->setAction($action);
+			return Login::getInstance()->renderLoginForm($action, $redirect, '<h2>Login</h2>');
 
 		}
 
diff --git a/inc/blocks/DrawerMenuBlock.php b/inc/blocks/DrawerMenuBlock.php
index 386c123..0ec9f1d 100644
--- a/inc/blocks/DrawerMenuBlock.php
+++ b/inc/blocks/DrawerMenuBlock.php
@@ -4,7 +4,9 @@
 use JVBase\managers\Cache;
 use JVBase\ui\Navigation;
 use WP_Block;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 class DrawerMenuBlock {
 	protected Cache $cache;
 	public function __construct()
@@ -60,8 +62,7 @@
 		}
 		$items = array_map(function($item) { return $item['text'];}, $menu_items);
 
-		$key = $cache->generateKey($items);
-		$menu =  $cache->remember($key,
+		$menu =  $cache->remember($items,
 			function () use ($menu_items, $menu_id, $collapsed) {
 				$menu = new Navigation($menu_id);
 				$menu->asDrawer($collapsed)->populateFromArray($menu_items);
diff --git a/inc/blocks/FAQBlock.php b/inc/blocks/FAQBlock.php
index fd6d84d..c43133c 100644
--- a/inc/blocks/FAQBlock.php
+++ b/inc/blocks/FAQBlock.php
@@ -5,16 +5,18 @@
 use JVBase\registrar\Registrar;
 use WP_Block;
 use WP_Query;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 class FAQBlock {
-	protected Cache $cache;
+//	protected Cache $cache;
 	protected string $postType;
 	protected string $section;
 	protected string $slug;
 	protected bool $isDirectory;
 	public function __construct()
 	{
-		$this->cache = Cache::for('faq_block', WEEK_IN_SECONDS)->connect('post', true)->connect('taxonomy', true);
+//		$this->cache = Cache::for('faq_block', 'post', WEEK_IN_SECONDS);
 		add_action('init', [ $this, 'registerBlock' ]);
 		add_action('enqueue_block_editor_assets', [$this, 'localizeData']);
 	}
@@ -261,7 +263,7 @@
 			}
 			$buttons = '';
 			if ($this->isDirectory) {
-				$directory = JVB()->directories()->directories($this->slug);
+				$directory = DirectoryManager::getInstance()->directories($this->slug);
 				$url = $directory['url']??'';
 				if (!empty($url)) {
 					$buttons .= '<li><a href="'.$url.'">'.__('See Alphabetical List', 'jvb').'</a></li>';
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index 223c554..3cc10ba 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -32,12 +32,11 @@
 
 	public function __construct()
 	{
-		// Initialize cache with connections
-		$this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
-		if (JVB_TESTING) {
-			$this->cache->flush();
-		}
-		$this->cache->flush();
+//		// Initialize cache with connections
+//		$this->cache = Cache::for('feed_block','post', WEEK_IN_SECONDS);
+//		if (JVB_TESTING) {
+//			$this->cache->flush();
+//		}
 
 		add_action('init', [$this, 'registerBlock']);
 	}
diff --git a/inc/blocks/FormBlock.php b/inc/blocks/FormBlock.php
index c34fee8..5cd1b22 100644
--- a/inc/blocks/FormBlock.php
+++ b/inc/blocks/FormBlock.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\blocks;
 
+use JVBase\integrations\services\Cloudflare;
 use JVBase\managers\Cache;
 use JVBase\meta\Form;
 use JVBase\base\Site;
@@ -33,7 +34,7 @@
 
 	public function __construct()
 	{
-		$this->cache = Cache::for('forms', WEEK_IN_SECONDS);
+		$this->cache = Cache::for('forms', null, WEEK_IN_SECONDS);
 
 		if (JVB_TESTING) {
 			$this->cache->flush();
@@ -63,7 +64,7 @@
 
 		// Enqueue Turnstile if needed
 		if (Site::hasIntegration('cloudflare')) {
-			$cloudflare = JVB()->connect('cloudflare');
+			$cloudflare = Cloudflare::getInstance();
 			if ($cloudflare->isSetUp()) {
 				$cloudflare->enqueueTurnstileScripts();
 			}
@@ -131,9 +132,8 @@
 			return '<div class="jvb-form-error">No valid form type selected. Please edit this block and select a form type.</div>';
 		}
 
-		$cache_key = $this->cache->generateKey($block);
 		return $this->cache->remember(
-			$cache_key,
+			$block,
 			function() use ($form_type, $block) {
 				return $this->renderForm($form_type, $block);
 			}
@@ -409,7 +409,7 @@
 			return;
 		}
 
-		$cloudflare = JVB()->connect('cloudflare');
+		$cloudflare = Cloudflare::getInstance();
 		if ($cloudflare->isSetUp()) {
 			$cloudflare->renderTurnstile();
 		}
diff --git a/inc/blocks/GMBReviewBlock.php b/inc/blocks/GMBReviewBlock.php
index c6255f3..a57c694 100644
--- a/inc/blocks/GMBReviewBlock.php
+++ b/inc/blocks/GMBReviewBlock.php
@@ -1,13 +1,20 @@
 <?php
 namespace JVBase\blocks;
 
-use JVBase\managers\Cache;
+use JVBase\base\Site;
+use JVBase\integrations\services\GoogleMyBusiness;
+//use JVBase\managers\Cache;
 use WP_Block;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 class GMBReviewBlock {
-	protected Cache $cache;
+//	protected Cache $cache;
 	public function __construct()
 	{
+		if (!Site::hasIntegration('gmb')){
+			return;
+		}
 		add_action('init', [ $this, 'registerBlock' ]);
 	}
 
@@ -34,6 +41,9 @@
 	 */
 	public function render(array $attributes, string $content, WP_Block $block):string
 	{
+		if (!Site::hasIntegration('gmb')) {
+			return '';
+		}
 		$count = $attributes['count'] ?? 5;
 		$showRating = $attributes['showRating'] ?? true;
 		$showDate = $attributes['showDate'] ?? true;
@@ -49,11 +59,16 @@
 			$inheritUser = null;
 		}
 		try {
-			$gmb = JVB()->connect('gmb', $inheritUser);
-			if (!$gmb->isSetUp()) {
+			$gmb = GoogleMyBusiness::getInstance();
+			$gmb->switchUser($inheritUser);
+			if (!$gmb->isSetUp()
+				|| !method_exists($gmb, 'getReviews')
+				|| !method_exists($gmb, 'getReviewsViewUrl')
+				|| !method_exists($gmb, 'getReviewUrl')) {
 				error_log('GMB Not set up for: '.(int)$inheritUser);
 				return '';
 			}
+
 			$gotReviews = $gmb->getReviews();
 			// Get all data
 			$allReviews = $gotReviews['reviews']??[];
diff --git a/inc/blocks/GlossaryBlock.php b/inc/blocks/GlossaryBlock.php
index c5c1a4f..bac2a49 100644
--- a/inc/blocks/GlossaryBlock.php
+++ b/inc/blocks/GlossaryBlock.php
@@ -22,7 +22,7 @@
 
 	public function __construct()
 	{
-		$this->cache = Cache::for('glossary_terms', WEEK_IN_SECONDS)->connect('post', true);
+		$this->cache = Cache::for('glossary_terms', 'post', WEEK_IN_SECONDS);
 		add_action('init', [ $this, 'registerBlock' ]);
 	}
 
diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 856b30e..24d3759 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -32,7 +32,7 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('menu', WEEK_IN_SECONDS)->connect('post');
+        $this->cache = Cache::for('menu', 'post', WEEK_IN_SECONDS);
 		if (Site::hasIntegration('square')) {
 			$this->base = '_square_';
 		} else if (Site::hasIntegration('helcim')) {
@@ -50,9 +50,12 @@
 
 	protected function buildParams():void
 	{
-		if (is_post_type_archive(BASE.'menu_item')) {
+//		if (is_post_type_archive(BASE.'menu_item') ||
+		if (is_post_type_archive('product') ||
+			(function_exists('is_shop') && is_shop())) {
 			$this->params = ['all'];
-		} elseif (is_tax(BASE.'section')) {
+//		} elseif (is_tax(BASE.'section')) {
+		} elseif (is_tax('product_cat')) {
 			$this->params = [];
 		} else {
 			$this->params = [];
@@ -66,10 +69,9 @@
 			return '';
 		}
 		jvbInlineStyles('forms');
-		$key = $this->cache->generateKey($this->params);
 		$this->cache->flush();
 		return $this->cache->remember(
-			$key,
+			$this->params,
 			function() {
 				ob_start();
 				$this->renderBlock();
@@ -99,7 +101,8 @@
 				<?php
 				$sections = $this->getSections();
 				foreach ($sections as $section) {
-					if (!jvbTermHasPosts($section['id'], BASE.'section')) {
+//					if (!jvbTermHasPosts($section['id'], BASE.'section')) {
+					if (!jvbTermHasPosts($section['id'], 'product_cat')) {
 						continue;
 					}
 					?>
@@ -129,13 +132,18 @@
     {
 		$sections = $this->getSections();
 		foreach ($sections as $section) {
+			if($section['name'] === 'Uncategorized') {
+				continue;
+			}
 			$items = new WP_Query([
-				'post_type' => BASE.'menu_item',
+				'post_type' => 'product',
+//				'post_type' => BASE.'menu_item',
 				'order' => 'ASC',
 				'posts_per_page' => -1,
 				'tax_query' => [
 					[
-						'taxonomy' => BASE.'section',
+						'taxonomy' => 'product_cat',
+//						'taxonomy' => BASE.'section',
 						'terms' => (int) $section['id'],
 					]
 				],
@@ -162,7 +170,108 @@
 		}
     }
 
-	protected function renderMenuItem(int $ID, string $slug, string $postType = 'menu_item') {
+	/**
+	 * Renders from WooCommerce product
+	 * @param int $ID
+	 * @param string $slug
+	 * @param string $postType
+	 * @return void
+	 */
+	protected function renderMenuItem(int $ID, string $slug, string $postType = 'product'):void {
+		$meta = Meta::forPost($ID);
+
+		$fields = $meta->getAll();
+
+//		jvbDump($fields, 'Fields');
+
+		$excerpt = get_the_excerpt($ID);
+		$isVariable = !empty($fields['variations']??[]);
+		$variations = $isVariable ? array_map('floatval', array_column($fields['variations'], 'price')) : [];
+		$priceRange = $isVariable ?
+			(min($variations).' - '.max($variations)) :
+			$fields['_price']??'';
+
+		$out = sprintf(
+	'<div class="menu-item%s" data-section="%s" data-label="%s" data-id="%s">
+				<div class="header row x-btw">
+					<h3>%s</h3>
+					<p class="price">$%s</p>
+				</div>
+				<div class="description">%s</div>
+				<div class="info col right">',
+			$isVariable ? ' variable' : '',
+			$slug,
+			$fields['post_title'],
+			$ID,
+			$fields['post_title'],
+			$priceRange,
+			$excerpt
+		);
+
+		if (!empty($fields['variations']??[])) {
+			foreach ($fields['variations'] as $index => $row) {
+				$out .= sprintf(
+				'<div class="row right nowrap">
+						<div>
+							<h4>%s — %s</h4>
+							%s
+						</div>', //Note: we had 3 %s%s%s before the last </div> last time. This was to add optional ingredients or preparation time duration, not default in WooCommerce
+						$row['title'],
+						jvbCurrency($row['price']),
+						empty($row['description']) ? '' : wpautop($row['description']),
+				);
+
+				$out .= Form::render(
+					$row['id'],
+					'',
+					[
+						'type'	=> 'quantity',
+						'label'	=> $row['title'],
+						'min'	=> 0,
+						'max'	=> 30,
+						'data'	=> [
+							'price'	=> $row['price'],
+							'id'	=> $row['id'],
+							'sku'	=> $row['sku'],
+							'name'	=> $row['title'],
+							'add-to-cart'	=> ''
+						],
+						'add'	=> 'Add 1 '.$row['title'].' to cart',
+						'remove'	=> 'Remove 1 '.$row['title']. ' from cart',
+					]
+				);
+				$out .= '</div>';
+			}
+		} else {
+			$out .= Form::render(
+				$ID,
+				'',
+				[
+					'type'	=> 'quantity',
+					'label'	=> 'Add to Order',
+					'min'	=> 0,
+					'max'	=> 30,
+					'step'	=> 1,
+					'data'	=> [
+						'price'	=> $fields['_price'],
+						'id'	=> $ID,
+						'name'	=> $fields['post_title'],
+						'step'	=> 1,
+						'add-to-cart'	=> '',
+					],
+					'add'	=> 'Add 1 '.$fields['post_title'].' to cart',
+					'remove'	=> 'Remove 1 '.$fields['title']. ' from cart',
+				]
+			);
+		}
+
+		$out .= '</div></div>';
+
+		echo $out;
+
+	}
+	/**
+	protected function renderMenuItemOLD(int $ID, string $slug, string $postType = 'menu_item') {
 		$meta = Meta::forPost($ID);
 		$fields = $meta->getAll();
 		$values = $meta->getAll([
@@ -278,7 +387,8 @@
 
 		</div>
 		<?php
-	}
+	} **/
+
 
 
     protected function renderOnThisPage():void
diff --git a/inc/blocks/SimpleCalendarBlock.php b/inc/blocks/SimpleCalendarBlock.php
index 8b46bca..e294792 100644
--- a/inc/blocks/SimpleCalendarBlock.php
+++ b/inc/blocks/SimpleCalendarBlock.php
@@ -30,7 +30,7 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('menu', DAY_IN_SECONDS)->connect('post');
+        $this->cache = Cache::for('calendar', 'post',  DAY_IN_SECONDS);
         add_action('init', [ $this, 'registerBlock' ]);
     }
 
@@ -82,10 +82,9 @@
 			return '';
 		}
 
-		$key = $this->cache->generateKey($this->params);
 		$this->cache->flush();
 		return $this->cache->remember(
-			$key,
+			$this->params,
 			function() {
 				ob_start();
 				$this->renderBlock();
diff --git a/inc/blocks/SummaryBlock.php b/inc/blocks/SummaryBlock.php
index 88aaa8c..186ebbd 100644
--- a/inc/blocks/SummaryBlock.php
+++ b/inc/blocks/SummaryBlock.php
@@ -25,7 +25,7 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('summary_block', WEEK_IN_SECONDS);
+        $this->cache = Cache::for('summary_block', 'post', WEEK_IN_SECONDS);
         add_action('init', [ $this, 'registerBlock' ]);
 		add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
 		if (JVB_TESTING) {
@@ -65,7 +65,15 @@
     {
         $this->config = $this->getConfig();
         $key = $this->generateKey();
-        $cache = $this->cache->get($key);
+		if (is_tax()) {
+			$this->cache = Cache::for('summary_tax', 'term', WEEK_IN_SECONDS);
+		} elseif (is_author()) {
+			$this->cache = Cache::for('summary_auth', 'user', WEEK_IN_SECONDS);
+		} else {
+			$this->cache = Cache::for('summary_post', 'post', WEEK_IN_SECONDS);
+		}
+
+        $cache = $this->cache->get(get_queried_object_id());
 
         if ($cache) {
             return $cache;
diff --git a/inc/blocks/TimelineBlock.php b/inc/blocks/TimelineBlock.php
index 85b9add..3bf86e2 100644
--- a/inc/blocks/TimelineBlock.php
+++ b/inc/blocks/TimelineBlock.php
@@ -4,7 +4,6 @@
 use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
-use JVBase\base\Site;
 use WP_Block;
 
 if (!defined('ABSPATH')) {
@@ -30,7 +29,7 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('timelines', WEEK_IN_SECONDS)->connect('post');
+        $this->cache = Cache::for('timelines', 'post', WEEK_IN_SECONDS);
 		if (JVB_TESTING){
 			$this->cache->flush();
 		}
diff --git a/inc/forms/PostSelector.php b/inc/forms/PostSelector.php
index 5fa2026..f6f564f 100644
--- a/inc/forms/PostSelector.php
+++ b/inc/forms/PostSelector.php
@@ -2,6 +2,7 @@
 namespace JVBase\forms;
 
 use JVBase\managers\Cache;
+use JVBase\managers\Roles;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Query;
@@ -23,7 +24,7 @@
 	public function __construct(string $post_type, array $config = [])
 	{
 		$this->post_type = $post_type;
-		$this->cache = Cache::for(jvbNoBase($post_type), WEEK_IN_SECONDS)->connect('post', true);
+		$this->cache = Cache::for(jvbNoBase($post_type), 'post',WEEK_IN_SECONDS);
 
 		$this->config = wp_parse_args($config, [
 			'multiple' => true,
@@ -237,7 +238,7 @@
 			'methods' => 'GET',
 			'callback' => [new self('post'), 'searchPosts'],
 			'permission_callback' => function($request) {
-				return JVB()->roles()->checkRole(get_current_user(), $request->get_param('post_type'));
+				return Roles::getInstance()()->checkRole(get_current_user(), $request->get_param('post_type'));
 			},
 			'args' => [
 				'post_type' => [
diff --git a/inc/helpers/breadcrumbs.php b/inc/helpers/breadcrumbs.php
index 262a4d4..77b5124 100644
--- a/inc/helpers/breadcrumbs.php
+++ b/inc/helpers/breadcrumbs.php
@@ -112,11 +112,11 @@
 
 	if (is_singular(BASE.'directory')) {
 		$type = get_post_meta(get_the_ID(), BASE.'for_type_slug', true);
-		return JVB()->directories()?->getDirectoryList()[$type] ?? [];
+		return DirectoryManager::getInstance()?->getDirectoryList()[$type] ?? [];
 	}
 
 	$obj = get_queried_object();
-	$directories = JVB()->directories()?->directories();
+	$directories = DirectoryManager::getInstance()?->directories();
 	if (is_tax()) {
 		$tax = jvbNoBase($obj->taxonomy);
 		return array_key_exists($tax, $directories) ? $directories[$tax] : [];
diff --git a/inc/helpers/crud.php b/inc/helpers/crud.php
index dc646c9..f90c873 100644
--- a/inc/helpers/crud.php
+++ b/inc/helpers/crud.php
@@ -5,7 +5,7 @@
 }
 
 use JVBase\managers\Cache;
-use JVBase\managers\RoleManager;
+use JVBase\managers\Roles;
 use JVBase\meta\Form;
 use JVBase\registrar\Registrar;
 
@@ -95,7 +95,7 @@
             'label' => 'Scrapped',
         ]
     ];
-	$permission = JVB()->roles()->getContentPlural($content);
+	$permission = Roles::getInstance()()->getContentPlural($content);
     $canPublish = current_user_can("publish_{$permission}") && current_user_can('skip_moderation');
     $out = '<div class="filters">';
     if (!empty($statusFilters)) {
@@ -194,7 +194,7 @@
             'disabled'    => true,
         ]
     ];
-	$permission = JVB()->roles()->getContentPlural($content);
+	$permission = Roles::getInstance()()->getContentPlural($content);
     if (current_user_can('skip_moderation') && current_user_can("publish_{$permission}")) {
         $bulkActions['publish']['disabled'] = false;
     }
@@ -217,7 +217,7 @@
  */
 function jvbRenderDateFilter(string $content):string
 {
-    $cache = Cache::for('date_filter')->connect('post', true);
+    $cache = Cache::for('date_filter');
     $check = $cache->get($content);
     if ($check) {
         return $check;
diff --git a/inc/helpers/dashboard.php b/inc/helpers/dashboard.php
index 64819c8..42b91a9 100644
--- a/inc/helpers/dashboard.php
+++ b/inc/helpers/dashboard.php
@@ -101,7 +101,7 @@
 
         $temp = [];
         foreach ($types as $t) {
-			$permission = JVB()->roles()->getPermission('edit',$t);
+			$permission = Roles::getInstance()()->getPermission('edit',$t);
             if (user_can($link, $permission)){
                 $temp[] = $t;
             }
diff --git a/inc/helpers/directory.php b/inc/helpers/directory.php
index 178c72a..f85728b 100644
--- a/inc/helpers/directory.php
+++ b/inc/helpers/directory.php
@@ -1,5 +1,8 @@
 <?php
 
+use JVBase\base\Site;
+use JVBase\managers\DirectoryManager;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -7,5 +10,5 @@
 function jvbIsDirectory():bool
 {
 
-	return JVB()->directories() && JVB()->directories()->isDirectory();
+	return Site::has('is_directory') && DirectoryManager::getInstance()->isDirectory();
 }
diff --git a/inc/helpers/formatting.php b/inc/helpers/formatting.php
index d294044..af67b71 100644
--- a/inc/helpers/formatting.php
+++ b/inc/helpers/formatting.php
@@ -77,7 +77,7 @@
  */
 function jvbFormatRating(int $ID, string $type = 'post'):string
 {
-    $cache = Cache::for('rating', WEEK_IN_SECONDS)->connect('post')->connect('taxonomy')->connect('user');
+    $cache = Cache::for('rating', $type, WEEK_IN_SECONDS);
 
     $cached = $cache->get($ID);
     if ($cached) {
diff --git a/inc/helpers/members.php b/inc/helpers/members.php
index b64c9d0..1052d26 100644
--- a/inc/helpers/members.php
+++ b/inc/helpers/members.php
@@ -1,6 +1,7 @@
 <?php
 
 use JVBase\managers\Cache;
+use JVBase\managers\Roles;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 
@@ -17,7 +18,7 @@
  */
 function jvbShareName(int $userID):string
 {
-    $cache = Cache::for('usernames')->connect('user');
+    $cache = Cache::for('usernames', 'user');
 	return $cache->remember(
 		$userID,
 		function() use ($userID) {
@@ -34,7 +35,7 @@
  */
 function jvbGetUserByFirstName(string $first_name):WP_User|false
 {
-    $cache = Cache::for('userFirstname')->connect('user', true);
+    $cache = Cache::for('userFirstname', 'user');
 	return $cache->remember(
 		$first_name,
 		function() use ($first_name) {
@@ -74,7 +75,7 @@
  */
 function jvbGetUserByDisplayName(string $display_name):WP_User|false
 {
-    $cache = Cache::for('displayNames')->connect('user', true);
+    $cache = Cache::for('displayNames', 'user');
     $cached = $cache->get($display_name)??false;
 
     if ($cached && is_int($cached)) {
@@ -113,7 +114,7 @@
 function jvbGetUsername(int $user_id):string
 {
     $key = 'user_display_names';
-    $cache = Cache::for('userNames', WEEK_IN_SECONDS)->connect('user');
+    $cache = Cache::for('userNames', 'user',WEEK_IN_SECONDS);
     $cached = $cache->get($user_id);
 
     if ($cached) {
@@ -154,7 +155,7 @@
         return false;
     }
 
-    $cache = Cache::for('artist', 3600)->connect('post');
+    $cache = Cache::for('artist', 'post', 3600);
 	$cached = $cache->get($userID);
 	if ($cached) {
 		return match ($return) {
@@ -222,7 +223,7 @@
 
 function  jvbUserProfileLink(int $userID):string|false
 {
-	$cache = Cache::for('userLink')->connect('user');
+	$cache = Cache::for('userLink', 'user');
 	return $cache->remember(
 		$userID,
 		function() use ($userID) {
@@ -238,7 +239,7 @@
 			$link = get_user_meta($userID, BASE.'profile_link', true);
 			//Try to create it
 			if (empty($link)) {
-				$link = JVB()->roles()->addUserLink($user, $role);
+				$link = Roles::getInstance()()->addUserLink($user, $role);
 				if (!$link) {
 					return false;
 				}
diff --git a/inc/helpers/renderFields.php b/inc/helpers/renderFields.php
index 150af0e..ec892dd 100644
--- a/inc/helpers/renderFields.php
+++ b/inc/helpers/renderFields.php
@@ -62,9 +62,9 @@
  * @param string $type
  * @return string
  */
-function jvbRenderLinks(int $ID, string $type =''):string
+function jvbRenderLinks(int $ID, string $type ='post'):string
 {
-    $cache = Cache::for('user_links', WEEK_IN_SECONDS)->connect('post')->connect('taxonomy')->connect('user');
+    $cache = Cache::for('user_links_'.$type, $type,WEEK_IN_SECONDS);
     $cached = $cache->get($ID);
     if ($cached) {
         return $cached;
@@ -143,13 +143,16 @@
 
 /**
  * @param int $ID
- * @param string $type
+ * @param ?string $type
  *
  * @return string
  */
-function jvbRenderContactInfo(int $ID, string $type = ''):string
+function jvbRenderContactInfo(int $ID, ?string $type = null):string
 {
-    $cache = Cache::for('contact', WEEK_IN_SECONDS)->connect('post')->connect('taxonomy');
+	if (is_null($type)) {
+		return '';
+	}
+    $cache = Cache::for('contact_'.$type, $type,WEEK_IN_SECONDS);
 
     $cached = $cache->get($ID);
     if($cached){
@@ -376,7 +379,7 @@
 			return '';
 		}
 	}
-	$cache = Cache::for($term->taxonomy.'_link')->connect('taxonomy');
+	$cache = Cache::for($term->taxonomy.'_link', 'term');
 	$key = $term->term_id;
 	return $cache->remember(
 		$key,
@@ -599,7 +602,7 @@
 		return '';
 	}
 
-	$cache = Cache::for('locations')->connect('taxonomy');
+	$cache = Cache::for('tax_locations', 'term');
 	$key = $cache->generateKey($location);
 
 	$cached = false;
diff --git a/inc/helpers/time.php b/inc/helpers/time.php
index 4c3483c..88ee1df 100644
--- a/inc/helpers/time.php
+++ b/inc/helpers/time.php
@@ -143,7 +143,10 @@
  */
 function jvbRenderHours(int $ID, string $type = ''):string
 {
-	$cache = Cache::for('hours_display', WEEK_IN_SECONDS)->connect('taxonomy')->connect('post')->connect('user');
+	if (!$type) {
+		return '';
+	}
+	$cache = Cache::for('hours_display_'.$type, $type, WEEK_IN_SECONDS);
 
 	$cached = $cache->get($ID);
 
diff --git a/inc/importers/JaneAppSalesImporter.php b/inc/importers/JaneAppSalesImporter.php
index a071406..3121a72 100644
--- a/inc/importers/JaneAppSalesImporter.php
+++ b/inc/importers/JaneAppSalesImporter.php
@@ -2,7 +2,7 @@
 namespace JVBase\importers;
 
 use WP_Error;
-use JVBase\managers\ReferralManager;
+use JVBase\managers\Referrals;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -20,7 +20,7 @@
 	protected string $referrals_table;
 	protected string $treatments_table;
 	protected string $rewards_table;
-	protected ReferralManager $referral_manager;
+	protected Referrals $referral_manager;
 	protected array $import_stats = [];
 	protected array $skipped_details = [];
 
@@ -58,7 +58,7 @@
 		$this->referrals_table = $wpdb->prefix . BASE . 'referrals';
 		$this->treatments_table = $wpdb->prefix . BASE . 'referral_treatments';
 		$this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
-		$this->referral_manager = new ReferralManager();
+		$this->referral_manager = Referrals::getInstance();
 	}
 
 	/**
diff --git a/inc/importers/_setup.php b/inc/importers/_setup.php
index 8da1879..d81e91c 100644
--- a/inc/importers/_setup.php
+++ b/inc/importers/_setup.php
@@ -1,3 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
+
 require(JVB_DIR . '/inc/importers/JaneAppClientImporter.php');
 require(JVB_DIR . '/inc/importers/JaneAppSalesImporter.php');
diff --git a/inc/integrations/Admin.php b/inc/integrations/Admin.php
index 11273b8..a243bb3 100644
--- a/inc/integrations/Admin.php
+++ b/inc/integrations/Admin.php
@@ -81,7 +81,7 @@
 
 	public function renderConnection(bool $return = false):string
 	{
-		if ($this->userID && !JVB()->userCanConnect($this->service_name, $this->userID)) {
+		if ($this->userID && !$this->userCanConnect($this->userID)) {
 			return '';
 		}
 
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index 262f798..21cf95f 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -338,7 +338,7 @@
 		array $data,
 		array $options = []
 	):bool {
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 
 		$queued =  $queue->queueOperation(
 			$type,
@@ -405,7 +405,7 @@
 				$this->addSavePost();
 			}
 
-			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
+//			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
 
 			if ($this->canSync['delete']) {
 				add_action('before_delete_post', [$this, 'handleDeletePost'], 10, 1);
@@ -607,7 +607,7 @@
 		if ($jpegVersion !== '' && is_int($jpegVersion)) {
 			return $jpegVersion;
 		}
-		$uploader = new UploadManager();
+		$uploader = UploadManager::getInstance();
 		$converted = $uploader->convertImageTo($imgID, 'jpeg', 80, false);
 		if ($converted && !is_wp_error($converted)) {
 			update_post_meta($imgID, BASE . 'jpeg_version', $converted['attachment_id']);
@@ -620,6 +620,7 @@
 
 	/**
 	 * Used by JVBase\registrar\helpers\AddIntegrationFields.php
+	 * @param string|null $content_type
 	 * @return array
 	 */
 	public function getAdditionalFields(?string $content_type = null):array
diff --git a/inc/integrations/OAuth.php b/inc/integrations/OAuth.php
index dcf89aa..df742fa 100644
--- a/inc/integrations/OAuth.php
+++ b/inc/integrations/OAuth.php
@@ -10,7 +10,7 @@
 
 trait OAuth
 {
-	use _Base, Admin;
+	use _Base, Admin, ServiceMap;
 	protected OAuthURLs $oauth;
 	/**
 	 * Child classes must set this (e.g. 'https://api.example.com/v1/')
@@ -38,6 +38,10 @@
 	{
 		return $this->getRequestHeaders();
 	}
+	public function getOAuthBaseURL():?string
+	{
+		return $this->baseOAuthUrl;
+	}
 
 	/**
 	 * Default to the base handleResponse method, but extensions can modify accordingly
@@ -291,12 +295,14 @@
 		}
 
 		// Get integration instance
-		$integration = JVB()->connect($this->service_name, $user_id);
+		$integration = $this->getService($this->service_name);
 
-		if (!$integration) {
+		if (!$integration || !method_exists($integration, 'switchUser')) {
 			wp_die('Invalid service: ' . esc_html($this->service_name), 'OAuth Error');
 		}
 
+		$integration->switchUser($user_id);
+
 
 		// Exchange code for tokens
 		$result = $integration->handleOAuthCode($code, $state);
diff --git a/inc/integrations/ServiceMap.php b/inc/integrations/ServiceMap.php
new file mode 100644
index 0000000..9386ea3
--- /dev/null
+++ b/inc/integrations/ServiceMap.php
@@ -0,0 +1,35 @@
+<?php
+namespace JVBase\integrations;
+
+use JVBase\base\Site;
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ServiceMap {
+	protected array $serviceMap = [
+		'maps' => 'JVBase\integrations\services\GoogleMaps',
+		'square' => 'JVBase\integrations\services\Square',
+		'facebook' => 'JVBase\integrations\services\Facebook',
+		'helcim' => 'JVBase\integrations\services\Helcim',
+		'instagram' => 'JVBase\integrations\services\Instagram',
+		'bluesky' => 'JVBase\integrations\services\BlueSky',
+		'gmb' => 'JVBase\integrations\services\GoogleMyBusiness',
+		'cloudflare' => 'JVBase\integrations\services\Cloudflare',
+		'umami' => 'JVBase\integrations\services\Umami',
+		'postmark' => 'JVBase\integrations\services\PostMark',
+	];
+
+	public function getService(string $service):Integrations|false
+	{
+		return array_key_exists($service, $this->serviceMap) ?
+			$this->serviceMap[$service]::getInstance()
+			: false;
+	}
+
+	public function getAvailableServices():array
+	{
+		return array_filter(array_keys($this->serviceMap), function ($service) {
+			return Site::hasIntegration($service);
+		}, ARRAY_FILTER_USE_KEY);
+	}
+}
diff --git a/inc/integrations/SyncFromPost.php b/inc/integrations/SyncFromPost.php
index b3d9bbc..f12beca 100644
--- a/inc/integrations/SyncFromPost.php
+++ b/inc/integrations/SyncFromPost.php
@@ -1 +1,4 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
diff --git a/inc/integrations/SyncFromTerm.php b/inc/integrations/SyncFromTerm.php
index b3d9bbc..f12beca 100644
--- a/inc/integrations/SyncFromTerm.php
+++ b/inc/integrations/SyncFromTerm.php
@@ -1 +1,4 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
diff --git a/inc/integrations/SyncFromUser.php b/inc/integrations/SyncFromUser.php
index b3d9bbc..f12beca 100644
--- a/inc/integrations/SyncFromUser.php
+++ b/inc/integrations/SyncFromUser.php
@@ -1 +1,4 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
diff --git a/inc/integrations/SyncHelpers.php b/inc/integrations/SyncHelpers.php
index 7abc2fe..8cdaf59 100644
--- a/inc/integrations/SyncHelpers.php
+++ b/inc/integrations/SyncHelpers.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\queue\executors\IntegrationExecutor;
 use JVBase\managers\queue\mergers\DefaultMerger;
+use JVBase\managers\queue\Queue;
 use JVBase\managers\queue\TypeConfig;
 use JVBase\meta\Meta;
 use WP_Query;
@@ -29,6 +30,7 @@
 		self::$syncTo = $this->service_name.'_sync_to';
 		self::$deleteFrom = $this->service_name.'_delete_from';
 		self::$syncFrom = $this->service_name.'_sync_from';
+		self::$syncCustomer = $this->service_name.'_sync_customer';
 		//TODO: What is the difference between sync_from and import?
 		self::$import = $this->service_name.'import_from';
 	}
@@ -42,6 +44,17 @@
 			default => ''
 		};
 	}
+	public function setServiceItemID(int $itemID, string $id, string $type = 'post'):bool
+	{
+
+		$result = match($type) {
+			'post'	=> Meta::forPost($itemID)->set("_{$this->service_name}_item_id", $id),
+			'term','taxonomy'	=> Meta::forTerm($itemID)->set("_{$this->service_name}_item_id", $id),
+			'user'	=> Meta::forUser($itemID)->set("_{$this->service_name}_item_id", $id),
+			default => false
+		};
+		return $result !== false;
+	}
 	protected function updateItemStatus(int|array $items, string $status = 'pending', string $type = 'post'):void
 	{
 		if (!is_array($items)) {
@@ -151,7 +164,7 @@
 		if (empty($this->syncTaxonomies) && empty($this->syncPostTypes)) {
 			return;
 		}
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		$executor = new IntegrationExecutor();
 
 		$queue->registry()->register(self::$syncTo, new TypeConfig(
diff --git a/inc/integrations/SyncToPost.php b/inc/integrations/SyncToPost.php
index 2226604..ae53b41 100644
--- a/inc/integrations/SyncToPost.php
+++ b/inc/integrations/SyncToPost.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\integrations;
 
+use JVBase\managers\queue\Queue;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 use WP_Post;
@@ -127,7 +128,7 @@
 			return;
 		}
 
-		JVB()->queue()->add(
+		Queue::getInstance()->add(
 			self::$deleteFrom,
 			$userID,
 			[
diff --git a/inc/integrations/SyncToTerm.php b/inc/integrations/SyncToTerm.php
index 0f830a6..41facf5 100644
--- a/inc/integrations/SyncToTerm.php
+++ b/inc/integrations/SyncToTerm.php
@@ -102,7 +102,7 @@
 		if (empty($fields["_{$this->service_name}_item_id"])) {
 			return;
 		}
-		JVB()->queue()->add(
+		Queue::getInstance()->add(
 			self::$deleteFrom,
 			0,
 			[
diff --git a/inc/integrations/SyncToUser.php b/inc/integrations/SyncToUser.php
index 51290e5..b53d480 100644
--- a/inc/integrations/SyncToUser.php
+++ b/inc/integrations/SyncToUser.php
@@ -170,7 +170,7 @@
 		if (empty($fields["_{$this->service_name}_item_id"])) {
 			return;
 		}
-		JVB()->queue()->add(
+		Queue::getInstance()->add(
 			self::$deleteFrom,
 			0,
 			[
diff --git a/inc/integrations/UserConnection.php b/inc/integrations/UserConnection.php
index b6ae00e..5185319 100644
--- a/inc/integrations/UserConnection.php
+++ b/inc/integrations/UserConnection.php
@@ -19,7 +19,7 @@
 	}
 	public function switchUser(?int $userID = null):bool
 	{
-		if (!$this->userCanConnect($userID)) {
+		if (!is_null($userID) && !$this->userCanConnect($userID)) {
 			return false;
 		}
 		$this->clearUser();
diff --git a/inc/integrations/_Base.php b/inc/integrations/_Base.php
index 0f96c90..03996ca 100644
--- a/inc/integrations/_Base.php
+++ b/inc/integrations/_Base.php
@@ -144,7 +144,7 @@
 		if (method_exists($this, 'initializeRequestLimiter')) {
 			$this->initializeRequestLimiter();
 		}
-		if (method_exists($this, 'intializeOAuthRateLimiter')) {
+		if (method_exists($this, 'initializeOAuthRateLimiter')) {
 			$this->initializeOAuthRateLimiter();
 		}
 	}
@@ -160,4 +160,14 @@
 		}
 		return $response;
 	}
+
+	public function getAllowedContent():array
+	{
+		return array_keys($this->contentTypes);
+	}
+
+	public function getServiceName():string
+	{
+		return $this->service_name;
+	}
 }
diff --git a/inc/integrations/_setup.php b/inc/integrations/_setup.php
index 733900f..1eef3d4 100644
--- a/inc/integrations/_setup.php
+++ b/inc/integrations/_setup.php
@@ -1,6 +1,10 @@
 <?php
 use JVBase\base\Site;
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/integrations/_Base.php');
+
 require(JVB_DIR . '/inc/integrations/Admin.php');
 	require(JVB_DIR . '/inc/integrations/AdminActionResult.php');
 	require(JVB_DIR . '/inc/integrations/AdminActions.php');
@@ -10,7 +14,7 @@
 require(JVB_DIR . '/inc/integrations/Endpoint.php');
 require(JVB_DIR . '/inc/integrations/EndpointField.php');
 require(JVB_DIR . '/inc/integrations/Field.php');
-require(JVB_DIR . '/inc/integrations/Instance.php');
+
 require(JVB_DIR . '/inc/integrations/OAuth.php');
 	require(JVB_DIR . '/inc/integrations/OAuthCredentials.php');
 	require(JVB_DIR . '/inc/integrations/OAuthURLs.php');
@@ -30,45 +34,50 @@
 require(JVB_DIR . '/inc/integrations/Webhooks.php');
 require(JVB_DIR . '/inc/integrations/Integrations.php');
 
-if (Site::hasIntegration('maps')) {
-	require(JVB_DIR . '/inc/integrations/services/GoogleMaps.php');
-}
+add_action('jvbloaded', 'jvbLoadIntegrations');
+function jvbLoadIntegrations():void
+{
+	if (Site::hasIntegration('maps')) {
+		require(JVB_DIR . '/inc/integrations/services/GoogleMaps.php');
+	}
 
-if (Site::hasIntegration('umami')) {
-	require(JVB_DIR . '/inc/integrations/services/Umami.php');
-}
-if (Site::hasIntegration('cloudflare')) {
-	require(JVB_DIR . '/inc/integrations/services/Cloudflare.php');
-}
+	if (Site::hasIntegration('umami')) {
+		require(JVB_DIR . '/inc/integrations/services/Umami.php');
+	}
+	if (Site::hasIntegration('cloudflare')) {
+		require(JVB_DIR . '/inc/integrations/services/Cloudflare.php');
+	}
 
-if (Site::hasIntegration('square')) {
-	require(JVB_DIR . '/inc/integrations/services/Square.php');
-}
+	if (Site::hasIntegration('square')) {
+		require(JVB_DIR . '/inc/integrations/services/Square.php');
+	}
 
-if (Site::hasIntegration('facebook')) {
-	require(JVB_DIR . '/inc/integrations/services/Facebook.php');
-}
-
-if (Site::hasIntegration('instagram')) {
-	if (!Site::hasIntegration('facebook')) {
+	if (Site::hasIntegration('facebook')) {
 		require(JVB_DIR . '/inc/integrations/services/Facebook.php');
 	}
-	require(JVB_DIR . '/inc/integrations/services/Instagram.php');
-}
 
-if (Site::hasIntegration('bluesky')) {
-	require(JVB_DIR . '/inc/integrations/services/BlueSky.php');
-}
+	if (Site::hasIntegration('instagram')) {
+		if (!Site::hasIntegration('facebook')) {
+			require(JVB_DIR . '/inc/integrations/services/Facebook.php');
+		}
+		require(JVB_DIR . '/inc/integrations/services/Instagram.php');
+	}
 
-if (Site::hasIntegration('gmb')) {
-	require(JVB_DIR . '/inc/integrations/services/GoogleMyBusiness.php');
-}
+	if (Site::hasIntegration('bluesky')) {
+		require(JVB_DIR . '/inc/integrations/services/BlueSky.php');
+	}
 
-if (Site::hasIntegration('postmark')) {
-	require(JVB_DIR . '/inc/integrations/services/PostMark.php');
-}
+	if (Site::hasIntegration('gmb')) {
+		require(JVB_DIR . '/inc/integrations/services/GoogleMyBusiness.php');
+	}
+
+	if (Site::hasIntegration('postmark')) {
+		require(JVB_DIR . '/inc/integrations/services/PostMark.php');
+	}
 
 
-if (Site::hasIntegration('helcim')) {
-	require(JVB_DIR . '/inc/integrations/services/Helcim.php');
+	if (Site::hasIntegration('helcim')) {
+		require(JVB_DIR . '/inc/integrations/services/Helcim.php');
+	}
 }
+
diff --git a/inc/integrations/services/Cloudflare.php b/inc/integrations/services/Cloudflare.php
index 168590b..4f1a98b 100644
--- a/inc/integrations/services/Cloudflare.php
+++ b/inc/integrations/services/Cloudflare.php
@@ -2,7 +2,7 @@
 
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 
 if (!defined('ABSPATH')) {
@@ -137,8 +137,7 @@
 
 	/**
 	 * Verify the Turnstile response
-	 * Can be called directly via JVB()->connect('cloudflare')->verifyTurnstile($token)
-	 *   or Cloudflare::getInstance()->verifyTurnstile($token)
+	 * Can be called directly via Cloudflare::getInstance()->verifyTurnstile($token)
 	 * @param string|null $token
 	 * @param string $remote_ip
 	 * @return bool
diff --git a/inc/integrations/services/CloudflareOld.php b/inc/integrations/services/CloudflareOld.php
index 96cb9b0..e69cf6f 100644
--- a/inc/integrations/services/CloudflareOld.php
+++ b/inc/integrations/services/CloudflareOld.php
@@ -188,7 +188,7 @@
 
 	/**
 	 * Verify Turnstile response
-	 * Can be called directly: JVB()->connect('cloudflare')->verifyTurnstile($token)
+	 * Can be called directly: Cloudflare::getInstance()->verifyTurnstile($token)
 	 */
 	public function verifyTurnstile(?string $token = null, string $remote_ip = ''): bool
 	{
diff --git a/inc/integrations/services/Facebook.php b/inc/integrations/services/Facebook.php
index 04c03f7..fcf7473 100644
--- a/inc/integrations/services/Facebook.php
+++ b/inc/integrations/services/Facebook.php
@@ -7,7 +7,7 @@
 namespace JVBase\integrations\services;
 use Exception;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\OAuth;
 use JVBase\integrations\OAuthCredentials;
@@ -17,6 +17,8 @@
 use JVBase\integrations\SyncFrom;
 use JVBase\integrations\SyncTo;
 use JVBase\integrations\UserConnection;
+use JVBase\meta\Meta;
+use JVBase\meta\Sanitizer;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -40,22 +42,25 @@
 		$this->title = 'Facebook';
 		$this->icon = 'facebook-logo';
 
-		$this->apiVersion = 'v25.0';
+		$this->apiVersion = 'v26.0';
 
 		$this->canSync = [
 			'create'	=> true,
-			'update'	=> true,
-			'delete'	=> true
+			'update'	=> false,
+			'delete'	=> false
 		];
 
 		$this->defineFields();
 		$this->defineInstructions();
 		$this->defineOAuth();
 		$this->defineActions();
+		$this->defineContentTypes();
 
 		$this->baseUrl = 'https://graph.facebook.com/'.$this->apiVersion;
 		$this->baseOAuthUrl = 'https://www.facebook.com/'.$this->apiVersion;
 
+		$this->supportsWebp = false;
+
 		parent::__construct();
 	}
 
@@ -134,6 +139,31 @@
 		{
 			return 'Connect your Facebook Page to share posts, events, and engage with your audience.';
 		}
+
+		protected function defineContentTypes():void
+		{
+			$base = $this->getBaseFields();
+			$this->contentTypes['post'] = array_merge($base, $this->contentTypePostFields());
+			$this->contentTypes['image'] = array_merge($base, $this->contentTypeImageFields());
+			//Looks like product requires some major heavy lifting. Best to skip for now
+//			$this->contentTypes['product'] = array_merge($base, $this->contentTypeProductFields());
+
+
+		}
+			protected function getBaseFields():array
+			{
+				return  [
+
+				];
+			}
+			protected function contentTypePostFields():array
+			{
+				return [];
+			}
+			protected function contentTypeImageFields():array
+			{
+				return [];
+			}
 	protected function initialize(): void
 	{
 		// TODO: Implement initialize() method.
@@ -148,23 +178,64 @@
 	{
 		return [
 			'Content-Type' => 'application/json',
-			'Accept' => 'application/json'
+			'Accept' => 'application/json',
 		];
 	}
 
 
-	protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
+	protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array
 	{
 		if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
 			return $this->response(false, 'Rate limit exceeded');
 		}
 
+		$errors = $this->checkErrors($response);
+		if (!empty($errors)) {
+			return $errors;
+		}
+
 		$endpoint = str_replace($this->baseUrl, '', $endpoint);
 		return match ($endpoint) {
 			'/me/accounts?access_token='.$this->getAccessToken() 	=> $this->handleGetPagesResponse($response, $method),
-			default => $this->response(false, 'Unhandled Response for ' . $endpoint . ' endpoint, with method: ' . $method),
+			default => $response
 		};
 	}
+		protected function checkErrors(WP_Error|array $response):array|false
+		{
+			if (!isset($response['error'])) {
+				return false;
+			}
+			$error = (string)$response['error']['code'];
+			switch ($error) {
+				//Invalid parameter
+				case '100':
+				//Too many calls by this account. Wait for a bit
+				case '80001':
+				//Permissions error
+				case '200':
+				//Action has been deemed abusive, or otherwise disallowed
+				case '368':
+				//Missing or invalid image file
+				case '324':
+				//Invalid OAuth 2.0 Access Token
+				case '190':
+					//Incorrect Signature
+				case '104':
+				//Desktop applications cannot call this function for other users
+				case '240':
+				//Requires additional permissions
+				case '283':
+				//Session is invalid; user has been checkpointed
+				case '459':
+				default:
+					$this->logError(
+						'handleResponse',
+						'Something went wrong: '.$response['Message']??'',
+						$response
+					);
+					return $this->response(false, 'Something went wrong: '.$response['Message']??'');
+			}
+		}
 	/************************************************************
 	 * UTILITY METHODS
 	************************************************************/
@@ -188,6 +259,7 @@
 			$resp = $this->getRequest(
 				$this->baseUrl.'/me?access_token='.$cred['access_token'],
 			);
+			$tmp = [];
 			if ($resp && !is_wp_error($resp)) {
 				$tmp['user_id'] = $resp['id'];
 				$tmp['user_name'] = $resp['name'];
@@ -275,4 +347,272 @@
 	{
 		return [];
 	}
+
+	public function getOrCreateImage(int $imgID, string $caption = '', bool $publish = true, bool $returnResponse = false):string|array|false
+	{
+		$imgID = $this->getSupportedImage($imgID);
+
+		$id = $this->getServiceItemID($imgID);
+		if (!empty($id)) {
+			return $id;
+		}
+
+		$url = wp_get_attachment_url($imgID);
+		if (!file_exists($url)) {
+			return false;
+		}
+
+		$args = [
+			'url'	=> $url
+		];
+		if (!empty($caption)) {
+			$caption = html_entity_decode(wp_strip_all_tags($caption));
+			if (!empty($caption)) {
+				$args['caption'] = $caption;
+			}
+		}
+
+		$alt_text = get_post_meta($imgID, '_wp_attachment_image_alt', true);
+		if (!empty($alt_text)) {
+			$args['alt_text_custom'] = esc_html($alt_text);
+		}
+
+		if (!$publish) {
+			$args['published'] = false;
+		}
+
+		$args['access_token'] = $this->getAccessToken();
+
+		$response = $this->postRequest(
+			sprintf(
+				'%s/%s/photos',
+				$this->baseUrl,
+				$this->getPageID(),
+			),
+			$args
+		);
+
+		if (isset($response['id'])) {
+			$this->setServiceItemID($imgID, $response['id']);
+		}
+
+		return $returnResponse ? $response : $response['id'];
+
+
+	}
+
+	/**********************************************************
+	 * POSTING UTILITIES
+	**********************************************************/
+	public function post(string $message):array
+	{
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up.');
+		}
+		$message = html_entity_decode(wp_strip_all_tags($message));
+		if (empty($message)) {
+			return $this->response(false, 'Message cannot be empty.');
+		}
+		$args = [
+			'message'	=> $message,
+		];
+
+		$args['access_token'] = $this->getAccessToken();
+
+		return $this->postRequest(
+			sprintf(
+				'%s/%s/feed',
+				$this->baseUrl,
+				$this->getPageID(),
+			),
+			$args
+		);
+	}
+	/**
+	 * From https://developers.facebook.com/docs/graph-api/reference/photo/#u_0_o_gP
+	 *
+	 * @param int $imgID
+	 * @param string $caption
+	 * @return array
+	 */
+	public function postSingleImage(int $imgID, string $caption):array
+	{
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up yet');
+		}
+
+		$response = $this->getOrCreateImage($imgID, $caption);
+
+		return $this->response(true, 'Posted Image', $response);
+	}
+
+	public function postGalleryImages(array $imgIDs, string $caption, int $postID = 0):array
+	{
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up yet');
+		}
+
+		$post = [];
+		$i = 0;
+		foreach ($imgIDs as $imgID) {
+			$id = $this->getOrCreateImage($imgID, '', false);
+			if ($id) {
+				$post['attached_media['.$i.']'] = json_encode(['media_fbid' => $id]);
+				$i++;
+			}
+		}
+
+		if (empty($post)) {
+			return $this->response(false, 'No images posted');
+		}
+
+		$post['access_token'] = $this->getAccessToken();
+
+		$caption = html_entity_decode(wp_strip_all_tags($caption));
+		if (!empty($caption)) {
+			$post['message'] = $caption;
+		}
+
+		$response = $this->postRequest(
+			sprintf(
+				'%s/%s/feed',
+				$this->baseUrl,
+				$this->getPageID(),
+			),
+			$post
+		);
+
+		if ($postID > 0 && isset($response['id'])) {
+			$this->setServiceItemID($postID, $response['id']);
+		}
+
+		return $this->response(true, 'Successfully posted to Facebook', $response);
+	}
+
+	public function getReviews():array
+	{
+		if (!$this->isSetUp() || !$this->getPageID()) {
+			return [];
+		}
+		$ratings = $this->getRequest(
+			sprintf(
+			'%s/%s/ratings',
+				$this->baseUrl,
+				$this->getPageID(),
+			)
+		);
+		if (!isset($ratings['data'])) {
+			return [];
+		}
+		return $ratings['data'];
+	}
+
+	/**
+	 * https://developers.facebook.com/docs/graph-api/reference/page/#parameters-3
+	 * @param array $data allowed keys are: about, description, phone, website, emails, location {json object with street, city, state, zip, country}
+	 * @return array
+	 * TODO: Cover photo? (cover)
+	 * TODO: Food style (food_styles)
+	 */
+	public function updateBusiness(array $data):array
+	{
+		if (!$this->isSetUp() || !$this->getPageID()) {
+			return $this->response(false, 'Not set up yet');
+		}
+		$data = array_filter($data, function($item) use ($data) {
+			if (!in_array($item, ['about', 'description', 'phone', 'website', 'emails', 'location', 'hours'])) {
+				$this->logError('updateBusiness', 'Attempted to set property that is not allowed: '.$item.' with value: '.print_r($data[$item], true));
+				return false;
+			}
+			return true;
+		}, ARRAY_FILTER_USE_KEY);
+		$out = [];
+		foreach ($data as $prop => $val) {
+			$out[$prop] = match($prop) {
+				'about', 'description'	=> sanitize_text_field($val),
+				'phone'	=> Sanitizer::sanitizePhone($val),
+				'emails' => $this->sanitizeEmails($val),
+				'website'	=> sanitize_url($val),
+				'location'	=> $this->sanitizeLocation($val),
+				'hours'	=> $this->format_hours($val),
+				default => null
+			};
+		}
+		$out = array_filter($out);
+
+		if (empty($out)) {
+			return $this->response(false, 'No data to update');
+		}
+
+		$out['access_token'] = $this->getAccessToken();
+
+		$response = $this->postRequest(
+			sprintf(
+				'%s/%s',
+				$this->baseUrl,
+				$this->getPageID()
+			),
+			$out
+		);
+
+		$success = $response['success']??false;
+		return $this->response($success, $success ? 'Successfully updated information' : 'Failed to update information');
+	}
+		protected function format_hours(array $days):array|null
+		{
+			//FB expects it to be:
+				// - first 3 characters of day of week (mon)
+				// - either 1 or 2, to allow for two set hours (ex: noon break)
+				// - open or close
+			//example: mon_1_open: 11:00, mon_1_close: 18:00
+			//To accomplish this, we will assume the hours are formatted as with the SEO schema
+			$days = array_filter($days, function ($day) {
+				return in_array($day, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']);
+			}, ARRAY_FILTER_USE_KEY);
+
+			$out = [];
+			foreach ($days as $day => $conf) {
+				if (array_key_exists('isOpen', $conf) && $conf['isOpen'] === 1
+					&& array_key_exists('opens', $conf) && !empty($conf['opens'])
+					&& array_key_exists('closes', $conf) && !empty($conf['closes'])
+				) {
+					$short = substr($day, 0, 3);
+
+					$out[$short.'_1_open'] = $conf['opens']; //Should be stored in 24hr time already
+					$out[$short.'_1_close'] = $conf['closes'];
+				}
+			}
+
+			return empty($out) ? null : $out;
+		}
+		protected function sanitizeLocation(array $location):array|null
+		{
+			if (array_key_exists('province', $location)) {
+				$location['state'] = $location['province'];
+				unset($location['province']);
+			}
+			if (array_key_exists('postal_code', $location)) {
+				$location['zip'] = $location['postal_code'];
+				unset($location['postal_code']);
+			}
+			$location = array_filter($location, function ($item) use ($location) {
+				if (!in_array($item, ['street', 'city', 'state', 'zip', 'country'])) {
+					$this->logError('sanitizeLocation', 'Attempted to set property that is not allowed: '.$item.', with value: '.print_r($location[$item], true));
+					return false;
+				}
+				if (!is_string($location[$item])) {
+					$this->logError('sanitizeLocation', 'Unexpected value for '.$item.': '.print_r($location[$item], true));
+					return false;
+				}
+				return true;
+			}, ARRAY_FILTER_USE_KEY);
+			return empty($location) ? null : $location;
+		}
+		protected function sanitizeEmails(array $emails):array|null
+		{
+			$emails = array_filter($emails, function($item) {
+				return (bool)filter_var($item, FILTER_VALIDATE_EMAIL);
+			});
+			return empty($emails) ? null : $emails;
+		}
 }
diff --git a/inc/integrations/services/GoogleMaps.php b/inc/integrations/services/GoogleMaps.php
index f1e986a..2dbce88 100644
--- a/inc/integrations/services/GoogleMaps.php
+++ b/inc/integrations/services/GoogleMaps.php
@@ -6,7 +6,7 @@
 
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\Requests;
 use WP_Error;
diff --git a/inc/integrations/services/GoogleMyBusiness.php b/inc/integrations/services/GoogleMyBusiness.php
index 89f4f58..25e98bb 100644
--- a/inc/integrations/services/GoogleMyBusiness.php
+++ b/inc/integrations/services/GoogleMyBusiness.php
@@ -2,12 +2,13 @@
 namespace JVBase\integrations\services;
 use Exception;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\OAuth;
 use JVBase\integrations\OAuthCredentials;
 use JVBase\integrations\OAuthURLs;
 use JVBase\integrations\Requests;
+use JVBase\integrations\SyncTo;
 use JVBase\integrations\UserConnection;
 
 
@@ -17,7 +18,7 @@
 
 class GoogleMyBusiness extends Integrations
 {
-	use Admin, Instance, OAuth, Requests, UserConnection;
+	use Admin, Instance, OAuth, Requests, UserConnection, SyncTo;
 
 	protected string $readMask = 'name,title,storefrontAddress,metadata,openInfo,storeCode,categories,phoneNumbers,labels,specialHours';
 
@@ -250,4 +251,14 @@
 			}
 			return $options;
 		}
+
+	protected function setContentTypes(): void
+	{
+		// TODO: Implement setContentTypes() method.
+	}
+
+	protected function formatForService(int $itemID, string $type = 'post'): array
+	{
+		// TODO: Implement formatForService() method.
+	}
 }
diff --git a/inc/integrations/services/GoogleMyBusinessOld.php b/inc/integrations/services/GoogleMyBusinessOld.php
index 348700f..7a33151 100644
--- a/inc/integrations/services/GoogleMyBusinessOld.php
+++ b/inc/integrations/services/GoogleMyBusinessOld.php
@@ -2482,7 +2482,8 @@
 			$gmb_item = $this->buildMenuItem($item, $defaults);
 
 			// Get section for this item
-			$categories = wp_get_post_terms($item->ID, BASE.'section');
+			$categories = wp_get_post_terms($item->ID, 'product_cat');
+//			$categories = wp_get_post_terms($item->ID, BASE.'section');
 			$section_slug = !empty($categories) ?
 				$categories[0]->slug :
 				'menu-items';
diff --git a/inc/integrations/services/Helcim.php b/inc/integrations/services/Helcim.php
index 6c41266..3681d5f 100644
--- a/inc/integrations/services/Helcim.php
+++ b/inc/integrations/services/Helcim.php
@@ -2,7 +2,7 @@
 
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\managers\queue\executors\IntegrationExecutor;
 use JVBase\managers\queue\TypeConfig;
diff --git a/inc/integrations/services/HelcimOld.php b/inc/integrations/services/HelcimOld.php
index 22c7dff..acc53d8 100644
--- a/inc/integrations/services/HelcimOld.php
+++ b/inc/integrations/services/HelcimOld.php
@@ -757,7 +757,7 @@
 
 	protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
 	{
-		$queue    = JVB()->queue();
+		$queue    = Queue::getInstance();
 
 		$queue->registry()->register(self::$import, new TypeConfig(
 			executor:   $executor,
diff --git a/inc/integrations/services/Instagram.php b/inc/integrations/services/Instagram.php
index 183679f..6a71ad8 100644
--- a/inc/integrations/services/Instagram.php
+++ b/inc/integrations/services/Instagram.php
@@ -9,7 +9,7 @@
 
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\OAuth;
 use JVBase\integrations\OAuthCredentials;
diff --git a/inc/integrations/services/PostMark.php b/inc/integrations/services/PostMark.php
index c8a4956..bba6656 100644
--- a/inc/integrations/services/PostMark.php
+++ b/inc/integrations/services/PostMark.php
@@ -2,9 +2,10 @@
 
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\Requests;
+use JVBase\meta\Meta;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -217,64 +218,79 @@
 			return $this->response(false, 'Rate limit exceeded');
 		}
 		$endpoint = str_replace($this->apiBase, '', $endpoint);
-		$error = $response['ErrorCode'] ?? false;
-		if ($error) {
-			switch ($error) {
-				case '0':
-					//Do nothing, no error found
-					break;
-				//Bad or missing API Token
-				case '10':
-				//Multiple errors
-				case '11':
-				//Resource Not found
-				case '12':
-				//Invalid Pagination Key
-				case '13':
-				//Maintenance; PostMark API down for a sec
-				case '100':
-				//Invalid Email Request
-				case '300':
-				//Sender Signature not found
-				case '400':
-				//Sender signature not confirmed
-				case '401':
-				//Invalid JSON
-				case '402':
-				//Invalid request field(s) - {FIELD_NAME}
-				case '403':
-				//Not allowed to send (no more credits in account)
-				case '405':
-				//Inactive recipient. Recipient hard bounces, or filed spam complaint
-				case '406':
-				//JSON required
-				case '409':
-				//Too many requests (500 max)
-				case '410':
-				//Forbidden Attachment type
-				case '411':
-				//Account is pending
-				case '412':
-				//Account may not exist
-				case '413':
-				//Invalid query string parameter
-				case '500':
-				default:
-					$this->logError(
-						'handleResponse',
-						'Something went wrong: '.$response['Message']??'',
-						$response
-					);
-					return $this->response(false, 'Something went wrong: '.$response['Message']??'');
-			}
+		$checkErrors = $this->checkErrors($response);
+		if (!empty($checkErrors)) {
+			return $checkErrors;
 		}
+
 		return match ($endpoint) {
 			'/email' => $this->handleEmailResponse($response, $method),
 			'/email/bulk'	=> $this->handleBulkEmailResponse($response, $method),
+			'/email/batch'	=> $this->handleBatchEmailResponse($response, $method),
 			default => $this->response(false, 'Unhandled Response for '. $endpoint . ' endpoint, with method: '. $method),
 		};
 	}
 
+		protected function checkErrors(WP_Error|array $response):array|false
+		{
+			$error = $response['ErrorCode'] ?? false;
+			if ($error) {
+				switch ($error) {
+					case '0':
+						//Do nothing, no error found
+						return false;
+					//Bad or missing API Token
+					case '10':
+						//Multiple errors
+					case '11':
+						//Resource Not found
+					case '12':
+						//Invalid Pagination Key
+					case '13':
+						//Maintenance; PostMark API down for a sec
+					case '100':
+						//Maybe schedule a retry?
+						//Invalid Email Request
+					case '300':
+						//Sender Signature not found
+					case '400':
+						//Sender signature not confirmed
+					case '401':
+						//Invalid JSON
+					case '402':
+						//Invalid request field(s) - {FIELD_NAME}
+					case '403':
+						//Not allowed to send (no more credits in account)
+					case '405':
+						//Inactive recipient. Recipient hard bounces, or filed spam complaint
+					case '406':
+						//JSON required
+					case '409':
+						//Too many requests (500 max)
+					case '410':
+						//Maybe schedule a retry?
+						//Forbidden Attachment type
+					case '411':
+						//Account is pending
+					case '412':
+						//Account may not exist
+					case '413':
+						//Some errors in bulk request
+					case '422':
+						//Invalid query string parameter
+					case '500':
+					default:
+						$this->logError(
+							'handleResponse',
+							'Something went wrong: '.$response['Message']??'',
+							$response
+						);
+						return $this->response(false, 'Something went wrong: '.$response['Message']??'');
+				}
+			}
+			return false;
+		}
+
 	/**********************************************************
 	 * UTILITY
 	**********************************************************/
@@ -379,16 +395,16 @@
 
 		$payload = $this->buildPayload($to, $subject, $message, $is_html, $headers, $attachments);
 
-		$response = $this->postRequest(
+		return $this->postRequest(
 			sprintf('%s/email', $this->apiBase),
 			$payload
 		);
 	}
 		protected function handleEmailResponse(WP_Error|array $response, string $method):array
 		{
-
+			return $this->response(true, 'Successfully sent request to PostMark', $response);
 		}
-		protected function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+		public function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
 		{
 			$to = is_array($to) ? implode(',',$to) : $to;
 
@@ -426,6 +442,8 @@
 
 			if (!empty($headers['reply-to'])) {
 				$payload['ReplyTo'] = $headers['reply-to'];
+			} else {
+				$payload['ReplyTo'] = $this->getFromEmail();
 			}
 
 			if (!empty($attachments)) {
@@ -440,4 +458,179 @@
 
 			return $payload;
 		}
+
+	/**
+	 * Sends the same message to multiple emails. Can use templates
+	 * @param array<int> $userIDs
+	 * @param string $subject
+	 * @param string $message
+	 * @param bool $is_html
+	 * @param array $headers
+	 * @param array $attachments
+	 * @return array
+	 */
+	public function sendMessageToMany(array $userIDs, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+	{
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up');
+		}
+
+		$tags = [];
+		$pattern = '/\{\{(.*?)\}\}/';
+
+		if (str_contains($subject, '{{')){
+			preg_match_all($pattern, $subject, $tags);
+		}
+		if (str_contains($message, '{{')) {
+			preg_match_all($pattern, $message, $tags);
+		}
+
+		$tags = array_unique($tags);
+		foreach ($tags as $tag) {
+			$t = implode(
+				array_map(function($word) {
+					return ucfirst($word);
+				}, explode('_', $tag)
+				));
+			str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $subject);
+			str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $message);
+		}
+
+
+		$to = array_map(function ($userID) use ($tags, $subject, $message) {
+			$meta = Meta::forUser($userID);
+			$email = $meta->get('user_email');
+			$out = [];
+			if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
+				$out['To'] = $email;
+			}
+
+			if (empty($out)) {
+				$this->logError('sendMessageToMany', 'User does not have an email address',['user' => $userID]);
+				return [];
+			}
+
+			$tmp = [];
+			foreach ($tags as $tag) {
+				$v = $meta->get($tag);
+
+				if (!empty($v)) {
+					$tag = implode(
+						array_map(function($word) {
+							return ucfirst($word);
+						}, explode('_', $tag)
+						));
+					$tmp[$tag] = $v;
+				}
+			}
+			if (!empty($tmp)) {
+				$out['TemplateModel'] = $tmp;
+			}
+			return $out;
+		}, $userIDs);
+		$to = array_filter($to, function($result) {
+			return array_key_exists('To', $result) && !empty($result['To']);
+		});
+
+		if (empty($to)) {
+			$this->logError('sendMessageToMany', 'No users set',['users' => $userIDs]);
+		}
+
+		//Onward!
+		$payload = [
+			'From'	=> sprintf(
+				'%s <%s>',
+				$headers['from-name'] ?? $this->getFromName(),
+				$headers['from'] ?? $this->getFromEmail()
+			),
+			'Subject'		=> sanitize_text_field($subject),
+			'TrackOpens'	=> $this->getTrackOpens() === 'true',
+			'TrackLinks'	=> $this->getTrackLinks(),
+			'MessageStream'	=> 'broadcast'
+		];
+
+		if ($is_html) {
+			$payload['HtmlBody'] = $message;
+			$payload['TextBody'] = $this->textFromHtml($message);
+		} else {
+			$payload['TextBody'] = $message;
+		}
+
+		if (!empty($headers['reply-to'])) {
+			$payload['ReplyTo'] = $headers['reply-to'];
+		} else {
+			$payload['ReplyTo'] = $this->getFromEmail();
+		}
+		if (!empty($attachments)) {
+			$payload['Attachments'] = $this->processAttachments($attachments);
+		}
+		if (!empty($headers['x-headers'])) {
+			$payload['Headers'] = $headers['x-headers'];
+		}
+		$payload['Tag'] = $this->determineEmailTag($subject, $headers);
+
+		$payload['Messages'] = $to;
+
+		return $this->postRequest(
+			sprintf('%s/email/bulk', $this->apiBase),
+			$payload
+		);
+	}
+		protected function handleBulkEmailResponse(WP_Error|array $response, string $method):array
+		{
+			return $this->response($response['Status']??'' === 'Accepted', 'Successfully sent request to Postmark', $response);
+		}
+
+	/**
+	 * @param array $emails An array of results from buildPayload, ready to batch send to Postmark. Limit of 500 at a time
+	 * @return array
+	 */
+	public function sendMany(array $emails):array
+	{
+		if (!$this->isSetUp()) {
+			return $this->response(false, 'Not set up');
+		}
+		$emails = array_filter($emails, function ($email) {
+			if (!array_key_exists('To', $email) || !filter_var($email['To'], FILTER_VALIDATE_EMAIL)) {
+				$this->logError('sendMany', 'Email does not have a To email address', $email);
+				return false;
+			}
+			//This shouldn't happen, but if it does, we'll just remove it for now.
+			if (!array_key_exists('From', $email)) {
+				$this->logError('sendMany', 'Email does not have a From email address', $email);
+				return false;
+			}
+
+			return true;
+		});
+
+		if (empty($emails)) {
+			$this->logError('sendMany', 'No emails have been sent', $emails);
+			return $this->response(false, 'No emails have been sent', $emails);
+		}
+
+		return $this->postRequest(
+			sprintf(
+				'%s/email/batch',
+				$this->apiBase
+			),
+			$emails
+		);
+	}
+		protected function handleBatchEmailResponse(WP_Error|array $response, string $method):array
+		{
+			$main = $response[0];
+			$checkErrors = $this->checkErrors($main);
+			if (!empty($checkErrors)) {
+				return $checkErrors;
+			}
+			if (count($response) > 1) {
+				$errors = $response;
+				unset($errors[0]);
+				foreach ($errors as $error) {
+					$this->checkErrors($error);
+				}
+			}
+			return $this->response($main['Message']??'' === 'OK', 'Successfully sent request to Postmark', $response);
+		}
 }
diff --git a/inc/integrations/services/Square.php b/inc/integrations/services/Square.php
index aeb001b..8b92d3f 100644
--- a/inc/integrations/services/Square.php
+++ b/inc/integrations/services/Square.php
@@ -4,7 +4,7 @@
 use Exception;
 use JVBase\integrations\Admin;
 use JVBase\integrations\APIEndpoint;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
 use JVBase\integrations\OAuth;
 use JVBase\integrations\OAuthCredentials;
@@ -18,6 +18,7 @@
 use JVBase\managers\queue\TypeConfig;
 use JVBase\meta\Sanitizer;
 use JVBase\registrar\Registrar;
+use JVBase\ui\CRUDSkeleton;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -163,6 +164,37 @@
 		add_action(BASE.'dashboard_page_orders', [$this, 'renderDashPage']);
 	}
 
+		public function addDashboardPages():void
+		{
+			$page = Dashboard::getInstance()->addPage('Your Orders', 'orders', 'receipt');
+			$page->setScripts(['jvb-crud']);
+		}
+
+	public function renderDashPage():void
+	{
+		$this->determineUser();
+
+		$crud = new CRUDSkeleton();
+		$crud->icon('receipt');
+		$crud->title('Your Orders','Here you can see your past orders, and reorder from there if you\'d like');
+		$crud->content($this->orderPostType, 'Order', 'Orders');
+		$crud->addSearch();
+		$crud->addCapabilities(['view']);
+		$crud->setEmptyState(sprintf(
+			'<div class="empty-state">
+				<h3>%sNothing here%s</h3>
+				<p>It doesn\'t look like you have any orders yet.</p>
+				<p>Head on over to <a href="%s">our menu</a> and make your first order!</p>
+			</div>',
+			jvbDashIcon($crud->getIcon()),
+			jvbDashIcon($crud->getIcon()),
+			get_post_type_archive_link(BASE.'menu_item')
+		));
+
+
+		$crud->render();
+	}
+
 	protected function initialize(): void
 	{
 		$this->defineEndpoints();
@@ -718,7 +750,7 @@
 	********************************************************************/
 	protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
 	{
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		$queue->registry()->register(
 			self::$syncCustomer,
 			new TypeConfig(
@@ -863,7 +895,7 @@
 	{
 		return $this->loadCredentials()['location_id']??false;
 	}
-	protected function getLocations():array|false
+	public function getLocations():array|false
 	{
 		if (!$this->getAccessToken()) {
 			return false;
@@ -954,7 +986,17 @@
 		return $this->contentTypes[$content_type];
 	}
 
+	public function handleImportFromSquare():array
+	{
 
+		return $this->response(true, 'Did a thing');
+	}
+
+	public function handleSyncToSquare():array
+	{
+
+		return $this->response(true, 'Did a thing');
+	}
 
 
 }
diff --git a/inc/integrations/services/SquareOld.php b/inc/integrations/services/SquareOld.php
index d037f42..1dd7da9 100644
--- a/inc/integrations/services/SquareOld.php
+++ b/inc/integrations/services/SquareOld.php
@@ -912,7 +912,7 @@
 
 	protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
 	{
-		$queue    = JVB()->queue();
+		$queue    = Queue::getInstance();
 
 		$queue->registry()->register(self::$syncCustomer, new TypeConfig(
 			executor:   $executor,
@@ -1594,11 +1594,11 @@
 			"If you didn't create this account, please ignore this email.\n\n" .
 			"Thanks,\n",
 			$site_name,
-			JVB()->email()->button('Reset Password', $reset_url),
-			JVB()->email()->link($reset_url),
+			Emails::getInstance()->button('Reset Password', $reset_url),
+			Emails::getInstance()->link($reset_url),
 		);
 
-		JVB()->email()->sendEmail(
+		Emails::getInstance()->sendEmail(
 			$user->user_email,
 			sprintf('[%s] Welcome! Set Your Password', $site_name),
 			$message
@@ -3438,7 +3438,7 @@
 
 	public function addDashboardPages():void
 	{
-		$page = JVB()->dashboard()->addPage('Your Orders', 'orders', 'receipt');
+		$page = Dashboard::getInstance()->addPage('Your Orders', 'orders', 'receipt');
 		$page->setScripts(['jvb-crud']);
 	}
 	public function renderDashPage():void
diff --git a/inc/integrations/services/Umami.php b/inc/integrations/services/Umami.php
index ebc22df..2547322 100644
--- a/inc/integrations/services/Umami.php
+++ b/inc/integrations/services/Umami.php
@@ -1,8 +1,10 @@
 <?php
 namespace JVBase\integrations\services;
 use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
 use JVBase\integrations\Integrations;
+use JVBase\managers\Roles;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -11,8 +13,212 @@
 class Umami extends Integrations
 {
 	use Admin, Instance;
+
+	protected function __construct()
+	{
+		$this->service_name = 'umami';
+		$this->title = 'umami';
+		$this->icon = 'chart-line';
+
+		$this->defineFields();
+		$this->defineInstructions();
+
+		parent::__construct();
+		$this->defineTables();
+	}
+
+	protected function defineFields(): void
+	{
+		$id = $this->addField('website_id', 'Website ID', 'text', 'manage_options');
+		$id->setHint('Found in your Umami dashboard under Settings->Websites');
+		$id->setRequired();
+
+		$api_url = $this->addField('api_url', 'API URL', 'text', 'manage_options');
+		$api_url->setHint('Your Umami server URL. Leave blank if using umami.is cloud service.');
+
+		$api_token = $this->addField('api_token', 'API Token (Optional)', 'text', 'manage_options');
+		$api_token->setSubType('password');
+		$api_token->setHint('Required only if you want to retrieve analytics data for dashboards.');
+	}
+	protected function defineInstructions():void
+	{
+		$this->instructions = [
+			'Login to your <a href="https://cloud.umami.is/settings/websites" target="_blank">umami.is</a> account, select your website, and copy the website ID'
+		];
+	}
+
+	protected function defineTables():void
+	{
+		//TODO: Once we have an actual umami setup, we can store the fetched data here for reference
+		//Ideally, we have the type (term, user, post), WP ID, created_date, and count
+	}
+
 	protected function initialize(): void
 	{
-		// TODO: Implement initialize() method.
+		// Add tracking script to frontend
+		add_action('wp_head', [$this, 'renderTrackingScript'], 5);
+
+		// Schedule data collection if API is configured
+		if ($this->getApiToken()) {
+			add_action('jvb_daily_umami_collection', [$this, 'collectDailyData']);
+
+			if (!wp_next_scheduled('jvb_daily_umami_collection')) {
+				wp_schedule_event(time(), 'daily', 'jvb_daily_umami_collection');
+			}
+		}
 	}
+
+	public function renderTrackingScript():void
+	{
+		if (JVB_TESTING || !$this->isSetUp() || is_admin()) {
+			return;
+		}
+
+		$script_url = $this->getTrackingScriptUrl();
+		if (!$this->getWebsiteID() || empty($script_url)) {
+			return;
+		}
+
+		echo sprintf(
+			'<link rel="preconnect" href="https://%s"/>' . "\n",
+			esc_attr(parse_url($script_url, PHP_URL_HOST))
+		);
+
+		$attributes = [
+			'defer',
+			sprintf(
+				'src="%s"',
+				esc_url($script_url)
+			),
+			sprintf(
+				'data-website-id="%s"',
+				esc_attr($this->getWebsiteID())
+			)
+		];
+
+		$tags = $this->getCurrentTag();
+		if (!empty($tags)) {
+			$attributes[] = $tags;
+		}
+
+		echo sprintf(
+			'<script %s></script>',
+			implode(' ', $attributes)
+		);
+
+		$this->outputCustomEvent();
+	}
+		protected function getCurrentTag():string
+		{
+			$contentTax = array_map('jvbCheckBase', Registrar::withFeature('is_content', 'term'));
+			if (!empty($contentTax) && is_tax($contentTax)) {
+				$tax = get_queried_object();
+				$reg = Registrar::getInstance($tax->taxonomy);
+				return 'data-tag="'.$reg->getUmamiEvent().'"';
+			}
+
+			$profiles = array_map('jvbCheckBase', Registrar::withFeature('profile_link'));
+			if (!empty($profiles) && is_singular($profiles)) {
+				$usr = get_queried_object();
+				$reg = Registrar::getInstance($usr->post_type);
+				return 'data-tag="'.$reg->getUmamiEvent().'"';
+			}
+
+			return '';
+		}
+
+		protected function outputCustomEvent():void
+		{
+			$args = [];
+			$event = false;
+			$contentTax = array_map('jvbCheckBase', Registrar::withFeature('is_content', 'term'));
+			if (!empty($contentTax) && is_tax($contentTax)) {
+				$tax = get_queried_object();
+				$reg = Registrar::getInstance($tax->taxonomy);
+
+				$event = $reg->getUmamiEvent();
+				$args = [
+					'id'	=> $tax->term_id,
+				];
+				if ($reg->isOwnable()) {
+					$owners = jvbGetTermOwners($tax->term_id);
+					if (!empty($owners)) {
+						$args['owners'] = $owners;
+					}
+				}
+			}
+
+			$profiles = array_map('jvbCheckBase', Registrar::withFeature('profile_link'));
+			if (!empty($profiles) && is_singular($profiles)) {
+				$usr = get_queried_object();
+				$reg = Registrar::getInstance($usr->post_type);
+				$event = $reg->getUmamiEvent();
+
+				global $post;
+				$args['id'] = get_the_ID();
+				$args['owner_id'] = $post->post_author;
+			}
+
+			if ($event && !empty($args)) {
+				echo sprintf(
+					'<script defer>umami.track(event_name: %s, data: %s);</script>',
+					$event,
+					json_encode($args)
+				);
+			}
+		}
+
+	/*********************************************************
+	 * UTILITY
+	 *********************************************************/
+	public function getWebsiteID(): string|false
+	{
+		return $this->loadCredentials()['website_id']??false;
+	}
+	protected function getApiUrl():string|false
+	{
+		$api = $this->loadCredentials()['api_url'];
+		return empty($api) ? false : $api;
+	}
+	protected function getApiToken():string|false
+	{
+		$api = $this->loadCredentials()['api_token'];
+		return empty($api) ? false : $api;
+	}
+	protected function getTrackingScriptUrl():string
+	{
+		if ($this->getApiUrl()) {
+			return rtrim($this->getApiUrl(), '/').'/script.js';
+		}
+		// Default to Umami cloud
+		return 'https://cloud.umami.is/script.js';
+	}
+
+	public function buildEvent(string $event, array $args = []):string
+	{
+		$base = 'data-umami-event';
+		$out = [
+			$base	=> esc_attr($event),
+		];
+
+		foreach ($args as $k => $v) {
+			$out[$base.$k] = esc_attr(jvbNoBase($v));
+		}
+
+		return $this->attributesToString($out);
+	}
+
+		protected function attributesToString(array $attributes):string
+		{
+			$strings = [];
+			foreach ($attributes as $key => $value) {
+				$strings[] = sprintf(
+					'%s="%s"',
+					$key,
+					$value
+				);
+			}
+
+			return implode(' ', $strings);
+		}
 }
diff --git a/inc/integrations/services/UmamiOld.php b/inc/integrations/services/UmamiOld.php
index 5c02071..eb1df1e 100644
--- a/inc/integrations/services/UmamiOld.php
+++ b/inc/integrations/services/UmamiOld.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\integrations\services;
 use JVBase\integrations\Integrations;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -216,6 +217,7 @@
 		$event = '';
 		$type = '';
 
+
 		if (is_singular([BASE . 'artist', BASE . 'partner'])) {
 			// Profile view
 			global $post;
diff --git a/inc/integrations/services/index.php b/inc/integrations/services/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/integrations/services/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/AdminPages.php b/inc/managers/AdminPages.php
index 5878e17..4f0056c 100644
--- a/inc/managers/AdminPages.php
+++ b/inc/managers/AdminPages.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 use WP_REST_Response;
@@ -16,6 +17,7 @@
  */
 class AdminPages
 {
+	use Instance;
     protected array $subpages;
     protected array $actions;
     protected string $page_hook;
@@ -291,7 +293,7 @@
     protected function renderStatusItems():void
     {
         // Get queue stats
-        $queue_status = JVB()->queue()->getStatus();
+        $queue_status = Queue::getInstance()->getStatus();
 		error_log('Queue Status: '.print_r($queue_status, true));
 
         // Other system checks
@@ -660,7 +662,7 @@
 			}
 
 			$group = sanitize_text_field($request->get_param('group'));
-			Cache::invalidateGroup($group);
+			Cache::for($group)->flush();
 
 			return new WP_REST_Response([
 				'success' => true,
diff --git a/inc/managers/ApprovalManager.php b/inc/managers/Approvals.php
similarity index 96%
rename from inc/managers/ApprovalManager.php
rename to inc/managers/Approvals.php
index 38a4f3a..58c412f 100644
--- a/inc/managers/ApprovalManager.php
+++ b/inc/managers/Approvals.php
@@ -4,19 +4,25 @@
 
 use DateTime;
 use JVBase\base\Site;
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\ApprovalRoutes;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
 
-class ApprovalManager {
+class Approvals {
+	use Instance;
 	protected array $tables =[];
 	protected int $expiresAt = 21; //days for request
 	protected int $requiredVotes = 3; //Number of votes before a term is approved
 	protected bool $hasApproval = false;
-	public function __construct()
+	protected Rest $route;
+	private function __construct()
 	{
+		$this->route = ApprovalRoutes::getInstance();
 		$this->defineTables();
 		if (empty($this->tables)) {
 			return;
@@ -370,7 +376,7 @@
 			]);
 
 
-		JVB()->notification()->notify($request['user_id'], $type.'approved', null, 'Your suggestion "'.$request['name'].'" was denied.');
+		NotificationManager::getInstance()->notify($request['user_id'], $type.'approved', null, 'Your suggestion "'.$request['name'].'" was denied.');
 	}
 	public function finalizeDenial(int $request_id, string $type):void
 	{
@@ -387,7 +393,7 @@
 				'request_id' => $request_id
 			]);
 
-		JVB()->notification()->notify($request['user_id'], $type.'denied', null, 'Your suggestion "'.$request['name'].'" was denied.');
+		NotificationManager::getInstance()->notify($request['user_id'], $type.'denied', null, 'Your suggestion "'.$request['name'].'" was denied.');
 	}
 
 	public function getApprovalRequests(int $userID, ?string $type = null):array
diff --git a/inc/managers/CRUDManager.php b/inc/managers/CRUDManager.php
index 1fedd00..4614004 100644
--- a/inc/managers/CRUDManager.php
+++ b/inc/managers/CRUDManager.php
@@ -27,14 +27,10 @@
 		if (!$this->registrar) {
 			return;
 		}
+		$this->cache = Cache::for('crud_date_ranges','post')->user();
 
 		$this->user_id = get_current_user_id();
 		$this->content = $content;
-		$this->cache = Cache::for('crud')->connect('post')->connect('taxonomy');
-
-		if (JVB_TESTING) {
-			$this->cache->flush();
-		}
 
 		// Create and configure skeleton
 		$this->skeleton = new CRUDSkeleton();
@@ -187,7 +183,7 @@
 
 	protected function addDateRanges():array
 	{
-		return $this->cache->user()->remember(
+		return $this->cache->remember(
 			'dateRanges',
 			function() {
 				$postType = jvbCheckBase($this->content);
diff --git a/inc/managers/Cache.php b/inc/managers/Cache.php
index 2f62928..fc03cb8 100644
--- a/inc/managers/Cache.php
+++ b/inc/managers/Cache.php
@@ -9,23 +9,78 @@
 {
 	private string $group;
 	private int $ttl;
-	private static array $timestamps = [];
-	private const TS_GROUP = 'jvb_http_ts';
-
-	private const CONNECTIONS_OPTION = 'jvb_cache_connections';
-	private static ?array $connections = null;
-	protected array $tags = [];
 
 	private static array $instances = [];
 	private bool $hasRedis;
 	private bool $varyByAuth = false;
 	private bool $varyByUser = false;
 
-	private function __construct(string $group, int $ttl)
+	private array $index=[];
+	private ?string $target = null;
+
+	private function __construct(string $group, ?string $target = null, int $ttl = WEEK_IN_SECONDS)
 	{
+		if (!is_null($target) && empty($this->index)) {
+			$this->target = null;
+			$this->index = get_option(BASE."cache_{$target}_index",[]);
+		}
 		$this->group = $group;
 		$this->ttl   = $ttl;
-		$this->hasRedis = (bool) wp_using_ext_object_cache();
+		$this->logGroup();
+		$this->hasRedis = (bool)wp_using_ext_object_cache();
+	}
+
+	protected function logGroup():void
+	{
+		$changed = false;
+		$groups = get_option(BASE."cache_groups", []);
+		if (!array_key_exists($this->group, $groups)) {
+			$groups[$this->group] = [
+				'auth'	=> $this->varyByAuth,
+				'user'	=> $this->varyByUser,
+				'target'=> $this->target,
+				'ttl'	=> $this->ttl,
+			];
+			$changed = true;
+		}
+
+		if ($groups[$this->group]['auth'] && !$this->varyByAuth) {
+			error_log('[Cache]Group '.$this->group.' previously defined as varying by auth, but is not set. Setting it now.');
+			$this->auth();
+		}
+		if ($groups[$this->group]['user'] && !$this->varyByUser) {
+			error_log('[Cache]Group '.$this->group.' previously defined as varying by user, but is not set. Setting it now.');
+			$this->user();
+		}
+		if (is_null($this->target) && !is_null($groups[$this->group]['target'])) {
+			error_log('[Cache]Group '.$this->group. ' previously defined with target '.$groups[$this->group]['target'].'... Setting it now.');
+			$this->target = $groups[$this->group]['target'];
+		} elseif ($this->target !== $groups[$this->group]['target']) {
+			error_log('[Cache]WARNING Group previously defined with target '.$groups[$this->group]['target'].' - but is currently defined with '.$this->target. '. You may want to look into this.');
+		}
+
+		if ($this->ttl !== $groups[$this->group]['ttl']) {
+			error_log('[Cache]Group '. $this->group. ' previously defined with ttl '.$groups[$this->group]['ttl']. ' - but is currently defined with '. $this->ttl);
+			$groups[$this->group]['ttl'] = $this->ttl;
+			$changed = true;
+		}
+
+		if ($this->varyByAuth && !$groups[$this->group]['auth']) {
+			$groups[$this->group]['auth'] = true;
+			$changed = true;
+		}
+		if ($this->varyByUser && !$groups[$this->group]['user']) {
+			$groups[$this->group]['user'] = true;
+			$changed = true;
+		}
+		if ($this->target !== $groups[$this->group]['target']) {
+			$groups[$this->group]['target'] = $this->target;
+			$changed = true;
+		}
+
+		if ($changed) {
+			update_option(BASE."cache_groups", $groups, false);
+		}
 	}
 
 	public static function registerHooks(): void
@@ -45,9 +100,9 @@
 		add_action('delete_term', [self::class, 'onTermDelete'], 10, 3);
 
 		// Term meta updates
-		add_action('updated_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
-		add_action('added_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
-		add_action('deleted_term_meta', [self::class, 'onTermMetaDelete'], 10, 2);
+//		add_action('updated_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
+//		add_action('added_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
+//		add_action('deleted_term_meta', [self::class, 'onTermMetaDelete'], 10, 2);
 
 		// User updates
 		add_action('profile_update', [self::class, 'onUserChange'], 10, 2);
@@ -55,9 +110,9 @@
 		add_action('deleted_user', [self::class, 'onUserDelete']);
 
 		// User meta updates
-		add_action('updated_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
-		add_action('added_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
-		add_action('deleted_user_meta', [self::class, 'onUserMetaDelete'], 10, 2);
+//		add_action('updated_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
+//		add_action('added_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
+//		add_action('deleted_user_meta', [self::class, 'onUserMetaDelete'], 10, 2);
 	}
 
 	public function auth(): static
@@ -83,41 +138,48 @@
 		return (string) $key;
 	}
 
-	/* ---------------------------------------------------------------------
-	 * Factory
-	 * ------------------------------------------------------------------- */
-
-	public static function for(string $group, int $ttl = HOUR_IN_SECONDS): self
+	public static function for(string $group, ?string $target = null, int $ttl = HOUR_IN_SECONDS): self
 	{
 		$group = sanitize_key($group);
 
 		if (!isset(self::$instances[$group])) {
-			self::$instances[$group] = new self($group, $ttl);
+			self::$instances[$group] = new self($group, $target, $ttl);
 		}
 
 		return self::$instances[$group];
 	}
 
+	/**
+	 * To help facilitate cache flushing on object updates, we track the keys that are used for a particular id
+	 * Generally, $this->target will either be 'post', 'user', 'term', or 'custom'
+	 * @param int $ID
+	 * @param string $key
+	 * @return void
+	 */
+	protected function trackID(int $ID, string $key):void
+	{
+		if (is_null($this->target)) {
+			return;
+		}
+		if (!array_key_exists($ID, $this->index[$this->target])) {
+			$this->index[$ID] = [];
+		}
+		if (!array_key_exists($this->group, $this->index[$ID])) {
+			$this->index[$ID][$this->group] = [];
+		}
+		if (!in_array($key, $this->index[$ID][$this->group])) {
+			$this->index[$this->target][$ID][$this->group][] = $key;
+			update_option(BASE."cache_{$this->target}_index", $this->index, false);
+		}
+	}
+
 	/* ---------------------------------------------------------------------
 	 * Core operations
 	 * ------------------------------------------------------------------- */
 
 	public function remember(int|string|array $key, callable $callback, ?int $ttl = null): mixed
 	{
-		//TODO: Redis is fucking this all up for some reason. While we figure it out, let's just return the callback
-		return $callback();
-
-		if (is_array($key)) {
-			$key = $this->generateKey($key);
-		}
-		if (!empty($this->tags)) {
-			return $this->rememberTagged(
-				$key,
-				$this->tags,
-				$callback,
-				$ttl
-			);
-		}
+		$key = $this->generateKey($key);
 
 		$value = $this->get($key);
 
@@ -133,44 +195,55 @@
 
 		return $value;
 	}
-
-	public function get(int|string|array $id): mixed
+	protected function transientKey(string $key):string
 	{
-		if (is_array($id)) {
-			$id = $this->generateKey($id);
-		}
-		if ($this->hasRedis) {
-			$value = wp_cache_get($id, $this->group);
-		} else {
-			$value = get_transient("jvb_{$this->group}_{$id}");
-		}
-
-		return $value;
+		return BASE."{$this->group}_{$key}";
 	}
 
-	public function set(int|string|array $id, mixed $value, ?int $ttl = null): void
+	public function get(int|string|array $key): mixed
 	{
-		if (is_array($id)) {
-			$id = $this->generateKey($id);
-		}
-		$ttl = $ttl ?? $this->ttl;
+		$key = $this->generateKey($key, false);
+
+		$value = $this->hasRedis ?
+			wp_cache_get($key, $this->group) :
+			get_transient($this->transientKey($key));
+
+		return $value['value']??false;
+	}
+	public function getTimestamp(int|string|array $key): mixed
+	{
+		$key = $this->generateKey($key, false);
+
+		$value = $this->hasRedis ?
+			wp_cache_get($key, $this->group) :
+			get_transient($this->transientKey($key));
+		return $value['modified']??false;
+	}
+
+	public function set(int|string|array $key, mixed $value, ?int $ttl = null): void
+	{
+		$key = $this->generateKey($key);
+		$ttl = $ttl??$this->ttl;
+		$store = [
+			'value'	=> $value,
+			'modified'	=> time()
+		];
 		if ($this->hasRedis) {
-			wp_cache_set($id, $value, $this->group, $ttl);
+			wp_cache_set($key, $store, $this->group, $ttl);
 		} else {
-			set_transient("jvb_{$this->group}_{$id}", $value, $ttl);
+			set_transient($this->transientKey($key), $store, $ttl);
 		}
 	}
 
-	public function forget(int|string|array $id): void
+	public function forget(int|string|array $key): void
 	{
-		if (is_array($id)) {
-			$id = $this->generateKey($id);
-		}
+		$key = $this->generateKey($key);
 		if ($this->hasRedis) {
-			wp_cache_delete($id, $this->group);
+			wp_cache_delete($key, $this->group);
 		} else {
-			delete_transient("jvb_{$this->group}_{$id}");
+			delete_transient($this->transientKey($key));
 		}
+
 	}
 
 	public function flush(): void
@@ -186,266 +259,53 @@
 		}
 	}
 
-	/* ---------------------------------------------------------------------
-	 * Invalidation
-	 * ------------------------------------------------------------------- */
-
-	public static function invalidateItem(string $group, int|string|array $id): void
+	public static function clearPost(int $ID):void
 	{
-		if (is_array($id)) {
-			$id = self::for($group)->generateKey($id);
+		$index = get_option(BASE."cache_post_index", []);
+		if (!array_key_exists($ID, $index)) {
+			return;
 		}
-		$group = sanitize_key($group);
-
-		if (wp_using_ext_object_cache()) {
-			wp_cache_delete($id, $group);
-		} else {
-			delete_transient("jvb_{$group}_{$id}");
-		}
-		self::touch($group);
-
-		foreach (self::connections()[$group] ?? [] as $conn) {
-			$target = $conn['target'] ?? $conn; // Backwards compat if still string
-			$flush = $conn['flush'] ?? false;
-
-			if ($flush) {
-				// Flush entire target group
-				self::invalidateGroup($target);
-			} else {
-				// Just delete this item ID
-				if (wp_using_ext_object_cache()) {
-					wp_cache_delete($id, $target);
-				} else {
-					delete_transient("jvb_{$target}_{$id}");
-				}
-				self::touch($target);
+		foreach ($index[$ID] as $group => $keys) {
+			$cache = self::for($group, 'post');
+			foreach ($keys as $key) {
+				$cache->forget($key);
 			}
 		}
-		self::invalidateByTag($group, $id);
 	}
+	public static function clearUser(int $ID):void
+	{
+		$index = get_option(BASE."cache_user_index", []);
+		if (!array_key_exists($ID, $index)) {
+			return;
+		}
+		foreach ($index[$ID] as $group => $keys) {
+			$cache = self::for($group, 'user');
+			foreach ($keys as $key) {
+				$cache->forget($key);
+			}
+		}
+	}
+	public static function clearTerm(int $ID):void
+	{
+		$index = get_option(BASE."cache_term_index", []);
+		if (!array_key_exists($ID, $index)) {
+			return;
+		}
+		foreach ($index[$ID] as $group => $keys) {
+			$cache = self::for($group, 'term');
+			foreach ($keys as $key) {
+				$cache->forget($key);
+			}
+		}
+	}
+
 
 	public static function invalidateGroup(string $group): void
 	{
-		$group = sanitize_key($group);
-
-		if (wp_using_ext_object_cache()) {
-			wp_cache_flush_group($group);
-		} else {
-			$instance = self::for($group);
-			$instance->clearGroupTransients();
-		}
-
-		self::touch($group);
-
-		foreach (self::connections()[$group] ?? [] as $conn) {
-			$target = $conn['target'] ?? $conn; // Backwards compat
-
-			// When flushing entire source group, always flush connected targets
-			// (regardless of flush flag - we don't know which items to delete)
-			if (wp_using_ext_object_cache()) {
-				wp_cache_flush_group($target);
-			} else {
-				$instance = self::for($target);
-				$instance->clearGroupTransients();
-			}
-			self::touch($target);
-		}
-	}
-
-	public static function touch(string $group): int
-	{
-		$group = sanitize_key($group);
-		$time  = time();
-
-		if (wp_using_ext_object_cache()) {
-			wp_cache_set($group, $time, self::TS_GROUP, WEEK_IN_SECONDS);
-		} else {
-			set_transient('jvb_ts_' . $group, $time, WEEK_IN_SECONDS);
-		}
-
-		self::$timestamps[$group] = $time;
-		return $time;
-	}
-
-	public static function lastModified(string|array $groups): int
-	{
-		if (is_array($groups)) {
-			return max(array_map([self::class, 'lastModified'], $groups));
-		}
-
-		$group = sanitize_key($groups);
-
-		if (isset(self::$timestamps[$group])) {
-			return self::$timestamps[$group];
-		}
-
-		if (wp_using_ext_object_cache()) {
-			$ts = (int) wp_cache_get($group, self::TS_GROUP);
-		} else {
-			$ts = (int) get_transient('jvb_ts_' . $group);
-		}
-
-		if (!$ts) {
-			$ts = time();
-			if (wp_using_ext_object_cache()) {
-				wp_cache_set($group, $ts, self::TS_GROUP, WEEK_IN_SECONDS);
-			} else {
-				set_transient('jvb_ts_' . $group, $ts, WEEK_IN_SECONDS);
-			}
-		}
-
-		return self::$timestamps[$group] = $ts;
-	}
-
-	public function getLastModifiedForTags(array $tags): ?int
-	{
-		if (!$this->hasRedis) {
-			return null;
-		}
-		$redis = self::redis();
-		if (!$redis) {
-			return null;
-		}
-
-		$lastModified = 0;
-
-		foreach ($tags as $tag) {
-			$ts = $redis->get("jvb:tag:{$tag}:lastModified");
-			if ($ts) {
-				$lastModified = max($lastModified, (int) $ts);
-			}
-		}
-
-		return $lastModified ?: null;
-	}
-
-	/****************************************************
-	 * CONNECTIONS
-	 ****************************************************/
-	private static function connections(): array
-	{
-		if (self::$connections === null) {
-			self::$connections = get_option(self::CONNECTIONS_OPTION, []);
-		}
-		return self::$connections;
+		self::for($group)->flush();
 	}
 
 
-	public function connect(string $source, bool $flush = false): self
-	{
-		$source = sanitize_key($source);
-		$target = $this->group;
-
-		$all = self::connections();
-		$all[$source] ??= [];
-
-		$before = count($all[$source]);
-
-		// Add the connection
-		$all[$source][] = ['target' => $target, 'flush' => $flush];
-
-		// Remove duplicates by serializing for comparison
-		$all[$source] = array_values(array_unique($all[$source], SORT_REGULAR));
-
-		// Only update if something actually changed
-		if (count($all[$source]) !== $before) {
-			update_option(self::CONNECTIONS_OPTION, $all, false);
-			self::$connections = $all;
-		}
-
-		return $this;
-	}
-	/****************************************************
-	 * REDIS
-	 ****************************************************/
-	private static function redis(): ?\Redis
-	{
-		global $wp_object_cache;
-
-		return $wp_object_cache instanceof \WP_Object_Cache
-		&& isset($wp_object_cache->redis)
-			? $wp_object_cache->redis
-			: null;
-	}
-
-	/**
-	 * Remember with tags for complex invalidation scenarios
-	 *
-	 * Example: Cache user favorites tagged by each post ID
-	 * When any post updates, this cache entry auto-invalidates
-	 *
-	 * @param int|string|array $key Cache key
-	 * @param array $tags Array of [group, id] pairs: [['post', 123], ['user', 456]]
-	 * @param callable $callback Function to generate value if cache miss
-	 * @return mixed Cached or generated value
-	 */
-	public function rememberTagged(
-		int|string|array $key,
-		array $tags,
-		callable $callback,
-		?int $ttl = null
-	): mixed {
-		if (is_array($key)) {
-			$key = $this->generateKey($key);
-		}
-		$instanceTags = array_map(fn($tag) => [$this->group, $tag], $this->getTags());
-		$tags = array_unique(array_merge(
-			$instanceTags,
-			array_map(fn($tag) => [sanitize_key($tag[0]), $tag[1]], $tags)
-		), SORT_REGULAR);
-
-
-		$value = wp_cache_get($key, $this->group);
-		if ($value !== false) {
-			return $value;
-		}
-
-		$value = $callback();
-		if ($value === null || $value === false) {
-			return $value;
-		}
-
-		wp_cache_set($key, $value, $this->group, $this->ttl);
-
-		if ($redis = self::redis()) {
-			foreach ($tags as [$tagGroup, $tagId]) {
-				$redis->sAdd("tag:$tagGroup:$tagId", "{$this->group}:$key");
-			}
-		}
-
-		return $value;
-	}
-
-	private static function invalidateByTag(string $group, int|string|array $id): void
-	{
-		if (is_array($id)) {
-			$id = self::for($group)->generateKey($id);
-		}
-		if (!$redis = self::redis()) {
-			return;
-		}
-
-		$key = "tag:$group:$id";
-		$targets = $redis->sMembers($key);
-
-		foreach ($targets as $target) {
-			[$group, $id] = explode(':', $target, 2);
-			wp_cache_delete($id, $group);
-		}
-
-		$redis->del($key);
-	}
-
-	public function tag(string $tag): static
-	{
-		$this->tags[] = sanitize_key($tag);
-		return $this;
-	}
-
-	public function getTags(): array
-	{
-		return array_unique($this->tags);
-	}
 	/****************************************************
 	 * TRANSIENT HELPER
 	 ****************************************************/
@@ -453,8 +313,8 @@
 	{
 		global $wpdb;
 
-		$pattern = '_transient_jvb_' . $this->group . '_%';
-		$timeout_pattern = '_transient_timeout_jvb_' . $this->group . '_%';
+		$pattern = '_transient_' .BASE. $this->group . '%';
+		$timeout_pattern = '_transient_timeout_' . BASE . $this->group . '%';
 
 		// Remove LIMIT to avoid table locks, add retry for deadlocks
 		$attempts = 0;
@@ -494,13 +354,13 @@
 		}
 
 //		error_log('[Clearing cache for post change: '.$postId.']');
-		self::invalidateItem('post', $postId);
+		self::clearPost($postId);
 	}
 
 	public static function onPostDelete(int $postId): void
 	{
 //		error_log('[Clearing cache for post delete: '.$postId.']');
-		self::invalidateItem('post', $postId);
+		self::clearPost($postId);
 	}
 
 //	public static function onPostMetaChange(int $metaId, int $objectId): void
@@ -521,25 +381,25 @@
 	public static function onTermChange(int $termId, int $ttId, string $taxonomy): void
 	{
 		error_log('[Clearing cache for term change: '.$termId.']');
-		self::invalidateItem('taxonomy', $termId);
+		self::clearTerm($termId);
 	}
 
 	public static function onTermDelete(int $termId): void
 	{
 //		error_log('[Clearing cache for term delete: '.$termId.']');
-		self::invalidateItem('taxonomy', $termId);
+		self::clearTerm($termId);
 	}
 
 	public static function onTermMetaChange(int $metaId, int $objectId): void
 	{
 //		error_log('[Clearing cache for term meta change: '.$objectId.']');
-		self::invalidateItem('taxonomy', $objectId);
+		self::clearTerm($objectId);
 	}
 
 	public static function onTermMetaDelete(array $metaIds, int $objectId): void
 	{
 //		error_log('[Clearing cache for term meta delete: '.$objectId.']');
-		self::invalidateItem('taxonomy', $objectId);
+		self::clearTerm($objectId);
 	}
 
 	/****************************************************
@@ -548,25 +408,25 @@
 	public static function onUserChange(int $userId): void
 	{
 //		error_log('[Clearing cache for user change: '.$userId.']');
-		self::invalidateItem('user', $userId);
+		self::clearUser($userId);
 	}
 
 	public static function onUserDelete(int $userId): void
 	{
 //		error_log('[Clearing cache for user delete: '.$userId.']');
-		self::invalidateItem('user', $userId);
+		self::clearUser($userId);
 	}
 
 	public static function onUserMetaChange(int $metaId, int $objectId): void
 	{
 //		error_log('[Clearing cache for user meta change: '.$objectId.']');
-		self::invalidateItem('user', $objectId);
+		self::clearUser($objectId);
 	}
 
 	public static function onUserMetaDelete(array $metaIds, int $objectId): void
 	{
 //		error_log('[Clearing cache for user meta delete: '.$objectId.']');
-		self::invalidateItem('user', $objectId);
+		self::clearUser($objectId);
 	}
 	/***************************************************
 	 * UTILITY
@@ -575,13 +435,33 @@
 	 * Generate a cache key from parameters
 	 * Useful for caching based on multiple variables
 	 *
-	 * @param array $params Key-value pairs that uniquely identify this cache entry
+	 * @param int|string|array $key Key-value pairs that uniquely identify this cache entry
 	 * @return string MD5 hash of sorted parameters
 	 */
-	public function generateKey(array $params): string
+	public function generateKey(int|string|array $key, bool $trackIt = true): string
 	{
-		ksort($params);
-		return md5(serialize($params));
+		$trackID = false;
+		$ID = false;
+
+		if (!is_null($this->target) && is_int($key)) {
+			//Likely an item id. Store it for reference
+			$trackID = true;
+			$ID = $key;
+		}
+		if (is_array($key)) {
+			ksort($key);
+			if (!is_null($this->target) && array_key_exists('id', $key)) {
+				$trackID = true;
+				$ID = $key['id'];
+			}
+			$key = md5(serialize($key));
+		}
+
+		$key = $this->resolveKey($key);
+		if ($trackIt && !is_null($this->target) && $ID && $trackID) {
+			$this->trackID($ID, $key);
+		}
+		return $key;
 	}
 
 	/**
@@ -592,90 +472,25 @@
 	 */
 	public static function flushAll(): int
 	{
-		$all = self::connections();
-		$groups = [];
-
-		// Collect all unique groups from connections
-		foreach ($all as $source => $targets) {
-			$groups[$source] = true;
-			foreach ($targets as $conn) {
-				$target = $conn['target'] ?? $conn;
-				$groups[$target] = true;
-			}
-		}
-
-		// Add any instantiated groups not in connections
-		foreach (array_keys(self::$instances) as $group) {
-			$groups[$group] = true;
-		}
-
-		// Flush each group
+		$groups = get_option(BASE."cache_groups", []);
 		$count = 0;
-		foreach (array_keys($groups) as $group) {
-			self::invalidateGroup($group);
-			$count++;
+		foreach ($groups as $group => $conf) {
+			$cache = Cache::for($group, $conf['target']??null, $conf['ttl']);
+			$cache->flush();
+			$count ++ ;
+
+			usleep(50000); // Wait 50ms before retry
 		}
-
-		// Also flush timestamp cache
-		if (wp_using_ext_object_cache()) {
-			wp_cache_flush_group(self::TS_GROUP);
-		}
-
-		// Clear in-memory caches
-		self::$timestamps = [];
-
 		return $count;
 	}
 
 	/**
-	 * Get all cache groups and their connections for admin display
+	 * Get all cache groups
 	 *
-	 * @return array Format: ['group' => ['connects_to' => [...], 'connected_from' => [...]]]
+	 * @return array
 	 */
 	public static function getAllGroups(): array
 	{
-		$connections = self::connections();
-		$groups = [];
-
-		// Build bidirectional view
-		foreach ($connections as $source => $targets) {
-			if (!isset($groups[$source])) {
-				$groups[$source] = ['connects_to' => [], 'connected_from' => []];
-			}
-
-			foreach ($targets as $conn) {
-				$target = $conn['target'] ?? $conn;
-				$flush = $conn['flush'] ?? false;
-
-				// Source connects to target
-				$groups[$source]['connects_to'][] = [
-					'group' => $target,
-					'flush' => $flush
-				];
-
-				// Target is connected from source
-				if (!isset($groups[$target])) {
-					$groups[$target] = ['connects_to' => [], 'connected_from' => []];
-				}
-				$groups[$target]['connected_from'][] = [
-					'group' => $source,
-					'flush' => $flush
-				];
-			}
-		}
-
-		// Add any instantiated groups not in connections
-		foreach (array_keys(self::$instances) as $group) {
-			if (!isset($groups[$group])) {
-				$groups[$group] = ['connects_to' => [], 'connected_from' => []];
-			}
-		}
-
-		return $groups;
-	}
-
-	public function hasRedis():bool
-	{
-		return $this->hasRedis;
+		return get_option(BASE."cache_groups", []);
 	}
 }
diff --git a/inc/managers/CacheBack.php b/inc/managers/CacheBack.php
new file mode 100644
index 0000000..2bf98e7
--- /dev/null
+++ b/inc/managers/CacheBack.php
@@ -0,0 +1,684 @@
+<?php
+namespace JVBase\managers;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class CacheBack
+{
+	private string $group;
+	private int $ttl;
+	private static array $timestamps = [];
+	private const TS_GROUP = 'jvb_http_ts';
+
+	private const CONNECTIONS_OPTION = 'jvb_cache_connections';
+	private static ?array $connections = null;
+	protected array $tags = [];
+
+	private static array $instances = [];
+	private bool $hasRedis = false;
+	private bool $varyByAuth = false;
+	private bool $varyByUser = false;
+
+	private function __construct(string $group, int $ttl)
+	{
+		return;
+		$this->group = $group;
+		$this->ttl   = $ttl;
+		$this->hasRedis = (bool) wp_using_ext_object_cache();
+	}
+
+	public static function registerHooks(): void
+	{
+		// Post updates (all post types including core)
+		add_action('save_post', [self::class, 'onPostChange'], 10, 2);
+		add_action('delete_post', [self::class, 'onPostDelete']);
+
+		// Post meta updates, now handled via Meta.php?
+//		add_action('updated_post_meta', [self::class, 'onPostMetaChange'], 10, 2);
+//		add_action('added_post_meta', [self::class, 'onPostMetaChange'], 10, 2);
+//		add_action('deleted_post_meta', [self::class, 'onPostMetaDelete'], 10, 2);
+
+		// Term updates (all taxonomies)
+		add_action('edited_term', [self::class, 'onTermChange'], 10, 3);
+		add_action('create_term', [self::class, 'onTermChange'], 10, 3);
+		add_action('delete_term', [self::class, 'onTermDelete'], 10, 3);
+
+		// Term meta updates
+		add_action('updated_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
+		add_action('added_term_meta', [self::class, 'onTermMetaChange'], 10, 2);
+		add_action('deleted_term_meta', [self::class, 'onTermMetaDelete'], 10, 2);
+
+		// User updates
+		add_action('profile_update', [self::class, 'onUserChange'], 10, 2);
+		add_action('user_register', [self::class, 'onUserChange'], 10, 1);
+		add_action('deleted_user', [self::class, 'onUserDelete']);
+
+		// User meta updates
+		add_action('updated_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
+		add_action('added_user_meta', [self::class, 'onUserMetaChange'], 10, 2);
+		add_action('deleted_user_meta', [self::class, 'onUserMetaDelete'], 10, 2);
+	}
+
+	public function auth(): static
+	{
+		$this->varyByAuth = true;
+		return $this;
+	}
+
+	public function user(): static
+	{
+		$this->varyByUser = true;
+		return $this;
+	}
+
+	private function resolveKey(int|string $key): string
+	{
+		if ($this->varyByUser) {
+			return $key . ':u' . get_current_user_id();
+		}
+		if ($this->varyByAuth) {
+			return $key . ':' . (is_user_logged_in() ? 'auth' : 'guest');
+		}
+		return (string) $key;
+	}
+
+	/* ---------------------------------------------------------------------
+	 * Factory
+	 * ------------------------------------------------------------------- */
+
+	public static function for(string $group, int $ttl = HOUR_IN_SECONDS): self
+	{
+		$group = sanitize_key($group);
+
+		if (!isset(self::$instances[$group])) {
+			self::$instances[$group] = new self($group, $ttl);
+		}
+
+		return self::$instances[$group];
+	}
+
+	/* ---------------------------------------------------------------------
+	 * Core operations
+	 * ------------------------------------------------------------------- */
+
+	public function remember(int|string|array $key, callable $callback, ?int $ttl = null): mixed
+	{
+		//TODO: Redis is fucking this all up for some reason. While we figure it out, let's just return the callback
+		return $callback();
+
+		if (is_array($key)) {
+			$key = $this->generateKey($key);
+		}
+		if (!empty($this->tags)) {
+			return $this->rememberTagged(
+				$key,
+				$this->tags,
+				$callback,
+				$ttl
+			);
+		}
+
+		$value = $this->get($key);
+
+		if ($value !== false) {
+			return $value;
+		}
+
+		$value = $callback();
+
+		if ($value !== null && $value !== false) {
+			$this->set($key, $value);
+		}
+
+		return $value;
+	}
+
+	public function get(int|string|array $id): mixed
+	{
+		return false;
+		if (is_array($id)) {
+			$id = $this->generateKey($id);
+		}
+		if ($this->hasRedis) {
+			$value = wp_cache_get($id, $this->group);
+		} else {
+			$value = get_transient("jvb_{$this->group}_{$id}");
+		}
+
+		return $value;
+	}
+
+	public function set(int|string|array $id, mixed $value, ?int $ttl = null): void
+	{
+		return;
+		if (is_array($id)) {
+			$id = $this->generateKey($id);
+		}
+		$ttl = $ttl ?? $this->ttl;
+		if ($this->hasRedis) {
+			wp_cache_set($id, $value, $this->group, $ttl);
+		} else {
+			set_transient("jvb_{$this->group}_{$id}", $value, $ttl);
+		}
+	}
+
+	public function forget(int|string|array $id): void
+	{
+		if (is_array($id)) {
+			$id = $this->generateKey($id);
+		}
+		if ($this->hasRedis) {
+			wp_cache_delete($id, $this->group);
+		} else {
+			delete_transient("jvb_{$this->group}_{$id}");
+		}
+	}
+
+	public function flush(): void
+	{
+		if ($this->hasRedis) {
+			if (function_exists('wp_cache_flush_group')) {
+				wp_cache_flush_group($this->group);
+			} else {
+				wp_cache_flush();
+			}
+		} else {
+			$this->clearGroupTransients();
+		}
+	}
+
+	/* ---------------------------------------------------------------------
+	 * Invalidation
+	 * ------------------------------------------------------------------- */
+
+	public static function invalidateItem(string $group, int|string|array $id): void
+	{
+		if (is_array($id)) {
+			$id = self::for($group)->generateKey($id);
+		}
+		$group = sanitize_key($group);
+
+		if (wp_using_ext_object_cache()) {
+			wp_cache_delete($id, $group);
+		} else {
+			delete_transient("jvb_{$group}_{$id}");
+		}
+		self::touch($group);
+
+		foreach (self::connections()[$group] ?? [] as $conn) {
+			$target = $conn['target'] ?? $conn; // Backwards compat if still string
+			$flush = $conn['flush'] ?? false;
+
+			if ($flush) {
+				// Flush entire target group
+				self::invalidateGroup($target);
+			} else {
+				// Just delete this item ID
+				if (wp_using_ext_object_cache()) {
+					wp_cache_delete($id, $target);
+				} else {
+					delete_transient("jvb_{$target}_{$id}");
+				}
+				self::touch($target);
+			}
+		}
+		self::invalidateByTag($group, $id);
+	}
+
+	public static function invalidateGroup(string $group): void
+	{
+		$group = sanitize_key($group);
+
+		if (wp_using_ext_object_cache()) {
+			wp_cache_flush_group($group);
+		} else {
+			$instance = self::for($group);
+			$instance->clearGroupTransients();
+		}
+
+		self::touch($group);
+
+		foreach (self::connections()[$group] ?? [] as $conn) {
+			$target = $conn['target'] ?? $conn; // Backwards compat
+
+			// When flushing entire source group, always flush connected targets
+			// (regardless of flush flag - we don't know which items to delete)
+			if (wp_using_ext_object_cache()) {
+				wp_cache_flush_group($target);
+			} else {
+				$instance = self::for($target);
+				$instance->clearGroupTransients();
+			}
+			self::touch($target);
+		}
+	}
+
+	public static function touch(string $group): int
+	{
+		$group = sanitize_key($group);
+		$time  = time();
+
+		if (wp_using_ext_object_cache()) {
+			wp_cache_set($group, $time, self::TS_GROUP, WEEK_IN_SECONDS);
+		} else {
+			set_transient('jvb_ts_' . $group, $time, WEEK_IN_SECONDS);
+		}
+
+		self::$timestamps[$group] = $time;
+		return $time;
+	}
+
+	public static function lastModified(string|array $groups): int
+	{
+		if (is_array($groups)) {
+			return max(array_map([self::class, 'lastModified'], $groups));
+		}
+
+		$group = sanitize_key($groups);
+
+		if (isset(self::$timestamps[$group])) {
+			return self::$timestamps[$group];
+		}
+
+		if (wp_using_ext_object_cache()) {
+			$ts = (int) wp_cache_get($group, self::TS_GROUP);
+		} else {
+			$ts = (int) get_transient('jvb_ts_' . $group);
+		}
+
+		if (!$ts) {
+			$ts = time();
+			if (wp_using_ext_object_cache()) {
+				wp_cache_set($group, $ts, self::TS_GROUP, WEEK_IN_SECONDS);
+			} else {
+				set_transient('jvb_ts_' . $group, $ts, WEEK_IN_SECONDS);
+			}
+		}
+
+		return self::$timestamps[$group] = $ts;
+	}
+
+	public function getLastModifiedForTags(array $tags): ?int
+	{
+		if (!$this->hasRedis) {
+			return null;
+		}
+		$redis = self::redis();
+		if (!$redis) {
+			return null;
+		}
+
+		$lastModified = 0;
+
+		foreach ($tags as $tag) {
+			$ts = $redis->get("jvb:tag:{$tag}:lastModified");
+			if ($ts) {
+				$lastModified = max($lastModified, (int) $ts);
+			}
+		}
+
+		return $lastModified ?: null;
+	}
+
+	/****************************************************
+	 * CONNECTIONS
+	 ****************************************************/
+	private static function connections(): array
+	{
+		if (self::$connections === null) {
+			self::$connections = get_option(self::CONNECTIONS_OPTION, []);
+		}
+		return self::$connections;
+	}
+
+
+	public function connect(string $source, bool $flush = false): self
+	{
+		$source = sanitize_key($source);
+		$target = $this->group;
+
+		$all = self::connections();
+		$all[$source] ??= [];
+
+		$before = count($all[$source]);
+
+		// Add the connection
+		$all[$source][] = ['target' => $target, 'flush' => $flush];
+
+		// Remove duplicates by serializing for comparison
+		$all[$source] = array_values(array_unique($all[$source], SORT_REGULAR));
+
+		// Only update if something actually changed
+		if (count($all[$source]) !== $before) {
+			update_option(self::CONNECTIONS_OPTION, $all, false);
+			self::$connections = $all;
+		}
+
+		return $this;
+	}
+	/****************************************************
+	 * REDIS
+	 ****************************************************/
+	private static function redis(): ?\Redis
+	{
+		global $wp_object_cache;
+
+		return $wp_object_cache instanceof \WP_Object_Cache
+		&& isset($wp_object_cache->redis)
+			? $wp_object_cache->redis
+			: null;
+	}
+
+	/**
+	 * Remember with tags for complex invalidation scenarios
+	 *
+	 * Example: Cache user favorites tagged by each post ID
+	 * When any post updates, this cache entry auto-invalidates
+	 *
+	 * @param int|string|array $key Cache key
+	 * @param array $tags Array of [group, id] pairs: [['post', 123], ['user', 456]]
+	 * @param callable $callback Function to generate value if cache miss
+	 * @return mixed Cached or generated value
+	 */
+	public function rememberTagged(
+		int|string|array $key,
+		array $tags,
+		callable $callback,
+		?int $ttl = null
+	): mixed {
+		if (is_array($key)) {
+			$key = $this->generateKey($key);
+		}
+		$instanceTags = array_map(fn($tag) => [$this->group, $tag], $this->getTags());
+		$tags = array_unique(array_merge(
+			$instanceTags,
+			array_map(fn($tag) => [sanitize_key($tag[0]), $tag[1]], $tags)
+		), SORT_REGULAR);
+
+
+		$value = wp_cache_get($key, $this->group);
+		if ($value !== false) {
+			return $value;
+		}
+
+		$value = $callback();
+		if ($value === null || $value === false) {
+			return $value;
+		}
+
+		wp_cache_set($key, $value, $this->group, $this->ttl);
+
+		if ($redis = self::redis()) {
+			foreach ($tags as [$tagGroup, $tagId]) {
+				$redis->sAdd("tag:$tagGroup:$tagId", "{$this->group}:$key");
+			}
+		}
+
+		return $value;
+	}
+
+	private static function invalidateByTag(string $group, int|string|array $id): void
+	{
+		if (is_array($id)) {
+			$id = self::for($group)->generateKey($id);
+		}
+		if (!$redis = self::redis()) {
+			return;
+		}
+
+		$key = "tag:$group:$id";
+		$targets = $redis->sMembers($key);
+
+		foreach ($targets as $target) {
+			[$group, $id] = explode(':', $target, 2);
+			wp_cache_delete($id, $group);
+		}
+
+		$redis->del($key);
+	}
+
+	public function tag(string $tag): static
+	{
+		$this->tags[] = sanitize_key($tag);
+		return $this;
+	}
+
+	public function getTags(): array
+	{
+		return array_unique($this->tags);
+	}
+	/****************************************************
+	 * TRANSIENT HELPER
+	 ****************************************************/
+	private function clearGroupTransients(): void
+	{
+		global $wpdb;
+
+		$pattern = '_transient_jvb_' . $this->group . '_%';
+		$timeout_pattern = '_transient_timeout_jvb_' . $this->group . '_%';
+
+		// Remove LIMIT to avoid table locks, add retry for deadlocks
+		$attempts = 0;
+		$max_attempts = 3;
+
+		while ($attempts < $max_attempts) {
+			$result = $wpdb->query(
+				$wpdb->prepare(
+					"DELETE FROM $wpdb->options
+                 WHERE option_name LIKE %s OR option_name LIKE %s",
+					$pattern,
+					$timeout_pattern
+				)
+			);
+
+			// Success or non-deadlock error
+			if ($result !== false || !str_contains($wpdb->last_error, 'Deadlock')) {
+				break;
+			}
+
+			$attempts++;
+			if ($attempts < $max_attempts) {
+				usleep(50000); // Wait 50ms before retry
+			}
+		}
+	}
+	/****************************************************
+	 * HOOKS
+	 ****************************************************/
+	/****************************************************
+	 * HOOKS - Posts
+	 ****************************************************/
+	public static function onPostChange(int $postId, \WP_Post $post): void
+	{
+		if (wp_is_post_revision($postId) || wp_is_post_autosave($postId)) {
+			return;
+		}
+
+//		error_log('[Clearing cache for post change: '.$postId.']');
+		self::invalidateItem('post', $postId);
+	}
+
+	public static function onPostDelete(int $postId): void
+	{
+//		error_log('[Clearing cache for post delete: '.$postId.']');
+		self::invalidateItem('post', $postId);
+	}
+
+//	public static function onPostMetaChange(int $metaId, int $objectId): void
+//	{
+////		error_log('[Clearing cache for post meta change: '.$objectId.']');
+//		self::invalidateItem('post', $objectId);
+//	}
+//
+//	public static function onPostMetaDelete(array $metaIds, int $objectId): void
+//	{
+////		error_log('[Clearing cache for post meta delete: '.$objectId.']');
+//		self::invalidateItem('post', $objectId);
+//	}
+
+	/****************************************************
+	 * HOOKS - Terms
+	 ****************************************************/
+	public static function onTermChange(int $termId, int $ttId, string $taxonomy): void
+	{
+		error_log('[Clearing cache for term change: '.$termId.']');
+		self::invalidateItem('taxonomy', $termId);
+	}
+
+	public static function onTermDelete(int $termId): void
+	{
+//		error_log('[Clearing cache for term delete: '.$termId.']');
+		self::invalidateItem('taxonomy', $termId);
+	}
+
+	public static function onTermMetaChange(int $metaId, int $objectId): void
+	{
+//		error_log('[Clearing cache for term meta change: '.$objectId.']');
+		self::invalidateItem('taxonomy', $objectId);
+	}
+
+	public static function onTermMetaDelete(array $metaIds, int $objectId): void
+	{
+//		error_log('[Clearing cache for term meta delete: '.$objectId.']');
+		self::invalidateItem('taxonomy', $objectId);
+	}
+
+	/****************************************************
+	 * HOOKS - Users
+	 ****************************************************/
+	public static function onUserChange(int $userId): void
+	{
+//		error_log('[Clearing cache for user change: '.$userId.']');
+		self::invalidateItem('user', $userId);
+	}
+
+	public static function onUserDelete(int $userId): void
+	{
+//		error_log('[Clearing cache for user delete: '.$userId.']');
+		self::invalidateItem('user', $userId);
+	}
+
+	public static function onUserMetaChange(int $metaId, int $objectId): void
+	{
+//		error_log('[Clearing cache for user meta change: '.$objectId.']');
+		self::invalidateItem('user', $objectId);
+	}
+
+	public static function onUserMetaDelete(array $metaIds, int $objectId): void
+	{
+//		error_log('[Clearing cache for user meta delete: '.$objectId.']');
+		self::invalidateItem('user', $objectId);
+	}
+	/***************************************************
+	 * UTILITY
+	 **************************************************/
+	/**
+	 * Generate a cache key from parameters
+	 * Useful for caching based on multiple variables
+	 *
+	 * @param array $params Key-value pairs that uniquely identify this cache entry
+	 * @return string MD5 hash of sorted parameters
+	 */
+	public function generateKey(array $params): string
+	{
+		ksort($params);
+		return md5(serialize($params));
+	}
+
+	/**
+	 * Nuclear option: Flush ALL registered cache groups
+	 * Use for debugging or after major updates
+	 *
+	 * @return int Number of groups flushed
+	 */
+	public static function flushAll(): int
+	{
+		$all = self::connections();
+		$groups = [];
+
+		// Collect all unique groups from connections
+		foreach ($all as $source => $targets) {
+			$groups[$source] = true;
+			foreach ($targets as $conn) {
+				$target = $conn['target'] ?? $conn;
+				$groups[$target] = true;
+			}
+		}
+
+		// Add any instantiated groups not in connections
+		foreach (array_keys(self::$instances) as $group) {
+			$groups[$group] = true;
+		}
+
+		// Flush each group
+		$count = 0;
+		foreach (array_keys($groups) as $group) {
+			self::invalidateGroup($group);
+			$count++;
+		}
+
+		// Also flush timestamp cache
+		if (wp_using_ext_object_cache()) {
+			wp_cache_flush_group(self::TS_GROUP);
+		}
+
+		// Clear in-memory caches
+		self::$timestamps = [];
+
+		return $count;
+	}
+
+	/**
+	 * Get all cache groups and their connections for admin display
+	 *
+	 * @return array Format: ['group' => ['connects_to' => [...], 'connected_from' => [...]]]
+	 */
+	public static function getAllGroups(): array
+	{
+		$connections = self::connections();
+		$groups = [];
+
+		// Build bidirectional view
+		foreach ($connections as $source => $targets) {
+			if (!isset($groups[$source])) {
+				$groups[$source] = ['connects_to' => [], 'connected_from' => []];
+			}
+
+			foreach ($targets as $conn) {
+				$target = $conn['target'] ?? $conn;
+				$flush = $conn['flush'] ?? false;
+
+				// Source connects to target
+				$groups[$source]['connects_to'][] = [
+					'group' => $target,
+					'flush' => $flush
+				];
+
+				// Target is connected from source
+				if (!isset($groups[$target])) {
+					$groups[$target] = ['connects_to' => [], 'connected_from' => []];
+				}
+				$groups[$target]['connected_from'][] = [
+					'group' => $source,
+					'flush' => $flush
+				];
+			}
+		}
+
+		// Add any instantiated groups not in connections
+		foreach (array_keys(self::$instances) as $group) {
+			if (!isset($groups[$group])) {
+				$groups[$group] = ['connects_to' => [], 'connected_from' => []];
+			}
+		}
+
+		return $groups;
+	}
+
+	public function hasRedis():bool
+	{
+		return $this->hasRedis;
+	}
+}
diff --git a/inc/managers/CustomTable.php b/inc/managers/CustomTable.php
index 1a6719c..c8a9171 100644
--- a/inc/managers/CustomTable.php
+++ b/inc/managers/CustomTable.php
@@ -38,6 +38,8 @@
 	protected static string $termIDType;
 	protected static string $postIDType;
 	protected static string $postTable;
+	protected ?CustomTable $karmaTable = null;
+	protected ?CustomTable $favouritesTable = null;
 
 	protected Cache $cache;
 
@@ -73,6 +75,15 @@
 		return self::$instances[$tableName];
 	}
 
+	public function setKarma(CustomTable $karma):void
+	{
+		$this->karmaTable = $karma;
+	}
+	public function setFavourite(CustomTable $favourites):void
+	{
+		$this->favouritesTable = $favourites;
+	}
+
 	public function ensureDefined() {
 		$this->ensureUserTable();
 		$this->ensureUserIDType();
@@ -143,6 +154,11 @@
 		return $this;
 	}
 
+	public function getColumnKeys():array
+	{
+		return array_keys($this->columns);
+	}
+
 	/**
 	 * @param array $keys An array of {string} $keys. If a $key is an array, you can define a custom $key => $value, example: 'UNIQUE' => $value, or 'PRIMARY' => $value
 	 * @return $this
@@ -318,6 +334,26 @@
 		return $this;
 	}
 
+	public function orderByKarma(string $metric = 'karmic_score', string $direction = 'DESC'): self
+	{
+		if (!$this->karmaTable || !in_array($metric, ['upvotes', 'downvotes', 'karmic_score'])) {
+			return $this->orderBy('created_at');
+		}
+		$this->builder['orderby'] = $metric;
+		$this->builder['order'] = strtoupper($direction);
+		return $this;
+	}
+
+	public function orderByFavourites(string $direction = 'DESC'): self
+	{
+		if (!$this->favouritesTable) {
+			return $this->orderBy('created_at');
+		}
+		$this->builder['orderby'] = 'favourites_count';
+		$this->builder['order'] = strtoupper($direction);
+		return $this;
+	}
+
 	/**
 	 * Set pagination
 	 *
@@ -577,7 +613,7 @@
 	public function get(array $where, string $output = OBJECT): object|array|null
 	{
 		return $this->cache->remember(
-			$this->cache->generateKey(array_merge(['output' => $output], $where)),
+			array_merge(['output' => $output], $where),
 			function() use ($where, $output) {
 				$query = "SELECT * FROM {$this->fullTableName}";
 				$where = $this->buildWhereClause($where);
@@ -607,7 +643,7 @@
 	public function getMany(array $args = [], bool $itemsOnly = true, string $output = OBJECT): array
 	{
 		$items = $this->cache->remember(
-			$this->cache->generateKey(array_merge($args, ['output' => $output])),
+			array_merge($args, ['output' => $output]),
 			function () use ($args, $output) {
 				$query = "SELECT * FROM {$this->fullTableName}";
 				$values = [];
@@ -622,7 +658,7 @@
 
 				// ORDER BY
 				if (!empty($args['orderby'])) {
-					$orderby = sanitize_sql_orderby($args['orderby']);
+					$orderby = $this->resolveOrderby($args['orderby']);
 					$order = (!empty($args['order']) && strtoupper($args['order']) === 'ASC') ? 'ASC' : 'DESC';
 					$query .= " ORDER BY {$orderby} {$order}";
 				}
@@ -664,6 +700,26 @@
 		];
 	}
 
+	protected function resolveOrderby(string $orderby):string
+	{
+		$orderby = strtolower($orderby);
+		if (in_array($orderby, ['upvotes', 'downvotes', 'karmic_score']) && $this->karmaTable) {
+			$tableName = $this->karmaTable->getFullTableName();
+			return match ($orderby) {
+				'upvotes'   => "(SELECT COUNT(*) FROM {$tableName} k WHERE k.item_id = {$this->fullTableName}.id AND k.vote = 'up')",
+				'downvotes' => "(SELECT COUNT(*) FROM {$tableName} k WHERE k.item_id = {$this->fullTableName}.id AND k.vote = 'down')",
+				'karmic_score'     => "(SELECT COALESCE(SUM(CASE WHEN k.vote = 'up' THEN 1 WHEN k.vote = 'down' THEN -1 ELSE 0 END), 0) FROM {$tableName} k WHERE k.item_id = {$this->fullTableName}.id)",
+			};
+		}
+
+		if ($orderby === 'favourites_count' && $this->favouritesTable) {
+			$tableName = $this->favouritesTable->getFullTableName();
+			return "(SELECT COUNT(*) FROM {$tableName} f WHERE f.target_id = {$this->fullTableName}.id)";
+		}
+
+		return sanitize_sql_orderby($orderby);
+	}
+
 
 	/**
 	 * Get a specific column value from all matches
@@ -676,7 +732,7 @@
 		$key = array_merge($where, ['column' => $column, 'orderby' => $orderby, 'order' => $order, 'limit' => $limit]);
 
 		return $this->cache->remember(
-			$this->cache->generateKey($key),
+			$key,
 			function() use ($column, $where, $orderby, $order, $limit) {
 				if (!empty($this->columns) && !array_key_exists($column, $this->columns)) {
 					$this->logError('pluck', ['reason' => "Column '{$column}' not in definition"]);
@@ -684,7 +740,7 @@
 				}
 				$args = ['where' => $where];
 				if ($orderby) {
-					$args['orderby'] = $orderby;
+					$args['orderby'] = $this->resolveOrderby($orderby);
 				}
 				if ($order) {
 					$args['order'] = $order;
@@ -708,7 +764,7 @@
 	public function count(array $where = []): int
 	{
 		return $this->cache->remember(
-			$this->cache->generateKey(array_merge(['source' => 'count'], $where)),
+			array_merge(['source' => 'count'], $where),
 			function() use ($where) {
 				$query = "SELECT COUNT(*) FROM {$this->fullTableName}";
 				$values = [];
@@ -1045,16 +1101,34 @@
 		foreach ($where as $column => $value) {
 			$column_safe = esc_sql($column);
 
+			// OR grouping: ['OR' => [ [...cond1], [...cond2] ]]
+			if ($column === 'OR' && is_array($value)) {
+				$orConditions = [];
+				foreach ($value as $orWhere) {
+					$orClause = $this->buildWhereClause($orWhere);
+					$orConditions[] = $orClause['sql'];
+					$values = array_merge($values, $orClause['values']);
+				}
+				$conditions[] = '(' . implode(' OR ', $orConditions) . ')';
+				continue;
+			}
+
 			if ($value === null) {
 				$conditions[] = "`{$column_safe}` IS NULL";
 			} elseif (is_array($value) && count($value) === 2 && is_string($value[0])) {
 				[$operator, $operand] = $value;
 				$operator = strtoupper(trim($operator));
 
-				if (in_array($operator, ['IN', 'NOT IN']) && is_array($operand)) {
+				if ($operator === 'IS NOT NULL') {
+					$conditions[] = "`{$column_safe}` IS NOT NULL";
+				} elseif (in_array($operator, ['IN', 'NOT IN']) && is_array($operand)) {
 					$placeholders = implode(',', array_map([$this, 'getPlaceholder'], $operand));
 					$conditions[] = "`{$column_safe}` {$operator} ({$placeholders})";
 					$values       = array_merge($values, $operand);
+				} elseif (in_array($operator, ['BETWEEN', 'NOT BETWEEN']) && is_array($operand) && count($operand) === 2) {
+					$conditions[] = "`{$column_safe}` {$operator} " . $this->getPlaceholder($operand[0]) . " AND " . $this->getPlaceholder($operand[1]);
+					$values[]     = $operand[0];
+					$values[]     = $operand[1];
 				} else {
 					// <, >, <=, >=, !=, LIKE, etc.
 					$conditions[] = "`{$column_safe}` {$operator} " . $this->getPlaceholder($operand);
@@ -1114,4 +1188,10 @@
 			error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . $this->wpdb->last_error);
 		}
 	}
+
+	public function sum(string $column, array $where):float
+	{
+		$rows = $this->pluck($column, $where);
+		return array_sum($rows);
+	}
 }
diff --git a/inc/managers/Dashboard/DashboardManager.php b/inc/managers/Dashboard/Dashboard.php
similarity index 93%
rename from inc/managers/Dashboard/DashboardManager.php
rename to inc/managers/Dashboard/Dashboard.php
index 2f7c43e..43d6c52 100644
--- a/inc/managers/Dashboard/DashboardManager.php
+++ b/inc/managers/Dashboard/Dashboard.php
@@ -1,12 +1,13 @@
 <?php
 namespace JVBase\managers\Dashboard;
 
-use JetBrains\PhpStorm\NoReturn;use JVBase\base\Site;use JVBase\forms\TaxonomySelector;use JVBase\managers\Cache;use JVBase\managers\IconsManager;use JVBase\managers\LoginManager;use JVBase\managers\RoleManager;use JVBase\meta\Form;use JVBase\meta\Meta;use JVBase\registrar\Fields;use JVBase\registrar\Registrar;use JVBase\ui\Navigation;use JVBase\ui\Tabs;
+use JetBrains\PhpStorm\NoReturn;use JVBase\base\Site;use JVBase\forms\TaxonomySelector;use JVBase\Instance;use JVBase\integrations\ServiceMap;use JVBase\managers\Cache;use JVBase\managers\IconsManager;use JVBase\managers\Login;use JVBase\managers\Roles;use JVBase\meta\Form;use JVBase\meta\Meta;use JVBase\registrar\Fields;use JVBase\registrar\Registrar;use JVBase\ui\Navigation;use JVBase\ui\Tabs;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
-class DashboardManager {
+class Dashboard {
+	use Instance, ServiceMap;
 	public static string $based;
 	protected string $slug = 'dash';
 	protected string $baseURl;
@@ -14,9 +15,10 @@
 	protected array $pages = [];
 	protected array $sections = [];
 	protected Cache $cache;
-	public function __construct() {
+
+	private function __construct() {
 		self::$based = BASE.$this->slug;
-		$this->cache = Cache::for('dashboard')->connect('post');
+		$this->cache = Cache::for('dashboard', 'post');
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
@@ -289,7 +291,7 @@
 				return;
 			}
 			//Redirect to custom dashboard
-			if (is_user_logged_in() && isOurPeople()) {
+			if (is_admin() && isOurPeople()) {
 				$this->redirectToDashboard();
 			}
 		}
@@ -351,11 +353,11 @@
 			foreach ($content as $c) {
 				$registrar = Registrar::getInstance($c);
 				$page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon());
-				$page->setPermission(RoleManager::getPermissionName('edit', $c));
+				$page->setPermission(Roles::getPermissionName('edit', $c));
 				$page->setSection('content');
 				$page->setScripts(['jvb-crud', 'jvb-creator']);
 				$this->sections[$c] = new Section($registrar->getPlural(), $c, $registrar->getIcon(), 'content');
-				$this->sections[$c]->setPermission(RoleManager::getPermissionName('edit', $c));
+				$this->sections[$c]->setPermission(Roles::getPermissionName('edit', $c));
 				$this->sections[$c]->setIsLink(true);
 
 				$taxonomies = $registrar->registrar->taxonomies;
@@ -363,7 +365,7 @@
 					$taxReg = Registrar::getInstance($tax);
 					if ($taxReg) {
 						$taxPage = $this->addPage($taxReg->getPlural(), $tax, $registrar->getIcon(), $page->getID());
-						$taxPage->setPermission(RoleManager::getPermissionName('edit', $c));
+						$taxPage->setPermission(Roles::getPermissionName('edit', $c));
 						$taxPage->setSection($c);
 
 						$taxPage->setScripts(['jvb-crud', 'jvb-creator']);
@@ -376,7 +378,7 @@
 			foreach ($contentTax as $c) {
 				$registrar = Registrar::getInstance($c);
 				$page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon());
-				$page->setPermission(RoleManager::getPermissionName('edit', $c));
+				$page->setPermission(Roles::getPermissionName('edit', $c));
 				$page->setSection($c);
 				//TODO: add artist management and invitations, if applicable
 			}
@@ -511,18 +513,7 @@
 				$this->sections['integrations']->setIsLink(true);
 
 				foreach (array_keys(Site::getIntegrations()) as $integration) {
-					$integration = match($integration) {
-						'maps' => 'JVBase\integrations\services\GoogleMaps',
-						'square' => 'JVBase\integrations\services\Square',
-						'facebook' => 'JVBase\integrations\services\Facebook',
-						'helcim' => 'JVBase\integrations\services\Helcim',
-						'instagram' => 'JVBase\integrations\services\Instagram',
-						'bluesky' => 'JVBase\integrations\services\BlueSky',
-						'gmb' => 'JVBase\integrations\services\GoogleMyBusiness',
-						'cloudflare' => 'JVBase\integrations\services\Cloudflare',
-						'umami' => 'JVBase\integrations\services\Umami',
-						'postmark' => 'JVBase\integrations\services\PostMark',
-					};
+					$integration = $this->getService($integration);
 					$title = $integration::title();
 					$icon = $integration::icon();
 					if ($integration::hasExtraOptions()) {
@@ -765,7 +756,7 @@
 			echo '<p>Alternatively, you can always login using the magic link, which sets a temporary password for 15 minutes.</p>';
 		}
 		 ?>
-		<?= LoginManager::getInstance()->renderLoginForm('resetpass'); ?>
+		<?= Login::getInstance()->renderLoginForm('resetpass'); ?>
 
 		<?php
 	}
diff --git a/inc/managers/Dashboard/DashboardPage.php b/inc/managers/Dashboard/DashboardPage.php
index 2096a87..bc0c040 100644
--- a/inc/managers/Dashboard/DashboardPage.php
+++ b/inc/managers/Dashboard/DashboardPage.php
@@ -47,7 +47,7 @@
 			$this->slug.'_ID',
 			function() use ($parentID) {
 				$existing = new WP_Query([
-					'post_type'	=> DashboardManager::$based,
+					'post_type'	=> Dashboard::$based,
 					'name'		=> $this->slug,
 					'fields'	=> 'ids',
 					'posts_per_page'=> 1,
@@ -59,7 +59,7 @@
 				$args = [
 					'post_title'	=> $this->title,
 					'post_name'		=> $this->slug,
-					'post_type'		=> DashboardManager::$based,
+					'post_type'		=> Dashboard::$based,
 					'post_status'	=> 'publish'
 				];
 				if ($parentID > 0) {
@@ -83,7 +83,7 @@
 	{
 
 		if ($this->slug === 'dash') {
-			$this->URL = get_post_type_archive_link(DashboardManager::$based);
+			$this->URL = get_post_type_archive_link(Dashboard::$based);
 			return;
 		}
 		$this->URL = get_the_permalink($this->ID);
diff --git a/inc/managers/Dashboard/_setup.php b/inc/managers/Dashboard/_setup.php
index ebf6ef0..3ede4fd 100644
--- a/inc/managers/Dashboard/_setup.php
+++ b/inc/managers/Dashboard/_setup.php
@@ -3,6 +3,6 @@
 	exit;
 }
 
-require_once JVB_DIR . '/inc/managers/Dashboard/DashboardManager.php';
+require_once JVB_DIR . '/inc/managers/Dashboard/Dashboard.php';
 require_once JVB_DIR . '/inc/managers/Dashboard/DashboardPage.php';
 require_once JVB_DIR . '/inc/managers/Dashboard/Section.php';
diff --git a/inc/managers/Dashboard/index.php b/inc/managers/Dashboard/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/Dashboard/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index e8fb17c..35cad18 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -3,7 +3,7 @@
 
 use JetBrains\PhpStorm\NoReturn;use JVBase\forms\TaxonomySelector;
 use JVBase\base\Site;
-use JVBase\meta\Form;
+use JVBase\integrations\ServiceMap;use JVBase\meta\Form;
 use JVBase\registrar\Registrar;
 use JVBase\ui\Navigation;
 use WP_Error;use WP_Query;use WP_User;
@@ -17,6 +17,8 @@
  */
 class DashboardManager
 {
+	use ServiceMap;
+
     protected WP_User $user;
     protected Cache $cache;
     protected string $role;
@@ -25,7 +27,7 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->connect('user')->user();
+        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->user();
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
@@ -49,7 +51,6 @@
     }
 
 	public function excludeDashboard(array $IDs):array {
-		$this->cache->flush();
 		$exclude = $this->cache->remember(
 			'dashboardIDs',
 			function() {
@@ -297,11 +298,10 @@
 	 */
 	protected function buildIntegrationPages(int $parentID):void
 	{
-		$integrations = JVB()->getAvailableServices(false);
-
-		foreach ($integrations as $name => $integration) {
+		$integrations = $this->getAvailableServices();
+		foreach ($integrations as $service) {
+			$integration = $this->getService($service);
 			$title = $integration->getTitle();
-
 			$slug = sanitize_title($title);
 			$this->createDashboardPage($slug, $title, $parentID);
 		}
@@ -360,7 +360,7 @@
 		}
 		$integrationSlugs = array_map(function($name) {
 			return sanitize_title(str_replace('_', '-', $name));
-		}, array_keys(JVB()->getAvailableServices(false)));
+		}, array_keys($this->getAvailableServices()));
 
 		// Check if this is an integration subpage
 		if (in_array($page, $integrationSlugs)) {
@@ -747,7 +747,7 @@
 			Registrar::withFeature('is_content', 'term')
 		);
 		$availableContent = array_filter($pages, function($page, $key) use($all) {
-			return !is_numeric($key) && in_array($key, $all) && JVB()->roles()->checkRole($this->user, $key);
+			return !is_numeric($key) && in_array($key, $all) && Roles::getInstance()()->checkRole($this->user, $key);
 		}, ARRAY_FILTER_USE_BOTH);
 		if (!empty ($availableContent)){
 			$content = $menu->addItem('Your Content', jvbDashIcon('book-bookmark'))
@@ -871,7 +871,7 @@
 		}
 
 		if (Site::has('referrals')) {
-			$whatever = JVB()->referrals()->getReferralWelcomeMessage($this->user->ID);
+			$whatever = Referrals::getInstance()->getReferralWelcomeMessage($this->user->ID);
 			if (!empty($whatever)) {
 				return $whatever;
 			}
@@ -933,18 +933,19 @@
 
 	protected function getIntegrationsMenu():string
 	{
-		$integrations = JVB()->getAvailableServices(false);
+		$integrations = $this->getAvailableServices();
 		$out = '';
 		if (!empty($integrations)) {
 			$out = '<nav class="integrations"><ul>';
 
 			$url = get_home_url(null, '/dash/integrations/');
 			$out .= '<li><a href="'.$url.'">'.jvbDashIcon('plugs-connected').'Integrations</a></li>';
-			foreach ($integrations as $name=> $integration) {
-				if (!JVB()->userCanConnect($name, $this->user->ID) || !$integration->hasDefaults()) {
+			foreach ($integrations as $service) {
+				$integration = $this->getService($service);
+				if (method_exists($integration, 'userCanConnect') && !$integration->userCanConnect($this->user->ID)) {
 					continue;
 				}
-				$link = sanitize_title(str_replace('_', '-',$name));
+				$link = sanitize_title(str_replace('_', '-',$service));
 				$out .= '<li><a href="'.$url.$link.'">'.jvbDashIcon($integration->icon).$integration->getTitle().'</a></li>';
 			}
 			$out .= '</ul></nav>';
@@ -971,15 +972,19 @@
 			?>
 			<section class="item-grid integrations">
 			<?php
-			$all = JVB()->getAvailableServices();
-			foreach ($all as $name) {
+
+			foreach ($this->getAvailableServices() as $name) {
 				if (current_user_can('manage_options')) {
 					$userID = null;
 				} else {
 					$userID = $this->user->ID;
 				}
-
-				JVB()->connect($name, $userID)->renderConnection();
+				$integration = $this->getService($name);
+				if (method_exists($integration, 'determineUser')
+					&& method_exists($integration, 'renderConnection')) {
+					$integration->determineUser();
+					$integration->renderConnection();
+				}
 			}
 			?>
 			</section>
@@ -1352,7 +1357,7 @@
 					$membership = Site::membership();
 					$type  = $registrar? $registrar->getType() : false;
 					if ($type) {
-						$permission = JVB()->roles()->getPermission('edit', $key);
+						$permission = Roles::getInstance()()->getPermission('edit', $key);
 					}
 					switch ($type) {
 						case 'content':
@@ -1362,7 +1367,7 @@
 							break;
 						case 'taxonomy':
 							$registrar = Registrar::getInstance($key);
-							if ($registrar && $registrar->hasFeature('is_content') && (!empty(JVB()->roles()->getOwnedTerms($userID, $key)) || !empty(JVB()->roles()->getManagedTerms($userID, $key)))){
+							if ($registrar && $registrar->hasFeature('is_content') && (!empty(Roles::getInstance()()->getOwnedTerms($userID, $key)) || !empty(Roles::getInstance()()->getManagedTerms($userID, $key)))){
 								$remove = false;
 							} else if (count(array_intersect($registrar->registrar->for, array_keys($pages))) > 0) {
 								$remove = false;
diff --git a/inc/managers/DirectoryManager.php b/inc/managers/DirectoryManager.php
index ffe4843..23bd63b 100644
--- a/inc/managers/DirectoryManager.php
+++ b/inc/managers/DirectoryManager.php
@@ -5,6 +5,7 @@
 	exit;
 }
 
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 use WP_Block;
@@ -12,6 +13,7 @@
 
 class DirectoryManager
 {
+	use Instance;
     protected array $directories;
     protected array $directoryPageIDs;
 	protected array $directoryList;
@@ -20,7 +22,7 @@
     protected static string $slug = BASE.'for_type_slug';
     protected Cache $cache;
 
-    public function __construct($perPage = 100)
+    private function __construct($perPage = 100)
     {
 		$this->directories = $this->getDirectories();
         if (empty($this->directories)) {
@@ -28,9 +30,6 @@
         }
 		$this->perPage = $perPage;
         $this->cache = Cache::for('directory', WEEK_IN_SECONDS);
-		$this->cache->connect('post', true)
-			->connect('taxonomy', true)
-			->connect('user', true);
 
 		if (JVB_TESTING) {
 			$this->cache->flush();
@@ -39,8 +38,16 @@
 		jvb_register_do_once('buildDirectories', [$this, 'activate']);
 		add_action('init', [$this, 'registerDirectories']);
         add_filter('pre_render_block', [$this, 'renderBlock'], 20, 3);
+		add_action('save_post', [$this, 'flushCache']);
+		add_action('edit_terms', [$this, 'flushCache']);
     }
 
+	public function flushPostCache(int $postID):void
+	{
+		//TODO: We can probably use a bit more finesse here.
+		$this->cache->flush();
+	}
+
     public function registerDirectories():void
     {
 
diff --git a/inc/managers/EmailManager.php b/inc/managers/Emails.php
similarity index 99%
rename from inc/managers/EmailManager.php
rename to inc/managers/Emails.php
index e480a06..bd58edf 100644
--- a/inc/managers/EmailManager.php
+++ b/inc/managers/Emails.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
 use WP_User;
 
 /**
@@ -21,9 +22,9 @@
  * Hooks into wp_mail to ensure a consistent look in our automatic emails
  */
 
-class EmailManager
+class Emails
 {
-
+	use Instance;
    public array $colours;
    private string $title = JVB_EMAIL['content']['title'];
    private string $prefix = JVB_EMAIL['content']['subjectPrefix'];
diff --git a/inc/managers/ErrorHandler.php b/inc/managers/Errors.php
similarity index 89%
rename from inc/managers/ErrorHandler.php
rename to inc/managers/Errors.php
index a3b1f33..997dfbc 100644
--- a/inc/managers/ErrorHandler.php
+++ b/inc/managers/Errors.php
@@ -1,7 +1,7 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\JVB;
+use JVBase\Instance;
 use WP_Error;
 use Exception;
 use WP_REST_Request;
@@ -10,8 +10,9 @@
     exit; // Exit if accessed directly
 }
 
-class ErrorHandler
+class Errors
 {
+	use Instance;
     protected int $notification_threshold = 5; // Critical errors within 1 hour
 
     protected array $error_levels = [
@@ -24,7 +25,7 @@
 
 	protected CustomTable $table;
 
-    public function __construct()
+    private function __construct()
     {
 		$this->defineTables();
 //        global $wpdb;
@@ -71,7 +72,7 @@
 	}
     public function registerAdminAction():void
     {
-        $admin = JVB()->admin();
+        $admin = AdminPages::getInstance();
 
         $admin->registerSubpage(
             'Error Log',
@@ -265,25 +266,25 @@
 		$admin_email = get_option('admin_email');
 		$subject = "[" . get_bloginfo('name') . " Critical Error] {$component}";
 
-		$body = JVB()->email()->alert(
+		$body = Emails::getInstance()->alert(
 			'A critical error has occurred and requires immediate attention',
 			'error'
 		);
 
-		$body .= JVB()->email()->h2('Error Details');
-		$body .= JVB()->email()->card(
+		$body .= Emails::getInstance()->h2('Error Details');
+		$body .= Emails::getInstance()->card(
 			'<p><strong>Component:</strong> ' . esc_html($component) . '</p>' .
 			'<p><strong>Message:</strong></p>' .
-			JVB()->email()->codeBlock($message),
+			Emails::getInstance()->codeBlock($message),
 			'Error Information'
 		);
 
 		if (!empty($context)) {
-			$body .= JVB()->email()->h3('Additional Context');
-			$body .= JVB()->email()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
+			$body .= Emails::getInstance()->h3('Additional Context');
+			$body .= Emails::getInstance()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
 		}
 
-		return JVB()->email()->sendEmail($admin_email, $subject, $body, 'CRITICAL ERROR');
+		return Emails::getInstance()->sendEmail($admin_email, $subject, $body, 'CRITICAL ERROR');
 	}
 
     /**
@@ -332,77 +333,77 @@
 		$subject = "[{$site_name}] Daily Error Summary - " . date('Y-m-d');
 
 		// Header with alert
-		$body = JVB()->email()->h1('Daily Error Summary');
+		$body = Emails::getInstance()->h1('Daily Error Summary');
 		$body .= sprintf('<p>Error summary for <strong>%s</strong></p>', $yesterday);
 
 		// Summary stats in a grid
 		if (!empty($summary['stats'])) {
 			$stats = [
-				JVB()->email()->stat($summary['stats']->total_errors, 'Total Errors'),
-				JVB()->email()->stat($summary['stats']->critical_count, 'Critical', 'Requires attention'),
-				JVB()->email()->stat($summary['stats']->error_count, 'Errors'),
-				JVB()->email()->stat($summary['stats']->warning_count, 'Warnings')
+				Emails::getInstance()->stat($summary['stats']->total_errors, 'Total Errors'),
+				Emails::getInstance()->stat($summary['stats']->critical_count, 'Critical', 'Requires attention'),
+				Emails::getInstance()->stat($summary['stats']->error_count, 'Errors'),
+				Emails::getInstance()->stat($summary['stats']->warning_count, 'Warnings')
 			];
-			$body .= JVB()->email()->grid($stats, 4);
+			$body .= Emails::getInstance()->grid($stats, 4);
 		}
 
 		// Alert if critical errors exist
 		if (!empty($summary['critical'])) {
-			$body .= JVB()->email()->alert(
+			$body .= Emails::getInstance()->alert(
 				sprintf('Found %d critical errors that need immediate attention', count($summary['critical'])),
 				'error'
 			);
 		}
 
-		$body .= JVB()->email()->spacer(20);
+		$body .= Emails::getInstance()->spacer(20);
 
 		// Frequent errors section
 		if (!empty($summary['frequent'])) {
-			$body .= JVB()->email()->h2('Most Frequent Errors');
+			$body .= Emails::getInstance()->h2('Most Frequent Errors');
 
 			foreach ($summary['frequent'] as $error) {
-				$cardContent = JVB()->email()->badge($error->count . 'x', 'warning') . ' ';
+				$cardContent = Emails::getInstance()->badge($error->count . 'x', 'warning') . ' ';
 				$cardContent .= '<strong>' . esc_html($error->error_type) . '</strong>';
 				$cardContent .= '<p style="margin:10px 0 5px 0;font-size:13px;">' . esc_html(wp_trim_words($error->message, 15)) . '</p>';
-				$cardContent .= '<p style="margin:0;font-size:12px;color:' . JVB()->email()->colours['dark-200'] . ';">
+				$cardContent .= '<p style="margin:0;font-size:12px;color:' . Emails::getInstance()->colours['dark-200'] . ';">
                 Source: ' . esc_html($error->source) . ' |
                 Logged in: ' . $error->logged_in_count . ' |
                 Logged out: ' . $error->logged_out_count . '
             </p>';
 
-				$body .= JVB()->email()->card($cardContent, $error->component);
+				$body .= Emails::getInstance()->card($cardContent, $error->component);
 			}
 		}
 
 		// Critical errors section
 		if (!empty($summary['critical'])) {
-			$body .= JVB()->email()->spacer(30);
-			$body .= JVB()->email()->h2('Recent Critical Errors');
+			$body .= Emails::getInstance()->spacer(30);
+			$body .= Emails::getInstance()->h2('Recent Critical Errors');
 
 			foreach ($summary['critical'] as $error) {
 				$cardContent = '<p><strong>Time:</strong> ' . esc_html($error->created_at) . '</p>';
 				$cardContent .= '<p><strong>Message:</strong></p>';
-				$cardContent .= JVB()->email()->codeBlock($error->message);
+				$cardContent .= Emails::getInstance()->codeBlock($error->message);
 
 				// Include context if available
 				if (!empty($error->context)) {
 					$context = json_decode($error->context, true);
 					if (is_array($context)) {
 						$cardContent .= '<p><strong>Context:</strong></p>';
-						$cardContent .= JVB()->email()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
+						$cardContent .= Emails::getInstance()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
 					}
 				}
 
-				$body .= JVB()->email()->card($cardContent, $error->component . ': ' . $error->error_type);
+				$body .= Emails::getInstance()->card($cardContent, $error->component . ': ' . $error->error_type);
 			}
 		}
 
 		// Dashboard link
 		$admin_url = admin_url('admin.php?page=jvb-error-logs');
-		$body .= JVB()->email()->spacer(30);
-		$body .= JVB()->email()->button($admin_url, 'View Detailed Logs');
+		$body .= Emails::getInstance()->spacer(30);
+		$body .= Emails::getInstance()->button($admin_url, 'View Detailed Logs');
 
-		return JVB()->email()->sendEmail($admin_email, $subject, $body, 'ERROR SUMMARY');
+		return Emails::getInstance()->sendEmail($admin_email, $subject, $body, 'ERROR SUMMARY');
 	}
 
     /**
diff --git a/inc/managers/FavouritesManager.php b/inc/managers/Favourites.php
similarity index 85%
rename from inc/managers/FavouritesManager.php
rename to inc/managers/Favourites.php
index c2f2c05..c48e42d 100644
--- a/inc/managers/FavouritesManager.php
+++ b/inc/managers/Favourites.php
@@ -2,22 +2,27 @@
 namespace JVBase\managers;
 
 use Exception;
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\FavouritesRoutes;
 
 if (!defined('ABSPATH')) {
 	exit; // Exit if accessed directly
 }
-class FavouritesManager
+class Favourites
 {
-	private Cache $cache;
+	use Instance;
 
 	protected CustomTable $favourites;
 	protected CustomTable $lists;
 	protected CustomTable $listItems;
 	protected CustomTable $listShares;
+	protected Rest $route;
 
 	public function __construct()
 	{
+		$this->route = FavouritesRoutes::getInstance();
 		$this->defineTables();
 		$this->registerHooks();
 	}
@@ -28,6 +33,11 @@
 		add_action('delete_term', [$this, 'cleanupTermFavourites'], 10, 3);
 		add_action('jvbUserRegistered', [$this, 'maybeAcceptListInvite'], 10, 3);
 		add_action('jvb_cleanupOrphanedFavourites', [$this, 'cleanupOrphanedFavourites']);
+
+		//adds favourite ordering to wp_query/tax_query
+		add_filter('posts_join', [$this, 'maybeJoinFavouriteOrderby'], 10, 2);
+		add_filter('posts_orderby', [$this, 'maybeApplyFavouriteOrderby'], 10, 2);
+		add_filter('terms_clauses', [$this, 'maybeApplyFavouriteTermOrderby'], 10, 3);
 	}
 	protected function defineTables():void
 	{
@@ -210,7 +220,7 @@
 			);
 
 			if ($owner) {
-				JVB()->notification()->addNotification(
+				NotificationManager::getInstance()->addNotification(
 					$owner,
 					'new_favourite',
 					$user_id,
@@ -229,10 +239,10 @@
 			]);
 
 			if (in_array($registerType, ['term', 'user'])) {
-				JVB()->notification()->deleteUserPreference($user_id, $target_id, $type);
+				NotificationManager::getInstance()->deleteUserPreference($user_id, $target_id, $type);
 			}
 			if ($owner) {
-				JVB()->notification()->removeNotification(
+				NotificationManager::getInstance()->removeNotification(
 					$owner,
 					[
 						'from_user' => $user_id,
@@ -254,6 +264,45 @@
 			return $this->toggleFavourite(false, $user_id, $target_id, $type);
 		}
 
+		public function hasFavourited(int $user_id, int $target_id, string $type):bool
+		{
+			return (bool) $this->favourites->get([
+				'user_id'	=> $user_id,
+				'target_id'	=> $target_id,
+				'type'		=> $type
+			]);
+		}
+
+		public function batchToggleFavourites(int $user_id, array $items):array
+		{
+			$items = array_filter($items, function ($item) {
+				$keys = ['target_id', 'type', 'action'];
+				foreach ($keys as $key) {
+					if (!array_key_exists($key, $item)) {
+						return false;
+					}
+					if (empty($item[$key])) {
+						return false;
+					}
+				}
+				if (!in_array($item['action'], ['add', 'remove'])) {
+					return false;
+				}
+				return true;
+			});
+
+			if (empty($items)) {
+				return [];
+			}
+
+			$results = [];
+			foreach ($items as $item) {
+				$results[$item['target_id']] =$this->toggleFavourite($item['action'] === 'add', $user_id, $item['target_id'], $item['type']);
+			}
+			return $results;
+		}
+
+
 		public function addNoteFromTarget(int $user_id, int $target_id, string $type, string $note):bool
 		{
 			$favourite = $this->favourites->get([
@@ -378,7 +427,7 @@
 			$args = [];
 			if ($shareWithUser) {
 				$args['user_id'] = $shareWithUser;
-				JVB()->notification()->addNotification($shareWithUser, 'list_shared', $user_id, '', $listId);
+				NotificationManager::getInstance()->addNotification($shareWithUser, 'list_shared', $user_id, '', $listId);
 			} elseif ($newUser) {
 				$email = sanitize_email($newUser['email']);
 				$name = sanitize_text_field($newUser['name']);
@@ -408,7 +457,7 @@
 			if (is_int($userIDorEmail)) {
 				$where['user_id'] = $userIDorEmail;
 
-				$notification = JVB()->notification()->table();
+				$notification = NotificationManager::getInstance()->table();
 				$n = $notification->get([
 					'for_user' => $userIDorEmail,
 					'from_user' => $user_id,
@@ -455,7 +504,7 @@
 
 					$message = $accept ? 'accepted' : 'rejected';
 					$message = $user->first_name . ' ' . $message . ' your list share invitation.';
-					JVB()->notification()->addNotification($list->user_id, 'list_share_status', $userIDorEmail, $message);
+					NotificationManager::getInstance()->addNotification($list->user_id, 'list_share_status', $userIDorEmail, $message);
 				}
 			}else if (is_email($userIDorEmail)) {
 				$args['email'] = sanitize_email($userIDorEmail);
@@ -517,7 +566,7 @@
 
 			public function getListDetails(int $list_id, int $user_id, int $page = 1):array
 			{
-				$list = JVB()->favourites()->getLists(['id' => $list_id, 'user_id' => $user_id]);
+				$list = Favourites::getInstance()->getLists(['id' => $list_id, 'user_id' => $user_id]);
 				if (!$list) {
 					return [];
 				}
@@ -792,4 +841,55 @@
 			]);
 		}
 	}
+
+	/**
+	 * Join favourite counts for WP_Query when orderby => 'favourite_count'
+	 */
+	public function maybeJoinFavouriteOrderby(string $join, \WP_Query $query): string
+	{
+		if ($query->get('orderby') !== 'favourites_count') {
+			return $join;
+		}
+		global $wpdb;
+		$table = $this->favourites->getFullTableName();
+		$type = $query->get('post_type') ?: 'post';
+
+		$join .= $wpdb->prepare(
+			" LEFT JOIN (SELECT target_id, COUNT(*) as fav_count FROM {$table} WHERE type = %s GROUP BY target_id) jvb_fav_counts ON jvb_fav_counts.target_id = {$wpdb->posts}.ID",
+			$type
+		);
+		return $join;
+	}
+
+	public function maybeApplyFavouriteOrderby(string $orderby, \WP_Query $query): string
+	{
+		if ($query->get('orderby') !== 'favourites_count') {
+			return $orderby;
+		}
+		$order = strtoupper($query->get('order')) === 'ASC' ? 'ASC' : 'DESC';
+		return "COALESCE(jvb_fav_counts.fav_count, 0) {$order}";
+	}
+
+	/**
+	 * Join + order for WP_Term_Query when orderby => 'favourite_count'
+	 */
+	public function maybeApplyFavouriteTermOrderby(array $clauses, array $taxonomies, array $args): array
+	{
+		if (($args['orderby'] ?? '') !== 'favourites_count') {
+			return $clauses;
+		}
+		global $wpdb;
+		$table = $this->favourites->getFullTableName();
+		$type = jvbCheckBase($taxonomies[0] ?? '');
+
+		$clauses['join'] .= $wpdb->prepare(
+			" LEFT JOIN (SELECT target_id, COUNT(*) as fav_count FROM {$table} WHERE type = %s GROUP BY target_id) jvb_fav_counts ON jvb_fav_counts.target_id = t.term_id",
+			$type
+		);
+		$order = strtoupper($args['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';
+		$clauses['orderby'] = "ORDER BY COALESCE(jvb_fav_counts.fav_count, 0)";
+		$clauses['order'] = $order;
+
+		return $clauses;
+	}
 }
diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index a57b8a7..cf48409 100644
--- a/inc/managers/IconsManager.php
+++ b/inc/managers/IconsManager.php
@@ -43,7 +43,7 @@
 	private function __construct(string $source)
 	{
 		$this->source = $source;
-		$this->cache = Cache::for('icons_' . $source, WEEK_IN_SECONDS);
+		$this->cache = Cache::for('icons_' . $source);
 		$this->style = Site::icon();
 
 		$this->addMap();
@@ -448,7 +448,7 @@
 			$instance->archiveCurrentVersion($css);
 
 			if (file_put_contents($css_path, $css) !== false) {
-				Cache::touch('icons_' . $source);
+				Cache::for('icons_'.$source)->flush();
 				error_log("[IconsManager] Updated {$source}.css");
 			} else {
 				error_log("[IconsManager] Could not write {$source}.css");
@@ -486,7 +486,7 @@
 			$instance->archiveCurrentVersion($css);
 
 			if (file_put_contents($css_path, $css) !== false) {
-				Cache::touch('icons_' . $source);
+				Cache::for('icons_'.$source)->flush();
 				error_log("[IconsManager] Updated {$source}.css");
 			} else {
 				error_log("[IconsManager] Could not write {$source}.css");
@@ -649,7 +649,7 @@
 
 	public function registerStyle(): void
 	{
-		$timestamp = Cache::lastModified('icons_' . $this->source);
+		$timestamp = Cache::for('icons_'.$this->source)->getTimestamp();
 		$handle = 'jvb-icons-' . $this->source;
 
 		wp_register_style(
@@ -803,7 +803,7 @@
 				if (file_put_contents($css_path, $entry['css']) !== false) {
 					$this->icons = $entry['iconList'];
 					$this->saveIcons();
-					Cache::touch('icons_' . $this->source);
+					Cache::for('icons_'.$this->source)->flush();
 					return true;
 				}
 
@@ -824,7 +824,8 @@
 		}
 		$needsUpdate[$this->source] = true;
 		update_option(BASE.'icons_needs_update', $needsUpdate);
-		Cache::touch('icons_' . $this->source);
+
+		Cache::for('icons_'.$this->source)->flush();
 	}
 
 	public function mergeVersions(array $timestamps): bool
diff --git a/inc/managers/ImageGenerator.php b/inc/managers/ImageGenerator.php
index 1a808a2..dc6479c 100644
--- a/inc/managers/ImageGenerator.php
+++ b/inc/managers/ImageGenerator.php
@@ -21,6 +21,7 @@
  *
  * Generates artist feature cards as webp images with artist information
  * Uses Imagick with GD fallback and leverages FileUploadManager storage
+ * TODO: Integrate better with UploadManager.php
  */
 class ImageGenerator
 {
@@ -146,7 +147,7 @@
 
         // Get directory structure from FileUploadManager if available
         if ($this->fileManager) {
-            $user_dir = $this->fileManager->getUploadDirectory();
+            $user_dir = $this->fileManager->generateDirectory('image', $this->data);
             $relative_dir = str_replace($base_dir, '', dirname($file_path));
             $destination_dir = $base_dir . '/' . $relative_dir;
         } else {
@@ -252,7 +253,7 @@
             $user_data = get_userdata($this->data['user_id']);
             $this->outputFilename = $this->fileManager->generateFilename(
                 $title.'.webp',
-                $user_data
+                $this->data
             );
         } else {
             // Fallback filename generation
diff --git a/inc/managers/InvitationsManager.php b/inc/managers/InvitationsManager.php
index da6cec1..45178b4 100644
--- a/inc/managers/InvitationsManager.php
+++ b/inc/managers/InvitationsManager.php
@@ -2,10 +2,13 @@
 namespace JVBase\managers;
 
 use Exception;
+use JVBase\Instance;
 use JVBase\managers\queue\executors\InvitationExecutor;
 use JVBase\managers\queue\TypeConfig;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\Invitations;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -14,16 +17,22 @@
 
 class InvitationsManager
 {
+	use Instance;
 	protected array $inviteConfig;
 	protected CustomTable $table;
 	protected int $expiryDays = 14;
 	protected Cache $cache;
-	public function __construct()
+	protected Rest $route;
+
+	private function __construct()
 	{
 		$this->defineTable();
 		if (!isset($this->table)) {
 			return;
 		}
+		if (Site::membership() && Site::membership()->has('can_invite')) {
+			$this->route = Invitations::getInstance();
+		}
 		$this->setInviteConfig();
 		$this->cache = Cache::for('invitations')->user();
 		add_action('init', [$this, 'registerInvitationExecutors'], 5);
@@ -118,7 +127,7 @@
 	 */
 	public function registerInvitationExecutors(): void
 	{
-		$registry = JVB()->queue()->registry();
+		$registry = Queue::getInstance()->registry();
 		$executor = new InvitationExecutor();
 
 		// Create invitations - chunked at 20
@@ -214,7 +223,7 @@
 		}
 
 		// User must be owner or manager of the term
-		return JVB()->roles()->isManager($userID, $termID);
+		return Roles::getInstance()()->isManager($userID, $termID);
 	}
 
 	/**
@@ -554,9 +563,9 @@
 		}
 		$termText = implode('', $termContent);
 
-		$button = JVB()->email()->button($signupUrl, 'Join the Scene!');
-		$link = JVB()->email()->link($signupUrl);
-		$signature = JVB()->email()->signature();
+		$button = Emails::getInstance()->button($signupUrl, 'Join the Scene!');
+		$link = Emails::getInstance()->link($signupUrl);
+		$signature = Emails::getInstance()->signature();
 
 		$message = sprintf(
 			'<p>Hi %s!</p>
@@ -593,7 +602,7 @@
 			$signature
 		);
 
-		JVB()->email()->sendEmail($email, $subject, $message);
+		Emails::getInstance()->sendEmail($email, $subject, $message);
 	}
 
 	/**
@@ -671,7 +680,7 @@
 		$this->notifyInvitersOfAcceptance($invitation, $userID);
 
 		// Invalidate cache
-		Cache::for('invitations')->forget('user_' . $userID);
+		$this->cache->forget('user_' . $userID);
 
 		return true;
 	}
@@ -681,7 +690,7 @@
 	 */
 	protected function processTermMembership(int $userID, object $invitation): void
 	{
-		foreach (JVB()->roles()->getInvitableTaxonomies() as $taxonomy) {
+		foreach (Roles::getInstance()()->getInvitableTaxonomies() as $taxonomy) {
 			$column = 'to_' . $taxonomy;
 			if (isset($invitation->$column) && $invitation->$column) {
 				$termID = (int) $invitation->$column;
@@ -706,7 +715,7 @@
 		$userData = get_userdata($userID);
 
 		foreach ($inviters as $inviter) {
-			JVB()->notification()->addNotification(
+			NotificationManager::getInstance()->addNotification(
 				$inviter['user_id'],
 				'invitation_accepted',
 				[
@@ -732,7 +741,7 @@
 		);
 
 		// Clear cache after cleanup
-		Cache::for('invitations')->flush();
+		$this->cache->flush();
 	}
 
 	/*****************************************************************
@@ -760,7 +769,7 @@
 			$name
 		);
 
-		JVB()->email()->sendEmail($email, $subject, $content, 'INVITATION REVOKED');
+		Emails::getInstance()->sendEmail($email, $subject, $content, 'INVITATION REVOKED');
 	}
 
 	/**
diff --git a/inc/managers/KarmaManager.php b/inc/managers/KarmaManager.php
index 7b4cccc..d09f090 100644
--- a/inc/managers/KarmaManager.php
+++ b/inc/managers/KarmaManager.php
@@ -2,6 +2,10 @@
 namespace JVBase\managers;
 
 use Exception;
+use JVBase\managers\queue\Queue;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\ApprovalRoutes;
+use JVBase\rest\routes\VoteRoutes;
 use WP_Error;
 use WP_Post;
 use WP_Term;
@@ -23,15 +27,18 @@
 	protected string $references;
 	protected CustomTable $table;
 	protected static Cache $cache;
+	protected Rest $route;
 
 	private function __construct(string $key, ?string $references = null)
 	{
+		$this->route = VoteRoutes::getInstance();
 		$this->key = $key;
 		if (is_null($references)) {
 			$references = match($key){
-				'post'	=> 'post',
-				'term'	=> 'term',
-				'user'	=> 'user',
+				'post', 'posts'	=> 'post',
+				'term','terms', 'taxonomy'	=> 'term',
+				'user', 'users'	=> 'user',
+				'response', 'responses' => 'response',
 				default => false
 			};
 			if (!$references) {
@@ -60,12 +67,98 @@
 //				break;
 //		}
 		if (!isset(self::$cache)) {
-			self::$cache = Cache::for('user_karma')->connect('user')->user();
+			self::$cache = Cache::for('user_karma')->user();
+		}
+
+		$this->registerHooks();
+
+	}
+
+	protected function registerHooks():void
+	{
+		if ($this->references === 'post') {
+			add_filter('posts_join', [$this, 'maybeJoinKarmaOrderby'], 10, 2);
+			add_filter('posts_orderby', [$this, 'maybeApplyKarmaOrderby'], 10, 2);
+		} elseif ($this->references === 'term') {
+			add_filter('terms_clauses', [$this, 'maybeApplyKarmaTermOrderby'], 10, 3);
 		}
 
 		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
 	}
 
+	/**
+	 * Supported orderby keys: 'karma_upvotes', 'karma_downvotes', 'karma_score'
+	 */
+	protected function karmaOrderExpr(string $orderby, string $alias = 'jvb_karma'): ?string
+	{
+		return match ($orderby) {
+			'upvotes'   => "COALESCE({$alias}.upvotes, 0)",
+			'downvotes' => "COALESCE({$alias}.downvotes, 0)",
+			'karmic_score'     => "COALESCE({$alias}.upvotes, 0) - COALESCE({$alias}.downvotes, 0)",
+			default           => null,
+		};
+	}
+
+		public function maybeJoinKarmaOrderby(string $join, \WP_Query $query): string
+		{
+			if (!in_array($query->get('orderby'), ['upvotes', 'downvotes', 'karmic_score'])) {
+				return $join;
+			}
+
+			global $wpdb;
+			$table = $this->table->getFullTableName();
+			$content = $query->get('post_type') ?: 'post';
+
+			$join .= $wpdb->prepare(
+				" LEFT JOIN (SELECT item_id,
+				SUM(vote = 'up') as upvotes,
+				SUM(vote = 'down') as downvotes
+				FROM {$table} WHERE content = %s GROUP BY item_id) jvb_karma
+				ON jvb_karma.item_id = {$wpdb->posts}.ID",
+				$content
+			);
+			return $join;
+		}
+
+		public function maybeApplyKarmaOrderby(string $orderby, \WP_Query $query): string
+		{
+			$expr = $this->karmaOrderExpr($query->get('orderby'));
+			if (!$expr) {
+				return $orderby;
+			}
+			$order = strtoupper($query->get('order')) === 'ASC' ? 'ASC' : 'DESC';
+			return "{$expr} {$order}";
+		}
+
+		public function maybeApplyKarmaTermOrderby(array $clauses, array $taxonomies, array $args): array
+		{
+			$expr = $this->karmaOrderExpr($args['orderby'] ?? '');
+			if (!$expr) {
+				return $clauses;
+			}
+			global $wpdb;
+			$table = $this->table->getFullTableName();
+			$content = jvbNoBase($taxonomies[0] ?? '');
+
+			$clauses['join'] .= $wpdb->prepare(
+				" LEFT JOIN (SELECT item_id,
+				SUM(vote = 'up') as upvotes,
+				SUM(vote = 'down') as downvotes
+				FROM {$table} WHERE content = %s GROUP BY item_id) jvb_karma
+				ON jvb_karma.item_id = t.term_id",
+				$content
+			);
+			$clauses['orderby'] = "ORDER BY {$expr}";
+			$clauses['order'] = strtoupper($args['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';
+
+			return $clauses;
+		}
+
+	public function karmaTable():CustomTable
+	{
+		return $this->table;
+	}
+
 	public static function getInstance(string $key):KarmaManager|false
 	{
 		if (!isset(self::$instances[$key])) {
@@ -78,7 +171,7 @@
 	{
 		$key = sanitize_key($key);
 		if (!isset(self::$instances[$key])) {
-			self::$instances[$key] = new KarmaManager($key, $references);
+			self::$instances[$key] = new self($key, $references);
 		}
 
 		return self::$instances[$key];
@@ -175,7 +268,7 @@
 			'post'	=> get_post($itemID),
 			'term'	=> get_term($itemID, $based),
 			'user'	=> get_userdata($itemID),
-			'response' => JVB()->responses()->response->get(['id' => $itemID])??false,
+			'response' => Responses::getInstance()->responseExists($itemID),
 			default => false	//TODO if we do have custom tables, we'll have to set that up here
 		};
 	}
@@ -221,7 +314,7 @@
 		}
 		self::$cache->forget($userID);
 
-		JVB()->queue()->queueOperation(
+		Queue::getInstance()->queueOperation(
 			'karmic',
 			0,
 			[
@@ -292,16 +385,7 @@
 						update_user_meta($item['itemID'], BASE.'karma', $karma);
 						break;
 					case 'response':
-						JVB()->responses()->response->update(
-							[
-								'upvotes'	=> $upvotes,
-								'downvotes'	=> $downvotes,
-								'karma'		=> $karma
-							],
-							[
-								'id'	=> $item['itemID']
-							]
-						);
+						Responses::getInstance()->updateKarma($item['itemID'], $upvotes, $downvotes, $karma);
 						break;
 				}
 				$results[$item['itemID']] = true;
diff --git a/inc/managers/LoginManager.php b/inc/managers/Login.php
similarity index 96%
rename from inc/managers/LoginManager.php
rename to inc/managers/Login.php
index af45d49..c344023 100644
--- a/inc/managers/LoginManager.php
+++ b/inc/managers/Login.php
@@ -3,17 +3,19 @@
 
 use JVBase\base\Site;
 use JVBase\forms\TaxonomySelector;
-use JVBase\meta\Form;
+use JVBase\Instance;use JVBase\integrations\services\Cloudflare;use JVBase\meta\Form;
 
-use JVBase\registrar\Registrar;use WP_Error;
+use JVBase\registrar\Registrar;use JVBase\rest\routes\LoginRoutes;use WP_Error;
 use WP_User;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
 
-class LoginManager
+class Login
 {
+	use Instance;
+
 	protected ?Form $form = null;
 	protected Cache $cache;
 
@@ -23,7 +25,6 @@
 	protected array $fields = [];
 	protected ?string $action = null;
 	protected string $title = '';
-	protected static LoginManager $instance;
 
 	// Token handlers registry
 	protected array $messageHandlers = [];
@@ -36,12 +37,13 @@
 	];
 	private int $max_file_size = 5242880; // 5MB in bytes
 
+	private LoginRoutes $routes;
 
-	public function __construct()
+	protected function __construct()
 	{
-		self::$instance = $this;
-		$this->cache = Cache::for('login');
-		$this->cache->flush();
+		$this->routes = new LoginRoutes();
+//		$this->cache = Cache::for('login');
+//		$this->cache->flush();
 		// Initialize magic link support if enabled
 		if (Site::has('magic_link')) {
 			$this->initMagicLinkSupport();
@@ -68,10 +70,6 @@
 		add_action('user_register', [$this, 'saveRegistrationFields'], 50, 2);
 		add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeLoginSitemap'], 8, 1);
 	}
-	public static function getInstance():self
-	{
-		return self::$instance;
-	}
 
 	public function excludeLoginSitemap(array $ids): array
 	{
@@ -378,15 +376,7 @@
 			exit;
 		}
 		$this->setup();
-		$page = $this->cache->remember(
-				$this->getAction(),
-				function() {
-					return $this->renderPage();
-				},
-				5
-			);
-
-		echo $page;
+		echo $this->renderPage();
 		return '';
 	}
 	protected function renderPage() {
@@ -789,12 +779,12 @@
 		if (!Site::hasIntegration('cloudflare')) {
 			return;
 		}
-		JVB()->connect('cloudflare')->enqueueTurnstileScripts();
+		Cloudflare::getInstance()->enqueueTurnstileScripts();
 	}
 
 	protected function verifyTurnstile(): bool
 	{
-		if (!Site::hasIntegration('cloudflare')) {
+		if (!Site::hasIntegration('cloudflare') || Cloudflare::getInstance()->isSetUp()) {
 			return true; // Not enabled, pass verification
 		}
 
@@ -803,7 +793,7 @@
 			return false;
 		}
 
-		return JVB()->connect('cloudflare')->verifyTurnstile($token);
+		return Cloudflare::getInstance()->verifyTurnstile($token);
 	}
 
 	/************************************************************************
@@ -865,7 +855,7 @@
 
 	protected function maybeMagicLink(): void
 	{
-		if (!JVB()->magicLink() || !in_array($this->action, ['login', 'lostpassword'])) {
+		if (!MagicLinks::getInstance() || !in_array($this->action, ['login', 'lostpassword'])) {
 			return;
 		}
 		?>
@@ -1037,5 +1027,3 @@
 	}
 }
 
-// Initialize the login manager
-new LoginManager();
diff --git a/inc/managers/MagicLinkManager.php b/inc/managers/MagicLinks.php
similarity index 87%
rename from inc/managers/MagicLinkManager.php
rename to inc/managers/MagicLinks.php
index 30c11f4..3217948 100644
--- a/inc/managers/MagicLinkManager.php
+++ b/inc/managers/MagicLinks.php
@@ -3,6 +3,8 @@
 
 use JetBrains\PhpStorm\NoReturn;
 use JVBase\base\Site;
+use JVBase\Instance;
+use JVBase\integrations\services\Cloudflare;
 use JVBase\meta\Form;
 use JVBase\ui\Tabs;
 use WP_User;
@@ -18,10 +20,12 @@
  * NOTE: Login form integration is now handled by LoginManager.php
  * This class focuses solely on magic link generation and verification
  */
-class MagicLinkManager
+class MagicLinks
 {
+	use Instance;
 	protected Cache $cache;
 	protected Cache $referral_cache;
+	protected Cache $rateLimitCache;
 
 	// Token settings
 	protected int $token_expiry = 900; // 15 minutes in seconds
@@ -36,8 +40,9 @@
 
 	public function __construct()
 	{
-		$this->cache = Cache::for('magic_links', $this->token_expiry);
-		$this->referral_cache = Cache::for('referral_magic_links', 14 * DAY_IN_SECONDS);
+		$this->cache = Cache::for('magic_links',null, $this->token_expiry);
+		$this->referral_cache = Cache::for('referral_magic_links', null, 14 * DAY_IN_SECONDS);
+		$this->rateLimitCache = Cache::for('magic_link_rate_limit', null, $this->rate_limit_window);
 
 		// Hook into WordPress auth flow
 		add_action('template_redirect', [$this, 'handleMagicLinkClick']);
@@ -145,20 +150,15 @@
 	 */
 	protected function checkRateLimit(string $email): bool|WP_Error
 	{
-		$cache_key = 'rate_limit_' . md5($email);
-		$attempts = $this->cache->get($cache_key);
+		$key = md5($email);
+		$attempts = $this->rateLimitCache->get($key);
 
 		if (!$attempts) {
-			$attempts = ['count' => 0, 'timestamp' => time()];
-		}
-
-		// Reset counter if window has passed
-		if (time() - $attempts['timestamp'] > $this->rate_limit_window) {
-			$attempts = ['count' => 0, 'timestamp' => time()];
+			$attempts = 1;
 		}
 
 		// Check if limit exceeded
-		if ($attempts['count'] >= $this->max_attempts_per_hour) {
+		if ($attempts >= $this->max_attempts_per_hour) {
 			return new WP_Error(
 				'rate_limit_exceeded',
 				'Too many magic link requests. Please try again in an hour.'
@@ -166,8 +166,8 @@
 		}
 
 		// Increment counter
-		$attempts['count']++;
-		$this->cache->set($cache_key, $attempts, $this->rate_limit_window);
+		$attempts++;
+		$this->rateLimitCache->set($key, $attempts);
 
 		return true;
 	}
@@ -200,7 +200,7 @@
 		$subject = 'Sign in to ' . get_bloginfo('name');
 		$message = $this->getLoginEmailTemplate($user->display_name, $magic_url);
 
-		$sent = JVB()->email()->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name'));
+		$sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Log in to '. get_bloginfo('name'));
 
 		return $sent ? true : new WP_Error('email_failed', 'Failed to send magic link');
 	}
@@ -232,7 +232,7 @@
 		$subject = 'Complete your ' . get_bloginfo('name') . ' registration';
 		$message = $this->getSignupEmailTemplate($context['name'] ?? '', $magic_url);
 
-		$sent = JVB()->email()->sendEmail($email, $subject, $message, 'Complete Registration');
+		$sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Complete Registration');
 
 		return $sent ? true : new WP_Error('email_failed', 'Failed to send signup link');
 	}
@@ -267,7 +267,7 @@
 		$subject = (array_key_exists('subject', $context) && $context['subject'] !== '') ? $context['subject'] : $referrer_name . ' invited you to join ' . get_bloginfo('name');
 		$message = $this->getReferralEmailTemplate($context['name'] ?? '', $referrer_name, $magic_url, $reward_text, $context);
 
-		$sent = JVB()->email()->sendEmail($email, $subject, $message, 'Accept Invitation');
+		$sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Accept Invitation');
 
 		return $sent ? true : new WP_Error('email_failed', 'Failed to send referral link');
 	}
@@ -295,7 +295,7 @@
 		$subject = 'Reset your password';
 		$message = $this->getResetEmailTemplate($user->display_name, $magic_url);
 
-		$sent = JVB()->email()->sendEmail($email, $subject, $message, 'Reset Password');
+		$sent = Emails::getInstance()->sendEmail($email, $subject, $message, 'Reset Password');
 
 		return $sent ? true : new WP_Error('email_failed', 'Failed to send reset link');
 	}
@@ -436,7 +436,7 @@
 			$this->processSignup($token_data);
 		}
 
-		$role = JVB()->referrals()->getRole();
+		$role = Referrals::getInstance()->getRole();
 		$pass = wp_generate_password(20, true, true);
 		$name = sanitize_text_field($token_data['name']);
 		$user_id = wp_insert_user([
@@ -515,31 +515,31 @@
 
 	protected function getLoginEmailTemplate(string $name, string $magic_url): string
 	{
-		$content = JVB()->email()->h2('Hey ' . esc_html($name) . '!');
+		$content = Emails::getInstance()->h2('Hey ' . esc_html($name) . '!');
 		$content .= '<p>Click the button below to sign in to your account instantly - no password needed!</p>';
-		$content .= JVB()->email()->button($magic_url, 'Sign In Now');
+		$content .= Emails::getInstance()->button($magic_url, 'Sign In Now');
 		$content .= '<p>Or copy and paste this link into your browser:</p>';
-		$content .= JVB()->email()->link($magic_url);
-		$content .= JVB()->email()->divider();
+		$content .= Emails::getInstance()->link($magic_url);
+		$content .= Emails::getInstance()->divider();
 		$content .= '<p>If you didn\'t request this, you can safely ignore this email. This link expires in 15 minutes.</p>';
 		return $content;
 	}
 
 	protected function getSignupEmailTemplate(string $name, string $magic_url): string
 	{
-		$content = JVB()->email()->h2('Welcome' . ($name ? ', ' . esc_html($name) : '') . '!');
+		$content = Emails::getInstance()->h2('Welcome' . ($name ? ', ' . esc_html($name) : '') . '!');
 		$content .= '<p>Click the button below to complete your registration and access your account!</p>';
-		$content .= JVB()->email()->button($magic_url, 'Complete Registration');
+		$content .= Emails::getInstance()->button($magic_url, 'Complete Registration');
 		$content .= '<p>Or copy and paste this link:</p>';
-		$content .= JVB()->email()->link($magic_url);
-		$content .= JVB()->email()->spacer(10);
+		$content .= Emails::getInstance()->link($magic_url);
+		$content .= Emails::getInstance()->spacer(10);
 		$content .= '<p><small>This link expires in 24 hours.</small></p>';
 		return $content;
 	}
 
 	protected function getReferralEmailTemplate(string $name, string $referrer_name, string $magic_url, string $reward_text, array $context): string
 	{
-		$content = JVB()->email()->h2('Hey' . ($name ? ' ' . esc_html($name) : '') . '!');
+		$content = Emails::getInstance()->h2('Hey' . ($name ? ' ' . esc_html($name) : '') . '!');
 		$content .= sprintf(
 			'<p><strong>%s</strong> thinks you\'d love %s!</p>',
 			esc_html($referrer_name),
@@ -547,20 +547,20 @@
 		);
 
 		if (!empty($context['message'])) {
-			$content .= JVB()->email()->callout(nl2br(esc_html($context['message'])));
+			$content .= Emails::getInstance()->callout(nl2br(esc_html($context['message'])));
 		}
 
 		if ($reward_text) {
-			$content .= JVB()->email()->alert(
+			$content .= Emails::getInstance()->alert(
 				'<strong>Special Welcome Offer:</strong> ' . esc_html($reward_text),
 				'success'
 			);
 		}
 
-		$content .= JVB()->email()->button($magic_url, 'Join Now');
+		$content .= Emails::getInstance()->button($magic_url, 'Join Now');
 		$content .= '<p>Or copy and paste this link:</p>';
-		$content .= JVB()->email()->link($magic_url);
-		$content .= JVB()->email()->spacer(10);
+		$content .= Emails::getInstance()->link($magic_url);
+		$content .= Emails::getInstance()->spacer(10);
 		$content .= '<p><small>This invitation expires in 14 days.</small></p>';
 
 		return $content;
@@ -569,7 +569,7 @@
 	public function renderForm():string
 	{
 		ob_start();
-		JVB()->connect('cloudflare')->renderTurnstile();
+		Cloudflare::getInstance()->renderTurnstile();
 		$turnstile = ob_get_clean();
 		global $wp;
 
@@ -599,7 +599,7 @@
 //			wp_login_url()
 		);
 
-		 $login = LoginManager::getInstance()->renderLoginForm('login', get_home_url(null,'/'.$wp->request));
+		 $login = Login::getInstance()->renderLoginForm('login', get_home_url(null,'/'.$wp->request));
 
 		 $tabs = new Tabs();
 		 $tabs->addTab('magic')
diff --git a/inc/managers/NewsRelationships.php b/inc/managers/NewsRelationships.php
index 5156256..0f6b7a9 100644
--- a/inc/managers/NewsRelationships.php
+++ b/inc/managers/NewsRelationships.php
@@ -15,8 +15,7 @@
  */
 class NewsRelationships
 {
-    private string $table_name;
-    private Cache $cache;
+	private CustomTable $relationships;
 
     public function __construct()
     {
@@ -626,7 +625,7 @@
         $wpdb->query("TRUNCATE TABLE {$this->table_name}");
 
 
-        JVB()->queue()->queueOperation(
+        Queue::getInstance()->queueOperation(
             'rebuild_news',
             0,
             [
diff --git a/inc/managers/NotificationManager.php b/inc/managers/NotificationManager.php
index 99a771f..9b12b4f 100644
--- a/inc/managers/NotificationManager.php
+++ b/inc/managers/NotificationManager.php
@@ -1,10 +1,13 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
 use JVBase\managers\Notifications\Content;
 use JVBase\managers\Notifications\EmailDigests;
 use JVBase\managers\Notifications\Notifications;
 use JVBase\managers\Notifications\Preferences;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\NotificationsRoutes;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -18,13 +21,16 @@
  */
 class NotificationManager
 {
+	use Instance;
    protected Notifications $notifications;
    protected Content $content;
    protected EmailDigests $digest;
    protected Preferences $preferences;
-
-   public function __construct()
+	protected Rest $route;
+   private function __construct()
    {
+	   $this->route = NotificationsRoutes::getInstance();
+
 	   $this->notifications = new Notifications();
 	   $this->content = new Content();
 	   $this->digest = new EmailDigests();
diff --git a/inc/managers/Notifications/Content.php b/inc/managers/Notifications/Content.php
index 776d585..06c4a55 100644
--- a/inc/managers/Notifications/Content.php
+++ b/inc/managers/Notifications/Content.php
@@ -18,7 +18,7 @@
 	protected static Cache $cache;
 	public function __construct()
 	{
-		self::$cache = Cache::for('content_notifications')->connect('post');
+		self::$cache = Cache::for('content_notifications', 'post');
 	}
 	public static function getSinceDate(string $date):string
 	{
diff --git a/inc/managers/Notifications/EmailDigests.php b/inc/managers/Notifications/EmailDigests.php
index 841ffaf..27ef2e6 100644
--- a/inc/managers/Notifications/EmailDigests.php
+++ b/inc/managers/Notifications/EmailDigests.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
+use JVBase\managers\Emails;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 use WP_Error;
@@ -122,7 +123,7 @@
 			return;
 		}
 
-		JVB()->queue()->add(
+		Queue::getInstance()->add(
 			'email_notification_digest',
 			0,
 			[
@@ -142,7 +143,7 @@
 		if (!in_array($frequency, ['never', 'daily', 'weekly', 'monthly'])) {
 			return [];
 		}
-		return JVB()->notification()->getUsersByFrequency($frequency);
+		return NotificationManager::getInstance()->getUsersByFrequency($frequency);
 	}
 
 	public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
@@ -176,10 +177,10 @@
 
 	protected function generateUserDigest(int $userID, string $frequency):string|false
 	{
-		$subscription = JVB()->notification()->getUserSubscriptions($userID, $frequency);
+		$subscription = NotificationManager::getInstance()->getUserSubscriptions($userID, $frequency);
 
 		//TODO
-		$notifications = JVB()->notification()->getUserNotifications($userID);
+		$notifications = NotificationManager::getInstance()->getUserNotifications($userID);
 
 		if (empty($subscription) && empty($notifications)) {
 			return false;
@@ -281,18 +282,18 @@
 			$image = sprintf(
 				'<a href="%s">%s</a>',
 				$url,
-				JVB()->email()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true))
+				Emails::getInstance()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true))
 			);
 			$t = sprintf(
 				'<a href="%s">%s</a>',
 				$url,
 				$m->get('post_title')
 			);
-			$output[] = JVB()->email()->card($image, $t);
+			$output[] = Emails::getInstance()->card($image, $t);
 		}
 
-		$final = JVB()->email()->button($goTo, 'Want to See More?');
-		return JVB()->email()->grid($output, 3, 'New from '.$title,'',$final);
+		$final = Emails::getInstance()->button($goTo, 'Want to See More?');
+		return Emails::getInstance()->grid($output, 3, 'New from '.$title,'',$final);
 	}
 
 	protected function getTermUpdates(int $termID, string $type, string $frequency):string|false
@@ -373,17 +374,17 @@
 			$image = sprintf(
 				'<a href="%s">%s</a>',
 				$url,
-				JVB()->email()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true))
+				Emails::getInstance()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true))
 			);
 			$t = sprintf(
 				'<a href="%s">%s</a>',
 				$url,
 				$m->get('post_title')
 			);
-			$output[] = JVB()->email()->card($image, $t);
+			$output[] = Emails::getInstance()->card($image, $t);
 		}
 
-		$final = JVB()->email()->button($goTo, 'See More');
-		return JVB()->email()->grid($output, 3, 'New in '.$title, '', $final);
+		$final = Emails::getInstance()->button($goTo, 'See More');
+		return Emails::getInstance()->grid($output, 3, 'New in '.$title, '', $final);
 	}
 }
diff --git a/inc/managers/Notifications/index.php b/inc/managers/Notifications/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/Notifications/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/OperationQueue.php b/inc/managers/OperationQueue.php
index 7ea6aaa..949e5c6 100644
--- a/inc/managers/OperationQueue.php
+++ b/inc/managers/OperationQueue.php
@@ -90,7 +90,7 @@
 	}
     public function registerAdminAction():void
     {
-        $admin = JVB()->admin();
+        $admin = AdminPages::getInstance();
         $admin->registerAction(
             'Restart Stuck Operations',
             'restart-stuck-operations',
@@ -1131,7 +1131,7 @@
 
         $message .= "Please check the error logs for more details.";
 
-        return JVB()->email()->sendEmail($admin_email, $subject, $message);
+        return Emails::getInstance()->sendEmail($admin_email, $subject, $message);
     }
 
     /**
@@ -1650,36 +1650,36 @@
 
 		$success_rate = round(($total_success / max(1, $total_ops)) * 100, 1);
 
-		$message = JVB()->email()->h1('Daily Queue Performance Report');
+		$message = Emails::getInstance()->h1('Daily Queue Performance Report');
 		$message .= sprintf('<p>Report for <strong>%s</strong></p>', $yesterday);
 
 		// Summary stats in grid
 		$stats = [
-			JVB()->email()->stat($total_ops, 'Operations'),
-			JVB()->email()->stat($total_success, 'Successful', '✓'),
-			JVB()->email()->stat($total_failed, 'Failed', $total_failed > 0 ? 'âš ' : ''),
-			JVB()->email()->stat($success_rate . '%', 'Success Rate')
+			Emails::getInstance()->stat($total_ops, 'Operations'),
+			Emails::getInstance()->stat($total_success, 'Successful', '✓'),
+			Emails::getInstance()->stat($total_failed, 'Failed', $total_failed > 0 ? 'âš ' : ''),
+			Emails::getInstance()->stat($success_rate . '%', 'Success Rate')
 		];
-		$message .= JVB()->email()->grid($stats, 4);
+		$message .= Emails::getInstance()->grid($stats, 4);
 
-		$message .= JVB()->email()->spacer(20);
+		$message .= Emails::getInstance()->spacer(20);
 
 		// Alert if success rate is low
 		if ($success_rate < 90) {
-			$message .= JVB()->email()->alert(
+			$message .= Emails::getInstance()->alert(
 				sprintf('Success rate of %s%% is below the 90%% threshold', $success_rate),
 				'warning'
 			);
 		}
 
-		$message .= JVB()->email()->h2('Details by Operation Type');
+		$message .= Emails::getInstance()->h2('Details by Operation Type');
 
 		// Details for each operation type
 		foreach ($metrics as $metric) {
 			$details = [];
 			$details[] = ['label' => 'Total', 'value' => $metric->total_operations];
-			$details[] = ['label' => 'Success', 'value' => JVB()->email()->badge($metric->successful_operations, 'success')];
-			$details[] = ['label' => 'Failed', 'value' => $metric->failed_operations > 0 ? JVB()->email()->badge($metric->failed_operations, 'error') : '0'];
+			$details[] = ['label' => 'Success', 'value' => Emails::getInstance()->badge($metric->successful_operations, 'success')];
+			$details[] = ['label' => 'Failed', 'value' => $metric->failed_operations > 0 ? Emails::getInstance()->badge($metric->failed_operations, 'error') : '0'];
 
 			if ($metric->average_duration) {
 				$details[] = ['label' => 'Avg Duration', 'value' => round($metric->average_duration, 2) . 's'];
@@ -1692,8 +1692,8 @@
 				$details[] = ['label' => 'Peak Memory', 'value' => $memory_mb . ' MB'];
 			}
 
-			$message .= JVB()->email()->card(
-				JVB()->email()->table($details),
+			$message .= Emails::getInstance()->card(
+				Emails::getInstance()->table($details),
 				esc_html($metric->type)
 			);
 		}
@@ -1701,16 +1701,16 @@
 		// Current queue status
 		$pending_count = $this->getCurrentQueueSize();
 		if ($pending_count > 0) {
-			$message .= JVB()->email()->spacer(20);
-			$message .= JVB()->email()->notice(
+			$message .= Emails::getInstance()->spacer(20);
+			$message .= Emails::getInstance()->notice(
 				sprintf('<strong>Current Queue:</strong> %d operations pending', $pending_count)
 			);
 		}
 
-		$message .= JVB()->email()->spacer(20);
-		$message .= JVB()->email()->button(admin_url('admin.php?page=jvb-queue'), 'View Queue Dashboard');
+		$message .= Emails::getInstance()->spacer(20);
+		$message .= Emails::getInstance()->button(admin_url('admin.php?page=jvb-queue'), 'View Queue Dashboard');
 
-		JVB()->email()->sendEmail($admin_email, $subject, $message, 'QUEUE REPORT');
+		Emails::getInstance()->sendEmail($admin_email, $subject, $message, 'QUEUE REPORT');
 	}
 
     /**
diff --git a/inc/managers/ReferralManager.php b/inc/managers/Referrals.php
similarity index 85%
rename from inc/managers/ReferralManager.php
rename to inc/managers/Referrals.php
index 626d036..71769f7 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/Referrals.php
@@ -1,7 +1,13 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
+use JVBase\integrations\services\Cloudflare;
 use JVBase\meta\Form;
+use JVBase\meta\Meta;
+use JVBase\meta\Sanitizer;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\ReferralRoutes;
 use JVBase\ui\CRUDSkeleton;
 use JVBase\ui\Tabs;
 use JVBase\base\Site;
@@ -18,9 +24,10 @@
  * Handles user referral codes, tracking, rewards, and reporting.
  * Keeps referrals separate from invitations system.
  */
-class ReferralManager
+class Referrals
 {
-	protected MagicLinkManager $magic_link;
+	use Instance;
+	protected MagicLinks $magic_link;
 	protected Cache $cache;
 	protected Cache $requestCache;
 	protected Cache $statsCache;
@@ -33,6 +40,7 @@
 	protected CustomTable $janeClients;
 	protected CustomTable $rewards;
 	protected CustomTable $treatments;
+	protected Rest $route;
 
 	// Default reward settings
 	protected array $default_settings = [
@@ -48,10 +56,11 @@
 
 	protected array $settings;
 
-	public function __construct()
+	private function __construct()
 	{
+		$this->route = ReferralRoutes::getInstance();
 		$this->defineTables();
-		$this->role = Site::getDefaultReferralRole();
+		$this->role = Site::getReferralRole();
 		$this->default_settings['referral_role'] = $this->role;
 
 		$this->cache = Cache::for('referrals', WEEK_IN_SECONDS);
@@ -88,9 +97,6 @@
 		// Schedule cron jobs for reports
 		$this->registerCronJobs();
 
-		// Add admin bar label for referral page
-		add_action('admin_bar_menu', [$this, 'addReferralPageLabel'], 999);
-
 		// Add admin notice to referral page edit screen
 		add_action('admin_notices', [$this, 'showReferralPageNotice']);
 
@@ -102,6 +108,8 @@
 		add_action('admin_init', [$this, 'registerSettings']);
 		// Handle admin page form submission
 		add_filter('jvb_admin_page_submission', [$this, 'handleAdminSubmission'], 10, 3);
+
+		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
 	}
 
 	protected function defineTables():void
@@ -118,14 +126,15 @@
 
 			$table->setColumns([
 				'id'		=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
-				'from_user'	=> "{$table->getUserIDType()} NOT NULL",
-				'to_user'	=> "{$table->getUserIDType()} NOT NULL",
-				'to_name'	=> 'varchar(255) NOT NULL',
-				'to_email'	=> 'varchar(255) NOT NULL',
-				'to_phone'	=> 'varchar(50) NOT NULL',
+				'from_user'	=> "{$table->getUserIDType()} NOT NULL",		//The person doing the referring
+				'to_user'	=> "{$table->getUserIDType()} DEFAULT NULL",	//The person being referred
+				'to_name'	=> 'varchar(255) DEFAULT NULL',
+				'to_email'	=> 'varchar(255) DEFAULT NULL',
+				'to_phone'	=> 'varchar(50) DEFAULT NULL',
 				'referral_code'=> 'varchar(50) NOT NULL',
-				'status'	=> "ENUM('pending', 'consulted', 'treated', 'cancelled') DEFAULT 'pending'",
+				'status'	=> "ENUM('invited', 'pending', 'consulted', 'treated', 'cancelled') DEFAULT 'pending'",
 				'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+				'last_invited'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
 				'consulted_at'=> 'datetime DEFAULT NULL',
 				'treated_at'=> 'datetime DEFAULT NULL',
 				'treatment_count' => 'int DEFAULT 0',
@@ -134,8 +143,9 @@
 
 			$table->setKeys([
 				['key' => 'PRIMARY', 'value' => 'id'],
-				['key' => 'UNIQUE', 'value' => '(`to_user`)'],
+//				['key' => 'UNIQUE', 'value' => '(`to_user`)'],
 				'from_user (`from_user`)',
+				'to_user (`to_user`)',
 				'status (`status`)',
 				'code (`referral_code`)',
 				'date (`created_at`)',
@@ -329,8 +339,8 @@
 			'jvb-data-store',
 		];
 
-		if (Site::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
-			JVB()->connect('cloudflare')->enqueueTurnstileScripts();
+		if (Site::hasIntegration('cloudflare') && Cloudflare::getInstance()->isSetUp()) {
+			Cloudflare::getInstance()->enqueueTurnstileScripts();
 		}
 		if (is_singular(BASE.'dash')) {
 			$requirements[] = 'jvb-form';
@@ -489,109 +499,140 @@
 	 *
 	 * @param int $user_id
 	 * @param array $userData
-	 * @return bool;
+	 * @return void
 	 */
-	public function processReferral(int $user_id, array $userData): bool
+	public function processReferral(int $user_id, array $userData):void
 	{
-		$referral = $this->referrals->get(['to_user' => $user_id]);
 
-		if (empty($referral)) {
-			$referral = $this->referrals->get(['to_email' => $userData['email']]);
+		if (!array_key_exists('ref', $_POST) || empty($_POST['ref'])) {
+			return;
 		}
-		if (empty($referral)) {
-			// Check session/cookie if not in meta
-			if (session_status() === PHP_SESSION_NONE) {
-				session_start();
-			}
-			$referral_code = $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? '';
-			if (!empty ($referral_code)) {
-				$referral = [
-					'to_user'	=> $user_id,
-					'referral_code'	=> $referral_code,
-					'to_email'		=> $userData['user_email']
-				];
-			}
+		$code = sanitize_text_field($_POST['ref']);
+		$referredBy = $this->codes->pluck('user_id', ['code' => $code]);
+		if (!empty($referredBy)) {
+			$this->createReferralFromID($referredBy[0], $user_id, $code);
 		}
-
-		if (empty($referral)) {
-			return false; // No referral code - regular registration
-		}
-
-		// Find the from_user
-		$from_user = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]);
-		if (empty($from_user)) {
-			//This should not happen, but whatever
-			return false;
-		}
-
-		// Check if this email already has a referral record
-		if ($this->isEmailInvited($referral['to_email'])) {
-			return false;
-		}
-
-		$from_user = $from_user[0];
-		$record = $this->referrals->findOrCreate([
-			'to_user'	=> $user_id,
-			'referral_code'	=> $referral['referral_code'],
-		], [
-			'from_user'		=> $from_user,
-			'to_email'		=> $referral['to_email'],
-			'to_name'		=> $userData['first_name'],
-//			'to_phone'		=>
-			'status'		=> 'pending'
-		]);
-
-		if (!$record) {
-			error_log('[ReferralManager]::processReferral Could not update record for user: '.print_r($referral, true));
-			return false;
-		}
-
-
-		// Clean up temp data
-		delete_user_meta($user_id, BASE . 'pending_referral_code');
-		if (isset($_SESSION[BASE . 'referral_code'])) {
-			unset($_SESSION[BASE . 'referral_code']);
-		}
-		if (isset($_COOKIE[BASE . 'referral_code'])) {
-			setcookie(BASE . 'referral_code', '', time() - 3600, '/');
-		}
-
-		// Clear caches
-		$this->cache->flush();
-
-		// Fire action for tracking
-		do_action('jvb_referral_processed', $user_id, $from_user->ID, $referral['referral_code']);
-
-		// Send notification to from_user
-		$this->sendReferrerNotification($from_user->ID, $userData['display_name']);
-		return true;
 	}
 
 	/**
 	 * Create a referral record in the database
 	 *
-	 * @param int $from_user_id
-	 * @param int $to_user_id
-	 * @param string $code
-	 * @return int|false
+	 * @param int $from_user_id The person sending the referral
+	 * @param string $name  The person who was referred
+	 * @param string $email The person who was referred's email
+	 * @param ?string $phone The person who was referred's phone (optional)
+	 * @param ?string $code The person sending the referral's custom code
+	 * @return array
 	 */
-	public function createReferral(int $from_user_id, int $to_user_id, string $code)
+	public function createReferralFromFields(int $from_user_id, string $name, string $email, ?string $phone = null, ?string $code = null):array
 	{
-		$user = get_user_by('ID', $to_user_id);
+		if (!get_userdata($from_user_id)) {
+			return [
+				'success'	=> false,
+				'message'	=> 'From user does not exist',
+			];
+		}
 
-		return $this->referrals->findOrCreate([
-			[
-				'to_user' => $to_user_id,
-			],
-			[
-				'from_user' => $from_user_id,
-				'to_name' => $user->display_name,
-				'to_email' => $user->user_email,
-				'to_phone' => get_user_meta($to_user_id, BASE . 'phone', true) ?: '',
-				'referral_code' => $code,
-				'status' => 'pending',
-			]
-		]);
+		if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
+			if(email_exists($email)) {
+				return [
+					'success'	=> false,
+					'message'	=> 'Email already exists',
+				];
+			}
+		}
+		if ($phone && !empty($this->referrals->pluck('to_phone', ['to_phone' => Sanitizer::sanitizePhone($phone)]))) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Phone already used'
+			];
+		}
+		$insert = [
+			'from_user'		=> $from_user_id,
+			'referral_code'	=> is_null($code)? $this->getUserReferralCode($from_user_id) : sanitize_text_field($code),
+			'to_name'		=> $name,
+			'to_email'		=> $email,
+			'status'		=> 'invited',
+		];
+		if ($phone) {
+			$insert['to_phone'] = $phone;
+		}
+
+		$created = $this->referrals->insert($insert);
+		$this->statsCache->forget($from_user_id);
+		return [
+			'success'	=> (bool) $created,
+			'message'	=> 'Referral created'
+		];
+	}
+	/**
+	 * Create a referral record in the database
+	 *
+	 * @param int $from_user_id The person sending the referral
+	 * @param int $to_user_id The person who was referred
+	 * @param ?string $code Custom code, if not default
+	 * @return array
+	 */
+	public function createReferralFromID(int $from_user_id, int $to_user_id, ?string $code = null):array
+	{
+		$user = get_userdata($to_user_id);
+
+		$code = !is_null($code) ? sanitize_text_field($code) : $this->getUserReferralCode($to_user_id);
+
+		$where = [
+			'to_email'	=> $user->user_email,
+			'to_name'	=> $user->first_name ?: $user->display_name
+		];
+		$phone = false;
+		if (array_key_exists('phone', $_POST) && !empty($_POST['phone'])) {
+			$phone = Sanitizer::sanitizePhone($_POST['phone']);
+			if (!empty($phone)) {
+				$where = [
+					'to_phone'	=> $phone,
+				];
+			}
+		} elseif (!empty(Meta::forUser($to_user_id)->get('phone'))) {
+			$phone = Meta::forUser($to_user_id)->get('phone');
+		}
+		$where = [
+			'OR'	=> $where,
+			'status' => 'invited'
+		];
+
+		$update = [
+			'from_user'	=> $from_user_id,
+			'to_user'	=> $to_user_id,
+			'to_name'	=> $user->first_name ?: $user->display_name,
+			'to_email'	=> $user->user_email,
+			'status'	=> 'pending',
+			'referral_code'	=> $code
+		];
+		if ($phone) {
+			$update ['to_phone'] = $phone;
+		}
+		$existing = $this->referrals->get($where);
+		if ($existing) {
+			if ($existing->from_user != $from_user_id) {
+				$this->referrals->update([
+					'status'	=> 'cancelled',
+				], $where);
+
+			} else {
+				$updated = $this->referrals->update($update, $where);
+				$this->sendReferrerNotification($from_user_id, $update['to_name']);
+				return [
+					'success'	=> (bool) $updated,
+					'message'	=> $updated ? 'Successfully created referral' : 'Referral could not be updated'
+				];
+			}
+		}
+		$created = $this->referrals->insert($update);
+
+		$this->statsCache->forget($from_user_id);
+		return [
+			'success'	=> (bool) $created,
+			'message'	=> $created ? 'Successfully created referral' : 'Referral could not be added'
+		];
 	}
 
 	/**
@@ -714,35 +755,49 @@
 			'order' => 'DESC'
 		];
 
+		$where = [
+			'from_user'	=> $user_id,
+		];
+
+		foreach ($this->referrals->getColumnKeys() as $col) {
+			if (array_key_exists($col, $args)) {
+				if ($col === 'status' && $args[$col] !== 'all') {
+					$where[$col] = $args[$col];
+				} else {
+					$where[$col] = $args[$col];
+				}
+			}
+		}
+
+
 		$args = wp_parse_args($args, $defaults);
 		$args['status'] = strtolower($args['status']);
 		$tableArgs = [
-			'where' => [
-				'from_user'	=> $user_id,
-			],
+			'where' 	=> $where,
 			'per_page'		=> $args['per_page'],
 			'page'		=> $args['page'],
 			'orderby'	=> $args['orderby'],
 			'order'		=> $args['order']
 		];
-		if (in_array($args['status'], ['pending', 'consulted', 'treated', 'cancelled'])){
+
+		if ($args['status'] !== 'all'){
 			$tableArgs['status'] = $args['status'];
 		}
-		$referrals = $this->referrals->getMany($tableArgs);
+		$referrals = $this->referrals->getMany($tableArgs, false);
 
 		return array_map(function($referral) {
-			$last_invite = get_transient('referral_last_invite_' . md5($referral->to_email));
-			$can_resend = !$last_invite || (time() - $last_invite) > WEEK_IN_SECONDS;
+			$can_resend = $this->checkCanResendEmail($referral->id);
 			$status = match($referral->status) {
 				'consulted' => 'Awaiting Treatment',
 				'treated'	=> 'Rewarded!',
+				'invited'	=> 'Invited, but has not used',
 				default => 'Pending',
 			};
 			return [
 				'id'			=> $referral->id,
 				'to_name'	=> $referral->to_name,
 				'to_email'	=> $referral->to_email,
-				'referred_at'	=> JVB()->routes('referral')->formatTimestamp($referral->referred_at),
+				'referred_at'	=> ReferralRoutes::getInstance()->formatTimestamp($referral->referred_at),
 				'referral_status'=> $status,
 				'can_resend'	=> $can_resend
 			];
@@ -750,6 +805,17 @@
 
 	}
 
+	protected function checkCanResendEmail(int|object $referral): bool
+	{
+		if (is_int($referral)) {
+			$referral = $this->referrals->get(['id'=>$referral]);
+			if (!$referral) {
+				return false;
+			}
+		}
+		return (time() - $referral->updated_at) > WEEK_IN_SECONDS;
+	}
+
 	/**
 	 * Get referral statistics for a user
 	 *
@@ -761,25 +827,15 @@
 		return $this->statsCache->remember(
 			$user_id,
 			function() use ($user_id) {
-				$stats = $this->referrals->queryResults(
-					"SELECT
-                    COUNT(*) as code_used,
-                    SUM(CASE WHEN status IN ('consulted', 'treated') THEN 1 ELSE 0 END) as consultations,
-                    SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treatments,
-                    SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
-                FROM {table}
-                WHERE from_user = %d",
-					[$user_id],
-					ARRAY_A
-				);
-				$stats = $stats[0] ?? [];
-
-				$rewards = $this->rewards->queryVar(
-					"SELECT SUM(amount) FROM {table} WHERE user_id = %d AND reward_type = 'from_user'",
-					[$user_id]
-				);
-
-				$stats['total_rewards'] = floatval($rewards ?? 0);
+				$stats = [
+					'total'		=> $this->referrals->count(['user_id' => $user_id]),
+					'invited'	=> $this->referrals->count(['user_id' => $user_id, 'status' => 'invited']),
+					'pending' 	=> $this->referrals->count(['user_id' => $user_id, 'status' => 'pending']),
+					'consulted'	=>$this->referrals->count(['user_id' => $user_id, 'status' => 'consulted']),
+					'treated' 	=> $this->referrals->count(['user_id' => $user_id, 'status' => 'treated']),
+					'total_rewards'	=> $this->rewards->sum('amount', ['user_id' => $user_id]),
+					'available_rewards'	=> $this->rewards->sum('amount', ['user_id' => $user_id, 'redeemed_at' => ['NOT NULL', null]]),
+				];
 				$stats['user_id'] = $user_id;
 				return $stats;
 			}
@@ -852,15 +908,15 @@
 			return;
 		}
 
-		$content = JVB()->email()->h1('Daily Referral Report');
-		$content .= JVB()->email()->stat(
+		$content = Emails::getInstance()->h1('Daily Referral Report');
+		$content .= Emails::getInstance()->stat(
 			count($new_referrals),
 			count($new_referrals) === 1 ? 'New Referral' : 'New Referrals',
 			'From ' . $yesterday
 		);
 
-		$content .= JVB()->email()->spacer(20);
-		$content .= JVB()->email()->h2('New Referrals');
+		$content .= Emails::getInstance()->spacer(20);
+		$content .= Emails::getInstance()->h2('New Referrals');
 
 		// Build list of referrals
 		foreach ($new_referrals as $ref) {
@@ -871,12 +927,12 @@
 			);
 			$cardContent .= sprintf(
 				'<p style="font-size:13px;color:%s;">Referred by: %s | Code: %s</p>',
-				JVB()->email()->colours['dark-200'],
+				Emails::getInstance()->colours['dark-200'],
 				esc_html($ref->from_name),
-				JVB()->email()->badge($ref->referral_code, 'info')
+				Emails::getInstance()->badge($ref->referral_code, 'info')
 			);
 
-			$content .= JVB()->email()->card($cardContent);
+			$content .= Emails::getInstance()->card($cardContent);
 		}
 
 		$to = get_option('admin_email');
@@ -887,7 +943,7 @@
 			count($new_referrals) !== 1 ? 's' : ''
 		);
 
-		JVB()->email()->sendEmail($to, $subject, $content, 'DAILY REPORT');
+		Emails::getInstance()->sendEmail($to, $subject, $content, 'DAILY REPORT');
 	}
 
 	/**
@@ -895,31 +951,29 @@
 	 */
 	public function sendWeeklyReport(): void
 	{
-		$top_from_users = $this->getTopReferrers(10, 'week');
-		$total_referrals = $this->referrals->queryVar(
-			"SELECT COUNT(*) FROM {table} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
-		);
+		$total_referrals = $this->referrals->count(['created_at' => ['>=', "DATE_SUB(NOW(), INTERVAL 1 WEEK)"]]);
 
 		if ($total_referrals == 0) {
 			return;
 		}
+		$top_from_users = $this->getTopReferrers(10, 'week');
 
-		$content = JVB()->email()->h1('Weekly Referral Summary');
-		$content .= JVB()->email()->stat(
+		$content = Emails::getInstance()->h1('Weekly Referral Summary');
+		$content .= Emails::getInstance()->stat(
 			$total_referrals,
 			'Total Referrals',
 			'This week'
 		);
 
-		$content .= JVB()->email()->spacer(30);
-		$content .= JVB()->email()->h2('Top 10 Referrers');
+		$content .= Emails::getInstance()->spacer(30);
+		$content .= Emails::getInstance()->h2('Top 10 Referrers');
 
 		// Leaderboard style
 		$rank = 1;
 		foreach ($top_from_users as $from_user) {
 			$rankBadge = $rank <= 3
-				? JVB()->email()->badge('#' . $rank, $rank === 1 ? 'success' : 'info')
-				: '<span style="font-weight:600;color:' . JVB()->email()->colours['dark-200'] . ';">#' . $rank . '</span>';
+				? Emails::getInstance()->badge('#' . $rank, $rank === 1 ? 'success' : 'info')
+				: '<span style="font-weight:600;color:' . Emails::getInstance()->colours['dark-200'] . ';">#' . $rank . '</span>';
 
 			$cardContent = sprintf(
 				'<p>%s <strong>%s</strong></p>',
@@ -928,19 +982,19 @@
 			);
 
 			$stats = [
-				JVB()->email()->stat($from_user->referral_count, 'Total Referrals'),
-				JVB()->email()->stat($from_user->treated_count, 'Treated')
+				Emails::getInstance()->stat($from_user->referral_count, 'Total Referrals'),
+				Emails::getInstance()->stat($from_user->treated_count, 'Treated')
 			];
-			$cardContent .= JVB()->email()->grid($stats, 2);
+			$cardContent .= Emails::getInstance()->grid($stats, 2);
 
-			$content .= JVB()->email()->card($cardContent);
+			$content .= Emails::getInstance()->card($cardContent);
 			$rank++;
 		}
 
 		$to = get_option('admin_email');
 		$subject = '[' . get_bloginfo('name') . '] Weekly Referral Summary - ' . date('F j, Y');
 
-		JVB()->email()->sendEmail($to, $subject, $content, 'WEEKLY SUMMARY');
+		Emails::getInstance()->sendEmail($to, $subject, $content, 'WEEKLY SUMMARY');
 	}
 
 	/**
@@ -1005,7 +1059,7 @@
 			];
 		}
 
-		$content .= JVB()->email()->table($from_users, 'Top 10 Referrers This Week');
+		$content .= Emails::getInstance()->table($from_users, 'Top 10 Referrers This Week');
 
 		return $content;
 	}
@@ -1220,7 +1274,7 @@
 	function getUnloggedInReferral(): string
 	{
 		ob_start();
-		JVB()->connect('cloudflare')->renderTurnstile();
+		Cloudflare::getInstance()->renderTurnstile();
 		$turnstile = ob_get_clean();
 
 		$reward_text = $this->getRewardText(true);
@@ -1512,8 +1566,8 @@
 			<p>Or click the button below:</p>
 			%s
 			</div>',
-				JVB()->email()->link($code),
-				JVB()->email()->button($share_url, 'Share Your Code')
+				Emails::getInstance()->link($code),
+				Emails::getInstance()->button($share_url, 'Share Your Code')
 			);
 		}
 
@@ -1555,31 +1609,39 @@
 			return new WP_Error('invalid_email', 'Invalid email address');
 		}
 
+
+
 		// Check if already registered
 		if (email_exists($invitee_email)) {
 			return new WP_Error('user_exists', 'This person already has an account');
 		}
 
 		// Get from_user info
-		$from_user = get_user_by('ID', $user_id);
+		$from_user = get_userdata($user_id);
 		$referral_code = $this->getUserReferralCode($user_id);
 
-		if ($referral_code) {
-			return $referral_code;
+		// Record the invitation attempt (for rate limiting only)
+		if ($this->emailAlreadyTreated($invitee_email)) {
+			return [
+				'success'	=> false,
+				'message'	=> 'User already treated'
+			];
 		}
 
-		// Record the invitation attempt (for rate limiting only)
-		$this->recordInvitationAttempt($user_id, $invitee_email, $invitee_name);
-
-		// Create registration URL with token (opens sidebar with prefilled form)
-		$token_data = [
-			'name' => sanitize_text_field($invitee_name),
-			'email' => $invitee_email,
-			'expires' => time() + (30 * DAY_IN_SECONDS)
-		];
+		$record = $this->referrals->findOrCreate(
+			[
+				'from_user'	=> $user_id,
+				'to_email'	=> $invitee_email,
+				'referral_code' => $referral_code,
+			],
+			[
+				'to_name'	=> $invitee_name,
+				'status'	=> 'invited',
+				'last_invited'	=> current_time('mysql')
+			]
+		);
 
 		// Encode the token
-		$token = base64_encode(json_encode($token_data));
 		$registration_url = add_query_arg([
 			'ref' => $referral_code,
 			'rname' => sanitize_text_field($invitee_name),
@@ -1606,11 +1668,11 @@
 				esc_html(get_bloginfo('name')),
 				nl2br(esc_html($message)),
 				esc_html($reward_text),
-				JVB()->email()->button($registration_url, 'Register & Get Your Reward')
+				Emails::getInstance()->button($registration_url, 'Register & Get Your Reward')
 			);
 
 		// Send email
-		$sent = JVB()->email()->sendEmail(
+		$sent = Emails::getInstance()->sendEmail(
 			$invitee_email,
 			$subject,
 			$email_content
@@ -1642,26 +1704,27 @@
 			'failed' => []
 		];
 
-		foreach ($invitations as $invite) {
-			$email = $invite['email'] ?? '';
-			$name = $invite['name'] ?? '';
-
-			if (empty($email) || empty($name)) {
-				$results['failed'][] = [
-					'email' => $email,
-					'name' => $name,
-					'reason' => 'Missing email or name'
-				];
-				continue;
+		$invitations = array_filter($invitations, function ($invite) {
+			if (!array_key_exists('email', $invite) || !filter_var($invite['email'], FILTER_VALIDATE_EMAIL)) {
+				return false;
 			}
+			if (!array_key_exists('name', $invite) || empty ($invite['name'])) {
+				return false;
+			}
+			return true;
+		});
+
+		foreach ($invitations as $invite) {
+			$email = $invite['email'];
+			$name = $invite['name'];
 
 			$result = $this->sendReferralInvitation($user_id, $email, $name, $subject, $message);
 
-			if (is_wp_error($result)) {
+			if (!$result['success']) {
 				$results['failed'][] = [
 					'email' => $email,
 					'name' => $name,
-					'reason' => $result->get_error_message()
+					'reason' => $result['message']
 				];
 			} else {
 				$results['success'][] = [
@@ -1693,98 +1756,25 @@
 	 */
 	protected function checkEmailRateLimit(int $user_id):bool|string
 	{
-		$hourly_key = 'referral_invites_hour_' . $user_id;
-		$count = (int) get_transient($hourly_key);
+		$key = BASE.'referral_email_limit';
+		$stored = get_user_meta($user_id, $key, true);
+		$count = empty($stored) ? 0 : count($stored);
 
 		if ($count >= 15) {
-			return 'hourly_limit_reached';
+			return false;
 		}
 
-		set_transient($hourly_key, $count + 1, HOUR_IN_SECONDS);
+		$stored = empty($stored) ? [] : $stored;
+		$stored[] = time();
+		$stored = array_filter($stored, function($time) {
+			return time() - $time < HOUR_IN_SECONDS;
+		});
+
+		update_user_meta($user_id, $key, $stored);
 		return true;
 	}
 
 	/**
-	 * Check if an email has already been invited
-	 *
-	 * @param string $email
-	 * @return bool
-	 */
-	protected function isEmailInvited(string $email): bool
-	{
-		// Check invitation tracking table
-		$invitation_key = 'referral_invite_' . md5($email);
-		$invited = get_transient($invitation_key);
-
-		if ($invited) {
-			return true;
-		}
-
-		// Check if there's a pending referral for this email
-		$existing = $this->referrals->pluck('id', ['to_email' => $email]);
-
-		return !empty($existing);
-	}
-
-	/**
-	 * Record invitation attempt (for tracking and preventing duplicates)
-	 *
-	 * @param int $user_id
-	 * @param string $email
-	 * @param string $name
-	 */
-	protected function recordInvitationAttempt(int $user_id, string $email, string $name): void
-	{
-		// Store for 30 days (same as magic link invitation validity)
-		$invitation_key = 'referral_invite_' . md5($email);
-		$data = [
-			'inviter_id' => $user_id,
-			'email' => $email,
-			'name' => $name,
-			'sent_at' => current_time('mysql')
-		];
-
-		set_transient($invitation_key, $data, 30 * DAY_IN_SECONDS);
-
-		// Also log in user meta for tracking
-		$sent_invites = get_user_meta($user_id, BASE . 'referral_invites_sent', true) ?: [];
-		$sent_invites[] = [
-			'email' => $email,
-			'name' => $name,
-			'sent_at' => current_time('mysql')
-		];
-		update_user_meta($user_id, BASE . 'referral_invites_sent', $sent_invites);
-	}
-
-	/**
-	 * Get user's invitation stats
-	 *
-	 * @param int $user_id
-	 * @return array
-	 */
-	public function getUserInvitationStats(int $user_id): array
-	{
-		$sent_invites = get_user_meta($user_id, BASE . 'referral_invites_sent', true) ?: [];
-
-		// Count invites sent in last hour
-		$one_hour_ago = strtotime('-1 hour');
-		$recent_count = 0;
-
-		foreach ($sent_invites as $invite) {
-			if (strtotime($invite['sent_at']) > $one_hour_ago) {
-				$recent_count++;
-			}
-		}
-
-		return [
-			'total_sent' => count($sent_invites),
-			'sent_last_hour' => $recent_count,
-			'remaining_this_hour' => max(0, 15 - $recent_count),
-			'can_send_more' => $recent_count < 15
-		];
-	}
-
-	/**
 	 * Export referrals for Jane App cross-reference
 	 *
 	 * @param string $start_date Y-m-d format
@@ -1871,22 +1861,12 @@
 			'menu_title' => 'Referrals',
 			'capability' => 'manage_options',
 			'menu_slug' => BASE . 'referral-admin',
-			'callback' => [self::class, 'renderAdminPageStatic']
+			'callback' => [self::class, 'renderAdminPage']
 		];
 		AdminPages::addSubPage(BASE.'referral-admin', $subpage);
 	}
 
 	/**
-	 * Static wrapper for renderAdminPage
-	 * Called by WordPress when admin page is rendered
-	 */
-	public static function renderAdminPageStatic(): void
-	{
-		// Get the properly initialized instance from JVB singleton
-		JVB()->referrals()->renderAdminPage();
-	}
-
-	/**
 	 * Register settings
 	 */
 	public function registerSettings(): void
@@ -2496,95 +2476,42 @@
 	 */
 	protected function renderReferralStats(bool $wrapCard = false): string
 	{
-		global $wpdb; // Use fresh global instead of stored reference
+		$isAdmin = current_user_can('manage_options');
+		$current = get_current_user_id();
+		$types = ['total' => 'Total Referrals', 'invited' => 'Invited', 'pending' => 'Pending', 'treated' => 'Treated'];
 
-		ob_start();
+		$out = '<table class="widefat">';
+		foreach ($types as $type => $label) {
+			$status = $type === 'total' ? [] : ['status' => $type];
 
-		// Get fresh table name references
-		$referrals_table = $wpdb->prefix . BASE . 'referrals';
-
-		// Use proper WordPress prepare even for COUNT
-		$total_referrals = $wpdb->get_var(
-			$wpdb->prepare(
-				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE 1=%d",
-				1
-			)
-		);
-
-		$pending_referrals = $wpdb->get_var(
-			$wpdb->prepare(
-				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
-				'pending'
-			)
-		);
-
-		$treated_referrals = $wpdb->get_var(
-			$wpdb->prepare(
-				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
-				'treated'
-			)
-		);
-
-		?>
-		<table class="widefat">
-			<tr>
-				<th>Total Referrals</th>
-				<td><?= esc_html($total_referrals ?? 0) ?></td>
-			</tr>
-			<tr>
-				<th>Pending</th>
-				<td><?= esc_html($pending_referrals ?? 0) ?></td>
-			</tr>
-			<tr>
-				<th>Treated</th>
-				<td><?= esc_html($treated_referrals ?? 0) ?></td>
-			</tr>
-		</table>
-		<?php
-		$table = ob_get_clean();
+			$count = $isAdmin ?
+				$this->referrals->count($status) :
+				$this->referrals->count(array_merge(
+					['from_user' => $current],
+					$status
+				));
+			$out .= sprintf(
+			'<tr>
+					<th>%s</th>
+					<td>%s</td>
+				</tr>',
+				$label,
+				esc_html($count)
+			);
+		}
+		$out .= '</table>';
 
 		if ($wrapCard) {
-			$table = '<div class="card">
+			$out = '<div class="card">
             <h2>Referral Statistics</h2>
-            ' . $table . '
+            ' . $out . '
         </div>';
 		}
 
-		return $table;
+		return $out;
 	}
 
-	/**
-	 * Add "Referral Page" label to admin bar
-	 *
-	 * @param WP_Admin_Bar $wp_admin_bar
-	 */
-	public function addReferralPageLabel($wp_admin_bar): void
-	{
-		if (!is_admin()) {
-			return;
-		}
 
-		if (!$this->referralPage) {
-			$this->referralPage = $this->getReferralPageId();
-		}
-		if (!$this->referralPage) {
-			return;
-		}
-
-		global $pagenow, $post;
-
-		// Check if we're editing the referral page
-		if ('post.php' === $pagenow && $post && $post->ID === $this->referralPage) {
-			$wp_admin_bar->add_node([
-				'id' => 'referral-page',
-				'parent' => 'top-secondary',
-				'title' => __('Referral Page', 'jvbase'),
-				'meta' => [
-					'class' => 'referral-page-notice'
-				]
-			]);
-		}
-	}
 
 	/**
 	 * Get the referral page ID
@@ -2612,7 +2539,7 @@
 		}
 		if ($post->ID === $this->referralPage) {
 			echo '<div class="notice notice-info">';
-			echo '<p>' . __('This page is designated as the <strong>Referral Page</strong>.', 'jvbase') . '</p>';
+			echo '<p>' . __('This page is designated as the <strong>Referral Page</strong>.', 'jvb') . '</p>';
 			echo '</div>';
 		}
 	}
@@ -2826,7 +2753,7 @@
 	 * @param array $post_data POST data
 	 * @return array|null Result array or null if not our page
 	 */
-	public function handleAdminSubmission($result, string $page_slug, array $post_data): ?array
+	public function handleAdminSubmission(mixed $result, string $page_slug, array $post_data): ?array
 	{
 		// Only handle our page
 		if ($page_slug !== BASE . 'referral-admin') {
@@ -2961,7 +2888,7 @@
 			home_url('/dash/referrals')
 		);
 
-		JVB()->email()->sendEmail(
+		Emails::getInstance()->sendEmail(
 			$from_user->user_email,
 			$subject,
 			$message
@@ -3033,11 +2960,14 @@
 		return ob_get_clean();
 	}
 
-	public function updateStatus(int $referral_id, string $status): bool|WP_Error
+	public function updateStatus(int $referral_id, string $status):array
 	{
 		$referral = $this->referrals->get(['id' => $referral_id]);
 		if (!$referral) {
-			return new WP_Error('not_found', 'Referral not found');
+			return [
+				'success'	=> false,
+				'message'	=> 'Referral not found'
+			];
 		}
 
 		$data = ['status' => $status, "{$status}_at" => current_time('mysql')];
@@ -3046,48 +2976,78 @@
 		}
 
 		$result = $this->referrals->update($data, ['id' => $referral_id]);
-		if ($result === false) {
-			return new WP_Error('update_failed', 'Failed to update referral status');
+		if (!$result) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Failed to update referral status'
+			];
 		}
 
 		if ($status === 'treated') {
 			$this->createRewardRecords($referral_id);
 		}
 
-		$this->cache->flush();
-		return true;
+		$this->cache->forget($referral_id);
+		return [
+			'success'	=> true,
+			'message'	=> 'Successfully set status to '.$status
+		];
 	}
 
-	public function removeReferral(int $referral_id, int $user_id): bool|WP_Error
+	public function removeReferral(int $referral_id, int $user_id):array
 	{
 		$referral = $this->referrals->get(['id' => $referral_id]);
 		if (!$referral) {
-			return new WP_Error('not_found', 'Referral not found');
+			return [
+				'success'	=> false,
+				'message'	=> 'Referral not found'
+			];
 		}
 
 		if ($referral->from_user != $user_id && !current_user_can('manage_options')) {
-			return new WP_Error('unauthorized', 'Unauthorized');
+			return [
+				'success'	=> false,
+				'message'	=> 'Unauthorized'
+			];
 		}
 
-		if ($referral->status !== 'pending') {
-			return new WP_Error('invalid_status', 'Can only remove pending referrals');
+		if (!in_array($referral->status, ['invited', 'pending'])) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Cannot remove a completed or cancelled referral'
+			];
 		}
 
 		$this->referrals->delete(['id' => $referral_id]);
-		$this->cache->flush();
-		return true;
+		$this->cache->forget($referral_id);
+		return [
+			'success' => true,
+			'message'	=> 'Successfully removed referral'
+		];
 	}
 
-	public function resendInvitation(int $referral_id, int $user_id): bool|WP_Error
+	public function resendInvitation(int $referral_id, int $user_id):array
 	{
 		$referral = $this->referrals->where(['id' => $referral_id, 'from_user' => $user_id])->first();
 		if (!$referral) {
-			return new WP_Error('not_found', 'Referral not found');
+			return [
+				'success'	=> false,
+				'message'	=> 'Referral not found'
+			];
 		}
 
-		$transient_key = 'referral_last_invite_' . md5($referral->to_email);
-		if (get_transient($transient_key)) {
-			return new WP_Error('rate_limit', 'Can only resend once per week');
+		if (!$this->checkCanResendEmail($referral)) {
+			$days = (time() - $referral->last_invited) / DAY_IN_SECONDS;
+			$days = match (true) {
+				$days <= 1 => 'until tomorrow',
+				$days <= 3 => 'a couple days',
+				default => absint($days).' days'
+			};
+
+			return [
+				'success'	=> false,
+				'message'	=> 'Can\'t resend email. Wait '.$days.'.'
+			];
 		}
 
 		$result = $this->sendReferralInvitation(
@@ -3098,15 +3058,13 @@
 			'Just a friendly reminder about my invitation!'
 		);
 
-		if (is_wp_error($result)) {
-			return $result;
-		}
-
-		set_transient($transient_key, time(), WEEK_IN_SECONDS);
-		return true;
+		return [
+			'success'	=> (bool) $result,
+			'message'	=> $result ? 'Sent email' : 'Could not send email'
+		];
 	}
 
-	public function getAllReferrals(array $args = []): array
+	public function getReferrals(array $args = []): array
 	{
 		$conditions = ['1=1'];
 		$values = [];
@@ -3145,5 +3103,60 @@
 			$values
 		);
 	}
+
+	public function emailAlreadyTreated(string $email):bool
+	{
+		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+			return false;
+		}
+		if (email_exists($email)) {
+			return false;
+		}
+		$referral = $this->referrals->get([
+			'to_email'	=> $email,
+			'status'	=> [
+				'IN',
+				['pending', 'consulted', 'treated', 'cancelled']
+			]
+		]);
+		return !empty($referral);
+	}
+
+
+	/**
+	 * Process queued referral operations
+	 */
+	public function processOperation(WP_Error|array $result, object $operation, array $data): array|WP_Error
+	{
+		if ($operation->type !== 'referral_invite') {
+			return $result;
+		}
+
+		$result = $this->sendBatchReferralInvitations(
+			$operation->user_id,
+			$data['invitations'],
+			$data['subject'],
+			$data['message']
+		);
+
+		if ($result['success']) {
+			$this->cache->flush();
+		}
+
+		return [
+			'success' => true,
+			'message' => sprintf(
+				'Sent invitations. Success: %d. Failed: %d.',
+				count($result['result']['success']),
+				count($result['result']['failed'])
+			),
+			'details' => [
+				'successful' => $result['result']['success'],
+				'failed' => $result['result']['failed'],
+				'total' => count($data['invitations'])
+			]
+		];
+	}
+
 }
 
diff --git a/inc/managers/ResponseManager.php b/inc/managers/ResponseManager.php
deleted file mode 100644
index fe12ede..0000000
--- a/inc/managers/ResponseManager.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-namespace JVBase\managers;
-
-if (!defined('ABSPATH')) {
-	exit; // Exit if accessed directly
-}
-/**
- * Response Manager
- *
- * Handles a responses
- * TODO
- */
-class ResponseManager
-{
-	protected CustomTable $response;
-	protected KarmaManager $karma;
-
-	public function __construct()
-	{
-		$this->defineTables();
-	}
-	protected function defineTables():void
-	{
-		$table = CustomTable::for('responses');
-
-		$table->setColumns([
-			'id'		=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
-			'item_id'	=> "{$table->getPostIDType()} NOT NULL",
-			'type'		=> 'varchar(50) NOT NULL',
-			'user_id'	=> "{$table->getUserIdType()} NOT NULL",
-			'parent_id'	=> 'bigint(20) unsigned DEFAULT 0',
-			'response'	=> 'text NOT NULL',
-			'upvotes'	=> 'int NOT NULL DEFAULT 0',
-			'downvotes'	=> 'int NOT NULL DEFAULT 0',
-			'karma'		=> 'int NOT NULL DEFAULT 0',
-			'status'	=> "ENUM('publish', 'hidden','flagged','deleted') DEFAULT 'publish'",
-			'is_user_deleted' => 'tinyint(1) DEFAULT 0',
-			'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
-			'updated_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
-		]);
-
-		$table->setKeys([
-			['key' => 'PRIMARY', 'value' => '`id`'],
-			'`item_lookup` (`item_id`)',
-			'`user_lookup` (`user_id`)',
-			'`parent_lookup` (`parent_id`)',
-			'`karma` (`karma`)',
-			'`upvote` (`upvotes`)',
-			'`downvote` (`downvotes`)',
-		]);
-
-		$base = BASE;
-		$table->setConstraints([
-			"CONSTRAINT `{$base}responses_item` FOREIGN KEY (`item_id`)
-			REFERENCES `{$table->getPostTable()}` (`ID`) ON DELETE CASCADE",
-		]);
-		$table->defineTable();
-
-		$this->response = $table;
-
-		$this->karma = KarmaManager::for('responses', 'responses');
-	}
-
-}
diff --git a/inc/managers/Responses.php b/inc/managers/Responses.php
new file mode 100644
index 0000000..73bf4dc
--- /dev/null
+++ b/inc/managers/Responses.php
@@ -0,0 +1,413 @@
+<?php
+namespace JVBase\managers;
+
+use JVBase\Instance;
+use JVBase\registrar\Registrar;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\ResponseRoutes;
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
+/**
+ * Response Manager
+ *
+ * Handles a responses
+ * TODO
+ */
+class Responses
+{
+	use Instance;
+	protected CustomTable $responses;
+	protected KarmaManager $karma;
+	protected ResponseRoutes $route;
+	protected int $perPage = 20;
+
+	private function __construct()
+	{
+		$this->route = ResponseRoutes::getInstance();
+
+		$this->defineTables();
+		$this->karma = KarmaManager::for('responses', $this->responses->getFullTableName());
+		$this->responses->setKarma($this->karma->karmaTable());
+		add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
+	}
+	protected function defineTables():void
+	{
+		$table = CustomTable::for('responses');
+
+		$table->setColumns([
+			'id'			=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+			'item_id'		=> "{$table->getPostIDType()} NOT NULL",
+			'type'			=> 'varchar(50) NOT NULL',
+			'user_id'		=> "{$table->getUserIdType()} NOT NULL",
+			'parent_id'		=> 'bigint(20) unsigned DEFAULT 0',
+			'response'		=> 'text NOT NULL',
+			'upvotes'		=> 'int NOT NULL DEFAULT 0',
+			'downvotes'		=> 'int NOT NULL DEFAULT 0',
+			'karmic_score'	=> 'int NOT NULL DEFAULT 0',
+			'history'		=> 'json DEFAULT {}',
+			'status'		=> "ENUM('publish', 'hidden','flagged','deleted') DEFAULT 'publish'",
+			'is_user_deleted'=> 'tinyint(1) DEFAULT 0',
+			'created_at'	=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+			'updated_at'	=> 'datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
+		]);
+
+		$table->setKeys([
+			['key' => 'PRIMARY', 'value' => '`id`'],
+			'`item_lookup` (`item_id`)',
+			'`user_lookup` (`user_id`)',
+			'`parent_lookup` (`parent_id`)',
+			'`karma` (`karmic_score`)',
+			'`upvote` (`upvotes`)',
+			'`downvote` (`downvotes`)',
+		]);
+
+		$base = BASE;
+		$table->setConstraints([
+			"CONSTRAINT `{$base}responses_item` FOREIGN KEY (`item_id`)
+			REFERENCES `{$table->getPostTable()}` (`ID`) ON DELETE CASCADE",
+		]);
+		$table->defineTable();
+
+		$this->responses = $table;
+	}
+	public function responseExists(int $id):bool
+	{
+		return (bool) $this->responses->get([
+			'id'	=> $id,
+		]);
+	}
+
+	public function updateKarma(int $id, int $upvotes, int $downvotes, int $score):bool
+	{
+		return $this->responses->update([
+			'upvotes'		=> $upvotes,
+			'downvotes'		=> $downvotes,
+			'karmic_score'	=> $score,
+		], [
+			'id'	=> $id
+		]);
+	}
+
+	protected function itemExists(int $item_id, string $type):bool
+	{
+		return (bool)$this->getOriginalItem($item_id, $type);
+	}
+	public function getOriginalItem(int $item_id, string $type):object|false
+	{
+		[$method, $type] = $this->determineType($type);
+		if (!$method) {
+			return false;
+		}
+		$item = match($method) {
+			'post'	=> get_post($item_id),
+			'term'	=> get_term($item_id, $type),
+			'user'	=> get_userdata($item_id),
+			'response' => $this->responses->get(['id' => $item_id]),
+			default => false
+		};
+		return (is_wp_error($item)) ? false : $item;
+	}
+	public function getOriginalItemAuthors(int $item_id, string $type):array
+	{
+		[$method, $type] = $this->determineType($type);
+		$item = $this->getOriginalItem($item_id, $type);
+		return match ($method) {
+			'post'	=> [$item->post_author],
+			'term'	=> jvbGetTermOwners($item_id),
+			'user'	=> [$item->ID],
+			'response'	=> [$item->user_id],
+		};
+	}
+		protected function determineType(string $type):array
+		{
+			$method = match ($type) {
+				'post', 'page', 'product' => 'post',
+				'post_tag', 'category', 'term', 'taxonomy' => 'term',
+				'user'	=> 'user',
+				'response'	=> 'response',
+				default => false
+			};
+			if (!$method) {
+				$registrar = Registrar::getInstance($type);
+				$type = jvbCheckBase($type);
+				if ($registrar) {
+					$method = $registrar->getType();
+				}
+			}
+			if (!$method) {
+				error_log('[Responses]itemExists Invalid type: '.$type);
+				return [false, false];
+			}
+			return [$method, $type];
+		}
+	public function respondTo(int $item_id, string $type, int $user_id, string $response, ?int $parent_id = null):array
+	{
+		if (!$this->itemExists($item_id, $type)) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Item does not exist',
+			];
+		}
+		if (empty($response)) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Response content is empty'
+			];
+		}
+		$created = $this->responses->insert([
+			'item_id'	=> $item_id,
+			'user_id'	=> $user_id,
+			'parent_id'	=> $parent_id,
+			'response'	=> $response,
+			'type'		=> $type
+		]);
+
+
+		$users = $this->getOriginalItemAuthors($item_id, $type);
+		if ($parent_id) {
+			$users = array_merge($users, $this->getNotifyableUsers($parent_id));
+		}
+		$users = array_unique($users);
+		NotificationManager::getInstance()->unnotify(
+			$users,
+			'new_response',
+			$user_id,
+		);
+
+		return [
+			'success'	=> (bool)$created,
+			'message'	=> $created ? 'Response posted successfully' : 'Response not posted'
+		];
+	}
+
+	public function editResponse(int $id, string $response):array
+	{
+		$original = $this->responses->get(['id' => $id]);
+		if (!$original) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Original response not found'
+			];
+		}
+		if (get_current_user_id() !== $original->user_id) {
+			return [
+				'success'	=> false,
+				'message'	=> 'This is not your response to edit'
+			];
+		}
+		$history = $this->pushHistory($original);
+
+		$updated = $this->responses->update([
+			'history'	=> $history,
+			'response'	=> sanitize_text_field($response)
+		], [
+			'user_id'	=> $original->user_id,
+			'id'		=> $id
+		]);
+		return [
+			'success'	=> (bool)$updated,
+			'message'	=> $updated ? 'Response edited successfully' : 'Response not edited'
+		];
+	}
+
+	public function deleteResponse(int $id):array
+	{
+		$original = $this->responses->get(['id' => $id]);
+		if (!$original) {
+			return [
+				'success'	=> true,
+				'message'	=> 'Item does not exist',
+			];
+		}
+		if ($original->user_id !== get_current_user_id()) {
+			return [
+				'success'	=> false,
+				'message'	=> 'This is not your response to delete'
+			];
+		}
+
+		$deleted = $this->responses->update([
+			'status'	=> 'deleted',
+			'history'	=> $this->pushHistory($original),
+			'response'	=> '[ deleted response ]'
+		], [
+			'id'	=> $id,
+		]);
+
+		NotificationManager::getInstance()->unnotify(
+			$this->getNotifyableUsers($id),
+			'new_response',
+			$original->user_id,
+		);
+		return [
+			'success'	=> (bool)$deleted,
+			'message'	=> $deleted ? 'Response deleted successfully' : 'Response not deleted'
+		];
+	}
+
+	public function hideResponse(int $id):array
+	{
+		$original = $this->responses->get(['id' => $id]);
+		if (!$original) {
+			return [
+				'success'	=> false,
+				'message'	=> 'Item does not exist',
+			];
+		}
+		$currentUser = get_current_user_id();
+		$isCommentOwner = $original->user_id === $currentUser;
+		if ($isCommentOwner) {
+			return $this->deleteResponse($id);
+		}
+		$item = $this->getOriginalItem($original->item_id, $original->type);
+		$authorIDs = $this->getOriginalItemAuthors($original->item_id, $original->type);
+
+		$isAuthor = in_array($currentUser, $authorIDs);
+		if (!$isAuthor) {
+			return [
+				'success'	=> false,
+				'message'	=> 'You cannot hide this response.'
+			];
+		}
+
+		$updated = $this->responses->update([
+			'status'	=> 'hidden',
+		], [
+			'id'	=> $id,
+		]);
+
+		return [
+			'success'	=> (bool)$updated,
+			'message'	=> $updated ? 'Response hidden successfully' : 'Response not hidden'
+		];
+	}
+
+	public function getUserResponses(int $userID, $args = [], bool $includeChildren = true):array
+	{
+		$args = wp_parse_args($args, [
+			'orderby'	=> 'created_at',
+			'order'		=> 'DESC',
+			'page'		=> 1,
+			'per_page'	=> 20,
+		]);
+
+		$where = [
+			'user_id'	=> $userID,
+			'parent_id'	=> null
+		];
+		if (array_key_exists('item_id', $args)) {
+			$where['item_id'] = $args['item_id'];
+		}
+		$responses =  $this->responses->getMany(array_merge([
+			'where' => $where,
+		], $args), false, ARRAY_A);
+
+		if ($includeChildren) {
+			$responses['items'] = array_map(function ($r) use ($args) {
+				$r['children'] = $this->getChildResponses($r['id'], $args);
+				return $r;
+			}, $responses['items']);
+		}
+		return ['items'=> $responses];
+	}
+	public function getChildResponses(int $parentID, array $args, bool $includeChildren = true):array
+	{
+		$args = wp_parse_args($args, [
+			'orderby'	=> 'created_at',
+			'order'		=> 'DESC',
+			'page'		=> 1,
+			'per_page'	=> 20,
+		]);
+
+		$responses = $this->responses->getMany(array_merge([
+			'where'	=> [
+				'id'	=> $parentID,
+			]
+		], $args), true, ARRAY_A);
+
+		if ($includeChildren) {
+			$responses = array_map(function ($r) use ($args) {
+				$r['children'] = $this->getChildResponses($r['id'], $args);
+				return $r;
+			}, $responses);
+		}
+		return $responses;
+	}
+
+	public function getAllItemResponses(int $itemID, array $args = [], bool $includeChildren = true):array
+	{
+		$args = wp_parse_args($args, [
+			'orderby'	=> 'created_at',
+			'order'		=> 'DESC',
+			'page'		=> 1,
+			'per_page'	=> 20,
+		]);
+
+		$where = [
+			'item_id'	=> $itemID,
+			'parent_id'	=> null
+		];
+
+		$responses =  $this->responses->getMany(array_merge([
+			'where' => $where,
+		], $args), false, ARRAY_A);
+
+		if ($includeChildren) {
+			$responses['items'] = array_map(function ($r) use ($args) {
+				$r['children'] = $this->getChildResponses($r['id'], $args);
+				return $r;
+			}, $responses['items']);
+		}
+		return ['items'=> $responses];
+	}
+
+	public function handleUserDeletion(int $user_id):void
+	{
+		$updated = $this->responses->update([
+			'is_user_deleted'	=> 1,
+		], [
+			'user_id'	=> $user_id,
+		]);
+
+		Errors::getInstance()->log(
+			'Responses',
+			'Anonymized responses for deleted user',
+			[
+				'user_id'	=> $user_id,
+				'count'		=> $updated,
+			],
+			'info'
+		);
+	}
+
+	protected function getNotifyableUsers(int $id):array
+	{
+		$original = $this->responses->get(['id' => $id]);
+		if (!$original) {
+			return [];
+		}
+		$users = [$original->user_id];
+		if ($original->parent_id) {
+			$users = array_merge($users, $this->getNotifyableUsers($original->parent_id));
+		}
+		$users = array_merge($users, $this->getOriginalItemAuthors($original->item_id, $original->type));
+
+		return array_unique($users);
+
+	}
+
+	protected function pushHistory(object $original):array
+	{
+		$history = json_decode($original->history, true);
+		$history[] = [
+			'edited_at'	=> $original->updated_at,
+			'response'	=> $original->response,
+			'upvotes'	=> $original->upvotes,
+			'downvotes'	=> $original->downvotes,
+		];
+		return $history;
+	}
+}
diff --git a/inc/managers/RoleManager.php b/inc/managers/Roles.php
similarity index 99%
rename from inc/managers/RoleManager.php
rename to inc/managers/Roles.php
index e859813..7caa663 100644
--- a/inc/managers/RoleManager.php
+++ b/inc/managers/Roles.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 use WP_User;
@@ -9,8 +10,10 @@
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
-class RoleManager
+class Roles
 {
+	use Instance;
+
     private array $roles;
 	private array $content;
 	private array $subTypes = [];
diff --git a/inc/managers/SEO/BreadcrumbManager.php b/inc/managers/SEO/BreadcrumbManager.php
index 2993c98..30bd448 100644
--- a/inc/managers/SEO/BreadcrumbManager.php
+++ b/inc/managers/SEO/BreadcrumbManager.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers\SEO;
 
 use JVBase\managers\Cache;
+use JVBase\managers\DirectoryManager;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 use WP_Post;
@@ -20,15 +21,17 @@
 class BreadcrumbManager
 {
 	private Cache $cache;
+	private Cache $termCache;
+	private Cache $postCache;
+	private Cache $userCache;
 	private static ?self $instance = null;
 
 	private function __construct()
 	{
-		$this->cache = Cache::for('breadcrumbs', MONTH_IN_SECONDS)->connect('post')->connect('taxonomy')->connect('user');
-		if (JVB_TESTING) {
-			$this->cache->flush();
-		}
-		$this->cache->flush();
+		$this->cache = Cache::for('breadcrumbs');
+		$this->termCache = Cache::for('breadcrumbs_tax', 'term');
+		$this->postCache = Cache::for('breadcrumbs_post', 'post');
+		$this->userCache = Cache::for('breadcrumbs_user', 'user');
 	}
 
 	public static function getInstance(): self
@@ -53,12 +56,18 @@
 
 		$key = match(true) {
 			is_singular() => get_queried_object_id(),
-			is_tax() => get_queried_object()->taxonomy,
+			is_tax() => get_queried_object()->term_id,
 			is_post_type_archive() => get_queried_object()->name,
 			default => 'home'
 		};
+		$cache = match(true) {
+			is_singular()	=> $this->postCache,
+			is_tax()		=> $this->termCache,
+			is_author()		=> $this->userCache,
+			default			=> $this->cache
+		};
 
-		return $this->cache->remember(
+		return $cache->remember(
 			$key,
 			function() {
 				$crumbs = $this->buildCrumbs();
@@ -111,9 +120,9 @@
 				$content = is_array($registrar->registrar->for) ? $registrar->registrar->for[0] : $registrar->registrar->for;
 				$contentRegistrar = Registrar::getInstance($content);
 
-				if($contentRegistrar && $contentRegistrar->hasFeature('show_directory')) {
-					$directory = JVB()->directories();
-					if ($directory && !empty($directory->directories($content)??[])){
+				if(Site::has('is_directory') && $contentRegistrar && $contentRegistrar->hasFeature('show_directory')) {
+					$directory = DirectoryManager::getInstance();
+					if (!empty($directory->directories($content)??[])){
 						$crumbs[] = [
 							'name'	=> $directory->directories($content)['title'],
 							'url'	=>$directory->directories($content)['url']
@@ -135,7 +144,7 @@
 
 		// Add directory if exists
 		if ($registrar && $registrar->hasFeature('show_directory')) {
-			$directory = JVB()->directories();
+			$directory = DirectoryManager::getInstance();
 			if ($directory && !empty($directory->directories($tax)??[])) {
 				$crumbs[] = [
 					'name'	=> $directory->directories($tax)['title'],
@@ -162,14 +171,14 @@
 		}
 
 		// Handle directory posts specially
-		if (JVB()->directories() && JVB()->directories()->isDirectory()) {
+		if (Site::has('is_directory') && DirectoryManager::getInstance()->isDirectory()) {
 			$pos = jvbGetDirectoryInfo();
 			if (!empty($pos)) {
 				// Special case for map
 				if ($pos['title'] == 'Map') {
 					$crumbs[] = [
 						'name' => 'Tattoo Shops',
-						'url'  => JVB()->directories()->directories(BASE.'shop')['url']
+						'url'  => DirectoryManager::getInstance()->directories(BASE.'shop')['url']
 					];
 				}
 
@@ -203,8 +212,8 @@
 
 		$registrar = Registrar::getInstance($name);
 
-		if($registrar && $registrar->hasFeature('show_directory') && JVB()->directories()) {
-			$directory = JVB()->directories();
+		if($registrar && $registrar->hasFeature('show_directory') && DirectoryManager::getInstance()) {
+			$directory = DirectoryManager::getInstance();
 
 			if ($directory && !empty($directory->directories($name)??[])){
 				$crumbs[] = [
@@ -215,7 +224,7 @@
 		} elseif (Site::has('is_directory') && $name === 'directory') {
 
 			$crumbs[] = [
-				'name'	=> JVB()->directories()->referAs(true),
+				'name'	=> DirectoryManager::getInstance()->referAs(true),
 				'url'	=> get_post_type_archive_link($type)
 			];
 		} elseif ($registrar) {
@@ -224,11 +233,22 @@
 				'url'	=> get_post_type_archive_link($type)
 			];
 		} else {
-			$postTypeObject = get_post_type_object($type);
-			$crumbs[] = [
-				'name'	=> $postTypeObject->label,
-				'url'	=> get_post_type_archive_link($type)
-			];
+
+			if (function_exists('is_shop') && is_shop()) {
+				$ID = get_option( 'woocommerce_shop_page_id' );
+				$crumbs[] = [
+					'name'	=> get_the_title($ID),
+					'url'	=> get_the_permalink($ID)
+				];
+
+			} else {
+				$postTypeObject = get_post_type_object($type);
+				$crumbs[] = [
+					'name'	=> $postTypeObject->label,
+					'url'	=> get_post_type_archive_link($type)
+				];
+			}
+
 		}
 
 		return $crumbs;
@@ -371,17 +391,6 @@
 		];
 	}
 
-	/**
-	 * Invalidate breadcrumb cache for specific object
-	 */
-	public function invalidateCache(?int $objectId = null): void
-	{
-		if ($objectId) {
-			$this->cache->forget($objectId);
-		} else {
-			$this->cache->flush();
-		}
-	}
 
 	public function addTaxToCrumbs(array $crumbs, string|array $taxonomy):array
 	{
diff --git a/inc/managers/SEO/SEOAdminPage.php b/inc/managers/SEO/SEOAdminPage.php
index bf0ef02..fc9f0b2 100644
--- a/inc/managers/SEO/SEOAdminPage.php
+++ b/inc/managers/SEO/SEOAdminPage.php
@@ -19,6 +19,7 @@
  */
 class SEOAdminPage
 {
+	use Instance;
 //    private SchemaBuilder $registry;
 
     public function __construct()
@@ -55,7 +56,7 @@
 
     public static function renderAdminPageStatic():void
     {
-        JVB()->seoAdmin()->renderAdminPage();
+        SEOAdmin::getInstance()->renderAdminPage();
     }
 
     /**
diff --git a/inc/managers/SEO/_edmonotonink.php b/inc/managers/SEO/_edmonotonink.php
index 6689413..6cce3aa 100644
--- a/inc/managers/SEO/_edmonotonink.php
+++ b/inc/managers/SEO/_edmonotonink.php
@@ -1,5 +1,7 @@
 	<?php
-
+	if (!defined('ABSPATH')) {
+		exit;
+	}
 /**
  * Edmonton.ink Configuration
  *
diff --git a/inc/managers/SEO/_setup.php b/inc/managers/SEO/_setup.php
index 247083b..1c19abf 100644
--- a/inc/managers/SEO/_setup.php
+++ b/inc/managers/SEO/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 //New System
 require(JVB_DIR . '/inc/managers/SEO/render/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/BreadcrumbManager.php');
diff --git a/inc/managers/SEO/render/DataType/_setup.php b/inc/managers/SEO/render/DataType/_setup.php
index 0889db9..1df5df9 100644
--- a/inc/managers/SEO/render/DataType/_setup.php
+++ b/inc/managers/SEO/render/DataType/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/DataType/Date.php');
 require(JVB_DIR . '/inc/managers/SEO/render/DataType/DateTime.php');
 require(JVB_DIR . '/inc/managers/SEO/render/DataType/Time.php');
diff --git a/inc/managers/SEO/render/DataType/index.php b/inc/managers/SEO/render/DataType/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/DataType/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/SchemaOutput.php b/inc/managers/SEO/render/SchemaOutput.php
index 25ac320..41ce0c8 100644
--- a/inc/managers/SEO/render/SchemaOutput.php
+++ b/inc/managers/SEO/render/SchemaOutput.php
@@ -1,17 +1,22 @@
 <?php
 namespace JVBase\managers\SEO\render;
 
+use JVBase\base\SchemaHelper;
 use JVBase\inc\managers\SEO\render\Thing\Intangible\ItemList\OfferCatalog;
+use JVBase\Instance;
 use JVBase\managers\Cache;
 use JVBase\managers\SEO\BreadcrumbManager;
 use JVBase\managers\SEO\render\Thing\Intangible\Service;
 use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
 use JVBase\registrar\config\seo\Resolver;
 use JVBase\registrar\Registrar;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 class SchemaOutput {
+	use Instance;
 	protected array $types;
-	public function __construct() {
+	protected function __construct() {
 		add_action('wp_head', [$this, 'outputSchema'], 0);
 		$this->init();
 	}
@@ -91,7 +96,7 @@
 
 		if (!empty($schema)) {
 			if (!is_front_page()) {
-				$website = JVB()->schemaHelper()::schema('website');
+				$website = SchemaHelper::getInstance()::schema('website');
 				if (!empty($website)) {
 					if (JVB_TESTING) {
 						Cache::for('websiteSchema')->flush();
@@ -117,8 +122,8 @@
 
 	protected function websiteSchema():array
 	{
-		$stored = JVB()->schemaHelper()::schema('website');
-		$website = JVB()->schemaHelper()::classFromConfig($stored);
+		$stored = SchemaHelper::getInstance()::schema('website');
+		$website = SchemaHelper::getInstance()::classFromConfig($stored);
 
 		if (!$website->getUrl() || empty($website->getUrl())){
 			$website->setUrl(get_home_url());
@@ -164,7 +169,7 @@
 		if (JVB_TESTING){
 			Cache::for('websiteSchema')->flush();
 		}
-		$storedWebsite = JVB()->schemaHelper()::schema('website');
+		$storedWebsite = SchemaHelper::getInstance()::schema('website');
 
 		if (!$full) {
 			return Cache::for('websiteSchema')->remember(
@@ -175,7 +180,7 @@
 						return in_array($key, $allowed);
 					}, ARRAY_FILTER_USE_KEY);
 
-					$website = JVB()->schemaHelper()::classFromConfig($storedWebsite);
+					$website = SchemaHelper::getInstance()::classFromConfig($storedWebsite);
 					if (!$website->getName() || empty($website->getName())) {
 						$website->setName(get_bloginfo('name'));
 					}
@@ -207,7 +212,7 @@
 		return Cache::for('websiteSchema')->remember(
 			'full',
 			function () use ($storedWebsite) {
-				$website = JVB()->schemaHelper()::classFromConfig($storedWebsite);
+				$website = SchemaHelper::getInstance()::classFromConfig($storedWebsite);
 				if (!$website->getName() || empty($website->getName())) {
 					$website->setName(get_bloginfo('name'));
 				}
@@ -307,9 +312,9 @@
 		if ($reference) {
 			return $this->getOptionSchemaReference('organization');
 		}
-		$config = JVB()->schemaHelper()::schema('organization');
+		$config = SchemaHelper::getInstance()::schema('organization');
 
-		return JVB()->schemaHelper()::classFromConfig($config);
+		return SchemaHelper::getInstance()::classFromConfig($config);
 	}
 
 	public function getOptionSchemaReference(string $option):mixed
@@ -323,7 +328,7 @@
 			return null;
 		}
 
-		$stored = JVB()->schemaHelper()::reference($option);
+		$stored = SchemaHelper::getInstance()::reference($option);
 
 		if (empty($stored)){
 			error_log('Attempted to get schema reference for: '.$option.', but defaults not set.');
@@ -354,9 +359,9 @@
 
 	public function buildOrganizationSchema():array
 	{
-		$config = JVB()->schemaHelper()::schema('organization');
+		$config = SchemaHelper::getInstance()::schema('organization');
 
-		$class = JVB()->schemaHelper()::classFromConfig($config);
+		$class = SchemaHelper::getInstance()::classFromConfig($config);
 		return ($class)? $class->outputSchema() : [];
 	}
 }
diff --git a/inc/managers/SEO/render/Thing/Action/_setup.php b/inc/managers/SEO/render/Thing/Action/_setup.php
index 6a9379d..b11b4e6 100644
--- a/inc/managers/SEO/render/Thing/Action/_setup.php
+++ b/inc/managers/SEO/render/Thing/Action/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Action/Action.php');
 	require(JVB_DIR . '/inc/managers/SEO/render/Thing/Action/InteractAction.php');
 		require(JVB_DIR . '/inc/managers/SEO/render/Thing/Action/CommunicateAction.php');
diff --git a/inc/managers/SEO/render/Thing/Action/index.php b/inc/managers/SEO/render/Thing/Action/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Action/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php
index 3abef7e..3b824bb 100644
--- a/inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 $base = JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Article/';
 require ($base.'Article.php');
 require ($base.'Report.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Article/index.php b/inc/managers/SEO/render/Thing/CreativeWork/Article/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Article/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Comment/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/Comment/_setup.php
index ff52393..a8a0839 100644
--- a/inc/managers/SEO/render/Thing/CreativeWork/Comment/_setup.php
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Comment/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Comment/Comment.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Comment/Answer.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Comment/Question.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Comment/index.php b/inc/managers/SEO/render/Thing/CreativeWork/Comment/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Comment/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php
index ec6beab..b69262c 100644
--- a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/index.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php
index 50ef64a..7c9b31d 100644
--- a/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php
+++ b/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/VisualArtwork.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/index.php b/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage/index.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/index.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/_setup.php
index 3d91997..11a316a 100644
--- a/inc/managers/SEO/render/Thing/CreativeWork/_setup.php
+++ b/inc/managers/SEO/render/Thing/CreativeWork/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Article/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/VisualArtwork/_setup.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/index.php b/inc/managers/SEO/render/Thing/CreativeWork/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Event/_setup.php b/inc/managers/SEO/render/Thing/Event/_setup.php
index f940337..64dff7a 100644
--- a/inc/managers/SEO/render/Thing/Event/_setup.php
+++ b/inc/managers/SEO/render/Thing/Event/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Event/Event.php');
diff --git a/inc/managers/SEO/render/Thing/Event/index.php b/inc/managers/SEO/render/Thing/Event/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Event/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php
index 9f21bc6..037fa2a 100644
--- a/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Brand/index.php b/inc/managers/SEO/render/Thing/Intangible/Brand/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Brand/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php
index 1ae10ab..37138bd 100644
--- a/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/ContactPoint/index.php b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php
index be6f6f4..47e67a9 100644
--- a/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php
@@ -1,4 +1,8 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
+
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/index.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php b/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php
index bea6a3f..f7700da 100644
--- a/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/OfferCatalog.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/ItemList/index.php b/inc/managers/SEO/render/Thing/Intangible/ItemList/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ItemList/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php
index 57767be..28e8289 100644
--- a/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/index.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php
index 835fe00..313898f 100644
--- a/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php');
 
diff --git a/inc/managers/SEO/render/Thing/Intangible/Rating/index.php b/inc/managers/SEO/render/Thing/Intangible/Rating/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Rating/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php
index 283a7a0..56ea68d 100644
--- a/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php
@@ -1,3 +1,6 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Reservation/index.php b/inc/managers/SEO/render/Thing/Intangible/Reservation/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Reservation/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php
index 6e3d35e..9def59e 100644
--- a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/index.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Intangible/_setup.php b/inc/managers/SEO/render/Thing/Intangible/_setup.php
index 56cc70e..04530c8 100644
--- a/inc/managers/SEO/render/Thing/Intangible/_setup.php
+++ b/inc/managers/SEO/render/Thing/Intangible/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/index.php b/inc/managers/SEO/render/Thing/Intangible/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/MedicalEntity/_setup.php b/inc/managers/SEO/render/Thing/MedicalEntity/_setup.php
index 4180661..9735d05 100644
--- a/inc/managers/SEO/render/Thing/MedicalEntity/_setup.php
+++ b/inc/managers/SEO/render/Thing/MedicalEntity/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 $base = JVB_DIR . '/inc/managers/SEO/render/Thing/MedicalEntity/';
 require ($base.'MedicalEntity.php');
 require ($base.'MedicalProcedure.php');
diff --git a/inc/managers/SEO/render/Thing/MedicalEntity/index.php b/inc/managers/SEO/render/Thing/MedicalEntity/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/MedicalEntity/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php
index 19615a3..239ea8a 100644
--- a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/index.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php
index 32c639d..dfdc3db 100644
--- a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/index.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php
index 3428001..caa9d1e 100644
--- a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/index.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Organization/_setup.php b/inc/managers/SEO/render/Thing/Organization/_setup.php
index 16b7167..d3835d7 100644
--- a/inc/managers/SEO/render/Thing/Organization/_setup.php
+++ b/inc/managers/SEO/render/Thing/Organization/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/Organization.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php');
 
diff --git a/inc/managers/SEO/render/Thing/Organization/index.php b/inc/managers/SEO/render/Thing/Organization/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Person/_setup.php b/inc/managers/SEO/render/Thing/Person/_setup.php
index b511fff..4a79a64 100644
--- a/inc/managers/SEO/render/Thing/Person/_setup.php
+++ b/inc/managers/SEO/render/Thing/Person/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Person/Person.php');
diff --git a/inc/managers/SEO/render/Thing/Person/index.php b/inc/managers/SEO/render/Thing/Person/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Person/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
index f6db0b9..bec762e 100644
--- a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 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');
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/index.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Place/_setup.php b/inc/managers/SEO/render/Thing/Place/_setup.php
index 45b607f..d0d3cfb 100644
--- a/inc/managers/SEO/render/Thing/Place/_setup.php
+++ b/inc/managers/SEO/render/Thing/Place/_setup.php
@@ -1,3 +1,6 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/Place.php');
diff --git a/inc/managers/SEO/render/Thing/Place/index.php b/inc/managers/SEO/render/Thing/Place/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Product/_setup.php b/inc/managers/SEO/render/Thing/Product/_setup.php
index 76b6efd..98fbedc 100644
--- a/inc/managers/SEO/render/Thing/Product/_setup.php
+++ b/inc/managers/SEO/render/Thing/Product/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/Product.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/ProductGroup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/ProductModel.php');
diff --git a/inc/managers/SEO/render/Thing/Product/index.php b/inc/managers/SEO/render/Thing/Product/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Product/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/Properties/_setup.php b/inc/managers/SEO/render/Thing/Properties/_setup.php
index dc01fa7..7b793a0 100644
--- a/inc/managers/SEO/render/Thing/Properties/_setup.php
+++ b/inc/managers/SEO/render/Thing/Properties/_setup.php
@@ -1,3 +1,6 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Properties/about.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php');
diff --git a/inc/managers/SEO/render/Thing/Properties/index.php b/inc/managers/SEO/render/Thing/Properties/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Properties/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Thing/_setup.php b/inc/managers/SEO/render/Thing/_setup.php
index 029dadd..b713ea7 100644
--- a/inc/managers/SEO/render/Thing/_setup.php
+++ b/inc/managers/SEO/render/Thing/_setup.php
@@ -1,5 +1,7 @@
 <?php
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Thing.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/Action/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/_setup.php');
diff --git a/inc/managers/SEO/render/Traits/Organization/_setup.php b/inc/managers/SEO/render/Traits/Organization/_setup.php
index 6bc27f5..ec958a8 100644
--- a/inc/managers/SEO/render/Traits/Organization/_setup.php
+++ b/inc/managers/SEO/render/Traits/Organization/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php');
diff --git a/inc/managers/SEO/render/Traits/Organization/index.php b/inc/managers/SEO/render/Traits/Organization/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Organization/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Traits/Place/_setup.php b/inc/managers/SEO/render/Traits/Place/_setup.php
index 814c300..9f0b024 100644
--- a/inc/managers/SEO/render/Traits/Place/_setup.php
+++ b/inc/managers/SEO/render/Traits/Place/_setup.php
@@ -1,2 +1,5 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/Place/PlaceSchema.php');
diff --git a/inc/managers/SEO/render/Traits/Place/index.php b/inc/managers/SEO/render/Traits/Place/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Place/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Traits/ThingSchema.php b/inc/managers/SEO/render/Traits/ThingSchema.php
index d41ad97..7b91c20 100644
--- a/inc/managers/SEO/render/Traits/ThingSchema.php
+++ b/inc/managers/SEO/render/Traits/ThingSchema.php
@@ -135,7 +135,7 @@
 
 	public static function createImageFromID(int $ID):ImageObject|false
 	{
-		$cache = Cache::for('schemaImage')->connect('post');
+		$cache = Cache::for('schemaImage', 'post');
 		return $cache->remember(
 			$ID,
 			function() use($ID) {
@@ -168,4 +168,5 @@
 	{
 		$this->showID = $showIt;
 	}
+
 }
diff --git a/inc/managers/SEO/render/Traits/_Helpers/_setup.php b/inc/managers/SEO/render/Traits/_Helpers/_setup.php
index 53046a4..e008e61 100644
--- a/inc/managers/SEO/render/Traits/_Helpers/_setup.php
+++ b/inc/managers/SEO/render/Traits/_Helpers/_setup.php
@@ -1,3 +1,6 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Helpers/enumerationHelper.php');
diff --git a/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php b/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php
index 1a8776f..9cd1b4b 100644
--- a/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php
+++ b/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php
@@ -28,7 +28,7 @@
 
 		return array_filter(array_map(function($item) {
 			if (is_array($item) && array_key_exists('type', $item)) {
-				return JVB()->schemaHelper()::classFromConfig($item);
+				return SchemaHelper::getInstance()::classFromConfig($item);
 			} else {
 				return $item;
 			}
diff --git a/inc/managers/SEO/render/Traits/_Helpers/index.php b/inc/managers/SEO/render/Traits/_Helpers/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Helpers/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Traits/_Properties/_setup.php b/inc/managers/SEO/render/Traits/_Properties/_setup.php
index 7c08ef4..98666a2 100644
--- a/inc/managers/SEO/render/Traits/_Properties/_setup.php
+++ b/inc/managers/SEO/render/Traits/_Properties/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/aboutTrait.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/abstractTrait.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/acceptedAnswerTrait.php');
diff --git a/inc/managers/SEO/render/Traits/_Properties/index.php b/inc/managers/SEO/render/Traits/_Properties/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/Traits/_setup.php b/inc/managers/SEO/render/Traits/_setup.php
index dd5bc0c..fcf779a 100644
--- a/inc/managers/SEO/render/Traits/_setup.php
+++ b/inc/managers/SEO/render/Traits/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Helpers/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/ThingSchema.php');
diff --git a/inc/managers/SEO/render/Traits/index.php b/inc/managers/SEO/render/Traits/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/SEO/render/_setup.php b/inc/managers/SEO/render/_setup.php
index 687844c..8049eb9 100644
--- a/inc/managers/SEO/render/_setup.php
+++ b/inc/managers/SEO/render/_setup.php
@@ -1,4 +1,7 @@
 <?php
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/SEO/render/Traits/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/DataType/_setup.php');
 require(JVB_DIR . '/inc/managers/SEO/render/Thing/_setup.php');
diff --git a/inc/managers/SEO/render/index.php b/inc/managers/SEO/render/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/managers/SEO/render/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/managers/ScriptLoader.php b/inc/managers/ScriptLoader.php
index 28fa034..c433857 100644
--- a/inc/managers/ScriptLoader.php
+++ b/inc/managers/ScriptLoader.php
@@ -1,5 +1,11 @@
 <?php
 
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
+
+use JVBase\base\Site;
+use JVBase\registrar\Registrar;
 use JVBase\rest\routes\LoginRoutes;
 
 add_action('init', 'jvbRegisterScripts', 5);
@@ -570,3 +576,206 @@
 		$strategy
 	);
 }
+
+/**
+ * Scripts
+ */
+add_action('wp_enqueue_scripts', 'jvbScripts', 10);
+
+function jvbScripts():void
+{
+	add_action('wp_head', 'jvbInlineNavStyles');
+
+	if (Site::has('dashboard')) {
+		wp_enqueue_script('jvb-queue');
+	}
+
+	wp_enqueue_script('jvb-auth');
+	wp_enqueue_script('jvb-settings');
+	wp_enqueue_script('jvb-navigation');
+//    wp_enqueue_script('jvb-ui');
+//    wp_enqueue_script('jvb-media');
+	wp_enqueue_script('jvb-gallery');
+	wp_enqueue_script('jvb-cache');
+
+
+	$interactions = [];
+	if (Site::has('favourites')) {
+		$interactions[] = 'favourites';
+	}
+	if (!empty(Registrar::withFeature('karma'))) {
+		$interactions[] = 'karma';
+	}
+	if (Site::has('notifications')) {
+		$interactions[] = 'notifications';
+	}
+
+	if (!empty($interactions)) {
+		foreach($interactions as $interaction) {
+			wp_enqueue_script('jvb-'.$interaction);
+		}
+	}
+
+
+	$queue = [
+		'api' => rest_url('jvb/v1/'),
+		'redirect' => get_home_url(null, '/login/'),
+		'labels' => Registrar::getLabels(),
+	];
+
+	wp_localize_script('jvb-auth', 'jvbSettings', $queue);
+
+
+	$initUserSettings = 'async function initUserItems() {
+	if (!jvbSettings.currentUser) return;
+
+	window.userFavourites = {};
+	window.userVotes = {};
+	';
+	if (Site::has('favourites')) {
+		wp_enqueue_script('jvb-favourites');
+		$initUserSettings .= '
+		//Fetch user favourites
+		try {
+			const result = await window.jvbCache.fetchWithCache(
+				`${jvbSettings.api}favourites?all=true`,
+                {
+                    method: \'GET\',
+                    headers: {\'X-WP-Nonce\': jvbSettings.nonce}
+                }
+            );
+
+            if (result && result.favourites) {
+				for (const key in result.favourites) {
+					window.userFavourites[key] = new Set();
+					result.favourites[key].forEach(id => {
+						window.userFavourites[key].add(parseInt(id));
+					});
+                }
+            }
+        } catch (error) {
+			console.error(\'Failed to fetch user favourites:\', error);
+		}';
+	}
+
+	if (!empty(Registrar::withFeature('karma'))) {
+		wp_enqueue_script('jvb-votes');
+		$initUserSettings .= '// Fetch user votes
+        try {
+			const result = await window.jvbCache.fetchWithCache(
+				`${jvbSettings.api}vote?user=${jvbSettings.currentUser}`,
+                {
+                    method: \'GET\',
+                    headers: {\'X-WP-Nonce\': jvbSettings.nonce}
+                },
+                {forceRefresh: true}
+            );
+
+            window.userVotes = {};
+            if (result) {
+				for (const key in result) {
+					window.userVotes[key] = new Map();
+					for (const [id, vote] of Object.entries(result[key])) {
+						window.userVotes[key].set(parseInt(id), vote);
+					}
+                }
+            }
+        } catch (error) {
+			console.error(\'Failed to fetch user votes:\', error);
+		}';
+	}
+
+	$initUserSettings .= '} initUserItems();';
+
+//	wp_add_inline_script('jvb-queue', $initUserSettings, 'after');
+
+
+	if (is_search() ||
+		is_tax(BASE.'shop') ||
+		is_singular(BASE.'artist') ||
+		jvbIsDirectory()
+	) {
+		wp_enqueue_script('jvb-page-nav');
+	}
+
+	if (has_block('jvb/summaryBlock')) {
+		wp_enqueue_script('jvb-page-nav');
+	}
+
+	// Only load on single shop pages or other relevant pages
+	if (is_tax(BASE.'shop') ||
+		is_singular(BASE.'partner')) {
+		$ID = get_queried_object_id();
+		if (is_tax()) {
+			$check = get_term_meta($ID, BASE.'has_map', true);
+		} else {
+			$check = get_post_meta($ID, BASE.'has_map', true);
+		}
+
+//        if ($check && $check !== '') {
+////            wp_enqueue_script(
+////                'google-maps-frontend',
+////                'https://maps.googleapis.com/maps/api/js?key=' . GOOGLE_API . '&callback=googleMapsLoaded',
+////                [],
+////                null,
+////                ['strategy' => 'async',
+////                 'in_footer' => true]
+////            );
+//
+//            // Add the callback function
+//            wp_add_inline_script('google-maps-frontend', '
+//            function googleMapsLoaded() {
+//                if (window.googleMapsCallbacks && window.googleMapsCallbacks.length) {
+//                    window.googleMapsCallbacks.forEach(callback => callback());
+//                }
+//            }
+//        ');
+//        }
+	}
+	if (is_user_logged_in() && Site::has('notifications')) {
+		wp_enqueue_script('jvb-notifications');
+
+		wp_localize_script('jvb-notifications', 'notificationSettings', array(
+			'homeUrl' => home_url(),
+			'strings' => array(
+				'loading' => 'Loading...',
+				'error' => 'Failed to load notifications',
+				'noNotifications' => 'No new notifications',
+				'viewAll' => 'View All Notifications',
+				'markRead' => 'Mark as read',
+				'review' => 'Review'
+			)
+		));
+	}
+
+//    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-queue');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-selector');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-data-store');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-cache');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-a11y');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-utility');
+	jvbAddScriptDependency('jvb-feed-view-script', 'jvb-gallery');
+//    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-loading');
+	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-queue');
+	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-quill');
+	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-form');
+	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-tabs');
+
+	jvbAddScriptDependency('jvb-timeline-view-script', 'jvb-gallery');
+}
+
+function jvbAddScriptDependency(string $handle, string $dep):bool
+{
+	global $wp_scripts;
+
+	$script = $wp_scripts->query($handle);
+	if (!$script) {
+		return false;
+	}
+
+	if (!in_array($dep, $script->deps)) {
+		$script->deps[] = $dep;
+	}
+
+	return true;
+}
diff --git a/inc/managers/TaxonomyRelationships.php b/inc/managers/TaxonomyRelationships.php
index 1f919bd..1462dd2 100644
--- a/inc/managers/TaxonomyRelationships.php
+++ b/inc/managers/TaxonomyRelationships.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers;
 
 use Exception;
+use JVBase\Instance;
 use WP_Error;
 use WP_Post;
 use WP_Query;
@@ -12,15 +13,15 @@
 
 class TaxonomyRelationships
 {
+	use Instance;
     protected Cache $cache;
 	protected CustomTable $relationships;
 
-    public function __construct()
+    private function __construct()
     {
 		$this->defineTables();
 
-        $this->cache = Cache::for('term_relationship', WEEK_IN_SECONDS);
-		$this->cache->connect('terms');
+        $this->cache = Cache::for('term_relationship', 'term', WEEK_IN_SECONDS);
 
         // Ensure the table exists
 //        $this->create_table_if_not_exists();
@@ -196,7 +197,7 @@
     {
 
         $term_posts = $this->cache->remember(
-			$this->cache->generateKey(['term' => $term_id, 'taxonomy' => $taxonomy]),
+			$this->cache->generateKey(['id' => $term_id, 'taxonomy' => $taxonomy]),
 			function () use ($term_id, $taxonomy) {
 				global $wpdb;
 				return $wpdb->get_col($wpdb->prepare(
@@ -211,7 +212,7 @@
 
 
         $related_term_posts = $this->cache->remember(
-			$this->cache->generateKey(['term' => $related_term_id, 'taxonomy' => $related_taxonomy]),
+			$this->cache->generateKey(['id' => $related_term_id, 'taxonomy' => $related_taxonomy]),
 			function () use($related_term_id, $related_taxonomy) {
 				global $wpdb;
 				return $wpdb->get_col($wpdb->prepare(
@@ -292,7 +293,7 @@
 
 
         // Queue the operation
-        $queue = JVB()->queue();
+        $queue = Queue::getInstance();
 
         $queue->queueOperation(
             'taxonomy_relationships',
diff --git a/inc/managers/UploadManager.php b/inc/managers/UploadManager.php
index 00bc05f..4889dbc 100644
--- a/inc/managers/UploadManager.php
+++ b/inc/managers/UploadManager.php
@@ -1,9 +1,14 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\JVB;
+use JVBase\base\Site;
+use JVBase\Instance;
 use Exception;
 use Imagick;
+use JVBase\managers\queue\executors\UploadExecutor;
+use JVBase\managers\queue\mergers\UploadMerger;
+use JVBase\managers\queue\Queue;
+use JVBase\managers\queue\TypeConfig;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -11,11 +16,32 @@
 }
 
 /**
- * Flexible file upload manager for images, videos, and documents
- * Supports configurable directory structures and processing options
+ * The goal here was mainly for membership sites, to organize files by userID, rather than the date structure.
+ * This way, if a user deletes their account, or performs a data request, we can simply copy their directory.
+ *
+ * I started with user name so we could search by name, but issues arose if a user changed their name/display name. So I set it to go to user_id.
+ *
+ * The general flow is:
+ * 		-> Upload, ensure files are stored in appropriate directory, usually {root}/tmp
+ * 		-> Conversion is queued with Queue, as with the /tmp cleanup
+ *
+ * Proposed directory structure:
+ * 		Site::has('membership') || Site::has('is_directory')
+ * 			-> defaults to {user_id}/{post_type}/file
+ * 		Default:
+ * 			-> defaults to {post_type}/file
+ * 				Drawback: if a post is uploaded to one post type, then used in another, the organization is slightly flawed.
+ *
+ * Also, we wanted to ensure images were converted to webp, for speed.
+ *		-> The front-end javascript attempts to do this in browser
+ *		-> If this couldn't be handled, it is queued for server processing
+ *	Of course, some integrations (even google, sillies) only accept jpeg/png, so in some cases we need a jpeg fallback
+ *
+ *
  */
 class UploadManager
 {
+	use Instance;
 	protected array $allowedTypes = [
 		// Images
 		'image/jpeg'    => 'image',
@@ -73,16 +99,179 @@
 		'original_retention' => 2592000, // 30 days
 	];
 
+	/******************************************************************
+	 * UPLOAD SETTINGS
+	******************************************************************/
+	/**
+	 * @var array|int[] Max size of file types
+	 */
+	protected array $maxSize = [
+		'image'		=> 5242880,		// 5MB
+		'video' 	=> 104857600,  // 100MB
+		'document' 	=> 15728640 // 15MB
+	];
+	//Image Specific
+	/**
+	 * @var bool Whether to convert the image
+	 */
+	protected bool $convertImage = true;
+	/**
+	 * @var string Default image conversion
+	 */
+	protected string $convertImageTo = 'webp';
+	/**
+	 * @var int Default image quality
+	 */
+	protected int $convertQuality = 80;
+	/**
+	 * @var bool Whether to optimize images
+	 */
+	protected bool $optimizeImages = true;
+	/**
+	 * @var bool Whether to create image thumbnails
+	 */
+	protected bool $createThumbnails = true;
+	//Video specific
+	/**
+	 * @var bool Whether to automatically extract a thumbnail from a video
+	 */
+	protected bool $extractVideoThumbnail = true;
+	/**
+	 * @var int Time, in microseconds, to grab thumbnail from
+	 */
+	protected int $videoThumbnailPosition = 0;
+	//Document specific
+	/**
+	 * @var bool Whether to extract thumbnail from document.
+	 */
+	protected bool $extractDocumentThumbnail = false;
+	/**
+	 * @var int How long to keep originals (default 3 months)
+	 */
+	protected int $keep_originals;
+	/******************************************************************
+	 * UPLOAD DIRECTORIES
+	******************************************************************/
+	/**
+	 * @var string WordPress' base upload directory
+	 */
 	protected string $upload_dir;
+	/**
+	 * @var string WordPress' base upload directory url
+	 */
 	protected string $upload_url;
-	public function __construct()
+	protected string $dir_pattern;
+	/*****************************************************************
+	 * FEATURE FLAG
+	*****************************************************************/
+	protected bool $has_imagick = false;
+	private function __construct()
 	{
 		$upload = wp_upload_dir();
 		$this->upload_dir = $upload['basedir'];
 		$this->upload_url = $upload['baseurl'];
 
-		// Check for Imagick
-		$this->defaultConfig['use_imagick'] = extension_loaded('imagick');
+		if (Site::has('membership') || Site::has('is_directory')) {
+			$this->dir_pattern = '{user_id}/{post_type}';
+		} else {
+			$this->dir_pattern = '{post_type}';
+		}
+		$this->has_imagick = extension_loaded('imagick');
+		$this->keep_originals = MONTH_IN_SECONDS * 3;
+
+		add_action('init', [$this, 'registerUploadExecutors'], 5);
+	}
+
+	/**
+	 * Register upload operation types with the queue's TypeRegistry
+	 */
+	public function registerUploadExecutors(): void
+	{
+		$registry = Queue::getInstance()->registry();
+		$executor = new UploadExecutor();
+		$merger = new UploadMerger('secured_files');
+
+		// Image uploads - chunked at 5 files
+		$registry->register('image_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 3
+		));
+
+		// Video uploads - one at a time (heavy processing)
+		$registry->register('video_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 1
+		));
+
+		// Document uploads - chunked at 10
+		$registry->register('document_upload', new TypeConfig(
+			mergeable: $merger,
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 5
+		));
+
+		// Metadata updates
+		$registry->register('update_image_meta', new TypeConfig(
+			executor: $executor
+		));
+
+		// Cleanup - chunked at 5
+		$registry->register('temporary_cleanup', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'files',
+			chunkSize: 5
+		));
+
+		// Attach to content (depends on upload completing)
+		$registry->register('attach_upload_to_content', new TypeConfig(
+			executor: $executor
+		));
+
+		// Process upload groups into posts
+		$registry->register('process_upload_groups', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'posts',
+			chunkSize: 5
+		));
+	}
+
+	/**
+	 * @param string|array $file_path
+	 * @param array $context
+	 * @return ?string
+	 * @throws Exception
+	 */
+	protected function testFile(string|array $file_path, array $context):?string
+	{
+		$file_type = null;
+		if (is_array($file_path)) {
+			if (!isset($file_path['tmp_name']) || $file_path['error'] !== UPLOAD_ERR_OK) {
+				throw new Exception('Invalid file upload');
+			}
+			$file_path = $file_path['tmp_name'];
+		}
+		// Validate file upload
+		if (!isset($file_path)) {
+			throw new Exception('Invalid file upload');
+		}
+
+		$mime_type = mime_content_type($file_path);
+		$file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
+		if ($file_type === 'unknown') {
+			throw new Exception('Unsupported file type: ' . $mime_type);
+		}
+
+		// Check allowed types if specified
+		if (array_key_exists('allowed_types', $context) && !in_array($mime_type, $context['allowed_types'])) {
+			throw new Exception('File type not allowed: ' . $mime_type);
+		}
+
+		return $file_type;
 	}
 
 	/**
@@ -94,26 +283,10 @@
 	public function secureUploadedFile(array $file_data, array $context): array
 	{
 		try {
-			// Validate file upload
-			if (!isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) {
-				throw new Exception('Invalid file upload');
-			}
+			$file_type = $this->testFile($file_data, $context);
 
-			$mime_type = mime_content_type($file_data['tmp_name']);
-			$file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
-			if ($file_type === 'unknown') {
-				throw new Exception('Unsupported file type: ' . $mime_type);
-			}
-
-			// Check allowed types if specified
-			if ($this->defaultConfig['allowed_types'] && !in_array($mime_type, $this->defaultConfig['allowed_types'])) {
-				throw new Exception('File type not allowed: ' . $mime_type);
-			}
-
-			$temp_dir = $this->generateDirectory($file_type, "{user_id}/temp", $context);
-
-			// Ensure directories exist
-			$this->ensureDirectories($temp_dir);
+			$context['pattern'] = $this->dir_pattern.'/tmp';
+			$temp_dir = $this->generateDirectory($file_type, $context);
 
 			// Generate a unique filename for temporary storage
 			$temp_filename = uniqid() . '_' . sanitize_file_name($file_data['name']);
@@ -129,15 +302,15 @@
 
 			// Return metadata about the stored file
 			return [
-				'temp_path' => $temp_path,
+				'temp_path' 	=> $temp_path,
 				'original_name' => $file_data['name'],
-				'mime_type' => $file_data['type'],
-				'file_type' => $file_type,
-				'file_size' => $file_data['size'],
-				'stored_at' => current_time('mysql')
+				'mime_type' 	=> $file_data['type'],
+				'file_type' 	=> $file_type,
+				'file_size' 	=> $file_data['size'],
+				'stored_at' 	=> current_time('mysql')
 			];
 		} catch (Exception $e) {
-			JVB()->error()->log('Failed to store uploaded file: ' . print_r($e->getMessage(), true));
+			Errors::getInstance()->log('UploadManager','Failed to store uploaded file: ' . print_r($e->getMessage(), true));
 			return [];
 		}
 	}
@@ -152,60 +325,32 @@
 	public function processUpload(string $temp_path, array $context): array|WP_Error
 	{
 		try {
-			// Validate temp file exists
-			if (!file_exists($temp_path)) {
-				throw new Exception('Temporary file not found: ' . $temp_path);
-			}
-
-			// Validate MIME type
-			$mime_type = mime_content_type($temp_path);
-			if (!$this->validateMimeType($temp_path, $mime_type)) {
-				throw new Exception('Invalid or potentially dangerous file type');
-			}
-			$file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
-
-			if ($file_type === 'unknown') {
-				throw new Exception('Unsupported file type: ' . $mime_type);
-			}
-
-			// Merge config
-			$config = array_merge($this->defaultConfig, $context['config'] ?? [], $context);
+			$file_type = $this->testFile($temp_path, $context);
 
 			// Extract context
 			$user_id = $context['user_id'] ?? get_current_user_id();
 			$post_id = $context['post_id'] ?? 0;
 
-			// Check allowed types if specified
-			if (!empty($config['allowed_types']) && !in_array($mime_type, $config['allowed_types'])) {
-				throw new Exception('File type not allowed: ' . $mime_type);
-			}
-
 			// Validate file size
-			$this->validateFileSize($temp_path, $file_type, $config);
+			$this->validateFileSize($temp_path, $file_type, $context);
 
 			// Generate directory structure for final storage
-			$directory = $this->generateDirectory($file_type, $config['directory_pattern'] ?? '{user_id}/{content}/{file_type}', $context);
-
-			// Ensure directories exist
-			$this->ensureDirectories($directory);
+			$directory = $this->generateDirectory($file_type, $context);
 
 			// Generate filename
 			$original_name = $context['original_name'] ?? basename($temp_path);
 			$filename = $this->generateFilename($original_name, $context);
 
-			// Store original from temp
-			$original_path = $this->storeOriginalFromTemp($temp_path, $directory, $filename);
-
 			// Process based on file type
 			return match($file_type) {
-				'image' => $this->processImage($original_path, $directory, $filename, $post_id, $config, $context),
-				'video' => $this->processVideo($original_path, $directory, $filename, $post_id, $config, $context),
-				'document' => $this->processDocument($original_path, $directory, $filename, $post_id, $config, $context),
+				'image' => $this->processImage($temp_path, $directory, $filename, $post_id, $context),
+				'video' => $this->processVideo($temp_path, $directory, $filename, $post_id, $context),
+				'document' => $this->processDocument($temp_path, $directory, $filename, $post_id, $context),
 				default => throw new Exception('Unknown file type')
 			};
 
 		} catch (Exception $e) {
-			JVB()->error()->log(
+			Errors::getInstance()->log(
 				'[UploadManager]:processUpload',
 				$e->getMessage(),
 				['temp_path' => $temp_path, 'context' => $context]
@@ -215,21 +360,6 @@
 		}
 	}
 
-	/**
-	 * Store original file from temp location (not from PHP upload)
-	 */
-	protected function storeOriginalFromTemp(string $temp_path, string $directory, string $filename): string
-	{
-		$ext = pathinfo($temp_path, PATHINFO_EXTENSION);
-		$original_path = "{$this->upload_dir}/{$directory}/originals/{$filename}.{$ext}";
-
-		// Copy from temp to final location
-		if (!copy($temp_path, $original_path)) {
-			throw new Exception('Failed to store original file');
-		}
-
-		return $original_path;
-	}
 
 	/**
 	 * Clean up empty temporary directories
@@ -262,24 +392,52 @@
 			@rmdir($temp_base);
 		}
 	}
+
 	/**
 	 * Generate directory structure based on pattern
+	 * @param string $file_type
+	 * @param array $config
+	 * @return string
+	 * @throws Exception
 	 */
-	protected function generateDirectory(string $file_type, string $pattern, array $context): string
+	public function generateDirectory(string $file_type, array $config): string
 	{
-		$replacements = [
-			'{user_id}' => $context['user_id'] ?? get_current_user_id(),
-			'{post_type}' => $context['content'] ?? '',
-			'{content}' => $context['content'] ?? '',
-			'{file_type}' => $file_type,
-			'{year}' => date('Y'),
-			'{month}' => date('m'),
-		];
+		$pattern = array_key_exists('pattern', $config) && is_string($config['pattern']) ? $config['pattern'] : $this->dir_pattern;
 
-		$directory = str_replace(array_keys($replacements), array_values($replacements), $pattern);
+		$pattern = explode('/', $pattern);
+		$parts = [];
+		foreach ($pattern as $part) {
+			$newPart = match($part) {
+				'{user_id}' 				=> $context['user_id'] ?? null,
+				'{post_type}', '{content}'	=> $context['content'] ?? null,
+				'{tax}', '{taxonomy}'		=> $context['taxonomy'] ?? null,
+				'{term}'					=> $context['term'] ?? null,
+				'{file_type}'				=> $file_type,
+				'{year}'					=> date('Y'),
+				'{month}'					=> date('m'),
+				'{day}'						=> date('d'),
+				default 					=> $part
+			};
+			if (is_null($newPart)) {
+				error_log('[Uploader] Pattern key was not provided for '.$part);
+			}
+			if (str_contains($newPart, '{')) {
+				error_log('[Uploader] Unhandled pattern found: '. $newPart);
+			}
+			$newPart = $this->sanitizeFileName($newPart);
+			$parts[] = $newPart;
+		}
+		//Remove any null values
+		$parts = array_filter($parts);
+
+		$directory = implode('/', $parts);
 
 		// Allow filtering
-		return apply_filters('jvb_upload_directory', $directory, $file_type, $context);
+		$directory = apply_filters('jvb_upload_directory', $directory, $file_type, $config);
+
+		// Ensure directories exist
+		$this->ensureDirectories($directory);
+		return $directory;
 	}
 
 	/**
@@ -288,8 +446,7 @@
 	protected function ensureDirectories(string $rel_path): void
 	{
 		$dirs = [
-			"{$this->upload_dir}/{$rel_path}",
-			"{$this->upload_dir}/{$rel_path}/originals"
+			"{$this->upload_dir}/{$rel_path}"
 		];
 
 		foreach ($dirs as $dir) {
@@ -299,28 +456,26 @@
 		}
 	}
 
-	/**
-	 * Store original file
-	 */
-	protected function storeOriginal(string $tmp_file, string $directory, string $filename): string
-	{
-		$ext = pathinfo($tmp_file, PATHINFO_EXTENSION);
-		$original_path = "{$this->upload_dir}/{$directory}/originals/{$filename}.{$ext}";
-
-		if (!move_uploaded_file($tmp_file, $original_path)) {
-			throw new Exception('Failed to store original file');
-		}
-
-		return $original_path;
-	}
 
 	/**
 	 * Validate file size based on type and config
+	 * @param string $file_path
+	 * @param string $file_type
+	 * @param array $config
+	 * @return void
+	 * @throws Exception
 	 */
 	protected function validateFileSize(string $file_path, string $file_type, array $config): void
 	{
 		$size = filesize($file_path);
-		$max_size = $config['max_size'][$file_type] ?? $this->defaultConfig['max_size'][$file_type];
+		$max_size = $this->maxSize[$file_type];
+		if (array_key_exists('max_size', $config)) {
+			if (is_int($config['max_size'])) {
+				$max_size = $config['max_size'];
+			} else if (is_array($config['max_size']) && array_key_exists($file_type, $config['max_size'])) {
+				$max_size = $config['max_size'][$file_type];
+			}
+		}
 
 		if ($size > $max_size) {
 			$max_mb = round($max_size / 1048576, 2);
@@ -354,7 +509,7 @@
 	 * Process image file
 	 * @throws Exception
 	 */
-	protected function processImage(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+	protected function processImage(string $original_path, string $directory, string $filename, int $post_id, array $context): array
 	{
 		$full_dir = "{$this->upload_dir}/{$directory}";
 		$original_mime = mime_content_type($original_path);
@@ -363,17 +518,18 @@
 		$should_convert = false;
 		$target_format = false;
 
-		if ($config['convert'] && $config['convert'] !== false) {
-			$target_format = strtolower($config['convert']);
-
-			// Validate format
-			if (!isset($this->supportedFormats[$target_format])) {
-				throw new Exception("Unsupported conversion format: {$target_format}");
+		if ((array_key_exists('convert', $context) && $context['convert'] !== false) || $this->convertImage) {
+			$target_format = empty($context['convertTo']??'') ? $this->convertImage : $context['convertTo'];
+			if (!empty($target_format)) {
+				// Validate format
+				if (!isset($this->supportedFormats[$target_format])) {
+					error_log('[Uploader] Unsupported conversion format: '.$target_format.'... will not convert');
+				} else {
+					// Check if conversion is needed
+					$target_mime = $this->supportedFormats[$target_format];
+					$should_convert = ($original_mime !== $target_mime);
+				}
 			}
-
-			// Check if conversion is needed
-			$target_mime = $this->supportedFormats[$target_format];
-			$should_convert = ($original_mime !== $target_mime);
 		}
 
 		// Convert or copy based on configuration
@@ -381,15 +537,18 @@
 			$final_path = "{$full_dir}/{$filename}.{$target_format}";
 
 			try {
+
 				$this->convertImage(
 					$original_path,
 					$final_path,
 					$target_format,
-					$config['quality'],
-					$config['use_imagick']
+					array_key_exists('quality', $context) && is_int($context['quality']) && $context['quality'] <= 100 ?
+						$context['quality'] :
+						$this->convertQuality,
 				);
+
 			} catch (Exception $e) {
-				JVB()->error()->log('Image conversion failed: ' . print_r($e->getMessage(), true));
+				Errors::getInstance()->log('Uploader', 'Image conversion failed: ' . print_r($e->getMessage(), true));
 				// Fallback to original
 				$ext = pathinfo($original_path, PATHINFO_EXTENSION);
 				$final_path = "{$full_dir}/{$filename}.{$ext}";
@@ -412,17 +571,19 @@
 		}
 
 		// Generate thumbnails
-		if ($config['create_thumbnails']) {
+		$hasOption = array_key_exists('create_thumbnails', $context);
+		if (($hasOption && $context['create_thumbnails']) || (!$hasOption && $this->createThumbnails)) {
 			$this->generateThumbnails($attachment_id);
 		}
 
 		return [
-			'success' => true,
+			'success' 	=> true,
 			'attachment_id' => $attachment_id,
-			'url' => wp_get_attachment_url($attachment_id),
-			'file' => $final_path,
-			'type' => 'image',
-			'format' => $target_format ?: pathinfo($final_path, PATHINFO_EXTENSION)
+			'url' 		=> wp_get_attachment_url($attachment_id),
+			'file' 		=> $final_path,
+			'type' 		=> 'image',
+			'format' 	=> $target_format ?: pathinfo($final_path, PATHINFO_EXTENSION),
+			'context'	=> $context
 		];
 	}
 
@@ -430,7 +591,7 @@
 	 * Process video file
 	 * @throws Exception
 	 */
-	protected function processVideo(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+	protected function processVideo(string $original_path, string $directory, string $filename, int $post_id, array $context): array
 	{
 		$full_dir = "{$this->upload_dir}/{$directory}";
 		$ext = pathinfo($original_path, PATHINFO_EXTENSION);
@@ -443,29 +604,32 @@
 		try {
 			$attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
 		} catch (Exception $e) {
-			JVB()->error()->log('Attachment creation failed: '.print_r($e->getMessage(), true));
+			Errors::getInstance()->log('Uploader', 'Attachment creation failed: '.print_r($e->getMessage(), true));
 			return ['success' => false];
 		}
 
 
 		// Extract thumbnail if enabled
-		if ($config['extract_video_thumbnail']) {
-			$this->extractVideoThumbnail($attachment_id, $final_path, $config['video_thumbnail_time']);
+		$hasOption = array_key_exists('extract_thumbnail', $context);
+		if (($hasOption && $context['extract_thumbnail']) || (!$hasOption && $this->extractVideoThumbnail)) {
+			$time = $context['thumbnail_time']??$this->videoThumbnailPosition;
+			$this->extractVideoThumbnail($attachment_id, $final_path, $time);
 		}
 
 		return [
-			'success' => true,
+			'success' 	=> true,
 			'attachment_id' => $attachment_id,
-			'url' => wp_get_attachment_url($attachment_id),
-			'file' => $final_path,
-			'type' => 'video'
+			'url' 		=> wp_get_attachment_url($attachment_id),
+			'file' 		=> $final_path,
+			'type' 		=> 'video',
+			'context'	=> $context
 		];
 	}
 
 	/**
 	 * Process document file
 	 */
-	protected function processDocument(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+	protected function processDocument(string $original_path, string $directory, string $filename, int $post_id, array $context): array
 	{
 		$full_dir = "{$this->upload_dir}/{$directory}";
 		$ext = pathinfo($original_path, PATHINFO_EXTENSION);
@@ -478,36 +642,36 @@
 		try {
 			$attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
 		} catch (Exception $e) {
-			JVB()->error()->log('Conversion failed: '.print_r($e->getMessage(), true));
+			Errors::getInstance()->log('Uploader','Conversion failed: '.print_r($e->getMessage(), true));
 			return ['success' => false];
 		}
 
-
 		return [
-			'success' => true,
+			'success' 	=> true,
 			'attachment_id' => $attachment_id,
-			'url' => wp_get_attachment_url($attachment_id),
-			'file' => $final_path,
-			'type' => 'document'
+			'url' 		=> wp_get_attachment_url($attachment_id),
+			'file' 		=> $final_path,
+			'type' 		=> 'document',
+			'context'	=> $context
 		];
 	}
 
 	/**
 	 * Generate SEO-friendly filename
 	 */
-	protected function generateFilename(string $original_name, array $context): string
+	public function generateFilename(string $original_name, array $context): string
 	{
 		$name_parts = pathinfo($original_name);
-		$base_name = sanitize_title($name_parts['filename']);
+		$base_name = $this->sanitizeFileName($name_parts['filename']);
 		$user_data = array_key_exists('user_id', $context) ? get_userdata((int)$context['user_id']) : get_current_user();
 		$username = $user_data ? sanitize_title($user_data->display_name) : 'user';
 		$timestamp = time();
 
-		return apply_filters(
+		return $this->sanitizeFileName(apply_filters(
 			'jvb_upload_filename',
 			"{$base_name}-{$timestamp}",
 			$context
-		);
+		));
 	}
 
 	protected function sanitizeFileName(string $filename): string
@@ -570,7 +734,7 @@
 	 * @param bool $replace Replace original file (default: true)
 	 * @return array|WP_Error Result with new attachment details
 	 */
-	public function convertImageTo(int $attachment_id, string $format, int $quality = 80, bool $replace = true): array|WP_Error
+	public function convertImageTo(int $attachment_id, string $format, int $quality = 80, bool $replace = false): array|WP_Error
 	{
 		try {
 			// Validate format
@@ -608,8 +772,7 @@
 			$new_path = $path_info['dirname'] . '/' . $new_filename;
 
 			// Perform conversion
-			$use_imagick = $this->defaultConfig['use_imagick'];
-			$this->convertImage($original_path, $new_path, $format, $quality, $use_imagick);
+			$this->convertImage($original_path, $new_path, $format, $quality);
 
 			if ($replace) {
 				// Replace the original file
@@ -645,16 +808,31 @@
 	/**
 	 * Generic image conversion method
 	 */
-	protected function convertImage(string $source, string $destination, string $format, int $quality, bool $use_imagick): void
+	/**
+	 * @param string $source
+	 * @param string $destination
+	 * @param string $format
+	 * @param int $quality
+	 * @return void
+	 * @throws Exception
+	 */
+	protected function convertImage(string $source, string $destination, string $format, int $quality): void
 	{
-		if ($use_imagick) {
+		if ($this->has_imagick) {
 			$this->convertWithImagick($source, $destination, $format, $quality);
 		} else {
 			$this->convertWithGd($source, $destination, $format, $quality);
 		}
 	}
+
 	/**
-	 * Convert image with Imagick (supports multiple formats)
+	 * Convert image with Imagick
+	 * @param string $source
+	 * @param string $destination
+	 * @param string $format
+	 * @param int $quality
+	 * @return void
+	 * @throws Exception
 	 */
 	protected function convertWithImagick(string $source, string $destination, string $format, int $quality): void
 	{
@@ -678,14 +856,19 @@
 
 			$image->writeImage($destination);
 			$image->clear();
-			$image->destroy();
 		} catch (Exception $e) {
 			throw new Exception("Imagick conversion to {$format} failed: " . $e->getMessage());
 		}
 	}
 
 	/**
-	 * Convert image with GD (supports multiple formats)
+	 * Convert image with GD
+	 * @param string $source
+	 * @param string $destination
+	 * @param string $format
+	 * @param int $quality
+	 * @return void
+	 * @throws Exception
 	 */
 	protected function convertWithGd(string $source, string $destination, string $format, int $quality): void
 	{
@@ -750,160 +933,4 @@
 		// Placeholder for future implementation
 		apply_filters('jvb_extract_video_thumbnail', null, $attachment_id, $video_path, $time);
 	}
-
-
-
-
-	/**
-	 * Phase 1: Validate and process file on disk (no DB writes)
-	 * Safe to run outside transactions — only does file I/O
-	 *
-	 * @return array Prepared file info for registerFile()
-	 */
-	public function prepareFile(string $temp_path, array $context): array
-	{
-		if (!file_exists($temp_path)) {
-			throw new Exception('Temporary file not found: ' . $temp_path);
-		}
-
-		$mime_type = mime_content_type($temp_path);
-		if (!$this->validateMimeType($temp_path, $mime_type)) {
-			throw new Exception('Invalid or potentially dangerous file type');
-		}
-
-		$file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
-		if ($file_type === 'unknown') {
-			throw new Exception('Unsupported file type: ' . $mime_type);
-		}
-
-		$config  = array_merge($this->defaultConfig, $context['config'] ?? [], $context);
-		$post_id = $context['post_id'] ?? 0;
-
-		if (!empty($config['allowed_types']) && !in_array($mime_type, $config['allowed_types'])) {
-			throw new Exception('File type not allowed: ' . $mime_type);
-		}
-
-		$this->validateFileSize($temp_path, $file_type, $config);
-
-		$directory = $this->generateDirectory(
-			$file_type,
-			$config['directory_pattern'] ?? '{user_id}/{content}/{file_type}',
-			$context
-		);
-		$this->ensureDirectories($directory);
-
-		$original_name = $context['original_name'] ?? basename($temp_path);
-		$filename      = $this->generateFilename($original_name, $context);
-		$original_path = $this->storeOriginalFromTemp($temp_path, $directory, $filename);
-
-		// File-type-specific I/O (conversion, copying) — no DB
-		$final_path = match ($file_type) {
-			'image'    => $this->prepareImage($original_path, $directory, $filename, $config),
-			'video'    => $this->prepareMedia($original_path, $directory, $filename),
-			'document' => $this->prepareMedia($original_path, $directory, $filename),
-			default    => throw new Exception('Unknown file type'),
-		};
-
-		return [
-			'final_path' => $final_path,
-			'file_type'  => $file_type,
-			'filename'   => $filename,
-			'post_id'    => $post_id,
-			'config'     => $config,
-			'context'    => $context,
-		];
-	}
-
-	/**
-	 * Phase 2: Register processed file in WordPress (DB writes only)
-	 * Quick operation — individual WP functions handle their own queries
-	 *
-	 * @param array $prepared Output from prepareFile()
-	 * @return array Standard upload result
-	 */
-	public function registerFile(array $prepared): array
-	{
-		$attachment_id = $this->createAttachment(
-			$prepared['final_path'],
-			$prepared['filename'],
-			$prepared['context'],
-			$prepared['post_id']
-		);
-
-		// Type-specific post-registration
-		if ($prepared['file_type'] === 'image') {
-			$alt_text = apply_filters('jvb_upload_alt_text', '', $prepared['context']);
-			if ($alt_text !== '') {
-				update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
-			}
-		}
-
-		if ($prepared['file_type'] === 'video' && ($prepared['config']['extract_video_thumbnail'] ?? false)) {
-			$this->extractVideoThumbnail(
-				$attachment_id,
-				$prepared['final_path'],
-				$prepared['config']['video_thumbnail_time'] ?? 0
-			);
-		}
-
-		return [
-			'success'       => true,
-			'attachment_id' => $attachment_id,
-			'url'           => wp_get_attachment_url($attachment_id),
-			'file'          => $prepared['final_path'],
-			'type'          => $prepared['file_type'],
-		];
-	}
-
-	/**
-	 * Image file I/O: convert or copy to final location
-	 */
-	protected function prepareImage(string $original_path, string $directory, string $filename, array $config): string
-	{
-		$full_dir      = "{$this->upload_dir}/{$directory}";
-		$original_mime = mime_content_type($original_path);
-
-		$should_convert = false;
-		$target_format  = false;
-
-		if ($config['convert'] && $config['convert'] !== false) {
-			$target_format = strtolower($config['convert']);
-			if (!isset($this->supportedFormats[$target_format])) {
-				throw new Exception("Unsupported conversion format: {$target_format}");
-			}
-			$should_convert = ($original_mime !== $this->supportedFormats[$target_format]);
-		}
-
-		if ($should_convert) {
-			$final_path = "{$full_dir}/{$filename}.{$target_format}";
-			try {
-				$this->convertImage($original_path, $final_path, $target_format, $config['quality'], $config['use_imagick']);
-			} catch (Exception $e) {
-				JVB()->error()->log('Image conversion failed: ' . $e->getMessage());
-				$ext = pathinfo($original_path, PATHINFO_EXTENSION);
-				$final_path = "{$full_dir}/{$filename}.{$ext}";
-				copy($original_path, $final_path);
-			}
-		} else {
-			$ext = pathinfo($original_path, PATHINFO_EXTENSION);
-			$final_path = "{$full_dir}/{$filename}.{$ext}";
-			copy($original_path, $final_path);
-		}
-
-		return $final_path;
-	}
-
-	/**
-	 * Video/document file I/O: copy to final location
-	 */
-	protected function prepareMedia(string $original_path, string $directory, string $filename): string
-	{
-		$full_dir   = "{$this->upload_dir}/{$directory}";
-		$ext        = pathinfo($original_path, PATHINFO_EXTENSION);
-		$final_path = "{$full_dir}/{$filename}.{$ext}";
-
-		copy($original_path, $final_path);
-
-		return $final_path;
-	}
 }
diff --git a/inc/managers/UserTermsManager.php b/inc/managers/UserTermsManager.php
index ffc982e..80995cc 100644
--- a/inc/managers/UserTermsManager.php
+++ b/inc/managers/UserTermsManager.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use WP_Post;
 use WP_Error;
@@ -12,10 +13,10 @@
 }
 class UserTermsManager
 {
-
+	use Instance;
 	protected CustomTable $index;
 
-    public function __construct()
+    private function __construct()
     {
 		$this->defineTables();
 
@@ -188,7 +189,7 @@
 			];
 		}
 
-        JVB()->queue()->queueOperation(
+        Queue::getInstance()->queueOperation(
             'rebuild_user_term_index',
             0,
             [
@@ -223,7 +224,7 @@
 			];
 		}
 
-        JVB()->queue()->queueOperation(
+        Queue::getInstance()->queueOperation(
             'rebuild_user_term_index',
             0,
             [
diff --git a/inc/managers/VerifyEntryManager.php b/inc/managers/VerifyEntryManager.php
index 01fb039..ac742dc 100644
--- a/inc/managers/VerifyEntryManager.php
+++ b/inc/managers/VerifyEntryManager.php
@@ -5,13 +5,20 @@
 	exit;
 }
 
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use JVBase\managers\CustomTable;
+use JVBase\rest\Rest;
+use JVBase\rest\routes\ApprovalRoutes;
+
 class VerifyEntryManager {
+	use Instance;
 	protected CustomTable $table;
 	protected int $max = 3;
-	public function __construct()
+	protected Rest $route;
+	private function __construct()
 	{
+		$this->route = ApprovalRoutes::getInstance();
 		$this->defineTables();
 	}
 
@@ -113,7 +120,7 @@
 		]);
 
 		if ($request && !empty($owners)) {
-			JVB()->notification()->notify($owners,'entry_requested',$userID, [
+			NotificationManager::getInstance()->notify($owners,'entry_requested',$userID, [
 				'target_id' => $termID,
 				'target_type' => $taxonomy
 			]);
@@ -143,9 +150,9 @@
 			wp_set_object_terms($request['content_id'], $termID, jvbCheckBase($request['taxonomy']));
 		}
 
-		JVB()->notification()?->notify($request['user_id'],$approve ? 'entry_approved' : 'entry_denied',$userID);
+		NotificationManager::getInstance()?->notify($request['user_id'],$approve ? 'entry_approved' : 'entry_denied',$userID);
 		//TODO: What if there are multiple managers, and one approved the request? Do we just delete the notification?
-		JVB()->notification()?->unnotify($owners, 'entry_requested', $request['user_id'], [
+		NotificationManager::getInstance()?->unnotify($owners, 'entry_requested', $request['user_id'], [
 			'target_id' => $termID,
 			'target_type' => $request['taxonomy']
 		]);
diff --git a/inc/managers/_setup.php b/inc/managers/_setup.php
index 890ddba..e297b3f 100644
--- a/inc/managers/_setup.php
+++ b/inc/managers/_setup.php
@@ -1,23 +1,26 @@
 <?php
 
-use JVBase\managers\ApprovalManager;
+use JVBase\managers\Approvals;
 use JVBase\managers\Cache;
 use JVBase\managers\IconsManager;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 require(JVB_DIR . '/inc/managers/ScriptLoader.php');
+require(JVB_DIR . '/inc/managers/Errors.php');
 require(JVB_DIR . '/inc/managers/CustomTable.php');
 //require(JVB_DIR . '/inc/managers/CacheManager.php');
 require(JVB_DIR . '/inc/managers/Cache.php');
 
 
 require(JVB_DIR . '/inc/managers/IconsManager.php');
-add_action('init', 'jvbInit', 0); // Priority 1 - very early
-function jvbInit(): void
+add_action('jvbloaded', 'jvbManagers'); // Priority 1 - very early
+function jvbManagers(): void
 {
 
-	Cache::registerHooks();
+//	Cache::registerHooks();
 	// Initialize base sources (this registers hooks and includes defaults)
 	IconsManager::for();
 	IconsManager::for('forms');
@@ -28,7 +31,7 @@
 	}
 
 	if (Site::has('magic_link')) {
-		require(JVB_DIR . '/inc/managers/MagicLinkManager.php');
+		require(JVB_DIR . '/inc/managers/MagicLinks.php');
 	}
 
 	if (Site::hasAny(['dashboard', 'feed_block'])) {
@@ -51,7 +54,7 @@
 	}
 
 	if (!empty(Registrar::withFeature('approve_new'))) {
-		require(JVB_DIR . '/inc/managers/ApprovalManager.php');
+		require(JVB_DIR . '/inc/managers/Approvals.php');
 	}
 
 
@@ -77,7 +80,7 @@
 	}
 
 	if (Site::has('referrals')) {
-		require(JVB_DIR . '/inc/managers/ReferralManager.php');
+		require(JVB_DIR . '/inc/managers/Referrals.php');
 	}
 
 	if (!empty(Registrar::withFeature('karma'))) {
@@ -93,10 +96,10 @@
 function jvb_load_karma():void
 {
 }
-require(JVB_DIR . '/inc/managers/ErrorHandler.php');
+
 require(JVB_DIR . '/inc/managers/queue/_setup.php');
-require(JVB_DIR . '/inc/managers/EmailManager.php');
-require(JVB_DIR . '/inc/managers/LoginManager.php');
+require(JVB_DIR . '/inc/managers/Emails.php');
+require(JVB_DIR . '/inc/managers/Login.php');
 
 
 //require(JVB_DIR . '/inc/managers/SchemaManager.php');
@@ -105,4 +108,4 @@
 
 require(JVB_DIR . '/inc/managers/ImageGenerator.php');
 require(JVB_DIR . '/inc/managers/AdminPages.php');
-require(JVB_DIR . '/inc/managers/RoleManager.php');
+require(JVB_DIR . '/inc/managers/Roles.php');
diff --git a/inc/managers/queue/Progress.php b/inc/managers/queue/Progress.php
index 3aa9367..f10e360 100644
--- a/inc/managers/queue/Progress.php
+++ b/inc/managers/queue/Progress.php
@@ -19,7 +19,7 @@
 			$this->operation->processedItems + $count,
 			$this->operation->totalItems
 		);
-		JVB()->queue()->storage()->saveProgress($this->operation);
+		Queue::getInstance()->storage()->saveProgress($this->operation);
 	}
 
 	public function failItem(mixed $item, string $reason): void
@@ -28,6 +28,6 @@
 			'item' => $item,
 			'reason' => $reason,
 		];
-		JVB()->queue()->storage()->saveProgress($this->operation);
+		Queue::getInstance()->storage()->saveProgress($this->operation);
 	}
 }
diff --git a/inc/managers/queue/Queue.php b/inc/managers/queue/Queue.php
index aee981e..c668f4c 100644
--- a/inc/managers/queue/Queue.php
+++ b/inc/managers/queue/Queue.php
@@ -4,21 +4,28 @@
 	exit;
 }
 
-use JVBase\managers\CustomTable;
+use JVBase\Instance;
+use JVBase\rest\routes\QueueRoutes;
 use WP_Error;
 use WP_REST_Request;
 use WP_REST_Response;
 
 class Queue
 {
+	use Instance;
 	private Storage $storage;
 	private Processor $processor;
 	private TypeRegistry $registry;
 	public Executor $executor;
 	private Locker $locker;
 
-	public function __construct()
+	private QueueRoutes $routes;
+
+	private function __construct()
 	{
+		//Load routes
+		$this->routes = new QueueRoutes();
+
 		$this->storage = new Storage();
 		$this->registry = new TypeRegistry();
 		$this->locker = new Locker();
@@ -422,7 +429,7 @@
 
 	public function registerAdminAction():void
 	{
-		$admin = JVB()->admin();
+		$admin = AdminPages::getInstance();
 		$admin->registerAction(
 			'Restart Stuck Operations',
 			'restart-stuck-operations',
diff --git a/inc/managers/queue/Storage.php b/inc/managers/queue/Storage.php
index 8793c6e..def1284 100644
--- a/inc/managers/queue/Storage.php
+++ b/inc/managers/queue/Storage.php
@@ -19,7 +19,7 @@
 	public function __construct()
 	{
 		$this->defineTables();
-		$this->cache = Cache::for('queue', DAY_IN_SECONDS)->user();
+		$this->cache = Cache::for('queue', null, DAY_IN_SECONDS)->user();
 	}
 
 	public function hasProcessingOperations(): bool
diff --git a/inc/managers/queue/TypeRegistry.php b/inc/managers/queue/TypeRegistry.php
index 9d5bf7b..f9c9a26 100644
--- a/inc/managers/queue/TypeRegistry.php
+++ b/inc/managers/queue/TypeRegistry.php
@@ -22,7 +22,7 @@
 
 	public function getExecutor(string $type): ?Executor
 	{
-		return array_key_exists($type, $this->configs) ? $this->configs[$type]->executor : JVB()->queue()->executor;
+		return array_key_exists($type, $this->configs) ? $this->configs[$type]->executor : Queue::getInstance()->executor;
 	}
 
 	public function getMergeable(string $type): ?Mergeable
diff --git a/inc/managers/queue/executors/ContentExecutor.php b/inc/managers/queue/executors/ContentExecutor.php
index 6b6aa1a..2755eb9 100644
--- a/inc/managers/queue/executors/ContentExecutor.php
+++ b/inc/managers/queue/executors/ContentExecutor.php
@@ -125,7 +125,7 @@
 
 		// Send notification
 //		if (jvbSiteHasNotifications()) {
-//			JVB()->notification()->addNotification(
+//			NotificationManager::getInstance()->addNotification(
 //				$this->userId,
 //				'content_update_complete',
 //				null,
@@ -596,7 +596,7 @@
 	{
 		error_log('Handling term '.$ID.' with data: '.print_r($data, true));
 		//Existing term update
-		if ($registrar->hasFeature('is_ownable') && (!JVB()->roles()->isOwner($this->userId, $ID) && !JVB()->roles()->isManager($this->userId, $ID))) {
+		if ($registrar->hasFeature('is_ownable') && (!Roles::getInstance()()->isOwner($this->userId, $ID) && !Roles::getInstance()()->isManager($this->userId, $ID))) {
 			error_log('Term is ownable. User does not own this term.');
 			$progress->failItem($ID, 'No permission to modify this term');
 			$results['errors'][$ID] = 'No permission';
diff --git a/inc/managers/queue/executors/ContentTermExecutor.php b/inc/managers/queue/executors/ContentTermExecutor.php
index 67d1cfe..010b5fe 100644
--- a/inc/managers/queue/executors/ContentTermExecutor.php
+++ b/inc/managers/queue/executors/ContentTermExecutor.php
@@ -6,7 +6,7 @@
 use JVBase\managers\queue\Operation;
 use JVBase\managers\queue\Progress;
 use JVBase\managers\queue\Result;
-use JVBase\managers\RoleManager;
+use JVBase\managers\Roles;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 use Exception;
@@ -25,11 +25,11 @@
  */
 class ContentTermExecutor implements Executor
 {
-	protected RoleManager $roleManager;
+	protected Roles $roleManager;
 
 	public function __construct()
 	{
-		$this->roleManager = new RoleManager();
+		$this->roleManager = new Roles();
 	}
 
 	public function execute(Operation $operation, Progress $progress): Result
@@ -319,7 +319,7 @@
 		$user = get_userdata($userID);
 
 		foreach ($managers as $managerID) {
-			JVB()->notification()->addNotification(
+			NotificationManager::getInstance()->addNotification(
 				$managerID,
 				$notificationType,
 				[
diff --git a/inc/managers/queue/executors/IntegrationExecutor.php b/inc/managers/queue/executors/IntegrationExecutor.php
index 428dc88..9af8556 100644
--- a/inc/managers/queue/executors/IntegrationExecutor.php
+++ b/inc/managers/queue/executors/IntegrationExecutor.php
@@ -18,7 +18,7 @@
  *          {integration}_sync_from, {integration}_sync_customer, etc.
  *
  * Registration (in Integrations::registerAdditionalHooks):
- *   $queue = JVB()->queue();
+ *   $queue = Queue::getInstance();
  *   $queue->registry()->register('helcim_sync_to', new TypeConfig(
  *       executor: new IntegrationExecutor(),
  *       chunkKey: 'items',
diff --git a/inc/managers/queue/executors/InvitationExecutor.php b/inc/managers/queue/executors/InvitationExecutor.php
index db23a21..737a923 100644
--- a/inc/managers/queue/executors/InvitationExecutor.php
+++ b/inc/managers/queue/executors/InvitationExecutor.php
@@ -6,7 +6,7 @@
 use JVBase\managers\queue\Operation;
 use JVBase\managers\queue\Progress;
 use JVBase\managers\queue\Result;
-use JVBase\managers\RoleManager;
+use JVBase\managers\Roles;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -16,13 +16,13 @@
 class InvitationExecutor implements Executor
 {
 	protected CustomTable $table;
-	protected RoleManager $roleManager;
+	protected Roles $roleManager;
 	protected int $expiryDays = 14;
 
 	public function __construct()
 	{
 		$this->table = CustomTable::for('invitations');
-		$this->roleManager = new RoleManager();
+		$this->roleManager = new Roles();
 	}
 
 	public function execute(Operation $operation, Progress $progress): Result
diff --git a/inc/managers/queue/executors/UploadExecutor.php b/inc/managers/queue/executors/UploadExecutor.php
index b825232..7e1637e 100644
--- a/inc/managers/queue/executors/UploadExecutor.php
+++ b/inc/managers/queue/executors/UploadExecutor.php
@@ -1,13 +1,13 @@
 <?php
 namespace JVBase\managers\queue\executors;
 
-use JVBase\managers\queue\{Executor, Operation, Progress, Result};
+use JVBase\managers\queue\{Executor, Operation, Progress, Queue, Result};
 use JVBase\managers\Cache;
+use JVBase\managers\Errors;
 use JVBase\managers\UploadManager;
 use JVBase\meta\Meta;
 use Exception;
 use JVBase\registrar\Registrar;
-use JVBase\base\Site;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
@@ -31,10 +31,16 @@
 		'process_upload_groups'
 	];
 
+	/**
+	 * @param Operation $operation
+	 * @param Progress $progress
+	 * @return Result
+	 * @throws Exception
+	 */
 	public function execute(Operation $operation, Progress $progress): Result
 	{
 		if (!in_array($operation->type, self::HANDLED_TYPES)) {
-			throw new Exception("UploadExecutor cannot handle type: {$operation->type}");
+			throw new Exception("UploadExecutor cannot handle type: $operation->type");
 		}
 
 		try {
@@ -48,11 +54,11 @@
 				'temporary_cleanup'       => $this->processTemporaryCleanup($operation, $data, $progress),
 				'attach_upload_to_content'=> $this->processAttachToContent($operation, $data, $progress),
 				'process_upload_groups'   => $this->processUploadGroups($operation, $data, $progress),
-				default                   => throw new Exception("Unknown type: {$operation->type}")
+				default                   => throw new Exception("Unknown type: $operation->type")
 			};
 
 		} catch (Exception $e) {
-			JVB()->error()->log(
+			Errors::getInstance()->log(
 				'[UploadExecutor]:execute',
 				$e->getMessage(),
 				[
@@ -74,74 +80,32 @@
 	 */
 	private function processFileUpload(Operation $operation, array $data, string $fileType, Progress $progress): Result
 	{
-		$uploader = new UploadManager();
-		$processedResults = [];
+		$uploader = UploadManager::getInstance();
 		$errors = [];
 		$uploadIds = [];
 
 		$securedFiles = $data['secured_files'] ?? [];
+		$args = $data;
+		unset($args['secured_files']);
 
-		// Phase 1: File I/O — no DB, no transactions
 		$prepared = [];
 		foreach ($securedFiles as $securedFile) {
 			$uploadIds[] = $securedFile['upload_id'];
 			try {
-				$prepared[$securedFile['upload_id']] = $uploader->prepareFile(
+				$prepared[$securedFile['upload_id']] = $uploader->processUpload(
 					$securedFile['temp_path'],
-					[
-						'file_type'     => $fileType,
-						'user_id'       => $operation->userId,
-						'post_id'       => (int)($data['post_id'] ?? 0),
-						'term_id'       => (int)($data['term_id'] ?? 0),
-						'original_name' => $securedFile['original_name'],
-						'mime_type'     => $securedFile['mime_type'],
-						'content'       => $data['content'] ?? '',
-					]
+					$args
 				);
+				$progress->advance();
 			} catch (Exception $e) {
 				$progress->failItem($securedFile['upload_id'], $e->getMessage());
 				$errors[] = $e->getMessage();
 			}
 		}
 
-		// Phase 2: DB registration — quick per-file writes
-		foreach ($prepared as $uploadId => $fileInfo) {
-			try {
-				$result = $uploader->registerFile($fileInfo);
 
-				if (!$result['success']) {
-					$progress->failItem($uploadId, 'Registration failed');
-					$errors[] = "Failed to register {$uploadId}";
-					continue;
-				}
-
-				$processedResults[$uploadId] = [
-					'upload_id'     => $uploadId,
-					'attachment_id' => $result['attachment_id'] ?? 0,
-					'url'           => $result['url'] ?? '',
-					'sizes'         => $result['sizes'] ?? [],
-				];
-
-				// Apply frontend metadata if provided
-				$securedFile = $this->findSecuredFile($securedFiles, $uploadId);
-				if ($securedFile && !empty($securedFile['metadata'])) {
-					$this->applyMeta($result['attachment_id'], $securedFile['metadata']);
-				}
-
-				$progress->advance();
-
-			} catch (Exception $e) {
-				$progress->failItem($uploadId, $e->getMessage());
-				$errors[] = $e->getMessage();
-			}
-		}
-
-		// Destination + cleanup unchanged
-		$this->handleUploadDestination($data, $processedResults);
-		$this->cleanupTempFiles($securedFiles, $operation->userId);
-
-		$outcome = count($processedResults) > 0 ? 'success' : 'failed';
-		if (count($processedResults) > 0 && !empty($errors)) {
+		$outcome = count($prepared) > 0 ? 'success' : 'failed';
+		if (count($prepared) > 0 && !empty($errors)) {
 			$outcome = 'partial';
 		}
 
@@ -149,8 +113,8 @@
 			outcome: $outcome,
 			result: [
 				'upload_ids'      => $uploadIds,
-				'uploads'         => $processedResults,
-				'processed_count' => count($processedResults),
+				'uploads'         => $prepared,
+				'processed_count' => count($prepared),
 				'total_count'     => count($uploadIds),
 				'errors'          => $errors,
 			]
@@ -158,20 +122,12 @@
 	}
 
 	/**
-	 * Find a secured file entry by upload_id
-	 */
-	private function findSecuredFile(array $securedFiles, string $uploadId): ?array
-	{
-		foreach ($securedFiles as $file) {
-			if (($file['upload_id'] ?? null) === $uploadId) {
-				return $file;
-			}
-		}
-		return null;
-	}
-
-	/**
 	 * Attach upload results to content
+	 * @param Operation $operation
+	 * @param array $data
+	 * @param Progress $progress
+	 * @return Result
+	 * @throws Exception
 	 */
 	private function processAttachToContent(Operation $operation, array $data, Progress $progress): Result
 	{
@@ -181,9 +137,9 @@
 			throw new Exception('No upload operation ID provided');
 		}
 
-		$uploadOp = JVB()->queue()->get($uploadOpId);
+		$uploadOp = Queue::getInstance()->get($uploadOpId);
 		if (!$uploadOp || !in_array($uploadOp->outcome, ['success', 'partial'])) {
-			throw new Exception("Upload operation {$uploadOpId} not completed successfully");
+			throw new Exception("Upload operation $uploadOpId not completed successfully");
 		}
 
 		$uploadResults = $uploadOp->result['uploads'] ?? [];
@@ -194,7 +150,7 @@
 		// Resolve post_id from content_update dependency for new posts
 		if (empty($data['post_id']) || str_starts_with((string)($data['item_id'] ?? ''), 'new')) {
 			foreach ($operation->dependencies as $depId) {
-				$dep = JVB()->queue()->get($depId);
+				$dep = Queue::getInstance()->get($depId);
 				if ($dep && $dep->type === 'content_update' && !empty($dep->result['new_posts'])) {
 					$itemId = $data['item_id'] ?? null;
 					if ($itemId && isset($dep->result['new_posts'][$itemId])) {
@@ -213,20 +169,13 @@
 			if ($data['field_name'] === 'timeline_gallery') {
 				$registrar = Registrar::getInstance($data['content']);
 				if ($registrar) {
-					switch ($registrar->getType()) {
-						case 'post':
-							$meta = Meta::forPost($data['item_id']);
-							break;
-						case 'term':
-							$meta = Meta::forTerm($data['item_id']);
-							break;
-						case 'user':
-							$meta = Meta::forUser($data['item_id']);
-							break;
-						default:
-							$meta = false;
+					$meta = match ($registrar->getType()) {
+						'post' => Meta::forPost($data['item_id']),
+						'term' => Meta::forTerm($data['item_id']),
+						'user' => Meta::forUser($data['item_id']),
+						default => false,
+					};
 
-					}
 					if ($meta) {
 						$title = $meta->get('post_title');
 						$current = $meta->get('number');
@@ -295,7 +244,7 @@
 			try {
 				if (array_key_exists('depends_on', $info)) {
 					// Get the dependency operation to find attachment ID
-					$depOp = JVB()->queue()->get($info['depends_on']);
+					$depOp = Queue::getInstance()->get($info['depends_on']);
 					if (!$depOp || !$depOp->result) {
 						$errors[] = "Dependency {$info['depends_on']} not found or has no result";
 						continue;
@@ -313,6 +262,10 @@
 					$errors[] = "No attachment found for: ".print_r($info, true);
 					continue;
 				}
+				$postID = wp_get_post_parent_id($attachmentId);
+				if ($postID && !in_array($postID, $postsAttachedTo)){
+					Cache::clearPost($postID);
+				}
 
 				$this->applyMeta($attachmentId, $info);
 				$updatedCount++;
@@ -323,20 +276,6 @@
 				$progress->failItem($uploadId, $e->getMessage());
 				$errors[] = $e->getMessage();
 			}
-
-			if ($success && $attachmentId) {
-				$postID = wp_get_post_parent_id($attachmentId);
-				if ($postID && !in_array($postID, $postsAttachedTo)){
-					$postsAttachedTo[] = $postID;
-					//TODO: is there a better way?
-				}
-			}
-
-		}
-		if (!empty ($postsAttachedTo)) {
-			foreach ($postsAttachedTo as $postId) {
-				Cache::invalidateItem('post', $postId);
-			}
 		}
 
 		$outcome = $updatedCount > 0 ? 'success' : 'failed';
@@ -354,11 +293,14 @@
 	}
 
 	/**
-	 * Cleanup temporary upload files
+	 * @param Operation $operation
+	 * @param array $data
+	 * @param Progress $progress
+	 * @return Result
 	 */
 	private function processTemporaryCleanup(Operation $operation, array $data, Progress $progress): Result
 	{
-		$uploader = new UploadManager();
+		$uploader = UploadManager::getInstance();
 
 		// Cleanup secured files
 		if (!empty($data['files'])) {
@@ -402,7 +344,7 @@
 		$uploads = [];
 		$uploadIds = [];
 		foreach ($dependencies as $dependency) {
-			$res = JVB()->queue()->getOperationValue($dependency, 'result');
+			$res = Queue::getInstance()->getOperationValue($dependency, 'result');
 			if (empty($res)) {
 				continue;
 			}
@@ -695,6 +637,11 @@
 	// Helper methods
 	// ─────────────────────────────────────────────────────────────
 
+	/**
+	 * @param int $attachmentId
+	 * @param array $metadata
+	 * @return void
+	 */
 	private function applyMeta(int $attachmentId, array $metadata): void
 	{
 		$postUpdates = [];
@@ -779,25 +726,6 @@
 
 	}
 
-	private function updateFieldValue(array $data, array $results): void
-	{
-		if (empty($data['field_name'])) {
-			return;
-		}
-
-		$attachmentIds = array_column($results, 'attachment_id');
-		$meta = $this->getMetaManager($data);
-		if (!$meta) {
-			return;
-		}
-
-		$existing = $meta->get($data['field_name']);
-		$existingIds = !empty($existing) ? explode(',', $existing) : [];
-		$allIds = array_unique(array_merge($existingIds, $attachmentIds));
-
-		$meta->set($data['field_name'], implode(',', $allIds));
-	}
-
 	private function getMetaManager(array $data): ?Meta
 	{
 		if (!empty($data['post_id'])) {
@@ -815,6 +743,11 @@
 		return null;
 	}
 
+	/**
+	 * @param array $data
+	 * @param array $results
+	 * @return array
+	 */
 	private function createIndividualPosts(array $data, array $results): array
 	{
 		if (empty($data['content'])) {
@@ -869,7 +802,7 @@
 
 	private function cleanupTempFiles(array $securedFiles, int $userId): void
 	{
-		$uploader = new UploadManager();
+		$uploader = UploadManager::getInstance();
 
 		foreach ($securedFiles as $secured) {
 			if (!empty($secured['temp_path']) && file_exists($secured['temp_path'])) {
diff --git a/inc/managers/queue/mergers/DefaultMerger.php b/inc/managers/queue/mergers/DefaultMerger.php
index 7cae0b2..79987f8 100644
--- a/inc/managers/queue/mergers/DefaultMerger.php
+++ b/inc/managers/queue/mergers/DefaultMerger.php
@@ -3,7 +3,9 @@
 
 use JVBase\managers\queue\Mergeable;
 use JVBase\managers\queue\Operation;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * Default merger, uses the chunkKey as the mergeable array
  */
diff --git a/inc/managers/queue/mergers/UploadMerger.php b/inc/managers/queue/mergers/UploadMerger.php
index 2ba5954..4d7b4e4 100644
--- a/inc/managers/queue/mergers/UploadMerger.php
+++ b/inc/managers/queue/mergers/UploadMerger.php
@@ -3,7 +3,9 @@
 
 use JVBase\managers\queue\Mergeable;
 use JVBase\managers\queue\Operation;
-
+if (!defined('ABSPATH')) {
+	exit;
+}
 /**
  * Field-aware merger for upload operations.
  * Ensures uploads only merge when targeting the same field + post/term context.
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index 9c2f926..a553b32 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -2,6 +2,8 @@
 namespace JVBase\meta;
 
 use DateTime;
+use JVBase\base\Site;
+use JVBase\integrations\services\GoogleMaps;
 use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
@@ -1448,7 +1450,10 @@
 
 	protected static function renderLocation(string $name, mixed $value, array $config): string
 	{
-		$googleMaps = JVB()->connect('maps');
+		if (!Site::hasIntegration('maps')) {
+			return '';
+		}
+		$googleMaps = GoogleMaps::getInstance();
 		if (!$googleMaps->isSetUp()) {
 			return '<div class="notice notice-warning"><p>Google Maps not configured. Please configure in Integrations settings.</p></div>';
 		}
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 8f83f18..1d34549 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -132,14 +132,134 @@
 			$meta = [];
 		}
 		$meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta);
+
+
 		$this->fields = [];
+
+		if (class_exists('WooCommerce') && $this->type === 'post' && $this->wpObject->post_type??'' === 'product') {
+			$fields['post_title'] = [
+				'type'	=> 'text',
+				'label'	=> 'Title',
+			];
+			$fields['post_content'] = [
+				'type'	=> 'text',
+				'label'	=> 'Content',
+			];
+			$fields['post_excerpt'] = [
+				'type'	=> 'text',
+				'label'	=> 'Short Description',
+			];
+			$fields['post_id'] = [
+				'type'	=> 'text',
+				'label'	=> 'ID',
+			];
+
+
+			$fields['_stock'] = [
+				'type' => 'number',
+				'label' => 'Stock',
+				'default'	=> null
+			];
+			$fields['_stock_status'] = [
+				'type'	=> 'select',
+				'label'	=> 'Stock Status',
+				'default'	=> 'instock',
+				'options'	=> [
+					'instock'	=> 'In Stock',
+					'outofstock'	=> 'Out of Stock',
+					'onbackorder'		=> 'On Backorder'
+				]
+			];
+			$fields['_price'] = [
+				'type'		=> 'text',
+				'subtype'	=> 'number',
+				'label'		=>	'Price'
+			];
+
+
+			$product = wc_get_product($id);
+			$fields['sku'] = [
+				'type'	=> 'text',
+				'label'	=> 'SKU',
+			];
+
+			$out = [];
+			if ($product && $product->is_type('variable')) {
+				$var = $product->get_children();
+				$var = array_filter(array_map(function($v) {
+					return wc_get_product($v);
+				}, $var));
+
+				$title= $product->get_title();
+				foreach ($var as $variation) {
+					$out[] = [
+						'id'	=> $variation->get_id(),
+						'sku'	=> $variation->get_sku(),
+						'title'	=> str_replace($title.' - ', '', $variation->get_name()),
+						'price'	=> $variation->get_price(),
+						'description'=> $variation->get_short_description(),
+						'sale'	=> $variation->get_sale_price(),
+						'img'	=> $variation->get_image_id(),
+						'stock'	=> $variation->get_stock_quantity(),
+						'stock_status'	=> $variation->get_stock_status()
+					];
+				}
+			}
+			if (!empty($out)) {
+				$this->fields['variations'] = new Field('variations', $out, [
+					'type'	=> 'repeater',
+					'label'	=> 'Variations',
+					'fields'	=> [
+						'id'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation ID',
+						],
+						'sku'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Keeping Unit (SKU)'
+						],
+						'title'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Variation Title'
+						],
+						'price'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation Price'
+						],
+						'sale'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Sale Price',
+						],
+						'image'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation Image ID'
+						],
+						'stock'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Quantity'
+						],
+						'stock_status'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Status'
+						]
+					]
+				]);
+			}
+
+
+		}
 		foreach ($fields as $fieldName => $config) {
 			$fieldName = jvbNoBase($fieldName);
+			$value = '';
 			if ($this->type === 'option') {
 				$value = get_option(BASE . $fieldName, $config['default'] ?? '');
 			} else if (
-				$this->wpObject && property_exists($this->wpObject, $fieldName)
-				|| $type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName)) {
+				($this->wpObject && property_exists($this->wpObject, $fieldName))
+				|| ($type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName))) {
 				$config['wp'] = true;
 				$value = $this->wpObject->$fieldName;
 
diff --git a/inc/meta/_setup.php b/inc/meta/_setup.php
index 975000d..ffdbe44 100644
--- a/inc/meta/_setup.php
+++ b/inc/meta/_setup.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 //REFACTOR
 require(JVB_DIR . '/inc/meta/Meta.php'); 		//Main facade
 require(JVB_DIR . '/inc/meta/Item.php'); 		//Data container for one object
diff --git a/inc/meta/index.php b/inc/meta/index.php
index 4f2541d..6220032 100644
--- a/inc/meta/index.php
+++ b/inc/meta/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/inc/registrar/Labels.php b/inc/registrar/Labels.php
index b3d9bbc..ea6680d 100644
--- a/inc/registrar/Labels.php
+++ b/inc/registrar/Labels.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/Registrar.php b/inc/registrar/Registrar.php
index b30b0cf..3cc1b10 100644
--- a/inc/registrar/Registrar.php
+++ b/inc/registrar/Registrar.php
@@ -61,7 +61,7 @@
 		//Shared Flags
 		'favouritable', 'karma', 'show_feed', 'show_directory', 'approve_new', 'has_responses', 'invitable',
 		//Post Flags
-		'hide_single', 'redirect_to_author', 'is_calendar', 'single_image', 'is_timeline', 'is_gallery', 'is_faq', 'is_glossary', 'rewrite_taxonomy', 'add_image_column',
+		'hide_single', 'redirect_to_author', 'is_calendar', 'single_image', 'is_timeline', 'is_gallery', 'is_faq', 'is_glossary', 'rewrite_taxonomy', 'add_image_column', 'is_profile',
 		//Taxonomy Flags
 		'is_content', 'is_ownable', 'verify_entry', 'track_changes', 'associate_user_content', 'prefix_post_type',
 		//User Flags
@@ -191,6 +191,7 @@
 	 * @var bool Whether to generate a profile for this user role
 	 */
 	public bool $profile_link;
+	public bool $is_profile = false;
 	/**
 	 * @var array|string
 	 */
@@ -228,6 +229,8 @@
 	protected MakeTrackChanges $trackChanges;
 	protected MakeVerification $verification;
 
+	protected string $umami_event;
+
 	private static array $instances = [];
 
 	protected string $based;
@@ -905,6 +908,11 @@
 			$this->karmaManager = KarmaManager::for($this->slug, $this->type);
 		}
 	}
+
+	public function getKarmaManager():?KarmaManager
+	{
+		return $this->karmaManager;
+	}
 	public static function getInstance(string $slug):Registrar|false
 	{
 		self::ensureInstanced();
@@ -1220,8 +1228,16 @@
 
 		$this->profile = $slug;
 
-		return Registrar::forPost($slug, $singular, $plural);
+		$reg = Registrar::forPost($slug, $singular, $plural);
+		$reg->setProfile();
+
+		return $reg;
 	}
+
+		public function setProfile():void
+		{
+			$this->is_profile = true;
+		}
 	public function getProfile():self|false
 	{
 		if (!$this->profile_link) {
@@ -1322,4 +1338,46 @@
 	{
 		$this->hasIntegrations = $has;
 	}
+
+	public function isOwnable():bool
+	{
+		return $this->is_ownable??false;
+	}
+
+	public function umamiEvent(string $event):void
+	{
+		$this->umami_event = str_replace('-', '_', sanitize_title($event));
+	}
+	public function getUmamiEvent():string
+	{
+		return $this->umami_event??$this->getDefaultUmamiType();
+	}
+		protected function getDefaultUmamiType():string
+		{
+			return match($this->type) {
+				'post'	=> $this->getDefaultPostUmamiType(),
+				'term'	=> $this->getDefaultTermUmamiType(),
+				'user'	=> $this->getDefaultUserUmamiType(),
+				default => ''
+			};
+		}
+			protected function getDefaultPostUmamiType():string
+			{
+				if ($this->is_profile) {
+					return 'view_profile';
+				}
+				return 'view_content';
+			}
+			protected function getDefaultTermUmamiType():string
+			{
+				if ($this->is_content) {
+					return 'view_collective';
+				}
+				return 'view_term';
+			}
+			protected function getDefaultUserUmamiType():string
+			{
+
+				return 'view_user';
+			}
 }
diff --git a/inc/registrar/Users.php b/inc/registrar/Users.php
index e69de29..ea6680d 100644
--- a/inc/registrar/Users.php
+++ b/inc/registrar/Users.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/_setup.php b/inc/registrar/_setup.php
index 46c8362..1b47c4e 100644
--- a/inc/registrar/_setup.php
+++ b/inc/registrar/_setup.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 require_once(JVB_DIR . '/inc/registrar/config/Config.php');
 require_once(JVB_DIR . '/inc/registrar/config/Breadcrumbs.php');
 require_once(JVB_DIR . '/inc/registrar/config/Dashboard.php');
diff --git a/inc/registrar/config/Breadcrumbs.php b/inc/registrar/config/Breadcrumbs.php
index 89d417f..40ebfcc 100644
--- a/inc/registrar/config/Breadcrumbs.php
+++ b/inc/registrar/config/Breadcrumbs.php
@@ -2,8 +2,6 @@
 namespace JVBase\registrar\config;
 
 
-use JVBase\registrar\Registrar;
-
 if (!defined('ABSPATH')) {
 	exit;
 }
diff --git a/inc/registrar/config/Integration.php b/inc/registrar/config/Integration.php
index 6316e6c..a496189 100644
--- a/inc/registrar/config/Integration.php
+++ b/inc/registrar/config/Integration.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\registrar\config;
 
+use JVBase\integrations\ServiceMap;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 
@@ -9,11 +10,13 @@
 }
 
 final class Integration extends Config{
+	use ServiceMap;
 	/**
 	 * @var string Must match as defined in the JVBase\integrations namespace
 	 */
 	protected string $service_name;
 	protected ?string $content_type = null;
+
 	/**
 	 * @var bool Whether to send to the integration on publish
 	 */
@@ -54,7 +57,7 @@
 		if (is_null($content)) {
 			return $this;
 		}
-		$connection = JVB()->connect($this->service_name);
+		$connection = $this->getService($this->service_name);
 		if (!$connection){
 			error_log('[Integration]::setContentType Service is not setup. '.$this->service_name);
 			return $this;
@@ -62,7 +65,8 @@
 
 		$allowed = $connection->getAllowedContent();
 		if (!in_array($content, $allowed)) {
-			error_log($this->service_name.' Connection does not support this content: '.$content);
+			//TODO
+//			error_log($this->service_name.' Connection does not support this content: '.$content);
 			return $this;
 		}
 		$this->content_type = $content;
@@ -105,7 +109,6 @@
 	public function setCustomer(bool $set):self
 	{
 		$this->isCustomer = $set;
-//		JVB()->connect($this->service_name)->addCustomerFields();
 
 		return $this;
 	}
diff --git a/inc/registrar/config/Management.php b/inc/registrar/config/Management.php
index e69de29..ea6680d 100644
--- a/inc/registrar/config/Management.php
+++ b/inc/registrar/config/Management.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/config/Responses.php b/inc/registrar/config/Responses.php
index e69de29..ea6680d 100644
--- a/inc/registrar/config/Responses.php
+++ b/inc/registrar/config/Responses.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/config/TrackChanges.php b/inc/registrar/config/TrackChanges.php
index e69de29..ea6680d 100644
--- a/inc/registrar/config/TrackChanges.php
+++ b/inc/registrar/config/TrackChanges.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/config/Verification.php b/inc/registrar/config/Verification.php
index e69de29..ea6680d 100644
--- a/inc/registrar/config/Verification.php
+++ b/inc/registrar/config/Verification.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/config/index.php b/inc/registrar/config/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/registrar/config/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/registrar/config/seo/Helpers.php b/inc/registrar/config/seo/Helpers.php
index c5f0f5f..a025ff9 100644
--- a/inc/registrar/config/seo/Helpers.php
+++ b/inc/registrar/config/seo/Helpers.php
@@ -8,6 +8,9 @@
 
 function jvbTSFDoIt(string $slug, ?array $args):bool
 {
+	if (!class_exists('THE_SEO_Framework')) {
+		return false;
+	}
 	$tsf = tsf();
 
 	return (null === $args)
diff --git a/inc/registrar/config/seo/Resolver.php b/inc/registrar/config/seo/Resolver.php
index 172e1c5..f7cde47 100644
--- a/inc/registrar/config/seo/Resolver.php
+++ b/inc/registrar/config/seo/Resolver.php
@@ -42,7 +42,7 @@
 
 		switch ($variable) {
 			case 'CREATOR':
-				return JVB()->seo()->getCreator();
+				return SchemaOutput::getInstance()->getCreator();
 			case 'name':
 				if (!$meta && is_post_type_archive()) {
 					$obj = get_queried_object();
@@ -265,7 +265,7 @@
 				//TODO generate from id
 
 			} else if (str_contains($value, 'CREATOR')) {
-				return JVB()->seo()->getCreator();
+				return SchemaOutput::getInstance()->getCreator();
 			}
 			return '';
 		}
diff --git a/inc/registrar/config/seo/Schema.php b/inc/registrar/config/seo/Schema.php
index 6692c5e..c87147d 100644
--- a/inc/registrar/config/seo/Schema.php
+++ b/inc/registrar/config/seo/Schema.php
@@ -142,9 +142,9 @@
 
 					$config = $this->getConfig();
 
-					$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
+					$class = SchemaHelper::getInstance()::classFromConfig($config, $meta);
 					if (property_exists($class, 'author')) {
-						$class->setAuthor(JVB()->seo()->getCreator(true));
+						$class->setAuthor(SchemaOutput::getInstance()->getCreator(true));
 					}
 
 
@@ -165,7 +165,7 @@
 				$ID,
 				function() use ($ID) {
 					$action = BASE.ucfirst($this->slug).'Schema';
-					$config = JVB()->schemaHelper()::schema($action);
+					$config = SchemaHelper::getInstance()::schema($action);
 
 					if (!array_key_exists('type', $config)) {
 						$config['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage';
@@ -174,7 +174,7 @@
 						error_log('No class found for archive schema output: '.$config['type']);
 						return [];
 					}
-					$class = JVB()->schemaHelper()::classFromConfig($config);
+					$class = SchemaHelper::getInstance()::classFromConfig($config);
 
 					$class->setIsPartOf(get_home_url().'/#website');
 					$itemList = new render\Thing\Intangible\ItemList\ItemList();
@@ -218,7 +218,7 @@
 					if (!empty($override)) {
 						return $override;
 					}
-					$config = JVB()->schemaHelper()->archive($this->slug);
+					$config = SchemaHelper::getInstance()->archive($this->slug);
 					if (!array_key_exists('type', $config)) {
 						$config['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage';
 					}
@@ -239,7 +239,7 @@
 					$obj = get_queried_object();
 					$meta = (property_exists($obj, 'taxonomy')) ? Meta::forTerm($obj->term_id) : null;
 
-					$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
+					$class = SchemaHelper::getInstance()::classFromConfig($config, $meta);
 
 					$class->setIsPartOf(get_home_url().'/#website');
 					$itemList = new render\Thing\Intangible\ItemList\ItemList();
@@ -306,7 +306,7 @@
 						$meta = null;
 				}
 				$config = $this->getConfig();
-				$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
+				$class = SchemaHelper::getInstance()::classFromConfig($config, $meta);
 				if ($class) {
 					$class->delete('about');
 				}
@@ -335,7 +335,7 @@
 			error_log('[SEO]Schema::getConfig Invalid type: '.$type);
 			return [];
 		}
-		return JVB()->schemaHelper()::getConfig($this->slug, $type);
+		return SchemaHelper::getInstance()::getConfig($this->slug, $type);
 	}
 
 	public function resolveMeta(array $config, Meta $meta):array
@@ -459,7 +459,7 @@
 		$question = SchemaHelper::classFromConfig($question);
 		$page->setMainEntity($question);
 
-		$page->setAuthor(JVB()->seo()->getCreator(true));
+		$page->setAuthor(SchemaOutput::getInstance()->getCreator(true));
 		return $page->outputSchema();
 
 	}
@@ -499,7 +499,7 @@
 		}
 
 		$page->setImage($images);
-		$page->setAuthor(JVB()->seo()->getCreator(true));
+		$page->setAuthor(SchemaOutput::getInstance()->getCreator(true));
 		return $page->outputSchema();
 
 	}
diff --git a/inc/registrar/config/seo/_setup.php b/inc/registrar/config/seo/_setup.php
index e3f1986..6e56ce9 100644
--- a/inc/registrar/config/seo/_setup.php
+++ b/inc/registrar/config/seo/_setup.php
@@ -1,5 +1,8 @@
 <?php
 
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 require(JVB_DIR . '/inc/registrar/config/seo/Helpers.php');
 require(JVB_DIR . '/inc/registrar/config/seo/Resolver.php');
 require(JVB_DIR . '/inc/registrar/config/seo/Archive.php');
diff --git a/inc/registrar/config/seo/index.php b/inc/registrar/config/seo/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/registrar/config/seo/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/registrar/fields/Calendar.php b/inc/registrar/fields/Calendar.php
index e69de29..ea6680d 100644
--- a/inc/registrar/fields/Calendar.php
+++ b/inc/registrar/fields/Calendar.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/registrar/fields/index.php b/inc/registrar/fields/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/registrar/fields/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/registrar/helpers/AddIntegrationFields.php b/inc/registrar/helpers/AddIntegrationFields.php
index fd656ec..1a6fe68 100644
--- a/inc/registrar/helpers/AddIntegrationFields.php
+++ b/inc/registrar/helpers/AddIntegrationFields.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\registrar\helpers;
 
+use JVBase\integrations\ServiceMap;
 use JVBase\registrar\config\Integration;
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
@@ -10,6 +11,7 @@
 }
 
 class AddIntegrationFields {
+	use ServiceMap;
 	protected string $service_name;
 	private Registrar $registrar;
 	protected Integration $config;
@@ -65,6 +67,10 @@
 
 	public function getIntegrationFields():array
 	{
+		if (!$this->getService($this->service_name)) {
+			error_log('Attempted to connect to unsupported service: '.$this->service_name);
+			return [];
+		}
 		$fields = [
 			'share_to_'.$this->service_name => [
 				'type'	=> 'true_false',
@@ -131,7 +137,8 @@
 				'default'	=> 1,
 			];
 		}
-		$additional = JVB()->connect($this->service_name)->getAdditionalFields($this->config->getContentType());
+
+		$additional = $this->getService($this->service_name)->getAdditionalFields($this->config->getContentType());
 		if (!empty($additional)) {
 			$additional = array_combine(
 				array_map(fn($k) => str_starts_with($k, '_'.$this->service_name) ? $k : "_{$this->service_name}_{$k}", array_keys($additional)),
diff --git a/inc/registrar/helpers/index.php b/inc/registrar/helpers/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/registrar/helpers/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/registrar/index.php b/inc/registrar/index.php
new file mode 100644
index 0000000..6220032
--- /dev/null
+++ b/inc/registrar/index.php
@@ -0,0 +1,2 @@
+<?php
+// Silence is golden.
diff --git a/inc/rest/RegisterRoutes.php b/inc/rest/RegisterRoutes.php
index 2754beb..76c0710 100644
--- a/inc/rest/RegisterRoutes.php
+++ b/inc/rest/RegisterRoutes.php
@@ -126,7 +126,7 @@
 				'message'   => 'This shop doesn\'t exist?'
 			]);
 		}
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		unset($data['user']);
 		$operationID = $data['id'];
 		unset($data['id']);
@@ -343,7 +343,7 @@
 
 
 		//Notify managers
-		JVB()->notification()->addNotification(
+		NotificationManager::getInstance()->addNotification(
 			$owners,
 			'entry_request',
 			[
diff --git a/inc/rest/Rest.php b/inc/rest/Rest.php
index 89f0343..4741e66 100644
--- a/inc/rest/Rest.php
+++ b/inc/rest/Rest.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\rest;
 
+use JVBase\Instance;
+use JVBase\integrations\services\Cloudflare;
 use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
@@ -30,7 +32,7 @@
 	protected string $namespace = 'jvb/v1';
 	protected ?Cache $cache = null;
 	protected string $cacheName = '';
-	protected int $cacheTtl = 3600;
+	protected int $cacheTtl = HOUR_IN_SECONDS;
 	protected static ?string $action;
 
 	public function __construct()
@@ -180,7 +182,7 @@
 	/**
 	 * Convert MySQL datetime to ISO 8601 timestamp
 	 */
-	protected function formatTimestamp(?string $mysqlDatetime): ?string
+	public function formatTimestamp(?string $mysqlDatetime): ?string
 	{
 		return Response::formatTimestamp($mysqlDatetime);
 	}
@@ -555,11 +557,11 @@
 	 */
 	protected function verifyTurnstile(string $token): bool
 	{
-		if (!Site::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) {
+		if (!Site::hasIntegration('cloudflare') || !Cloudflare::getInstance()->isSetUp()) {
 			return true;
 		}
 
-		return !empty($token) && JVB()->connect('cloudflare')->verifyTurnstile($token);
+		return !empty($token) && Cloudflare::getInstance()->verifyTurnstile($token);
 	}
 
 	/**
diff --git a/inc/rest/RestRouteManager.php b/inc/rest/RestRouteManager.php
index 91778c7..e2efa84 100644
--- a/inc/rest/RestRouteManager.php
+++ b/inc/rest/RestRouteManager.php
@@ -587,12 +587,9 @@
 			$key = $cache->generateKey($key);
 		}
 
-		// Prefer tag freshness if available
-		$tags = $cache->getTags();
 
-		$lastModified = $tags
-			? $cache->getLastModifiedForTags($tags)
-			: $cache::lastModified($group);
+
+		$lastModified = $cache::lastModified($group);
 
 		if (!$lastModified) {
 			return false;
diff --git a/inc/rest/_setup.php b/inc/rest/_setup.php
index 6a8dcf0..85fc914 100644
--- a/inc/rest/_setup.php
+++ b/inc/rest/_setup.php
@@ -1,5 +1,8 @@
 <?php
 
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 
@@ -10,10 +13,6 @@
 require(JVB_DIR . '/inc/rest/Rest.php');
 require(JVB_DIR . '/inc/rest/RateLimits.php');
 
-//OLD METHOD
-//require(JVB_DIR . '/inc/rest/RateLimiter.php');
-//require(JVB_DIR . '/inc/rest/RestRouteManager.php');
-//require(JVB_DIR . '/inc/rest/RegisterRoutes.php');
 
 //if (Site::has('feed_block')) {
 	require(JVB_DIR . '/inc/rest/routes/FeedRoutes.php');
diff --git a/inc/rest/index.php b/inc/rest/index.php
index 4f2541d..6220032 100644
--- a/inc/rest/index.php
+++ b/inc/rest/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/inc/rest/routes/AdminRoutes.php b/inc/rest/routes/AdminRoutes.php
index 4f89e76..c69a32e 100644
--- a/inc/rest/routes/AdminRoutes.php
+++ b/inc/rest/routes/AdminRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\JVB;
 use JVBase\managers\Cache;
 use JVBase\managers\IconsManager;
@@ -18,15 +19,13 @@
 
 class AdminRoutes extends Rest
 {
-    protected array $fields;
-    protected $meta;
-    protected string $metaType;
-    protected string $content;
+	use Instance;
 
     public function __construct()
     {
-        $this->cacheName = 'itsme';
+//        $this->cacheName = 'itsme';
         parent::__construct();
+//		$this->cache = Cache::for($this->cacheName, null, $this->cacheTtl);
     }
     public function registerRoutes():void
     {
@@ -70,25 +69,6 @@
         );
     }
 
-
-    /**
-     * @param array $filters
-     *
-     * @return array
-     */
-    protected function checkFilters(array $filters)
-    {
-        global $karma;
-        $out = [];
-        foreach ($filters as $type => $value) {
-            if (!array_key_exists($type, $karma)) {
-                continue;
-            }
-            $out[$type]  = jvbSanitizeIDList($value);
-        }
-        return $out;
-    }
-
 	/**
 	 * Handle cache-related actions
 	 */
diff --git a/inc/rest/routes/ApprovalRoutes.php b/inc/rest/routes/ApprovalRoutes.php
index b4129a7..963ff45 100644
--- a/inc/rest/routes/ApprovalRoutes.php
+++ b/inc/rest/routes/ApprovalRoutes.php
@@ -2,7 +2,10 @@
 
 namespace JVBase\rest\routes;
 
-use JVBase\managers\CustomTable;
+use JVBase\Instance;
+use JVBase\managers\Approvals;
+use JVBase\managers\Cache;
+
 use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
@@ -11,7 +14,6 @@
 use JVBase\base\Site;
 use WP_REST_Request;
 use WP_REST_Response;
-use Exception;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -19,6 +21,7 @@
 
 class ApprovalRoutes extends Rest
 {
+	use Instance;
     protected array $userTypes;
     protected array $termTypes;
     protected array $allTypes;
@@ -31,6 +34,7 @@
         $this->cacheName = 'approvals';
 		$this->hasMemberApproval = Site::membership() && Site::membership()->has('member_verified');
         parent::__construct();
+		$this->cache = Cache::for('approvals', 'custom', $this->cacheTtl);
 
         $this->initTypes();
     }
@@ -106,7 +110,7 @@
 		if (!in_array($vote, ['approve', 'reject', 'dismiss'])) {
 			return false;
 		}
-		$result = JVB()->approvals()->markApproval($request_id, $user_id, $type, $vote, $notes);
+		$result = Approvals::getInstance()->markApproval($request_id, $user_id, $type, $vote, $notes);
 		return $result['success'];
 	}
 
@@ -147,11 +151,11 @@
      */
 	public function getVerificationDetails(int $requestID, string $type): array|false
 	{
-		$request = JVB()->approvals()->getRequest($requestID, $type);
+		$request = Approvals::getInstance()->getRequest($requestID, $type);
 		if (!$request) {
 			return false;
 		}
-		$votes = JVB()->approvals()->getVotes($requestID, $type);
+		$votes = Approvals::getInstance()->getVotes($requestID, $type);
 
 		// Join with user data for display names
 		foreach ($votes as &$vote) {
@@ -187,7 +191,7 @@
 		int $required_approvals = 3
 	): int|false {
 
-		$result = JVB()->approvals()->createApproval(
+		$result = Approvals::getInstance()->createApproval(
 			$user_id,
 			$taxonomy,
 			$name,
@@ -196,64 +200,16 @@
 		return $result['success'];
 	}
 
-	protected function getTableName(string $type, string $suffix): string
-	{
-		return match ($type) {
-			'term' => "approval_term_{$suffix}",
-			default => "approval_{$type}_{$suffix}",
-		};
-	}
-
 	public function getApprovals(WP_REST_Request $request): WP_REST_Response
 	{
 		$user_id = get_current_user_id();
 		$type = sanitize_text_field($request->get_param('type') ?? 'all');
 		$status = sanitize_text_field($request->get_param('status') ?? 'pending');
 
-		$cacheKey = compact('user_id', 'type', 'status');
-
-		$result = $this->cache->remember($cacheKey, function() use ($type, $status) {
-			$data = [];
-
-			if ($type === 'user' || $type === 'all') {
-				$data['user_approvals'] = $this->getUserApprovals($status);
-			}
-
-			if ($type === 'term' || $type === 'all') {
-				$data['term_approvals'] = $this->getTermApprovals($status);
-			}
-
-			return $data;
-		});
+		$result = Approvals::getInstance()->getApprovalRequests($user_id, $type === 'all' ? null : $type);
 
 		return $this->success($result);
 	}
 
-	private function getUserApprovals(string $status = 'pending'): array
-	{
 
-		$table = CustomTable::for($this->getTableName('artist', 'requests'));
-
-		$query = $table;
-
-		if ($status !== 'all') {
-			$query = $query->where(['status' => $status]);
-		}
-
-		return $query->orderBy('created_at', 'DESC')->getResults(ARRAY_A);
-	}
-
-	private function getTermApprovals(string $status = 'pending'): array
-	{
-		$table = CustomTable::for($this->getTableName('term', 'requests'));
-
-		if ($status === 'all') {
-			return $table->orderBy('created_at', 'DESC')->getResults(ARRAY_A);
-		}
-
-		return $table
-			->where(['status' => $status])
-			->orderBy('created_at', 'DESC')
-			->getResults(ARRAY_A);
-	}
 }
diff --git a/inc/rest/routes/CRUDRoutes.php b/inc/rest/routes/CRUDRoutes.php
index b3d9bbc..ea6680d 100644
--- a/inc/rest/routes/CRUDRoutes.php
+++ b/inc/rest/routes/CRUDRoutes.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/rest/routes/ContentRoutes.php b/inc/rest/routes/ContentRoutes.php
index 9375fa2..7d5ef42 100644
--- a/inc/rest/routes/ContentRoutes.php
+++ b/inc/rest/routes/ContentRoutes.php
@@ -2,7 +2,10 @@
 
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\managers\Cache;
 use JVBase\managers\queue\executors\ContentExecutor;
+use JVBase\managers\queue\Queue;
 use JVBase\managers\queue\TypeConfig;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
@@ -23,6 +26,7 @@
 
 class ContentRoutes extends Rest
 {
+	use Instance;
 	protected array $fields = [];
 	protected array $taxonomies = [];
 	protected string $post_type = '';
@@ -33,16 +37,21 @@
 	protected array $timelineUniqueFields = [];
 	protected static ?string $action = 'dash-';
 	protected Meta $meta;
+	protected Cache $termCache;
 
 	public function __construct()
 	{
-		$this->cacheName = 'user_content_' . get_current_user_id();
+		$this->cacheName = 'user_content';
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName, 'post', DAY_IN_SECONDS);
+		$this->cache->user();
+		$this->termCache = Cache::for($this->cacheName.'_terms', 'term', DAY_IN_SECONDS);
+		$this->termCache->user();
+
+
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
-		$this->cache->connect('post', true);
-		$this->cache->connect('term', true);
 		add_action('init', [$this, 'registerContentExecutors'], 5);
 	}
 
@@ -51,7 +60,7 @@
 	 */
 	public function registerContentExecutors(): void
 	{
-		$registry = JVB()->queue()->registry();
+		$registry = Queue::getInstance()->registry();
 		$executor = new ContentExecutor();
 
 		// Content updates - chunked at 10 posts
@@ -174,7 +183,7 @@
 		unset($data['id']);
 
 		error_log('[CONTENT]:'.print_r($data, true));
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		$queue->queueOperation(
 			'content_update',
 			$user_id,
@@ -271,7 +280,7 @@
 		}
 
 		$key = $this->cache->generateKey($args);
-		$cached = $this->checkCache($key, $request);
+		$cached = $this->cache->get($key);
 		if ($cached) {
 			return $cached;
 		}
@@ -301,8 +310,7 @@
 
 		$this->cache->set($key, $data);
 
-		$response = Response::success($data);
-		return $this->addCacheHeaders($response);
+		return Response::success($data);
 	}
 	public function getTerms(WP_REST_Request $request, array $params, Registrar $registrar):WP_REST_Response
 	{
@@ -337,15 +345,14 @@
 
 		$key = $this->cache->generateKey($args);
 		// Check HTTP cache headers with the specific content type
-		$cache_check = $this->checkHeaders($request, $key);
-		if ($cache_check) {
-			return $cache_check;
-		}
+//		$cache_check = $this->checkHeaders($request, $key);
+//		if ($cache_check) {
+//			return $cache_check;
+//		}
 
 		$cache = $this->cache->get($key);
 		if ($cache) {
-			$response = Response::success($cache);
-			return $this->addCacheHeaders($response);
+			return Response::success($cache);
 		}
 		$this->post_type = jvbCheckBase($params['content'] ?? $params['type']);
 		// Only expand search to taxonomies if we're actually going to query
@@ -384,8 +391,7 @@
 
 		$this->cache->set($key, $data);
 
-		$response = Response::success($data);
-		return $this->addCacheHeaders($response);
+		return Response::success($data);
 	}
 
 	protected function applySearchFilters(array $args, array $params): array
diff --git a/inc/rest/routes/ContentTermsRoutes.php b/inc/rest/routes/ContentTermsRoutes.php
index 08fda0d..8886996 100644
--- a/inc/rest/routes/ContentTermsRoutes.php
+++ b/inc/rest/routes/ContentTermsRoutes.php
@@ -1,8 +1,12 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\managers\Cache;
 use JVBase\managers\queue\executors\ContentTermExecutor;
+use JVBase\managers\queue\Queue;
 use JVBase\managers\queue\TypeConfig;
+use JVBase\managers\Roles;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
@@ -31,12 +35,13 @@
  */
 class ContentTermsRoutes extends Rest
 {
+	use Instance;
 	protected string $taxonomy;
 	protected Registrar $registrar;
 	protected ?CustomTable $historyTable = null;
 	protected ?CustomTable $requestsTable = null;
-
-	public function __construct(string $taxonomy = '')
+	//TODO: This needs some work. We need to move most logic into it's own manager class
+	protected function __construct(string $taxonomy = '')
 	{
 		$this->taxonomy = jvbNoBase($taxonomy);
 
@@ -44,6 +49,7 @@
 			$this->registrar = Registrar::getInstance($this->taxonomy);
 			$this->cacheName = $this->taxonomy;
 			parent::__construct();
+			$this->cache = Cache::for($this->taxonomy, 'term', DAY_IN_SECONDS);
 			$this->setupTables();
 			$this->setupCacheConnections();
 		}
@@ -53,7 +59,7 @@
 
 	public function registerContentTermsExecutors():void
 	{
-		$registry = JVB()->queue()->registry();
+		$registry = Queue::getInstance()->registry();
 		$executor = new ContentTermExecutor();
 		$taxonomies = Registrar::withFeature('is_content', 'term');
 
@@ -205,7 +211,7 @@
 		}
 
 		return user_can($userID, 'manage_options') ||
-			JVB()->roles()->isManager($userID, $termID);
+			Roles::getInstance()()->isManager($userID, $termID);
 	}
 
 	public function checkOwnerPermission(WP_REST_Request $request): bool
@@ -218,7 +224,7 @@
 		}
 
 		return user_can($userID, 'manage_options') ||
-			JVB()->roles()->isOwner($userID, $termID);
+			Roles::getInstance()()->isOwner($userID, $termID);
 	}
 
 	/**
@@ -233,7 +239,7 @@
 		unset($data['user'], $data['term_id']);
 
 		// Queue the update
-		$op = JVB()->queue()->add(
+		$op = Queue::getInstance()->add(
 			"{$this->taxonomy}_update",
 			$userID,
 			array_merge(['term_id' => $termID], $data),
@@ -306,7 +312,7 @@
 		$targetUserID = (int)$request->get_param('target_user');
 
 		// Queue the operation
-		$op = JVB()->queue()->add(
+		$op = Queue::getInstance()->add(
 			"{$this->taxonomy}_member_{$action}",
 			$userID,
 			[
@@ -469,7 +475,7 @@
 			});
 
 			// Notify user
-			JVB()->notification()->addNotification(
+			NotificationManager::getInstance()->addNotification(
 				$requestData['user_id'],
 				'request_' . $status,
 				[
@@ -508,7 +514,7 @@
 			return $this->error('Invalid target user');
 		}
 
-		$roleManager = JVB()->roles();
+		$roleManager = Roles::getInstance()();
 
 		try {
 			$result = match($role) {
@@ -548,13 +554,13 @@
 	 */
 	protected function notifyTermManagers(int $termID, int $actingUserID, string $notificationType): void
 	{
-		$roleManager = JVB()->roles();
+		$roleManager = Roles::getInstance()();
 		$managers = $roleManager->getManagedTerms($actingUserID, $this->taxonomy);
 
 		$term = get_term($termID, jvbCheckBase($this->taxonomy));
 
 		foreach ($managers as $managerID) {
-			JVB()->notification()->addNotification(
+			NotificationManager::getInstance()->addNotification(
 				$managerID,
 				$notificationType,
 				[
diff --git a/inc/rest/routes/ErrorRoutes.php b/inc/rest/routes/ErrorRoutes.php
index aab3658..824f4bd 100644
--- a/inc/rest/routes/ErrorRoutes.php
+++ b/inc/rest/routes/ErrorRoutes.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\managers\Errors;
 use JVBase\rest\Response;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
@@ -12,6 +14,7 @@
 }
 class ErrorRoutes extends Rest
 {
+	use Instance;
     /**
      * Registers error routes
      * @return void
@@ -51,7 +54,7 @@
 		// Determine severity based on error type
 		$severity = $this->getSeverityFromType($error_type);
 
-		JVB()->error()->log(
+		Errors::getInstance()->log(
 			$contextData['component'] ?? 'client-js',
 			$message,
 			$contextData,
diff --git a/inc/rest/routes/FavouritesRoutes.php b/inc/rest/routes/FavouritesRoutes.php
index a9d767d..9e9c0fc 100644
--- a/inc/rest/routes/FavouritesRoutes.php
+++ b/inc/rest/routes/FavouritesRoutes.php
@@ -1,8 +1,10 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
+use JVBase\managers\Favourites;
 use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Response;
@@ -18,6 +20,7 @@
 
 class FavouritesRoutes extends Rest
 {
+	use Instance;
 	protected array $valid_types;
 	protected Cache $listsCache;
 	protected Cache $sharedListsCache;
@@ -32,20 +35,16 @@
 		$this->cacheName = 'favourites';
 		$this->cacheTtl = HOUR_IN_SECONDS;
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName)->user();
 
 		// Set up cache connections
-		$this->cache->connect('post')->connect('user')->connect('taxonomy')->user();
-		$this->listsCache = Cache::for('lists')->connect('favourites', true)->user();
-		$this->sharedListsCache = Cache::for('sharedLists')->connect('favourites', true)->user();
-		$this->favouritesCache = Cache::for('allFavourites')->connect('favourites', true)->user();
+		$this->listsCache = Cache::for('fav_lists')->user();
+		$this->sharedListsCache = Cache::for('fav_shared_list')->user();
+		$this->favouritesCache = Cache::for('allFavourites')->user();
 
 		$this->valid_types = array_merge(Registrar::getRegistered('post'), Registrar::getRegistered('term'));
 
-		// Initialize CustomTable instances
-		$this->favourites = CustomTable::for('favourites');
-		$this->lists = CustomTable::for('favourites_lists');
-		$this->listItems = CustomTable::for('favourites_list_items');
-		$this->listShares = CustomTable::for('favourites_list_shares');
+
 	}
 
 	public function registerRoutes(): void
@@ -108,17 +107,17 @@
 		}
 
 		$args = $this->buildParams($request);
-		$key = $this->cache->generateKey($args);
+		$result = $this->cache->remember(
+			$args,
+			function() use ($user_id, $args) {
+				return Favourites::getInstance()->getFavourites($args);
+			}
+		);
 
-		// Check cache headers
-		$cache_check = $this->checkHeaders($request, $key);
-		if ($cache_check) {
-			return $cache_check;
-		}
-		$result = JVB()->favourites()->getFavourites($args);
-		$result['items'] = $this->formatItems($result['items']);
-
-		return $this->addCacheHeaders(Response::success($result));
+		$response = [
+			'items' => $this->formatItems($result['items'])
+		];
+		return Response::success($response);
 	}
 
 
@@ -131,29 +130,65 @@
 		$user_id = get_current_user_id();
 
 		$action = strtolower(sanitize_text_field($params['action']));
-		$action = in_array($action, ['add', 'remove']) ? $action : false;
+		$action = in_array($action, ['add', 'remove', 'toggle', 'batch', 'notes']) ? $action : false;
 		if (!$action) {
 			return $this->error('Invalid favourite action');
 		}
-		$target_id = absint($params['target_id']??0);
-		if ($target_id === 0) {
-			return $this->error('Invalid target id');
-		}
 
 		$type = sanitize_text_field($params['type']??'');
 		if (empty($type)) {
 			return $this->error('No type provided');
 		}
 
-		$result = JVB()->favourites()->toggleFavourite(
-			$action === 'add',
-			$user_id,
-			$target_id,
-			$type
-		);
+		$target_id = absint($params['target_id']??0);
+		if ($action !== 'batch' && $target_id === 0) {
+			return $this->error('Invalid target id');
+		}
+
+		$favourites = Favourites::getInstance();
+		$result = false;
+		switch ($action) {
+			case 'notes':
+				$note = sanitize_text_field($params['note']??'');
+				$result = $favourites->addNoteFromTarget($user_id, $target_id, $type, $note);
+				break;
+			case 'toggle':
+				$isFavourited = $favourites->hasFavourited($user_id, $target_id, $type);
+				$result = $favourites->toggleFavourite(!$isFavourited, $user_id, $target_id, $type);
+				break;
+			case 'add':
+				$result = $favourites->favourite($user_id, $target_id, $type);
+				break;
+			case 'remove':
+				$result = $favourites->unfavourite($user_id, $target_id, $type);
+				break;
+			case 'batch':
+				$items = $params['items']??[];
+				$items = array_filter($items, function ($item) {
+					$keys = ['target_id', 'type', 'action'];
+					foreach ($keys as $key) {
+						if (!array_key_exists($key, $item)) {
+							return false;
+						}
+						if (empty($item[$key])) {
+							return false;
+						}
+					}
+					if (!in_array($item['action'], ['add', 'remove'])) {
+						return false;
+					}
+					return true;
+				});
+				if (empty($items)) {
+					$result = $this->success(['message' => 'No items to update']);
+				} else {
+					$result = $favourites->batchToggleFavourites($user_id, $items);
+				}
+				break;
+		}
 
 		if ($result) {
-			return $this->success();
+			return $this->success(is_bool($result) ? [] : ['result' => $result]);
 		}
 		return $this->error('Something went wrong');
 	}
@@ -163,10 +198,8 @@
 	 */
 	public function getLists(WP_REST_Request $request): WP_REST_Response
 	{
-		$params = $request->get_params();
 		$user_id = get_current_user_id();
 
-
 		$args = $this->buildParams($request);
 		$args['per_page'] = 20;
 		$listId = $request->get_param('id');
@@ -174,21 +207,13 @@
 			$args['where']['id'] = sanitize_text_field($listId);
 		}
 
-		$key = $this->listsCache->generateKey($args);
-
-		// Check cache headers
-		$cache_check = $this->checkHeaders($request, $key);
-		if ($cache_check) {
-			return $cache_check;
-		}
-
 		$includeShares = !empty($request->get_param('include_shares'));
 
 		$response = !empty($listId)
-			? JVB()->favourites()->getListDetails($listId, $user_id)
-			: JVB()->favourites()->getAvailableLists($args, $includeShares);
+			? Favourites::getInstance()->getListDetails($listId, $user_id)
+			: Favourites::getInstance()->getAvailableLists($args, $includeShares);
 
-		return $this->addCacheHeaders(Response::success($response));
+		return Response::success($response);
 	}
 
 
@@ -202,131 +227,12 @@
 		$user_id = get_current_user_id();
 
 
-		$counts = JVB()->favourites()->getFavouriteCounts($user_id);
+		$counts = Favourites::getInstance()->getFavouriteCounts($user_id);
 
 		return Response::success(['counts' => $counts]);
 	}
 
 	/**
-	 * Process favourite operations using transactions
-	 */
-	public function processOperation($result, object $operation, array $data)
-	{
-		$action_map = [
-			'favourite_add' => 'addFavourite',
-			'favourite_remove' => 'removeFavourite',
-			'favourite_batch' => 'batchFavourites',
-			'favourite_note' => 'processNote',
-			'favourite_list_create' => 'createList',
-			'favourite_list_update' => 'updateList',
-			'favourite_list_delete' => 'deleteList',
-			'favourite_list_add_items' => 'addToList',
-			'favourite_list_remove_items' => 'removeFromList',
-			'favourite_list_share' => 'shareList',
-			'favourite_list_unshare' => 'unshareList',
-		];
-
-		if (!isset($action_map[$operation->type])) {
-			return $result;
-		}
-
-		try {
-			$method = $action_map[$operation->type];
-			$response = $this->$method($operation->user_id, $data);
-
-			// Clear cache on success
-			if ($response['success'] ?? false) {
-				Cache::invalidateItem('favourites', $operation->user_id);
-				$this->listsCache->flush();
-				$this->sharedListsCache->flush();
-			}
-
-			return $response;
-		} catch (Exception $e) {
-			$this->logError('processOperation', [
-				'error' => $e->getMessage(),
-				'operation_id' => $operation->id,
-				'type' => $operation->type
-			]);
-
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Add a favourite using findOrCreate pattern
-	 */
-	protected function addFavourite(int $user_id, array $data): array
-	{
-		$type = $data['type'];
-		$target_id = $data['target_id'];
-
-		if (!str_starts_with($type, BASE)) {
-			$type = BASE . $type;
-		}
-
-		if (!isset($this->valid_types[$type])) {
-			return ['success' => false, 'result' => 'Invalid type'];
-		}
-
-		// Use findOrCreate pattern
-		$result = $this->favourites->findOrCreate([
-			'user_id' => $user_id,
-			'type' => $type,
-			'target_id' => $target_id
-		]);
-
-		if ((bool)$result) {
-			$this->updateFavouriteCount($type, $target_id);
-			$this->maybeNotifyOwner($type, $target_id, $user_id);
-		}
-
-		return [
-			'success' => true,
-			'result' => [
-				'action' => $result['created'] ? 'added' : 'already_exists',
-				'favourite_id' => $result['id'],
-				'count' => $this->getFavouriteCount($type, $target_id)
-			]
-		];
-	}
-
-	/**
-	 * Remove a favourite
-	 */
-	protected function removeFavourite(int $user_id, array $data): array
-	{
-		$type = $data['type'];
-		$target_id = $data['target_id'];
-
-		if (!str_starts_with($type, BASE)) {
-			$type = BASE . $type;
-		}
-
-		$deleted = $this->favourites->where([
-			'user_id' => $user_id,
-			'type' => $type,
-			'target_id' => $target_id
-		])->deleteResults();
-
-		if ($deleted) {
-			$this->updateFavouriteCount($type, $target_id);
-			$this->removeRelatedNotifications($type, $target_id, $user_id);
-		}
-
-		return [
-			'success' => true,
-			'result' => [
-				'action' => $deleted ? 'removed' : 'not_found',
-				'count' => $this->getFavouriteCount($type, $target_id)
-			]
-		];
-	}
-
-	/**
 	 * Batch favourites using transaction
 	 */
 	protected function batchFavourites(int $user_id, array $data): array
@@ -334,7 +240,7 @@
 		$items = $data['items'] ?? [];
 
 		if (empty($items)) {
-			return ['success' => false, 'result' => 'No items provided'];
+			return $this->success(['result' => 'No items provided']);
 		}
 
 		return $this->favourites->transaction(function($table) use ($user_id, $items) {
@@ -605,7 +511,7 @@
 	public function maybeAcceptListInvite(int $user_id, string $email, array $data):void
 	{
 		if (array_key_exists('list_token', $data) && !empty($data['list_token'])) {
-			JVB()->favourites()->acceptListShare($data['list_token'], $user_id);
+			Favourites::getInstance()->acceptListShare($data['list_token'], $user_id);
 		}
 	}
 
@@ -615,8 +521,8 @@
 	protected function getListDetails(int $list_id, int $user_id): array
 	{
 		// Check access - either owner or has share
-		$is_owner = JVB()->favourites()->userOwnsList($list_id, $user_id);
-		$is_shared = JVB()->favourites()->userCanViewList($list_id, $user_id);
+		$is_owner = Favourites::getInstance()->userOwnsList($list_id, $user_id);
+		$is_shared = Favourites::getInstance()->userCanViewList($list_id, $user_id);
 
 
 		if (!$is_owner && !$is_shared) {
@@ -626,7 +532,7 @@
 			];
 		}
 
-		$list = JVB()->favourites()->getListDetails($list_id, $user_id);
+		$list = Favourites::getInstance()->getListDetails($list_id, $user_id);
 
 		if (empty($list)) {
 			return [
@@ -1121,7 +1027,7 @@
 				]);
 
 				// Send notification
-				JVB()->notification()->addNotification(
+				NotificationManager::getInstance()->addNotification(
 					$share_user->ID,
 					'list_shared',
 					$user_id,
@@ -1241,7 +1147,7 @@
 				$list = $this->lists->where(['id' => $list_id])->first();
 
 				if ($list) {
-					JVB()->notification()->addNotification(
+					NotificationManager::getInstance()->addNotification(
 						$existing_share->user_id,
 						'list_share_revoked',
 						$user_id,
@@ -1315,7 +1221,7 @@
 			$invite_url
 		);
 
-		return JVB()->email()->sendEmail($email, $subject, $message);
+		return Emails::getInstance()->sendEmail($email, $subject, $message);
 	}
 
 	/**
@@ -1389,7 +1295,7 @@
 			$user = get_userdata($user_id);
 			$display_name = $user ? $user->display_name : $email;
 
-			JVB()->notification()->addNotification(
+			NotificationManager::getInstance()->addNotification(
 				$list->user_id,
 				'list_share_accepted',
 				$user_id,
@@ -1676,7 +1582,7 @@
 			return $this->error('Invalid action', 'invalid_action', 400);
 		}
 
-		JVB()->queue()->queueOperation(
+		Queue::getInstance()->queueOperation(
 			$operation_type,
 			$user_id,
 			$data,
diff --git a/inc/rest/routes/FeedRoutes.php b/inc/rest/routes/FeedRoutes.php
index 814b821..5d43c6a 100644
--- a/inc/rest/routes/FeedRoutes.php
+++ b/inc/rest/routes/FeedRoutes.php
@@ -2,11 +2,13 @@
 namespace JVBase\rest\routes;
 
 use JVBase\base\Site;
+use JVBase\Instance;
+use JVBase\integrations\services\Umami;
+use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
-use Umami;
 use WP_Post;
 use WP_Query;
 use WP_REST_Request;
@@ -19,6 +21,7 @@
 
 class FeedRoutes extends Rest
 {
+	use Instance;
 	protected int $per_page = 36;
 	protected ?Umami $tracker = null;
 
@@ -31,10 +34,7 @@
 		$this->cacheName = 'feed';
 		$this->cacheTtl = 86400;
 		parent::__construct();
-		$this->cache
-			->connect('post', true)
-			->connect('taxonomy', true)
-			->connect('user', true);
+		$this->cache = Cache::for('feed');
 
 		if (JVB_TESTING) {
 			$this->cache->flush();
@@ -45,7 +45,7 @@
 	public function init():void
 	{
 		if (Site::hasIntegration('umami')) {
-			$this->tracker = JVB()->connect('umami');
+			$this->tracker = Umami::getInstance();
 		}
 	}
 
@@ -115,11 +115,13 @@
 				$post = get_post($postID);
 				$type = jvbNoBase($post->post_type);
 				$metaType = 'post';
+				$this->cache = Cache::for('feedPosts', 'post');
 				break;
 			default:
 				$post = get_term($postID, jvbCheckBase($type));
 				$type = jvbNoBase($type);
 				$metaType = 'term';
+				$this->cache = Cache::for('feedTerms', 'term');
 				break;
 		}
 		if (!$post || is_wp_error($post)) {
@@ -296,6 +298,7 @@
 
 	protected function formatTaxonomy(WP_Term|int $term, int $postID, string $type)
 	{
+		$this->cache = Cache::for('feedTaxonomies', 'term');
 		return $this->cache->remember(
 			$term->term_id,
 			function () use ($term, $postID, $type) {
@@ -316,6 +319,8 @@
 
 	protected function getAuthorData(WP_Post $post)
 	{
+		//Connects to their profile, not user id
+		$this->cache = Cache::for('feedUsers', 'post');
 		$author = $post->post_author;
 		$userLink = get_user_meta($author, BASE.'profile_link', true);
 		return $this->cache->remember(
@@ -342,6 +347,10 @@
 	protected function getTaxonomies(int $postID, string $content): array
 	{
 		$registrar = Registrar::getInstance($content)??false;
+		if (!$registrar) {
+			return [];
+		}
+		$this->cache = Cache::for('feedTaxonomyLinks', 'term');
 		$taxonomies = $registrar->registrar->taxonomies;
 		$out = [];
 		foreach ($taxonomies as $tax) {
@@ -413,41 +422,16 @@
 	public function handleFeedRequest(WP_REST_Request $request): WP_REST_Response
 	{
 		$args = $this->buildRequestArgs($request);
-		$key = $this->cache->generateKey($args);
 
-		// Check HTTP cache headers first
-		$cache_check = $this->checkHeaders(
-			$request,
-			$key
-		);
-		if ($cache_check) {
-			return $cache_check; // Returns 304 Not Modified
-		}
-
-		$cached = $this->cache->get($key);
-		if ($cached) {
-			if ($request->get_param('highlight')) {
-				$highlight = json_decode($request->get_param('highlight'), true);
-				$args['highlight'] = $highlight;
-			}
-			$cached['items'] = $this->processHighlightedItem($cached['items'], $args);
-			$response = $this->success($cached);
-			return $this->addCacheHeaders($response);
-		}
-		// Fetch and format items
 		$items = $this->fetchFeedItems($args);
-
-		$ttl = (str_contains($args['orderby'], 'RAND')) ? 300 : $this->cacheTtl;
-		$this->cache->set($key, $items, $ttl);
-
-		if ($request->get_param('highlight')) {
-			$highlight = json_decode($request->get_param('highlight'), true);
+		$highlight = $request->get_param('highlight');
+		if (!empty($highlight)) {
+			$highlight = json_decode($highlight, true);
 			$args['highlight'] = $highlight;
+			$items['items'] = $this->processHighlightedItem($items['items'], $args);
 		}
 
-		$items['items'] = $this->processHighlightedItem($items['items'], $args);
-		$response = $this->success($items);
-		return $this->addCacheHeaders($response);
+		return $this->success($items);
 	}
 
 	/**
@@ -594,15 +578,11 @@
 		$args['fields'] = 'ids';
 		// Get post IDs
 		$query = new WP_Query($args);
-
-		// Batch prefetch related data
-		update_meta_cache('post', $query->posts);
-		update_object_term_cache($query->posts, $args['post_type']);
-
 		// Format regular items
 		$items = array_map(fn($post) => $this->formatItem($post), $query->posts);
 
 		wp_reset_postdata();
+
 		return [
 			'items' => $items,
 			'has_more' => $query->max_num_pages > $args['paged'],
@@ -1122,16 +1102,7 @@
 	 */
 	public function getFeedTypes(WP_REST_Request $request): WP_REST_Response
 	{
-		// Check HTTP cache
-		$cache_check = $this->checkHeaders($request, 'feed_types');
-		if ($cache_check) {
-			return $cache_check;
-		}
-
-		$feedTypes = $this->buildFeedTypesConfig();
-
-		$response = $this->success($feedTypes);
-		return $this->addCacheHeaders($response);
+		return $this->success($this->buildFeedTypesConfig());
 	}
 
 	public function getFeedTypesConfig():array
@@ -1151,7 +1122,6 @@
 				// Get content types with show_feed
 				$contentTypes = Registrar::withFeature('show_feed', 'post');
 				foreach ($contentTypes as $slug) {
-					$this->cache->tag('content:'.$slug);
 					$registrar = Registrar::getInstance($slug);
 					if (!$registrar) continue;
 
@@ -1171,9 +1141,6 @@
 					if (!$registrar || !($registrar->hasFeature('is_content') ?? false)) {
 						continue;
 					}
-
-					$this->cache->tag('taxonomy:'.$slug);
-
 					$config[$slug] = [
 						'type' => 'taxonomy',
 						'singular' => $registrar->getSingular(),
diff --git a/inc/rest/routes/FormRoutes.php b/inc/rest/routes/FormRoutes.php
index 1cf8b23..f2bd9de 100644
--- a/inc/rest/routes/FormRoutes.php
+++ b/inc/rest/routes/FormRoutes.php
@@ -1,6 +1,9 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\integrations\services\Cloudflare;
+use JVBase\managers\Emails;
 use JVBase\meta\Sanitizer;
 use JVBase\meta\Validator;
 use JVBase\rest\PermissionHandler;
@@ -25,6 +28,7 @@
  */
 class FormRoutes extends Rest
 {
+	use Instance;
 	protected FormBlock $form_block;
 
 	public function __construct()
@@ -121,15 +125,18 @@
 
 	protected function verifyTurnstile(string $token): bool
 	{
-		if (!Site::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) {
+		if (!Site::hasIntegration('cloudflare') ) {
 			return true;
 		}
-
+		$cloudflare = Cloudflare::getInstance();
+		if (!$cloudflare->isSetUp()) {
+			return true;
+		}
 		if (empty($token)) {
 			return false;
 		}
 
-		return JVB()->connect('cloudflare')->verifyTurnstile($token);
+		return $cloudflare->verifyTurnstile($token);
 	}
 
 	/**
@@ -313,14 +320,14 @@
 		}
 
 		// Build email body
-		$body = JVB()->email()->h2($form_config['title']);
-		$body .= '<p style="font-size:13px;color:' . JVB()->email()->colours['dark-200'] . ';">
+		$body = Emails::getInstance()->h2($form_config['title']);
+		$body .= '<p style="font-size:13px;color:' . Emails::getInstance()->colours['dark-200'] . ';">
         Submitted: ' . current_time('F j, Y \a\t g:i A') . '
     </p>';
 
 		// Add thread notice if CC'd
 		if ($submitter_email) {
-			$body .= JVB()->email()->notice(
+			$body .= Emails::getInstance()->notice(
 				'<strong>Email Thread Created</strong><br>Reply to this email to continue the conversation with ' .
 				esc_html($submitter_name ?: 'the submitter') . '.'
 			);
@@ -360,21 +367,21 @@
 			];
 		}
 
-		$body .= JVB()->email()->spacer(20);
-		$body .= JVB()->email()->table($form);
+		$body .= Emails::getInstance()->spacer(20);
+		$body .= Emails::getInstance()->table($form);
 
 		// Show attachment info
 		$attachments = $this->processFileAttachments($files);
 		if (!empty($attachments)) {
-			$body .= JVB()->email()->spacer(10);
-			$body .= JVB()->email()->alert(
+			$body .= Emails::getInstance()->spacer(10);
+			$body .= Emails::getInstance()->alert(
 				sprintf('%d file(s) attached to this email', count($attachments)),
 				'info'
 			);
 		}
 
 		// Send the email
-		return JVB()->email()->sendEmail(
+		return Emails::getInstance()->sendEmail(
 			$to,
 			$subject,
 			$body,
diff --git a/inc/rest/routes/ImporterRoutes.php b/inc/rest/routes/ImporterRoutes.php
index b5debfa..59840d1 100644
--- a/inc/rest/routes/ImporterRoutes.php
+++ b/inc/rest/routes/ImporterRoutes.php
@@ -4,6 +4,7 @@
 
 use JVBase\importers\JaneAppClientImporter;
 use JVBase\importers\JaneAppSalesImporter;
+use JVBase\Instance;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use WP_REST_Request;
@@ -21,6 +22,7 @@
  */
 class ImporterRoutes extends Rest
 {
+	use Instance;
 	/**
 	 * Register REST routes
 	 */
diff --git a/inc/rest/routes/IntegrationsHelcimRoutes.php b/inc/rest/routes/IntegrationsHelcimRoutes.php
index b626ecb..f735618 100644
--- a/inc/rest/routes/IntegrationsHelcimRoutes.php
+++ b/inc/rest/routes/IntegrationsHelcimRoutes.php
@@ -1,6 +1,9 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\base\Site;
+use JVBase\Instance;
+use JVBase\integrations\services\Helcim;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use WP_REST_Request;
@@ -13,6 +16,7 @@
 
 class IntegrationsHelcimRoutes extends Rest
 {
+	use Instance;
 	public function registerRoutes(): void
 	{
 		Route::for('helcim/initialize-checkout')
@@ -56,6 +60,9 @@
 	{
 		$data    = $request->get_json_params();
 		$user_id = get_current_user_id();
+		if (!Site::hasIntegration('helcim')) {
+			return $this->validationError(['message' => 'Helcim not setup']);
+		}
 		if (!$user_id) {
 			return $this->unauthorized();
 		}
@@ -65,7 +72,12 @@
 		}
 
 		try {
-			$helcim = JVB()->connect('helcim');
+			$helcim = Helcim::getInstance();
+			if (!method_exists($helcim, 'resolveCustomerId') ||
+				!method_exists($helcim, 'initializeCheckout')
+			) {
+				return $this->validationError(['message' => 'Helcim not complete']);
+			}
 
 			// Auto-resolve customer ID from logged-in user
 			if (empty($data['customerId']) && $user_id) {
@@ -96,9 +108,16 @@
 		if (!$user_id) {
 			return $this->validationError(['message' => 'Not logged in']);
 		}
+		if (!Site::hasIntegration('helcim')) {
+			return $this->validationError(['message' => 'Helcim not setup']);
+		}
+
 
 		try {
-			$helcim    = JVB()->connect('helcim');
+			$helcim    = Helcim::getInstance();
+			if (!method_exists($helcim, 'handleGetInvoices')) {
+				return $this->validationError(['message' => 'Helcim not complete']);
+			}
 			$user      = get_userdata($user_id);
 			$result    = $helcim->handleGetInvoices([
 				'email' => $user->user_email,
@@ -122,8 +141,15 @@
 			return $this->validationError(['message' => 'Invoice ID required']);
 		}
 
+		if (!Site::hasIntegration('helcim')) {
+			return $this->validationError(['message' => 'Helcim not setup']);
+		}
+
 		try {
-			$helcim = JVB()->connect('helcim');
+			$helcim = Helcim::getInstance();
+			if (!method_exists($helcim, 'handleGetInvoice')) {
+				return $this->validationError(['message' => 'Helcim not complete']);
+			}
 			$result = $helcim->handleGetInvoice(['invoiceId' => $invoiceId]);
 
 			return $this->success($result);
@@ -143,9 +169,15 @@
 		if (!$user_id) {
 			return $this->validationError(['message' => 'Not logged in']);
 		}
+		if (!Site::hasIntegration('helcim')) {
+			return $this->validationError(['message' => 'Helcim not setup']);
+		}
 
 		try {
-			$helcim = JVB()->connect('helcim');
+			$helcim = Helcim::getInstance();
+			if (!method_exists($helcim, 'handleGetCustomerCards')) {
+				return $this->validationError(['message' => 'Helcim not complete']);
+			}
 			$result = $helcim->handleGetCustomerCards([
 				'email' => get_userdata($user_id)->user_email,
 			]);
@@ -171,9 +203,15 @@
 		if (empty($data['secretToken']) || empty($data['transactionData'])) {
 			return $this->validationError(['message' => 'Missing secretToken or transactionData']);
 		}
+		if (!Site::hasIntegration('helcim')) {
+			return $this->validationError(['message' => 'Helcim not setup']);
+		}
 
 		try {
-			$helcim = JVB()->connect('helcim');
+			$helcim = Helcim::getInstance();
+			if (!method_exists($helcim, 'validateTransaction')) {
+				return $this->validationError(['message' => 'Helcim not complete']);
+			}
 			$valid  = $helcim->validateTransaction(
 				$data['secretToken'],
 				$data['transactionData']
diff --git a/inc/rest/routes/IntegrationsRoutes.php b/inc/rest/routes/IntegrationsRoutes.php
index 4bfb20b..8df07a2 100644
--- a/inc/rest/routes/IntegrationsRoutes.php
+++ b/inc/rest/routes/IntegrationsRoutes.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\integrations\ServiceMap;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use WP_REST_Request;
@@ -13,7 +15,8 @@
 
 class IntegrationsRoutes extends Rest
 {
-
+	use Instance;
+	use ServiceMap;
 	/**
 	 * Register REST routes
 	 */
@@ -22,7 +25,7 @@
 		Route::for('integrations')
 			->post([$this, 'handleAction'])
 			->args([
-				'service'	=> 'string|required|enum:'.implode(',',JVB()->getAvailableServices()),
+				'service'	=> 'string|required|enum:'.implode(',',$this->getAvailableServices()),
 				'action'	=> 'string|required',
 				'user_id'	=> 'int',
 				'context'	=> 'string|enum:admin,user',
@@ -57,12 +60,17 @@
 		if ($theUserID === false) {
 			return $this->validationError(['message' => 'Not logged in']);
 		}
-		$integration = JVB()->connect($service, $theUserID);
+		$integration = $this->getService($service);
+		if (!method_exists($integration, 'switchUser')) {
+			return $this->validationError(['message' => 'Cannot get user integration']);
+		}
 
-		if (!$integration) {
+		if (!$integration || !method_exists($integration, 'processAction')) {
 			return $this->validationError(['message'=>'Invalid service']);
 		}
 
+		$integration->switchUser($theUserID);
+
 		$integration->getCredentials();
 
 		// Handle the action
@@ -88,13 +96,26 @@
 		$user_id = $request->get_param('user_id');
 		$return_url = $request->get_param('return_url');
 
-		$integration = JVB()->connect($service, $user_id);
+		$theUserID = current_user_can('manage_options') ? null : get_current_user_id();
+		if ($theUserID === false) {
+			return $this->validationError(['message' => 'Not logged in']);
+		}
+		$integration = $this->getService($service);
+		if (!method_exists($integration, 'switchUser')) {
+			return $this->validationError(['message' => 'Cannot get user integration']);
+		}
 
-		if (!$integration || !$integration->isOAuthService) {
+		if (!$integration || !method_exists($integration, 'processAction')) {
 			return $this->validationError(['message'=>'Invalid service']);
 		}
 
-		$auth_url = $integration->getOAuthUrl($return_url);
+		$integration->switchUser($theUserID);
+
+		if (!$integration || !method_exists($integration, 'getOAuthBaseURL') || is_null($integration->getOAuthBaseURL())) {
+			return $this->validationError(['message'=>'Invalid service']);
+		}
+
+		$auth_url = $integration->getOAuthBaseURL($return_url);
 
 		if ($auth_url) {
 			return $this->success($auth_url);
diff --git a/inc/rest/routes/IntegrationsSquareRoutes.php b/inc/rest/routes/IntegrationsSquareRoutes.php
index 79cbd0d..e4ce0e9 100644
--- a/inc/rest/routes/IntegrationsSquareRoutes.php
+++ b/inc/rest/routes/IntegrationsSquareRoutes.php
@@ -3,6 +3,7 @@
 
 
 use Exception;
+use JVBase\Instance;
 use JVBase\integrations\services\Square;
 use JVBase\meta\Meta;
 use JVBase\rest\Rest;
@@ -16,6 +17,7 @@
 
 class IntegrationsSquareRoutes extends Rest
 {
+	use Instance;
 	public function registerRoutes():void
 	{
 		Route::for('square/process-payment')
@@ -80,7 +82,13 @@
 
 		try {
 			/** @var Square $square */
-			$square = JVB()->connect('square');
+			$square = Square::getInstance();
+			if (!method_exists($square, 'getOrCreateSquareCustomer') ||
+				!method_exists($square, 'createSquareOrder') ||
+				!method_exists($square, 'saveOrderToWordPress') ||
+				!method_exists($square, 'createSquarePayment')) {
+				return $this->error('Square not set up');
+			}
 			// Step 1: Get or create Square customer
 			$customer_id = $square->getOrCreateSquareCustomer($data['customer']);
 
diff --git a/inc/rest/routes/Invitations.php b/inc/rest/routes/Invitations.php
index 6322115..90f0385 100644
--- a/inc/rest/routes/Invitations.php
+++ b/inc/rest/routes/Invitations.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\managers\InvitationsManager;
 use JVBase\rest\Rest;
 use JVBase\managers\CustomTable;
 use JVBase\rest\Route;
@@ -20,6 +22,7 @@
  */
 class Invitations extends Rest
 {
+	use Instance;
 	protected array $inviteConfig;
 	protected CustomTable $table;
 
@@ -29,7 +32,7 @@
 		parent::__construct();
 
 		// Get invitation configuration
-		$this->inviteConfig = JVB()->invitations()->getInviteConfig();
+		$this->inviteConfig = InvitationsManager::getInstance()->getInviteConfig();
 		$this->table = CustomTable::for('invitations');
 
 		// Cache connections
@@ -107,7 +110,7 @@
 	protected function getTermInvitations(array $args): array
 	{
 		// Check permission
-		if (!JVB()->roles()->isManager($args['user'], $args['to_term'])) {
+		if (!Roles::getInstance()()->isManager($args['user'], $args['to_term'])) {
 			return $this->forbidden('You cannot view invitations for this ' . $args['taxonomy'])->get_data();
 		}
 
@@ -213,7 +216,7 @@
 		}
 
 		// Queue using fluent interface
-		$op = JVB()->queue()->add(
+		$op = Queue::getInstance()->add(
 			'invitation_create',
 			$userID,
 			['invitations' => $validated],
@@ -231,7 +234,7 @@
 	 */
 	protected function validateInvitations(int $userID, array $invites): array
 	{
-		return JVB()->invitations()->validateInvitations($userID, $invites);
+		return InvitationsManager::getInstance()->validateInvitations($userID, $invites);
 	}
 
 
@@ -248,7 +251,7 @@
 			return $this->error('Invitation ID required');
 		}
 
-		$op = JVB()->queue()->add(
+		$op = Queue::getInstance()->add(
 			'invitation_revoke',
 			$userID,
 			['invitation_id' => $invitationID],
@@ -269,7 +272,7 @@
 			return $this->error('Invitation ID required');
 		}
 
-		$op = JVB()->queue()->add(
+		$op = Queue::getInstance()->add(
 			'invitation_resend',
 			$userID,
 			['invitation_id' => $invitationID],
@@ -308,7 +311,7 @@
 		];
 
 		// Add term information if present
-		foreach (JVB()->roles()->getInvitableTaxonomies() as $taxonomy) {
+		foreach (Roles::getInstance()()->getInvitableTaxonomies() as $taxonomy) {
 			$column = 'to_' . $taxonomy;
 			if (isset($invitation->$column) && $invitation->$column) {
 				$termID = (int) $invitation->$column;
diff --git a/inc/rest/routes/LoginRoutes.php b/inc/rest/routes/LoginRoutes.php
index 5d8218b..47fa450 100644
--- a/inc/rest/routes/LoginRoutes.php
+++ b/inc/rest/routes/LoginRoutes.php
@@ -1,7 +1,9 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\managers\Cache;
+use JVBase\managers\Emails;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
@@ -22,6 +24,7 @@
  */
 class LoginRoutes extends Rest
 {
+	use Instance;
 	protected ?string $requestId = null;
 	protected bool $hasMagicLink = false;
 
@@ -280,7 +283,7 @@
 
 		// Send magic link for email verification
 		if ($this->hasMagicLink) {
-			JVB()->magicLink()->sendMagicLink($email, 'signup', [
+			MagicLinks::getInstance()->sendMagicLink($email, 'signup', [
 				'name' => $name,
 				'role' => $role,
 			]);
@@ -331,7 +334,7 @@
 
 		// Use magic link if available, otherwise standard WP reset
 		if ($this->hasMagicLink) {
-			$result = JVB()->magicLink()->sendMagicLink($email, 'reset');
+			$result = MagicLinks::getInstance()->sendMagicLink($email, 'reset');
 
 			if (is_wp_error($result)) {
 				$this->logError('Magic link send failed', [
@@ -348,7 +351,7 @@
 					'error' => $key->get_error_message(),
 				]);
 			} else {
-				$success = JVB()->email()->sendPasswordResetEmail($user, $key);
+				$success = Emails::getInstance()->sendPasswordResetEmail($user, $key);
 			}
 		}
 
@@ -450,7 +453,7 @@
 		}
 
 		// Send magic link
-		$result = JVB()->magicLink()->sendMagicLink($email, $type, $context);
+		$result = MagicLinks::getInstance()->sendMagicLink($email, $type, $context);
 
 		if (is_wp_error($result)) {
 			$this->logError('Magic link send failed', [
@@ -662,7 +665,7 @@
 		}
 
 		try {
-			JVB()->referrals()->processReferralCode($user_id, $referral_code);
+			Referrals::getInstance()->processReferralCode($user_id, $referral_code);
 		} catch (\Exception $e) {
 			$this->logError('Referral processing failed', [
 				'user_id' => $user_id,
diff --git a/inc/rest/routes/MagicLinkRoutes.php b/inc/rest/routes/MagicLinkRoutes.php
index efc6571..417bba7 100644
--- a/inc/rest/routes/MagicLinkRoutes.php
+++ b/inc/rest/routes/MagicLinkRoutes.php
@@ -2,7 +2,7 @@
 namespace JVBase\rest\routes;
 
 use JVBase\rest\RestRouteManager;
-use JVBase\managers\MagicLinkManager;
+use JVBase\managers\MagicLinks;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -110,7 +110,7 @@
 			return $this->error('Security verification failed', 'turnstile_failed', 403);
 		}
 		$email = sanitize_email($request->get_param('email')??$request->get_param('user_email')??'');
-		$type = sanitize_text_field($request->get_param('type')) ?? MagicLinkManager::TYPE_LOGIN;
+		$type = sanitize_text_field($request->get_param('type')) ?? MagicLinks::TYPE_LOGIN;
 		$context = $request->get_param('context') ?? [];
 
 		// Validate email
@@ -123,20 +123,20 @@
 
 		// Check if email exists
 		$exists = email_exists($email);
-		if ($type === MagicLinkManager::TYPE_LOGIN && !$exists) {
+		if ($type === MagicLinks::TYPE_LOGIN && !$exists) {
 			return new WP_REST_Response([
 				'success' => true,
 				'message' => 'Invalid email address'
 			]);
 		}
 
-		if ($type === MagicLinkManager::TYPE_SIGNUP && $exists) {
+		if ($type === MagicLinks::TYPE_SIGNUP && $exists) {
 			// Redirect to login instead
-			$type = MagicLinkManager::TYPE_LOGIN;
+			$type = MagicLinks::TYPE_LOGIN;
 		}
 
 		// Send the magic link
-		$result = JVB()->magicLink()?->sendMagicLink($email, $type, $context);
+		$result = MagicLinks::getInstance()?->sendMagicLink($email, $type, $context);
 
 		if (is_wp_error($result)) {
 			return new WP_REST_Response([
@@ -178,7 +178,7 @@
 		$email = sanitize_email($request->get_param('email'));
 
 		// This returns array|WP_Error - check for error first
-		$token_data = JVB()->magicLink()?->verifyToken($token, $email);
+		$token_data = MagicLinks::getInstance()?->verifyToken($token, $email);
 
 		if (is_wp_error($token_data)) {
 			return new WP_REST_Response([
diff --git a/inc/rest/routes/NewsRoutes.php b/inc/rest/routes/NewsRoutes.php
index 485e389..5040862 100644
--- a/inc/rest/routes/NewsRoutes.php
+++ b/inc/rest/routes/NewsRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
 use JVBase\meta\Meta;
@@ -17,6 +18,7 @@
 
 class NewsRoutes extends Rest
 {
+	use Instance;
     protected int $per_page;
     protected bool|object $manager = false;
     public function __construct()
@@ -77,7 +79,7 @@
      */
     public function handleNewsOperation(WP_REST_Request $request):WP_REST_Response
     {
-        $queue = JVB()->queue();
+        $queue = Queue::getInstance();
         $data = $request->get_params();
         $user = get_current_user_id();
         $operationID = sanitize_text_field($data['id']);
diff --git a/inc/rest/routes/NotificationsRoutes.php b/inc/rest/routes/NotificationsRoutes.php
index b3c2e05..90af2be 100644
--- a/inc/rest/routes/NotificationsRoutes.php
+++ b/inc/rest/routes/NotificationsRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
 use JVBase\registrar\Registrar;
@@ -23,6 +24,7 @@
  */
 class NotificationsRoutes extends Rest
 {
+	use Instance;
 	protected int $user_id;
 	protected array $notification_types = [];
 	protected object $manager;
@@ -133,7 +135,7 @@
 	 */
 	public function init(): void
 	{
-		$this->manager = JVB()->notification();
+		$this->manager = NotificationManager::getInstance();
 		$this->notification_types = $this->manager->getNotificationTypes(true);
 	}
 
@@ -967,7 +969,7 @@
 		}
 
 		if (!empty($queueData)) {
-			JVB()->queue()->queueOperation(
+			Queue::getInstance()->queueOperation(
 				'notification_' . $action,
 				$args['user_id'],
 				$queueData
diff --git a/inc/rest/routes/OptionsRoutes.php b/inc/rest/routes/OptionsRoutes.php
index 0ba6861..06d4ba9 100644
--- a/inc/rest/routes/OptionsRoutes.php
+++ b/inc/rest/routes/OptionsRoutes.php
@@ -2,6 +2,7 @@
 namespace JVBase\rest\routes;
 
 use JVBase\base\Options;
+use JVBase\Instance;
 use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
@@ -18,6 +19,7 @@
 
 class OptionsRoutes extends Rest
 {
+	use Instance;
 
 	public function __construct()
 	{
@@ -48,7 +50,7 @@
 		$operationID = $data['id'];
 		unset($data['id']);
 
-		JVB()->queue()->queueOperation(
+		Queue::getInstance()->queueOperation(
 			'update_options',
 			$user,
 			$data,
diff --git a/inc/rest/routes/QueueRoutes.php b/inc/rest/routes/QueueRoutes.php
index e7fd1d7..3540edc 100644
--- a/inc/rest/routes/QueueRoutes.php
+++ b/inc/rest/routes/QueueRoutes.php
@@ -1,8 +1,10 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\managers\Cache;
 use JVBase\managers\queue\Operation;
+use JVBase\managers\queue\Queue;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use JVBase\rest\Response;
@@ -15,11 +17,13 @@
 
 class QueueRoutes extends Rest
 {
+	use Instance;
 	public function __construct()
 	{
 		$this->cacheName = 'queue';
 		$this->cacheTtl = 300;
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName, 'custom', $this->cacheTtl)->user();
 
 		if (JVB_TESTING) {
 			$this->cache->flush();
@@ -91,32 +95,16 @@
 		if (!$user_id) {
 			return $this->unauthorized();
 		}
-		$this->cache = Cache::for('queue')->user();
-		$status = sanitize_text_field($params['status']);
-		$ids = !empty($params['ids'])
-			? array_map('trim', array_map('sanitize_text_field', explode(',', $params['ids'])))
-			: [];
-		$limit = absint($params['limit']);
 
-		$cacheKey = $this->cache->generateKey(compact('user_id', 'status', 'ids', 'limit'));
-		if ($cached = $this->checkHeaders($request, $cacheKey)) {
-			return $cached;
-		}
+		$filters = $this->buildFilters($params);
+		$ops = Queue::getInstance()->getUserOperations($user_id, $filters);
 
-		$data = $this->cache->tag("user:{$user_id}")->remember($cacheKey, function() use ($user_id, $params) {
-			$filters = $this->buildFilters($params);
-			$operations = JVB()->queue()->getUserOperations($user_id, $filters);
-
-			return [
-				'items' => array_map([$this, 'formatOperation'], $operations),
-				'total' => count($operations),
-				'queue_stats' => $this->getQueueStats($user_id),
-				'server_time' => date('c'),
-			];
-		});
-
-		$response = Response::success($data);
-		return $this->addCacheHeaders($response);
+		return Response::success([
+			'items'	=> array_map([$this, 'formatOperation'], $ops),
+			'total'	=> count($ops),
+			'queue_stats' => $this->getQueueStats($user_id),
+			'server_time'	=> date('c')
+		]);
 	}
 
 	private function buildFilters(array $params): array
@@ -168,7 +156,7 @@
 		}
 
 		// Get operations via Queue - verifies ownership
-		$operations = JVB()->queue()->getUserOperations($user_id, [
+		$operations = Queue::getInstance()->getUserOperations($user_id, [
 			'ids' => $ids,
 			'limit' => count($ids),
 		]);
@@ -202,7 +190,7 @@
 			$filters['ids'] = array_map('trim', explode(',', $ids));
 		}
 
-		$operations = JVB()->queue()->getUserOperations($userId, $filters);
+		$operations = Queue::getInstance()->getUserOperations($userId, $filters);
 
 		if ($since) {
 			$sinceTime = strtotime($since);
@@ -235,7 +223,7 @@
 			return $this->unauthorized();
 		}
 		$this->cache = Cache::for($user_id.'_queue');
-		$operations = JVB()->queue()->getUserOperations($user_id, [
+		$operations = Queue::getInstance()->getUserOperations($user_id, [
 			'state' => 'completed',
 			'outcome' => ['failed', 'failed_permanent', 'partial'],
 			'has_errors' => true,
@@ -265,7 +253,7 @@
 		}
 		$this->cache = Cache::for($userId.'_queue');
 
-		$op = JVB()->queue()->get($id);
+		$op = Queue::getInstance()->get($id);
 
 		if (!$op || $op->userId !== $userId) {
 			return Response::notFound('Operation not found');
@@ -282,7 +270,7 @@
 	{
 		return array_merge(
 			['queued' => 0, 'localProcessing' => 0, 'uploading' => 0],
-			JVB()->queue()->getUserStats($userId)
+			Queue::getInstance()->getUserStats($userId)
 		);
 	}
 
@@ -390,7 +378,7 @@
 
 	private function processAction(string $action, array $operations, int $userId): array
 	{
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		$processed = 0;
 		$processedIds = [];
 
diff --git a/inc/rest/routes/ReferralRoutes.php b/inc/rest/routes/ReferralRoutes.php
index e977513..9071c20 100644
--- a/inc/rest/routes/ReferralRoutes.php
+++ b/inc/rest/routes/ReferralRoutes.php
@@ -4,12 +4,14 @@
 use JVBase\base\Site;
 use JVBase\importers\JaneAppClientImporter;
 use JVBase\importers\JaneAppSalesImporter;
-use JVBase\managers\CustomTable;
+use JVBase\Instance;
+use JVBase\managers\Cache;
+use JVBase\managers\queue\Queue;
+use JVBase\managers\Referrals;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
-use WP_Error;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -20,13 +22,13 @@
  */
 class ReferralRoutes extends Rest
 {
+	use Instance;
 	public function __construct()
 	{
 		$this->cacheName = 'referrals';
 		$this->cacheTtl = (int)HOUR_IN_SECONDS;
 		parent::__construct();
-
-		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+		$this->cache = Cache::for($this->cacheName, 'custom', $this->cacheTtl)->user();
 	}
 
 	public function registerRoutes(): void
@@ -35,7 +37,7 @@
 		Route::for('referrals')
 			->get([$this, 'getReferrals'])
 			->args([
-				'status' => 'string|enum:all,pending,consulted,treated,unused,registered,completed|default:all',
+				'status' => 'string|enum:all,invited,pending,consulted,treated,cancelled|default:all',
 				'date_start' => 'string',
 				'date_end' => 'string',
 				'limit' => 'integer|default:50',
@@ -109,31 +111,12 @@
 			return $this->getAllReferrals($request);
 		}
 
-		// Build cache key
-		$args = [
-			'status' => $request->get_param('status') ?? 'all',
-			'limit' => $request->get_param('limit') ?? 50,
-			'offset' => $request->get_param('offset') ?? 0,
-			'date_start' => $request->get_param('date_start'),
-			'date_end' => $request->get_param('date_end'),
-		];
-		$cache_key = "user_{$user_id}_" . $this->cache->generateKey($args);
-
-		// Check 304 Not Modified
-		$cache_check = $this->checkHeaders($request, $cache_key);
-		if ($cache_check instanceof WP_REST_Response) {
-			return $cache_check;
-		}
+		$args = $this->buildArgs($request);
 
 		// Get referrals from manager
-		$referrals = JVB()->referrals()->getUserReferrals($user_id, $args);
+		$referrals = Referrals::getInstance()->getUserReferrals($user_id, $args);
 
-		$data = [
-			'items' => $referrals,
-			'total' => count($referrals)
-		];
-
-		$response = $this->success($data);
+		$response = $this->success($referrals);
 		return $this->addCacheHeaders($response);
 	}
 
@@ -165,17 +148,8 @@
 			return $this->unauthorized();
 		}
 
-		//Additional check to not send too many emails in an hour
-		$transient_key = "referral_invite_limit_{$user}";
-		$recent_invites = get_transient($transient_key) ?: 0;
-
-		if ($recent_invites >= 20) { // Max 5 batch invites per hour
-			return $this->error('Too many invitations sent. Please try again later.', 'rate_limit', 429);
-		}
-		set_transient($transient_key, $recent_invites + 1, HOUR_IN_SECONDS);
-
-		$subject = sanitize_text_field($request->get_param('subject'));
-		$message = sanitize_textarea_field($request->get_param('message'));
+		$subject = sanitize_text_field($request->get_param('subject')??'');
+		$message = sanitize_textarea_field($request->get_param('message')??'');
 		$invitations = $request->get_param('invite');
 
 		// Validate and sanitize invitations
@@ -194,7 +168,7 @@
 		}
 
 		$operationID = sanitize_text_field($request->get_param('id'));
-		JVB()->queue()->queueOperation(
+		Queue::getInstance()->queueOperation(
 			'referral_invite',
 			$user,
 			[
@@ -222,13 +196,12 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$result = JVB()->referrals()->updateStatus($referral_id, $status);
+		[$success, $message] = Referrals::getInstance()->updateStatus($referral_id, $status);
 
-		if (is_wp_error($result)) {
-			$code = $result->get_error_code();
-			return $code === 'not_found'
-				? $this->notFound($result->get_error_message())
-				: $this->error($result->get_error_message(), $code, 500);
+		if (!$success) {
+			return $message === 'Referral not found'
+				? $this->notFound($message)
+				: $this->error($message);
 		}
 
 		$this->cache->flush();
@@ -245,20 +218,12 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$result = JVB()->referrals()->removeReferral($referral_id, get_current_user_id());
+		$result = Referrals::getInstance()->removeReferral($referral_id, get_current_user_id());
 
-		if (is_wp_error($result)) {
-			$code = $result->get_error_code();
-			$status = match($code) {
-				'not_found'      => 404,
-				'unauthorized'   => 403,
-				'invalid_status' => 400,
-				default          => 500
-			};
-			return $this->error($result->get_error_message(), $code, $status);
+		if (!$result['success']) {
+			return $this->error($result['message']);
 		}
 
-		$this->cache->flush();
 		return $this->success(['message' => 'Referral removed']);
 	}
 
@@ -272,16 +237,10 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$result = JVB()->referrals()->resendInvitation($referral_id, get_current_user_id());
+		$result = Referrals::getInstance()->resendInvitation($referral_id, get_current_user_id());
 
-		if (is_wp_error($result)) {
-			$code = $result->get_error_code();
-			$status = match($code) {
-				'not_found'  => 404,
-				'rate_limit' => 429,
-				default      => 500
-			};
-			return $this->error($result->get_error_message(), $code, $status);
+		if (!$result['success']) {
+			return $this->error($result['message']);
 		}
 
 		return $this->success(['message' => 'Invitation resent']);
@@ -299,7 +258,7 @@
 			return $this->unauthorized();
 		}
 
-		$code = JVB()->referrals()->getUserReferralCode($user_id);
+		$code = Referrals::getInstance()->getUserReferralCode($user_id);
 
 		if (is_wp_error($code)) {
 			return $this->error($code->get_error_message(), 'code_error', 400);
@@ -317,13 +276,13 @@
 	 */
 	public function validateCode(WP_REST_Request $request): WP_REST_Response
 	{
-		$code = strtoupper(sanitize_text_field($request->get_param('code')));
+		$code = strtoupper(sanitize_text_field($request->get_param('code')??''));
 
 		if (empty($code)) {
 			return $this->error('Code required', 'missing_code', 400);
 		}
 
-		$referrer = JVB()->referrals()->getUserByReferralCode($code);
+		$referrer = Referrals::getInstance()->getUserByReferralCode($code);
 
 		if (!$referrer) {
 			return $this->error('Invalid referral code', 'invalid_code', 404);
@@ -331,7 +290,7 @@
 
 		// Check self-referral
 		if (is_user_logged_in() && get_current_user_id() === $referrer->ID) {
-			return $this->error('Cannot use your own referral code', 'self_referral', 400);
+			return $this->error('Cannot use your own referral code', 'self_referral');
 		}
 
 		return $this->success([
@@ -351,18 +310,10 @@
 		if (!$user_id) {
 			return $this->unauthorized();
 		}
-		$cache_key = "stats_{$user_id}";
 
-		// Check 304 Not Modified
-		$cache_check = $this->checkHeaders($request, $cache_key);
-		if ($cache_check instanceof WP_REST_Response) {
-			return $cache_check;
-		}
+		$stats = Referrals::getInstance()->getUserStats($user_id);
 
-		$stats = JVB()->referrals()->getUserStats($user_id);
-
-		$response = $this->success(['items' => [$stats]]);
-		return $this->addCacheHeaders($response);
+		return $this->success(['items' => [$stats]]);
 	}
 
 	/**
@@ -370,7 +321,7 @@
 	 */
 	public function getSettings(WP_REST_Request $request): WP_REST_Response
 	{
-		$settings = JVB()->referrals()->getRewardSettings();
+		$settings = Referrals::getInstance()->getRewardSettings();
 		return $this->success(['settings' => $settings]);
 	}
 
@@ -400,57 +351,34 @@
 	/**
 	 * Helper: Get all referrals (admin only)
 	 */
+	protected function buildArgs(WP_REST_Request $request):array
+	{
+		$params = $request->get_params();
+		$args = [
+			'limit'		=> absint($params['limit']??20),
+			'offset'	=> absint($params['offset']??0)
+		];
+		if (array_key_exists('status', $params) && $params['status'] !== 'all') {
+			$args['status'] = sanitize_text_field($params['status']);
+		}
+		if (array_key_exists('search', $params)) {
+			$args['search'] = sanitize_text_field($params['search']);
+		}
+		if (array_key_exists('date_start', $params) && array_key_exists('date_end', $params)) {
+			$args['created_at'] = ['BETWEEN', [sanitize_text_field($params['date_start']), sanitize_text_field($params['date_end'])]];
+		}
+		return $args;
+	}
 	protected function getAllReferrals(WP_REST_Request $request): WP_REST_Response
 	{
-		$items = JVB()->referrals()->getAllReferrals([
-			'status'     => $request->get_param('status') ?? 'all',
-			'search'     => $request->get_param('search'),
-			'date_start' => $request->get_param('date_start'),
-			'date_end'   => $request->get_param('date_end'),
-			'limit'      => $request->get_param('limit') ?? 50,
-			'offset'     => $request->get_param('offset') ?? 0,
-		]);
+		$args = $this->buildArgs($request);
+		$items = Referrals::getInstance()->getReferrals($args);
 
-		return $this->success(['items' => $items, 'total' => count($items)]);
+		return $this->success($items);
 	}
 
 
 	/**
-	 * Process queued referral operations
-	 */
-	public function processOperation(WP_Error|array $result, object $operation, array $data): array|WP_Error
-	{
-		if ($operation->type !== 'referral_invite') {
-			return $result;
-		}
-
-		$result = JVB()->referrals()->sendBatchReferralInvitations(
-			$operation->user_id,
-			$data['invitations'],
-			$data['subject'],
-			$data['message']
-		);
-
-		if ($result['success']) {
-			$this->cache->flush();
-		}
-
-		return [
-			'success' => true,
-			'message' => sprintf(
-				'Sent invitations. Success: %d. Failed: %d.',
-				count($result['result']['success']),
-				count($result['result']['failed'])
-			),
-			'details' => [
-				'successful' => $result['result']['success'],
-				'failed' => $result['result']['failed'],
-				'total' => count($data['invitations'])
-			]
-		];
-	}
-
-	/**
 	 * Handle client CSV upload
 	 */
 	public function handleClientUpload(WP_REST_Request $request): WP_REST_Response
@@ -482,7 +410,7 @@
 
 		// Import using JaneAppClientImporter
 		$importer = new JaneAppClientImporter();
-		$default_role = get_option(BASE . 'referral_role', Site::getDefaultReferralRole());
+		$default_role = get_option(BASE . 'referral_role', Site::getReferralRole());
 
 		$options = [
 			'update_existing' => true,
diff --git a/inc/rest/routes/ResponseRoutes.php b/inc/rest/routes/ResponseRoutes.php
index e84c254..0e8589d 100644
--- a/inc/rest/routes/ResponseRoutes.php
+++ b/inc/rest/routes/ResponseRoutes.php
@@ -1,7 +1,12 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\JVB;
+use JVBase\managers\Cache;
+use JVBase\managers\NotificationManager;
+use JVBase\managers\Notifications\Notifications;
+use JVBase\managers\Responses;
 use JVBase\rest\Rest;
 use JVBase\managers\CustomTable;
 use WP_REST_Request;
@@ -19,6 +24,7 @@
  */
 class ResponseRoutes extends Rest
 {
+	use Instance;
 	protected int $perPage = 20;
 	protected CustomTable $table;
 	protected CustomTable $karmaTable;
@@ -28,12 +34,8 @@
 		$this->cacheName = 'responses';
 		$this->cacheTtl = 1800; // 30 minutes
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName, 'response', $this->cacheTtl);
 
-		$this->table = CustomTable::for('responses');
-		$this->karmaTable = CustomTable::for('karma_response');
-
-		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
-		add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
 	}
 
 	public function registerRoutes(): void
@@ -57,38 +59,15 @@
 	 */
 	public function getResponses(WP_REST_Request $request): WP_REST_Response
 	{
-		$item_id = (int) $request->get_param('item_id');
-
-		if (!$item_id) {
+		$params = $request->get_params();
+		if (!array_key_exists('item_id', $params)) {
 			return $this->error('Missing item ID', 'missing_item_id');
 		}
+		$item_id = absint($params['item_id']);
 
-		// Build query args
 		$args = $this->buildQueryArgs($request);
-		$cacheKey = $this->cache->generateKey(array_merge(['item_id' => $item_id], $args));
 
-		// Check headers for 304 Not Modified
-		$headerCheck = $this->checkHeaders($request, $cacheKey);
-		if ($headerCheck) {
-			return $headerCheck;
-		}
-
-		// Check cache
-		$cached = $this->cache->get($cacheKey);
-		if ($cached) {
-			return $this->success($cached);
-		}
-
-		// Get responses
-		$responses = $this->getItemResponse($item_id, $args);
-
-		if (is_wp_error($responses)) {
-			return $this->notFound($responses->get_error_message());
-		}
-
-		// Cache and return
-		$this->cache->set($cacheKey, $responses);
-		return $this->success($responses);
+		return $this->success(Responses::getInstance()->getAllItemResponses($item_id, $args));
 	}
 
 	/**
@@ -103,7 +82,6 @@
 			return $this->unauthorized();
 		}
 
-		$operation_id = $data['id'] ?? uniqid('response_');
 		$action = sanitize_text_field($data['action'] ?? '');
 
 		if (!in_array($action, ['create', 'delete', 'update'])) {
@@ -116,39 +94,26 @@
 				return $this->error('Missing required fields', 'missing_fields');
 			}
 		}
-
-		// Prepare data for queue
-		$queue_data = match($action) {
-			'create' => [
-				'item_id' => (int) $data['item_id'],
-				'parent_id' => isset($data['parent_id']) ? (int) $data['parent_id'] : null,
-				'response' => wp_kses_post($data['response']),
-				'content' => sanitize_text_field($data['content'])
-			],
-			'update' => [
-				'response_id' => (int) $data['response_id'],
-				'response' => isset($data['response']) ? wp_kses_post($data['response']) : null,
-				'status' => isset($data['status']) && in_array($data['status'], ['published', 'hidden', 'flagged'])
-					? $data['status']
-					: null
-			],
-			'delete' => [
-				'response_id' => (int) $data['response_id']
-			]
+		$responses = Responses::getInstance();
+		$response = match($action) {
+			'create'	=> $responses->respondTo(
+				absint($data['item_id']),
+				sanitize_text_field($data['content']),
+				$user_id,
+				wp_kses_post($data['response']),
+				isset($data['parent_id']) ? absint($data['parent_id']) : null
+			),
+			'update'	=> $responses->editResponse(
+				absint($data['response_id']),
+				wp_kses_post($data['response']??'')
+			),
+			'delete'	=> $responses->deleteResponse(absint($data['response_id']))
 		};
 
-		// Queue the operation
-		JVB()->queue()->queueOperation(
-			$action . '_response',
-			$user_id,
-			$queue_data,
-			[
-				'operation_id' => 'u' . $user_id . '_' . $operation_id,
-				'priority' => 'high'
-			]
-		);
-
-		return $this->queued($operation_id);
+		if ($response['success']) {
+			return $this->success($response);
+		}
+		return $this->error($response['message']);
 	}
 
 	/**
@@ -156,244 +121,11 @@
 	 */
 	protected function getItemResponse(int $item_id, array $args = []): array|WP_Error
 	{
-		$defaults = [
-			'page' => 1,
-			'per_page' => $this->perPage,
-			'orderby' => 'created_at',
-			'order' => 'DESC',
-			'parent_id' => null
-		];
-
-		$args = wp_parse_args($args, $defaults);
-
-		// Verify post exists
-		$post = get_post($item_id);
-		if (!$post) {
-			return new WP_Error('not_found', 'Item not found');
-		}
-
-		// Build query with karma calculations
-		$query = "
-            SELECT r.*,
-                COALESCE((SELECT COUNT(*) FROM {$this->table->getFullTableName()} WHERE parent_id = r.id), 0) as reply_count,
-                COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'up'), 0) as upvotes,
-                COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'down'), 0) as downvotes
-            FROM {$this->table->getFullTableName()} r
-            WHERE r.item_id = %d
-        ";
-
-		$query_args = [$item_id];
-
-		// Filter by parent_id
-		if (isset($args['parent_id'])) {
-			$query .= " AND r.parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
-			if ($args['parent_id'] !== null) {
-				$query_args[] = $args['parent_id'];
-			}
-		}
-
-		// Get total count for pagination
-		$count_query = str_replace(
-			['SELECT r.*,', 'COALESCE((SELECT COUNT(*) FROM', 'as reply_count,', 'as upvotes,', 'as downvotes'],
-			['SELECT COUNT(*)', '', '', '', ''],
-			$query
-		);
-		$count_query = preg_replace('/COALESCE\([^)]+\)[^,]*,?/', '', $count_query);
-		$total_items = (int) $this->table->queryVar($count_query, $query_args);
-
-		// Add ordering
-		$order_column = in_array($args['orderby'], ['created_at', 'updated_at'])
-			? "r.{$args['orderby']}"
-			: $args['orderby'];
-
-		$query .= " ORDER BY {$order_column} {$args['order']}";
-
-		// Add pagination
-		$query .= " LIMIT %d OFFSET %d";
-		$query_args[] = $args['per_page'];
-		$query_args[] = ($args['page'] - 1) * $args['per_page'];
-
-		// Get responses
-		$responses = $this->table->queryResults($query, $query_args);
-
+		$responses = Responses::getInstance()->getAllItemResponses($item_id, $args);
 		// Format responses
-		$items = array_map([$this, 'formatItem'], $responses);
+		$responses['items'] = array_map([$this, 'formatItem'], $responses);
 
-		return [
-			'items' => $items,
-			'has_more' => $args['page'] < ceil($total_items / $args['per_page']),
-			'total_items' => $total_items,
-			'total_pages' => (int) ceil($total_items / $args['per_page'])
-		];
-	}
-
-	/**
-	 * Process queue operations
-	 */
-	public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
-	{
-		if (!in_array($operation->type, ['create_response', 'update_response', 'delete_response'])) {
-			return $result;
-		}
-
-		return match($operation->type) {
-			'create_response' => $this->createResponse($operation, $data),
-			'update_response' => $this->updateResponse($data),
-			'delete_response' => $this->deleteResponse($data),
-			default => $result
-		};
-	}
-
-	/**
-	 * Create a new response
-	 */
-	protected function createResponse(object $operation, array $data): array
-	{
-		$response_id = $this->table->insert([
-			'item_id' => $data['item_id'],
-			'content' => $data['content'],
-			'user_id' => $operation->user_id,
-			'parent_id' => $data['parent_id'],
-			'response' => $data['response'],
-			'status' => 'published'
-		]);
-
-		if (!$response_id) {
-			$this->logError('Failed to insert response', [
-				'data' => $data,
-				'error' => $this->table->getLastError()
-			]);
-
-			return ['success' => false, 'result' => 'Failed to create response'];
-		}
-
-		// Send notifications
-		$this->sendNotifications($data['item_id'], $data['parent_id'], $response_id, $operation->user_id);
-
-		// Clear cache
-		$this->cache->flush();
-
-		return ['success' => true, 'result' => $response_id];
-	}
-
-	/**
-	 * Update a response
-	 */
-	protected function updateResponse(array $data): array
-	{
-		$update_data = [];
-
-		if (isset($data['response'])) {
-			$update_data['response'] = $data['response'];
-			$update_data['updated_at'] = current_time('mysql');
-		}
-
-		if (isset($data['status'])) {
-			$update_data['status'] = $data['status'];
-		}
-
-		if (empty($update_data)) {
-			return ['success' => false, 'result' => 'No fields to update'];
-		}
-
-		$updated = $this->table->update(
-			$update_data,
-			['id' => $data['response_id']]
-		);
-
-		if ($updated === false) {
-			$this->logError('Failed to update response', [
-				'data' => $data,
-				'error' => $this->table->getLastError()
-			]);
-
-			return ['success' => false, 'result' => 'Failed to update response'];
-		}
-
-		$this->cache->flush();
-		return ['success' => true, 'result' => $updated];
-	}
-
-	/**
-	 * Delete a response (or mark as deleted if it has replies)
-	 */
-	protected function deleteResponse(array $data): array
-	{
-		$response = $this->table->get(['id' => $data['response_id']]);
-
-		if (!$response) {
-			return ['success' => false, 'result' => 'Response not found'];
-		}
-
-		// Check if it has replies
-		$has_replies = $this->table->where(['parent_id' => $data['response_id']])->countResults() > 0;
-
-		if ($has_replies) {
-			// Don't delete, just mark as deleted
-			$updated = $this->table->update(
-				[
-					'response' => '[ deleted ]',
-					'status' => 'deleted',
-					'updated_at' => current_time('mysql')
-				],
-				['id' => $data['response_id']]
-			);
-
-			$this->cache->flush();
-			return ['success' => true, 'result' => $updated];
-		}
-
-		// No replies, safe to delete
-		$deleted = $this->table->delete(['id' => $data['response_id']]);
-
-		if ($deleted === false) {
-			$this->logError('Failed to delete response', [
-				'data' => $data,
-				'error' => $this->table->getLastError()
-			]);
-
-			return ['success' => false, 'result' => 'Failed to delete response'];
-		}
-
-		$this->cache->flush();
-		return ['success' => true, 'result' => $deleted];
-	}
-
-	/**
-	 * Send notifications for new responses
-	 */
-	protected function sendNotifications(int $item_id, ?int $parent_id, int $response_id, int $user_id): void
-	{
-		// Notify post author
-		$post = get_post($item_id);
-		if ($post && $post->post_author != $user_id) {
-			JVB()->notification()->addNotification(
-				$post->post_author,
-				'new_response',
-				[
-					'message' => 'Someone responded to your post',
-					'item_id' => $item_id,
-					'response_id' => $response_id
-				]
-			);
-		}
-
-		// Notify parent response author if this is a reply
-		if ($parent_id) {
-			$parent = $this->table->get(['id' => $parent_id]);
-
-			if ($parent && $parent->user_id != $user_id) {
-				JVB()->notification()->addNotification(
-					$parent->user_id,
-					'response_reply',
-					[
-						'message' => 'Someone replied to your response',
-						'item_id' => $item_id,
-						'response_id' => $response_id
-					]
-				);
-			}
-		}
+		return $responses;
 	}
 
 	/**
@@ -420,7 +152,7 @@
 		// Ordering
 		if ($request->has_param('orderby')) {
 			$orderby = $request->get_param('orderby');
-			$valid = ['created_at', 'updated_at', 'upvotes', 'downvotes'];
+			$valid = ['created_at', 'updated_at', 'upvotes', 'downvotes', 'karmic_score'];
 
 			if (in_array($orderby, $valid)) {
 				$args['orderby'] = $orderby;
@@ -438,74 +170,26 @@
 	}
 
 	/**
-	 * Get child responses recursively
-	 */
-	protected function getChildren(int $item_id, int $parent_id): array
-	{
-		return $this->getItemResponse($item_id, ['parent_id' => $parent_id]);
-	}
-
-	/**
 	 * Format response for API output
 	 */
-	protected function formatItem(object $response): array
+	protected function formatItem(array $response): array
 	{
-		$formatted = [
-			'id' => (int) $response->id,
-			'item_id' => (int) $response->item_id,
-			'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
-			'response' => $response->response,
-			'status' => $response->status,
-			'created_at' => $response->created_at,
-			'updated_at' => $response->updated_at,
-			'children' => $this->getChildren($response->item_id, $response->id),
-			'upvotes' => (int) ($response->upvotes ?? 0),
-			'downvotes' => (int) ($response->downvotes ?? 0),
-			'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0),
-			'reply_count' => (int) ($response->reply_count ?? 0)
-		];
-
 		// Handle deleted users
-		if ($response->is_user_deleted) {
-			$formatted['user'] = [
+		if ($response['is_user_deleted']) {
+			$response['user'] = [
 				'id' => null,
 				'name' => '[deleted user]',
 				'is_deleted' => true
 			];
 		} else {
-			// Add artist info
-			$artist = jvbContentFromUser($response->user_id);
-			if (!empty($artist)) {
-				$formatted['artist'] = [
-					'name' => $artist['name'],
-					'url' => $artist['url'],
-					'shop' => $artist['shop'] ?? null
-				];
-			}
+			$user = get_userdata($response['user_id']);
+			$response['user'] = [
+				'id'	=> $response['user_id'],
+				'name'	=> $user->display_name,
+				'url'	=> jvbUserProfileLink($response['user_id']),
+			];
 		}
 
-		return $formatted;
-	}
-
-	/**
-	 * Handle user deletion by anonymizing responses
-	 */
-	public function handleUserDeletion(int $user_id): void
-	{
-		$updated = $this->table->update(
-			[
-				'is_user_deleted' => 1,
-				'updated_at' => current_time('mysql')
-			],
-			['user_id' => $user_id]
-		);
-
-		$this->logError(
-			'Anonymized responses for deleted user',
-			['user_id' => $user_id, 'count' => $updated],
-			'info'
-		);
-
-		$this->cache->flush();
+		return $response;
 	}
 }
diff --git a/inc/rest/routes/SEORoutes.php b/inc/rest/routes/SEORoutes.php
index 786452a..4b03d3e 100644
--- a/inc/rest/routes/SEORoutes.php
+++ b/inc/rest/routes/SEORoutes.php
@@ -1,15 +1,15 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\base\SchemaHelper;
+use JVBase\Instance;
+use JVBase\managers\Cache;
+use JVBase\managers\SEO\render\SchemaOutput;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
-use JVBase\managers\Cache;
-use JVBase\managers\SEO\ConfigManager;
-use JVBase\managers\SEO\SchemaBuilder;
 use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
-use WP_Error;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -23,12 +23,14 @@
  */
 class SEORoutes extends Rest
 {
+	use Instance;
 //	protected SchemaBuilder $registry;
 
 	public function __construct()
 	{
 		$this->cacheName = 'schema';
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName, null, $this->cacheTtl);
 //		$this->registry = SchemaBuilder::getInstance();
 	}
 
@@ -106,7 +108,7 @@
 	{
 		$errors = [];
 
-		$config = ConfigManager::for($context);
+		$config = SchemaHelper::getInstance()::for($context);
 		$result = $config->updateConfig($data);
 
 		if (is_wp_error($result)) {
@@ -131,7 +133,7 @@
 	 */
 	protected function saveTypeSettings(string $type, array $data): WP_REST_Response
 	{
-		$config = ConfigManager::for($type);
+		$config = SchemaHelper::getInstance();
 
 		// Separate meta and schema data if needed
 		$meta = $data['meta'] ?? [];
@@ -141,23 +143,17 @@
 
 		// Save meta configuration
 		if (!empty($meta)) {
-			$metaResult = $config->updateMeta($meta);
-			if (is_wp_error($metaResult)) {
-				$errors['meta'] = [
-					'message' => $metaResult->get_error_message(),
-					'errors' => $metaResult->get_error_data()
-				];
+			$metaResult =  $config->updateMeta($type, $meta);
+			if (!$metaResult) {
+				$errors[] = 'meta';
 			}
 		}
 
 		// Save schema configuration
 		if (!empty($schema)) {
-			$schemaResult = $config->updateConfig($schema);
-			if (is_wp_error($schemaResult)) {
-				$errors['schema'] = [
-					'message' => $schemaResult->get_error_message(),
-					'errors' => $schemaResult->get_error_data()
-				];
+			$schemaResult = $config->updateSchema($type, $schema);
+			if (!$schemaResult) {
+				$errors[] = 'schema';
 			}
 		}
 
@@ -179,23 +175,11 @@
 		$context = $request->get_param('context');
 		$data = $request->get_json_params();
 
-		$resetMeta = $data['resetMeta'] ?? false;
-		$resetSchema = $data['resetSchema'] ?? false;
-
-		$config = ConfigManager::for($context);
-
-		// Reset based on what was requested
-		if ($resetMeta) {
-			$config->resetMeta();
-		}
-
-		if ($resetSchema) {
-			$config->resetConfig();
-		}
-
-		if (!$resetMeta && !$resetSchema) {
-			// Default: reset everything
-			$config->resetAll();
+		$type = $data['type']??false;
+		$format = $data['resetMeta'] ? 'meta' : ($data['resetSchema'] ? 'schema' : ($data['resetArchive'] ? 'archive' : false));
+		$hasIt = $type && $format;
+		if ($hasIt) {
+			SchemaHelper::reset($type, $format);
 		}
 
 		// Invalidate cache
@@ -204,8 +188,7 @@
 		return $this->success([
 			'status'	=> 'completed',
 			'message' => 'Reset to defaults successfully',
-			'meta' => $config->meta(),
-			'schema' => $config->schema()
+			'result' => $hasIt ? SchemaHelper::getConfig($type, $format) : [],
 		]);
 	}
 
@@ -215,25 +198,42 @@
 	protected function previewSchema(WP_REST_Request $request): WP_REST_Response
 	{
 		$data = $request->get_json_params();
-		$schemaType = $data['type'] ?? 'Thing';
-
-		// Get field definitions from registry
-		$fieldDefinitions = $this->registry->getMetaConfigForType($schemaType);
+		//Whether it is the Schema, Meta, Archive, or Reference schema
+		$format = $data['format']??false;
+		if (!$format) {
+			return $this->error('Required field "Format" missing');
+		}
+		//WebSite, Organization, or any registered post type/taxonomy
+		$type = $data['type']??false;
+		if (!$type) {
+			return $this->error('Required field "Type" missing');
+		}
+		$class = $data['class']??false;
+		if (!$class) {
+			return $this->error('Required "Schema Class" field missing (Ex: Thing, LocalBusiness, etc)');
+		}
 
 		// Build schema with actual form values
 		$schema = [
-			'@type' => $schemaType,
-			'@id'   => get_home_url() . '/#' . strtolower($schemaType),
+			'@type' => $class,
 		];
 
+		$theClass = SchemaHelper::classFromConfig($schema);
+
 		// Add fields with their actual values
 		foreach ($data['fields'] ?? [] as $fieldName => $value) {
 			if (empty($value)) {
 				continue;
 			}
 
-			$schema[$fieldName] = $value;
+			$method = 'set'.ucfirst($fieldName);
+			if (!method_exists($theClass, $method)) {
+				error_log('Attempted field not found in class '.$class.': '.$fieldName);
+				continue;
+			}
+			$theClass->$method($value);
 		}
+		$schema = $theClass->outputSchema();
 
 		return $this->success([
 			'schema' => $schema
@@ -243,11 +243,14 @@
 	/**
 	 * Get fields for a schema type
 	 * Used for dynamic type switching
+	 * TODO
 	 */
 	public function getFields(WP_REST_Request $request): WP_REST_Response
 	{
 		$type = $request->get_param('type');
 
+		$class = SchemaHelper::classFromConfig(['type' => $type]);
+
 		// Get Meta field definitions from registry
 		$fields = $this->registry->getMetaConfigForType($type);
 
diff --git a/inc/rest/routes/SettingsRoutes.php b/inc/rest/routes/SettingsRoutes.php
index 66d1068..6d2adf4 100644
--- a/inc/rest/routes/SettingsRoutes.php
+++ b/inc/rest/routes/SettingsRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
 use JVBase\JVB;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
@@ -18,11 +19,13 @@
 }
 class SettingsRoutes extends Rest
 {
+	use Instance;
     protected int $count;
     public function __construct()
     {
         $this->cacheName = 'user_settings';
         parent::__construct();
+		$this->cache = Cache::for($this->cacheName, null, $this->cacheTtl);
 //        $this->action = 'dash-';
 //        $this->operation_type = 'user_settings';
         $this->count = 1;
@@ -84,7 +87,7 @@
             $data['notification_preferences'] = $add;
         }
 
-        JVB()->queue()->queueOperation(
+        Queue::getInstance()->queueOperation(
             'user_settings',
             $user_id,
             $data,
diff --git a/inc/rest/routes/ShopRoutes.php b/inc/rest/routes/ShopRoutes.php
index 780b253..26b9dfa 100644
--- a/inc/rest/routes/ShopRoutes.php
+++ b/inc/rest/routes/ShopRoutes.php
@@ -2,6 +2,7 @@
 namespace JVBase\rest\routes;
 
 use JVBase\JVB;
+use JVBase\managers\Emails;
 use JVBase\managers\ImageGenerator;
 use JVBase\managers\UploadManager;
 use JVBase\registrar\Registrar;
@@ -104,7 +105,7 @@
             ]);
         }
 
-        $queue = JVB()->queue();
+        $queue = Queue::getInstance();
         unset($data['user']);
         $operationID = $data['id'];
         unset($data['id']);
@@ -239,7 +240,7 @@
                 }
             }
         }
-        $queue = JVB()->queue();
+        $queue = Queue::getInstance();
         $queue->queueOperation(
             'shop_image',
             $user_id,
@@ -261,7 +262,7 @@
             return $result;
         }
         $data['imageType'] = 'shop';
-        $fileGenerator = new UploadManager('shop', $operation->user_id);
+        $fileGenerator = UploadManager::getInstance(); //('shop', $operation->user_id);
         $generator = new ImageGenerator($data, $fileGenerator);
         $result = $generator->generate();
         if ($result['success']) {
@@ -544,7 +545,7 @@
 
         // Notify each manager
         foreach ($owners as $owner) {
-            JVB()->notification()->addNotification(
+            NotificationManager::getInstance()->addNotification(
                 $owner,
                 'artist_request',
                 [
@@ -680,7 +681,7 @@
 
         // If user exists, send notification through the system
         if ($existing_user_id) {
-            JVB()->notification()->addNotification(
+            NotificationManager::getInstance()->addNotification(
                 $existing_user_id,
                 'artist_invitation',
                 $manager_id,
@@ -737,7 +738,7 @@
             esc_url($invitation_url)
         );
 
-        return JVB()->email()->sendEmail($email, $subject, $message);
+        return Emails::getInstance()->sendEmail($email, $subject, $message);
     }
 
     /**
@@ -801,7 +802,7 @@
         // If invitation was for an existing user, notify them
         $user_id = email_exists($invitation->email);
         if ($user_id) {
-            JVB()->notification()->addNotification(
+            NotificationManager::getInstance()->addNotification(
                 $user_id,
                 'invitation_revoked',
                 $manager_id,
@@ -869,7 +870,7 @@
 
         // Notify the artist
         $notification_type = $status === 'accepted' ? 'shop_accepted' : 'shop_rejected';
-        JVB()->notification()->addNotification(
+        NotificationManager::getInstance()->addNotification(
             $request->user_id,
             $notification_type,
             [
@@ -990,7 +991,7 @@
             $inviters = json_decode($invitation['inviters'], true);
             if (!empty($inviters)) {
                 foreach ($inviters as $inviter_id) {
-                    JVB()->notification()->addNotification(
+                    NotificationManager::getInstance()->addNotification(
                         $inviter_id,
                         'invitation_accepted',
                         [
@@ -1060,7 +1061,7 @@
             $inviters = json_decode($invitation['inviters'], true);
             if (!empty($inviters)) {
                 foreach ($inviters as $inviter_id) {
-                    JVB()->notification()->addNotification(
+                    NotificationManager::getInstance()->addNotification(
                         $inviter_id,
                         'invitation_declined',
                         [
@@ -1226,7 +1227,7 @@
 
             foreach ($owners as $owner_id) {
                 if (!empty($owner_id)) {
-                    JVB()->notification()->addNotification(
+                    NotificationManager::getInstance()->addNotification(
                         $owner_id,
                         'artist_left',
                         $user_id,
diff --git a/inc/rest/routes/TermRoutes.php b/inc/rest/routes/TermRoutes.php
index 8954448..8178cad 100644
--- a/inc/rest/routes/TermRoutes.php
+++ b/inc/rest/routes/TermRoutes.php
@@ -1,6 +1,9 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\Instance;
+use JVBase\managers\Cache;
+use JVBase\managers\TaxonomyRelationships;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\managers\UserTermsManager;
@@ -15,6 +18,7 @@
 }
 class TermRoutes extends Rest
 {
+	use Instance;
     protected object $term_index_manager;
     protected int $per_page;
 
@@ -22,14 +26,14 @@
     {
         $this->cacheName = 'terms';
         parent::__construct();
+		$this->cache = Cache::for('terms', 'term');
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
-		$this->cache->connect('taxonomy', true);
+
         $this->per_page = 20;
 
         add_action('edited_term', [$this, 'deleteTermPath']);
-        add_action('wp_login', [$this, 'clearUserTaxonomyCache'], 10, 2);
     }
 
     /**
@@ -88,7 +92,7 @@
 
         // Add relationship data if requested
         if ($request->get_param('include_relationships')) {
-            $relationship_manager = JVB()->termRelationships();
+            $relationship_manager = TaxonomyRelationships::getInstance();
             $related_taxonomies = $request->get_param('related_taxonomies') ?: ['jvb_style', 'jvb_theme', 'jvb_city'];
 
             $data['relationships'] = [];
@@ -122,15 +126,9 @@
 	public function getTermDetails(WP_REST_Request $request): WP_REST_Response
 	{
 		$data = $request->get_params();
-		$taxonomies = array_keys($data);
-
-		$cache_check = $this->checkHeaders($request, $this->cache->generateKey(['termDetails' => $data]));
-		if ($cache_check) {
-			return $cache_check;
-		}
 
 		$terms = $this->cache->remember(
-			$this->cache->generateKey(['termDetails' => $data]),
+			['termDetails' => $data],
 			function() use ($data) {
 				$result = [];
 				foreach ($data as $tax => $IDs) {
@@ -144,7 +142,7 @@
 			}
 		);
 
-		return $this->addCacheHeaders($this->success(['items' => $terms]));
+		return $this->success(['items' => $terms]);
 	}
 
     /**
@@ -156,11 +154,8 @@
     {
 		$data = $request->get_params();
 		$taxonomy = sanitize_text_field($data['taxonomy'])??'';
-		// Check HTTP cache headers
-		$cache_check = $this->checkHeaders($request, $taxonomy);
-		if ($cache_check) {
-			error_log('Header Check failed');
-			return $cache_check;
+		if (empty($taxonomy)) {
+			return $this->success(['message' => 'No taxonomy selected']);
 		}
 
 		// Handle batch request (multiple taxonomies)
@@ -175,41 +170,40 @@
 				'include'	=> $data['termIDs'],
 				'hide_empty'	=> true,
 			];
-			$key = $this->cache->generateKey($args);
-			$cached = $this->cache->get($key);
-			if ($cached) {
-				$response = $this->success($cached);
-				return $this->addCacheHeaders($response);
-			}
+			$data = $this->cache->remember(
+				$args,
+				function() use ($args, $taxonomy) {
+					return $this->formatTerms($args, $taxonomy);
+				}
+			);
 
-			$formatted = $this->formatTerms($args, $taxonomy);
 			$response = [
-				'items'	=> $formatted
+				'items'	=> $data
 			];
-			$this->cache->set($key, $response);
-			$response = $this->success($response);
-			return $this->addCacheHeaders($response);
+
+			return $this->success($response);
 		}
 		if (array_key_exists('content', $data)) {
 			// If content_type is provided, use the specialized endpoint
 			$content_type = $request->get_param('content');
 			$registrar = Registrar::getInstance($content_type);
 			if ($registrar->hasFeature('is_content')) {
-				$response = $this->getTermsForContentType($request);
-				return $this->addCacheHeaders($response);
+				//TODO: Not sure what we're trying for here. Doesn't seem to be used.
+				//TODO: Likely for taxonomy content types, like tattoo shops
+				return $this->emptyResult();
 			}
 		}
 
-        $taxonomy = BASE.$request->get_param('taxonomy');
+        $taxonomy = jvbCheckBase($request->get_param('taxonomy'));
+		if (!taxonomy_exists($taxonomy)) {
+			return $this->emptyResult();
+		}
         $search = $request->get_param('search');
 
 		$parent = (int)$data['parent']??0;
 		$page = max(1, (int)($data['page']??1));
 		$per_page = 25;
 
-        if (!taxonomy_exists($taxonomy)) {
-			return $this->emptyResult();
-        }
 
         $tax_obj = get_taxonomy($taxonomy);
         $is_hierarchical = $tax_obj->hierarchical;
@@ -217,8 +211,8 @@
         // If searching, handle differently
         if (!empty($search)) {
 			error_log('Handling search...');
-            $response = $this->handleTermSearch($request);
-			return $this->addCacheHeaders($response);
+
+			return $this->handleTermSearch($request);
         }
 
         // Get terms for current level with child count
@@ -237,12 +231,11 @@
 
             $main_context = json_decode($request->get_param('main_context'), true);
             $userID = get_post_meta($main_context['id'], BASE.'profile_link', true);
-            $manager = new UserTermsManager();
+            $manager = UserTermsManager::getInstance();
             $related = $manager->fetchUserTerms($userID, $taxonomy);
 
             if (empty($related)) {
-                $response = $this->emptyResult();
-				return $this->addCacheHeaders($response);
+				return $this->emptyResult();
             }
 
             $args['include'] = $related;
@@ -251,12 +244,11 @@
             $main_context = json_decode($request->get_param('main_context'), true);
             $thisTaxonomy = str_replace('taxonomy:', '', $main_context['context']);
             $ID = (int)$main_context['id'];
-            $manager = JVB()->termRelationships();
+            $manager = TaxonomyRelationships::getInstance();
             $related = $manager->getRelatedTerms($ID, BASE.$request->get_param('taxonomy'));
 
             if (empty($related)) {
-                $response = $this->emptyResult();
-				return $this->addCacheHeaders($response);
+				return $this->emptyResult();
             }
             $args['tax_query'] = [
                 'taxonomy'  => $taxonomy,
@@ -269,7 +261,7 @@
             $match = $request->get_param('match') ?? 'any';
             $context = json_decode($request['context'], true);
 
-            $relationshipManager = JVB()->termRelationships();
+            $relationshipManager = TaxonomyRelationships::getInstance();
             // Prepare array to collect term IDs that match the context
             $related_term_ids = [];
 
@@ -305,49 +297,40 @@
                 $args['include'] = $related_term_ids;
             } else {
                 // No related terms found, return empty result
-                $response = $this->emptyResult();
-
-				return $this->addCacheHeaders($response);
+				return $this->emptyResult();
             }
         }
 
+		$response = $this->cache->remember(
+			$args,
+			function() use ($args, $taxonomy, $parent, $per_page, $page, $is_hierarchical) {
+				$formatted_terms = $this->formatTerms($args, $taxonomy);
+				// Get total count for pagination
+				$total_terms = wp_count_terms([
+					'taxonomy' => $taxonomy,
+					'hide_empty' => false,
+					'parent' => $parent
+				]);
 
+				// Calculate pagination
+				$total_pages = ceil($total_terms / $per_page);
+				$has_more = $page < $total_pages;
 
-        $key = $this->cache->generateKey($args);
-        $cache = $this->cache->get($key);
+				return [
+					'items' => $formatted_terms,
+					'is_hierarchical' => $is_hierarchical,
+					'pagination' => [
+						'page' => $page,
+						'per_page' => $per_page,
+						'total_pages' => $total_pages,
+						'total_terms' => (int)$total_terms
+					],
+					'has_more' => $has_more
+				];
+			}
+		);
 
-        if ($cache) {
-            $response = $this->success($cache);
-			return $this->addCacheHeaders($response);
-        }
-
-        $formatted_terms = $this->formatTerms($args, $taxonomy);
-        // Get total count for pagination
-        $total_terms = wp_count_terms([
-            'taxonomy' => $taxonomy,
-            'hide_empty' => false,
-            'parent' => $parent
-        ]);
-
-        // Calculate pagination
-        $total_pages = ceil($total_terms / $per_page);
-        $has_more = $page < $total_pages;
-
-        $response = [
-            'items' => $formatted_terms,
-            'is_hierarchical' => $is_hierarchical,
-            'pagination' => [
-                'page' => $page,
-                'per_page' => $per_page,
-                'total_pages' => $total_pages,
-                'total_terms' => (int)$total_terms
-            ],
-			'has_more' => $has_more
-        ];
-
-        $this->cache->set($key, $response);
-        $response = $this->success($response);
-		return $this->addCacheHeaders($response);
+		return $this->success($response);
     }
 
 	protected function handleBatchTermRequest(string $taxonomy, array $data, WP_REST_Request $request):WP_REST_Response
@@ -384,8 +367,7 @@
 			'has_more' => true,
 		];
 
-		$response = $this->success($response);
-		return $this->addCacheHeaders($response);
+		return $this->success($response);
 	}
 
 
@@ -398,7 +380,7 @@
 	protected function formatTerms(array $args, string $taxonomy): array
 	{
 		return $this->cache->remember(
-			$this->cache->generateKey($args),
+			$args,
 			function() use ($args, $taxonomy) {
 				$terms = get_terms($args);
 
@@ -408,7 +390,7 @@
 
 				$formatted_terms = [];
 				foreach ($terms as $term) {
-					$formatted_terms[] = $this->formatSingleTerm($term, $taxonomy, true);
+					$formatted_terms[] = $this->formatSingleTerm($term, $taxonomy);
 				}
 
 				return $formatted_terms;
@@ -478,7 +460,7 @@
 		];
 
 		$data = $this->cache->remember(
-			$this->cache->generateKey($args),
+			$args,
 			function() use ($args, $taxonomy, $page, $per_page) {
 				$terms = get_terms($args);
 
@@ -508,7 +490,7 @@
 			}
 		);
 
-		return $this->addCacheHeaders($this->success($data));
+		return $this->success($data);
 	}
 
     /**
@@ -550,167 +532,6 @@
         delete_term_meta($termID, BASE.'term_path');
     }
 
-    /**
-     * @param string $username
-     * @param object $user
-     *
-     * @return void
-     */
-    public function clearUserTaxonomyCache(string $username, object $user):void
-    {
-        global $wpdb;
-
-        // Get all transients for this user
-        $like_pattern = $wpdb->esc_like(BASE.'user_' . $user->ID) . '_%';
-
-        $transients = $wpdb->get_col($wpdb->prepare("
-            SELECT option_name
-            FROM {$wpdb->options}
-            WHERE option_name LIKE %s
-            AND option_name LIKE %s
-        ", '_transient_' . $like_pattern, '%'));
-
-        // Delete all found transients
-        foreach ($transients as $transient) {
-            delete_transient(str_replace('_transient_', '', $transient));
-        }
-    }
-
-    /**
-     * @param WP_REST_Request $request
-     *
-     * @return WP_REST_Response
-     */
-    public function getTermsForContentType(WP_REST_Request $request):WP_REST_Response
-    {
-        $manager = JVB()->termRelationships();
-        $content_type = BASE . $request->get_param('content');
-        $taxonomy = BASE . $request->get_param('taxonomy');
-        $search = $request->get_param('search');
-        $page = max(1, intval($request->get_param('page')));
-        $per_page = $request->get_param('per_page') ?: 20;
-
-        // Create cache key
-        $cache_key = "terms_for_{$content_type}_{$taxonomy}_" . md5("{$search}_{$page}_{$per_page}");
-
-        $cache_key = $this->cache->generateKey([
-            'terms_for'     => $content_type,
-            'taxonomy'      => $taxonomy,
-            'search'        => $search,
-            'page'          => $page,
-            'per_page'      => $per_page
-        ]);
-        $cache = $this->cache->get($cache_key);
-        // Try cache first
-        if ($cache !== false) {
-			$response = $this->success($cache);
-            return $this->addCacheHeaders($response);
-        }
-
-        try {
-            global $wpdb;
-
-            // Starting the query building
-            $query_args = [];
-            $terms_query = "
-                SELECT DISTINCT t.term_id, t.name, t.slug, tt.count, tt.taxonomy,
-                       (SELECT COUNT(DISTINCT tm.meta_key) FROM {$wpdb->termmeta} tm WHERE tm.term_id = t.term_id) AS meta_count
-                FROM {$wpdb->terms} t
-                JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
-                JOIN {$manager->getTableName()} tr ON (
-                    (tr.primary_taxonomy = %s AND tr.primary_term_id = t.term_id)
-                    OR (tr.related_taxonomy = %s AND tr.related_term_id = t.term_id)
-                )
-            ";
-
-            $query_args[] = $taxonomy;
-            $query_args[] = $taxonomy;
-
-            // Add content type filter through posts
-            $query_where = " WHERE tt.taxonomy = %s";
-            $query_args[] = $taxonomy;
-
-            // Add search condition if provided
-            if ($search) {
-                $query_where .= " AND (t.name LIKE %s OR t.slug LIKE %s)";
-                $search_like = '%' . $wpdb->esc_like($search) . '%';
-                $query_args[] = $search_like;
-                $query_args[] = $search_like;
-            }
-
-            // Add relationship strength ordering
-            $query_order = " GROUP BY t.term_id";
-            $query_order .= " ORDER BY COUNT(tr.id) DESC, tt.count DESC";
-
-            // Add pagination
-            $query_limit = " LIMIT %d OFFSET %d";
-            $query_args[] = $per_page;
-            $query_args[] = ($page - 1) * $per_page;
-
-            // Combine the query
-            $final_query = $query_where . $query_order . $query_limit;
-
-            // Get terms
-            $terms = $wpdb->get_results(
-                $wpdb->prepare(
-                    $terms_query . $final_query,
-                    $query_args
-                )
-            );
-            // Get total count for pagination
-            $count_query = str_replace($query_limit, '', $query_where);
-            $count_query = "SELECT COUNT(DISTINCT t.term_id) FROM {$wpdb->terms} t
-                           JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
-                           JOIN {$manager->getTableName()} tr ON (
-                               (tr.primary_taxonomy = %s AND tr.primary_term_id = t.term_id)
-                               OR (tr.related_taxonomy = %s AND tr.related_term_id = t.term_id)
-                           ) " . $count_query;
-
-            // Remove pagination args
-            array_pop($query_args);
-            array_pop($query_args);
-
-            $total = $wpdb->get_var(
-                $wpdb->prepare(
-                    $count_query,
-                    $query_args
-                )
-            );
-
-            // Format terms
-            $formatted_terms = [];
-            $is_hierarchical = is_taxonomy_hierarchical($taxonomy);
-
-			foreach ($terms as $term) {
-				$formatted = $this->formatSingleTerm($term, $taxonomy, false);
-				// Add relationship strength which is unique to this method
-				$formatted['relationship_strength'] = $term->relationship_count ?? 0;
-				$formatted_terms[] = $formatted;
-			}
-
-            // Build response
-            $total_pages = ceil($total / $per_page);
-            $results = [
-                'items' => $formatted_terms,
-                'pagination'    => [
-                    'page'      => (int)$page,
-                    'per_page'  => (int)$per_page,
-                    'total_terms'=> $total,
-                    'total_pages'=> $total_pages
-                ],
-				'has_more'  => $page < $total_pages
-            ];
-
-            // Cache results
-            $this->cache->set($cache_key, $results);
-			$response = $this->success($results);
-            return $this->addCacheHeaders($response);
-
-        } catch (Exception $e) {
-			return $this->error('Error getting terms for content: '.$e->getMessage(), 'get_terms_for_content');
-        }
-    }
-
 
     /**
      * @param WP_REST_Request $request
diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index a0e8afa..0187f75 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -1,9 +1,9 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\managers\queue\executors\UploadExecutor;
-use JVBase\managers\queue\mergers\UploadMerger;
-use JVBase\managers\queue\TypeConfig;
+use JVBase\Instance;
+use JVBase\managers\Errors;
+use JVBase\managers\queue\Queue;
 use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
@@ -12,7 +12,6 @@
 use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
-use WP_Error;
 use Exception;
 
 if (!defined('ABSPATH')) {
@@ -20,71 +19,15 @@
 }
 class UploadRoutes extends Rest
 {
+	use Instance;
 
     public function __construct()
     {
         parent::__construct();
 
-		add_action('init', [$this, 'registerUploadExecutors'], 5);
     }
 
-	/**
-	 * Register upload operation types with the queue's TypeRegistry
-	 */
-	public function registerUploadExecutors(): void
-	{
-		$registry = JVB()->queue()->registry();
-		$executor = new UploadExecutor();
-		$merger = new UploadMerger('secured_files');
 
-		// Image uploads - chunked at 5 files
-		$registry->register('image_upload', new TypeConfig(
-			mergeable: $merger,
-			executor: $executor,
-			chunkKey: 'secured_files',
-			chunkSize: 3
-		));
-
-		// Video uploads - one at a time (heavy processing)
-		$registry->register('video_upload', new TypeConfig(
-			mergeable: $merger,
-			executor: $executor,
-			chunkKey: 'secured_files',
-			chunkSize: 1
-		));
-
-		// Document uploads - chunked at 10
-		$registry->register('document_upload', new TypeConfig(
-			mergeable: $merger,
-			executor: $executor,
-			chunkKey: 'secured_files',
-			chunkSize: 5
-		));
-
-		// Metadata updates
-		$registry->register('update_image_meta', new TypeConfig(
-			executor: $executor
-		));
-
-		// Cleanup - chunked at 5
-		$registry->register('temporary_cleanup', new TypeConfig(
-			executor: $executor,
-			chunkKey: 'files',
-			chunkSize: 5
-		));
-
-		// Attach to content (depends on upload completing)
-		$registry->register('attach_upload_to_content', new TypeConfig(
-			executor: $executor
-		));
-
-		// Process upload groups into posts
-		$registry->register('process_upload_groups', new TypeConfig(
-			executor: $executor,
-			chunkKey: 'posts',
-			chunkSize: 5
-		));
-	}
 
     /**
      * Registers upload routes
@@ -305,7 +248,7 @@
 
 		} catch (Exception $e) {
 			// Error handling...
-			JVB()->error()->log(
+			Errors::getInstance()->log(
 				'[UploadRoutes]:handleUploadRequest',
 				$e->getMessage(),
 				[
@@ -323,7 +266,7 @@
 	 */
 	protected function secureFiles(array $files, array $args): array
 	{
-		$uploader = new UploadManager();
+		$uploader = UploadManager::getInstance();
 		$secured_files = [];
 		$errors = [];
 
@@ -411,7 +354,7 @@
 
 		error_log('Queueing Operation: '.print_r($operation_type, true));
 		error_log('With ID: '.print_r($args['upload'], true));
-		$queuedProcessing = JVB()->queue()->queueOperation(
+		$queuedProcessing = Queue::getInstance()->queueOperation(
 			$operation_type,
 			get_current_user_id(),
 			array_merge(
@@ -435,7 +378,7 @@
 			// When merged, the original upload's attach_upload_to_content
 			// will handle all files after the merged image_upload completes.
 			if (!$queuedProcessing['updated_existing']) {
-				JVB()->queue()->queueOperation(
+				Queue::getInstance()->queueOperation(
 					'attach_upload_to_content',
 					get_current_user_id(),
 					$args,
@@ -448,21 +391,6 @@
 			}
 		}
 
-		JVB()->queue()->queueOperation(
-			'temporary_cleanup',
-			get_current_user_id(),
-			[
-				'files'     => $secured_data['files'],
-				'context'   => $args,
-			],
-			[
-				'priority'      => 'low',
-				'chunk_size'    => 5,
-				'chunk_key'     => 'files',
-				'depends_on'    => [$uploadOpId]
-			]
-		);
-
 		return $args['id'];
 	}
 
@@ -480,145 +408,8 @@
 		};
 	}
 
-	/**
-	 * Step 3: Process operation from queue
-	 * Called by OperationQueue
-	 * @param WP_Error|array $result
-	 * @param object $operation
-	 * @param array $data
-	 *
-	 * @return array|WP_Error
-	 */
-	public function processOperation(array $result, object $operation, array $data): array
-	{
-		// Only handle our operation types
-		$handled_types = [
-			'image_upload',
-			'video_upload',
-			'document_upload',
-			'update_metadata',
-			'temporary_cleanup',
-			'attach_upload_to_content',
-			'process_upload_groups'
-		];
 
-		if (!in_array($operation->type, $handled_types)) {
-			return $result; // Not our operation, pass through
-		}
 
-		try {
-			// Route to appropriate handler
-			$handler_result = match($operation->type) {
-				'image_upload' => $this->processImageUpload($operation, $data),
-				'video_upload' => $this->processVideoUpload($operation, $data),
-				'document_upload' => $this->processDocumentUpload($operation, $data),
-				'update_metadata' => $this->processUploadMeta($result, $operation, $data),
-				'temporary_cleanup' => $this->processTemporaryCleanup($result, $operation, $data),
-				'attach_upload_to_content' => $this->processAttachToContent($operation, $data),
-				'process_upload_groups'	=> $this->processUploadGroups($result, $operation, $data),
-				default => new WP_Error('unknown_type', 'Unknown operation type')
-			};
-
-			// Handle WP_Error
-			if (is_wp_error($handler_result)) {
-				return [
-					'success' => false,
-					'result' => $handler_result->get_error_message()
-				];
-			}
-
-			return $handler_result;
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processOperation',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'operation_type' => $operation->type
-				]
-			);
-
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
-		}
-	}
-	/**
-	 * Standardize processing result
-	 */
-	protected function standardizeResult(array $result): array
-	{
-		return [
-			'attachment_id' => $result['attachment_id'],
-			'url' => $result['url'],
-			'file' => $result['file'],
-			'upload_id' => $result['upload_id'] ?? null
-		];
-	}
-
-	protected function processAttachToContent(object $operation, array $data): array
-	{
-		try {
-			// Get the results from the upload operation
-			$upload_results = JVB()->queue()->getOperationValue($data['upload'], 'result', true);
-
-			if (empty($upload_results)) {
-				throw new Exception('No upload results found for operation: ' . $data['upload']);
-			}
-
-			if (empty($data['post_id']) || str_starts_with((string)($data['item_id'] ?? ''), 'new')) {
-				foreach ($operation->dependencies as $depId) {
-					$dep = JVB()->queue()->get($depId);
-					if ($dep && $dep->type === 'content_update' && !empty($dep->result['new_posts'])) {
-						$itemId = $data['item_id'] ?? null;
-						if ($itemId && isset($dep->result['new_posts'][$itemId])) {
-							$data['post_id'] = $dep->result['new_posts'][$itemId];
-							break;
-						}
-					}
-				}
-
-				if (empty($data['post_id'])) {
-					throw new Exception('Could not resolve post_id from dependencies');
-				}
-			}
-
-			// Now attach to the specified content
-			if (!empty($data['field_name'])) {
-				$this->updateFieldValue($data, $upload_results);
-			}
-
-			return [
-				'success' => true,
-				'result' => 'Attachments linked to content'
-			];
-
-		} catch (Exception $e) {
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Cleanup temporary files after processing
-	 */
-	protected function cleanupTempFiles(array $secured_files, int $user_id): void
-	{
-		$uploader = new UploadManager();
-
-		foreach ($secured_files as $secured) {
-			if (!empty($secured['temp_path']) && file_exists($secured['temp_path'])) {
-				@unlink($secured['temp_path']);
-			}
-		}
-
-		// Clean up empty directories
-		$uploader->cleanupEmptyTempDirs($user_id);
-	}
 
 	/**
 	 * Update field value with new attachment IDs
@@ -650,159 +441,6 @@
 		$meta->set($data['field_name'], implode(',', $all_ids));
 	}
 
-	/**
-	 * Generic file upload processor - works for images, videos, documents
-	 */
-	protected function processFileUpload(object $operation, array $data, string $file_type): WP_Error|array
-	{
-		try {
-			$uploader = new UploadManager();
-			$processed_results = [];
-			$config = $this->getFieldConfig($data);
-
-			$args = $data;
-			unset($args['secured_files']);
-
-			foreach ($data['secured_files'] as $secured_file) {
-				$result = $uploader->processUpload(
-					$secured_file['temp_path'],
-					array_merge(
-						$config,
-						[
-							'file_type' => $file_type,
-							'user_id' => $operation->user_id,
-							'post_id' => (int)($data['post_id'] ?? 0),
-							'term_id' => (int)($data['term_id'] ?? 0),
-							'original_name' => $secured_file['original_name'],
-							'mime_type' => $secured_file['mime_type'],
-							'content' => $data['content'] ?? '',
-						]
-					)
-				);
-
-				if (!is_wp_error($result)) {
-					$standardized = $this->standardizeResult($result);
-
-					// Use embedded upload_id from the secured file itself
-					$standardized['upload_id'] = $secured_file['upload_id'] ?? null;
-
-					if ($standardized['upload_id']) {
-						$processed_results[$standardized['upload_id']] = $standardized;
-					} else {
-						$processed_results[] = $standardized;
-					}
-
-					// Apply frontend metadata if provided
-					if (!empty($secured_file['metadata'])) {
-						$this->applyMeta($standardized['attachment_id'], $secured_file['metadata']);
-					}
-				}
-			}
-
-			$this->handleUploadDestination($data, $processed_results);
-
-			// Cleanup temporary files
-			$this->cleanupTempFiles($data['secured_files'], $operation->user_id);
-
-			return [
-				'success' => true,
-				'result' => $processed_results
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processFileUpload',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'file_type' => $file_type,
-					'user_id' => $operation->user_id
-				]
-			);
-			return [
-				'success'   => false,
-				'result'    => $e->getMessage()
-			];
-		}
-	}
-
-	/**
-	 * Process image uploads
-	 */
-	protected function processImageUpload(object $operation, array $data): WP_Error|array
-	{
-		return $this->processFileUpload($operation, $data, 'image');
-	}
-
-	/**
-	 * Process video uploads
-	 */
-	protected function processVideoUpload(object $operation, array $data): WP_Error|array
-	{
-		return $this->processFileUpload($operation, $data, 'video');
-	}
-
-	/**
-	 * Process document uploads
-	 */
-	protected function processDocumentUpload(object $operation, array $data): WP_Error|array
-	{
-		return $this->processFileUpload($operation, $data, 'document');
-	}
-
-	/**
-	 * Process temporary cleanup operation
-	 * Called by OperationQueue for 'temporary_cleanup' operations
-	 *
-	 * @param array $result
-	 * @param object $operation
-	 * @param array $data
-	 * @return array
-	 */
-	protected function processTemporaryCleanup(array $result, object $operation, array $data): array
-	{
-		try {
-			// Cleanup temporary files if they exist
-			if (!empty($data['secured_files'])) {
-				$this->cleanupTempFiles($data['secured_files'], $operation->user_id);
-			}
-
-			// If specific temp paths provided
-			if (!empty($data['temp_paths']) && is_array($data['temp_paths'])) {
-				foreach ($data['temp_paths'] as $temp_path) {
-					if (file_exists($temp_path)) {
-						@unlink($temp_path);
-					}
-				}
-			}
-
-			// Cleanup empty temp directories for this user
-			if (!empty($operation->user_id)) {
-				$uploader = new UploadManager();
-				$uploader->cleanupEmptyTempDirs($operation->user_id);
-			}
-
-			return [
-				'success' => true,
-				'result' => 'Temporary files cleaned up successfully'
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processTemporaryCleanup',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'user_id' => $operation->user_id
-				]
-			);
-
-			return [
-				'success' => false,
-				'result' => $e->getMessage()
-			];
-		}
-	}
 
 	/**
 	 * Handle metadata update requests
@@ -836,6 +474,8 @@
 			}
 
 
+
+
 			return $this->sendResponse(
 				false,
 				['error_code' => 'missing_identifiers'],
@@ -843,7 +483,7 @@
 			);
 
 		} catch (Exception $e) {
-			JVB()->error()->log(
+			Errors::getInstance()->log(
 				'[UploadRoutes]:handleMetadataUpdate',
 				$e->getMessage(),
 				[
@@ -904,7 +544,7 @@
 	 */
 	protected function queueMetaUpdate(array $data, int $user, ?string $operationId = null): WP_REST_Response
 	{
-		$queue = JVB()->queue();
+		$queue = Queue::getInstance();
 		$depends_on = [];
 		$errors = [];
 		$original = count($data);
@@ -936,76 +576,7 @@
 		);
 	}
 
-	/**
-	 * Process metadata update operation (called by queue processor)
-	 */
-	public function processUploadMeta(WP_Error|array $result, object $operation, array $data): WP_Error|array
-	{
-		try {
-			if (!is_array($operation->depends_on)) {
-				$operation->depends_on = [$operation->depends_on];
-			}
-			$updated_count = 0;
-			$errors = [];
-			foreach ($operation->depends_on as $dependency) {
-				$operationData = JVB()->queue()->getOperation($dependency);
-				if (!$operationData || $operationData->state !== 'completed') {
-					throw new Exception('Original upload operation not found or not completed');
-				}
 
-				$uploadResults = json_decode($operationData->result, true);
-				if (!$uploadResults || !$uploadResults['success']) {
-					throw new Exception('Original upload operation failed');
-				}
-				$attachmentsToUpdate = array_filter($data, function ($item) use ($dependency) {
-					return $item['depends_on'] === $dependency;
-				});
-
-				$uploadIDToAttachment = $this->buildUploadToAttachmentMapping(
-					array_keys($attachmentsToUpdate),
-					$uploadResults['data']
-				);
-
-				if (empty($uploadIDToAttachment)) {
-					throw new Exception('No valid upload ID to attachment ID mappings found');
-				}
-
-				foreach ($uploadIDToAttachment as $uploadId => $attachmentId) {
-					try {
-						$this->applyMeta($attachmentId, $attachmentsToUpdate[$uploadId]);
-						$updated_count++;
-					} catch (Exception $e) {
-						$errors[] = "Upload {$uploadId} (Attachment {$attachmentId}): " . $e->getMessage();
-					}
-				}
-			}
-
-			return [
-				'success' => $updated_count > 0,
-				'result'	=> [
-					'updated_count' => $updated_count,
-					'mappings' => $uploadIDToAttachment,
-					'errors' => $errors,
-					'message' => "Applied metadata to {$updated_count} attachment(s)"
-				]
-			];
-
-		} catch (Exception $e) {
-			JVB()->error()->log(
-				'[UploadRoutes]:processUploadMeta',
-				$e->getMessage(),
-				[
-					'operation_id' => $operation->id,
-					'original_operation_id' => $data['original_operation_id'] ?? 'unknown'
-				]
-			);
-
-			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
-			];
-		}
-	}
 
 	protected function applyMeta(int $attachment_id, array $metadata): void
 	{
@@ -1031,31 +602,9 @@
 	}
 
 	/**
-	 * Build mapping from frontend upload IDs to WordPress attachment IDs
-	 */
-	protected function buildUploadToAttachmentMapping(array $upload_ids, array $results): array
-	{
-		$mapping = [];
-
-		foreach ($results as $result) {
-			if (!isset($result['upload_id']) || !isset($result['attachment_id'])) {
-				continue;
-			}
-
-			$upload_id = $result['upload_id'];
-			$attachment_id = $result['attachment_id'];
-
-			// Validate that this upload_id was requested
-			if (in_array($upload_id, $upload_ids)) {
-				$mapping[$upload_id] = $attachment_id;
-			}
-		}
-
-		return $mapping;
-	}
-
-	/**
-	 * Verify user has access to attachment
+	 * @param int $attachment_id
+	 * @param int $user_id
+	 * @return bool
 	 */
 	protected function verifyAttachmentAccess(int $attachment_id, int $user_id): bool
 	{
@@ -1070,59 +619,9 @@
 	}
 
 	/**
-	 * Get field configuration for upload processing
-	 */
-	protected function getFieldConfig(array $data): array
-	{
-		static $config_cache = [];
-
-		$cache_key = md5(json_encode([
-			$args['content'] ?? '',
-			$args['field_name'] ?? ''
-		]));
-
-		if (isset($config_cache[$cache_key])) {
-			return $config_cache[$cache_key];
-		}
-
-		$config = [
-			'allowed_types' => null,
-			'max_size' => null,
-			'convert' => 'webp',
-			'quality' => 80,
-			'create_thumbnails' => true,
-		];
-
-		// Get field definition from registry if available
-		if (!empty($args['content']) && !empty($args['field_name'])) {
-			$content_type = $args['content'];
-			$field_name = $args['field_name'];
-			$registrar = Registrar::getInstance($content_type);
-			if ($registrar) {
-				$content_fields = $registrar->getFields();
-				if (array_key_exists($field_name, $content_fields)) {
-					$field_def = $content_fields[$field_name];
-
-					// Extract relevant config from field definition
-					$config = array_merge($config, [
-						'allowed_types' => $field_def['accepted_types'] ?? null,
-						'max_size' => $field_def['max_size'] ?? null,
-						'convert' => $field_def['convert'] ?? 'webp',
-						'quality' => $field_def['quality'] ?? 80,
-						'create_thumbnails' => $field_def['create_thumbnails'] ?? true,
-					]);
-				}
-			}
-		}
-
-		$config_cache[$cache_key] = $config;
-		return $config;
-	}
-
-
-
-	/**
 	 * Get files info for error logging
+	 * @param array $files
+	 * @return array
 	 */
 	protected function getFilesInfo(array $files): array
 	{
@@ -1181,7 +680,7 @@
 				$chunkSize = 10;
 			}
 
-			JVB()->queue()->queueOperation(
+			Queue::getInstance()->queueOperation(
 				$operation_type,
 				$args['user'],
 				array_merge(
@@ -1195,7 +694,7 @@
 				]
 			);
 
-			$ID = JVB()->queue()->queueOperation(
+			$ID = Queue::getInstance()->queueOperation(
 				'process_upload_groups',
 				$args['user'],
 				$args,
diff --git a/inc/rest/routes/UserRoutes.php b/inc/rest/routes/UserRoutes.php
index b3f3cb1..4ecacfe 100644
--- a/inc/rest/routes/UserRoutes.php
+++ b/inc/rest/routes/UserRoutes.php
@@ -1,5 +1,7 @@
 <?php
 
+use JVBase\Instance;
+use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
@@ -16,12 +18,14 @@
  */
 class UserRoutes extends Rest
 {
+	use Instance;
 	public function __construct()
 	{
 		$this->cacheName = 'user';
 		$this->cacheTtl = WEEK_IN_SECONDS;
 
 		parent::__construct();
+		$this->cache = Cache::for($this->cacheName, 'user', $this->cacheTtl);
 
 	}
 
@@ -50,6 +54,7 @@
 		try {
 			$meta = Meta::forUser($user_id);
 			$meta->setAll($data['changes']);
+			Cache::clearUser($user_id);
 			return $this->success();
 		} catch (Exception $e) {
 			return $this->error($e->getMessage());
diff --git a/inc/rest/routes/VoteRoutes.php b/inc/rest/routes/VoteRoutes.php
index 528cc2d..eafe526 100644
--- a/inc/rest/routes/VoteRoutes.php
+++ b/inc/rest/routes/VoteRoutes.php
@@ -1,7 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\managers\CustomTable;
+use JVBase\Instance;
 use JVBase\managers\KarmaManager;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Response;
@@ -9,14 +9,13 @@
 use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
-use WP_Error;
-use Exception;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
 class VoteRoutes extends Rest
 {
+	use Instance;
     public function __construct()
     {
         $this->cacheName = 'karma';
@@ -77,7 +76,7 @@
 		}
 
 
-		$man = KarmaManager::getInstance($type);
+		$man = $registrar->getKarmaManager();
 		if (!$man) {
 			return Response::validationError(['message' => __('Karma not set up', 'jvb')]);
 		}
@@ -104,15 +103,6 @@
 			return $this->unauthorized();
 		}
 
-		$cache = $this->cache->get($user);
-		if ($cache) {
-			return Response::success($cache);
-		}
-
-		$votes = KarmaManager::getUserVotes($user);
-
-		$this->cache->set($user, $votes);
-
-		return Response::success($votes);
+		return Response::success(KarmaManager::getUserVotes($user));
 	}
 }
diff --git a/inc/rest/routes/index.php b/inc/rest/routes/index.php
index 4f2541d..6220032 100644
--- a/inc/rest/routes/index.php
+++ b/inc/rest/routes/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/inc/ui/Checkout.php b/inc/ui/Checkout.php
index 82c96a6..3eb26fd 100644
--- a/inc/ui/Checkout.php
+++ b/inc/ui/Checkout.php
@@ -3,6 +3,7 @@
 
 use JVBase\base\Site;
 use JVBase\integrations\Integrations;
+use JVBase\integrations\ServiceMap;
 use JVBase\meta\Form;
 use JVBase\meta\Meta;
 
@@ -29,13 +30,14 @@
  */
 class Checkout
 {
+	use ServiceMap;
     public static function getProvider():Integrations|false
     {
 		$int = ['square','helcim'];
 		foreach ($int as $i) {
 			if (Site::hasIntegration($i)) {
-				error_log('Site has '.$i);
-				return JVB()->connect($i);
+				$self = new self();
+				return $self->getService($i);
 			}
 		}
         return false;
@@ -70,7 +72,7 @@
 			$tabs->addTab('thelogin')
 				->title('Login')
 				->description('Login to see past orders and access your saved cards')
-				->content(JVB()->magicLink()->renderForm());
+				->content(MagicLinks::getInstance()->renderForm());
 		} else {
 			$tabs->addTab('account')
 				->title('Account')
diff --git a/inc/ui/_setup.php b/inc/ui/_setup.php
index 5e9a8bf..3d23b56 100644
--- a/inc/ui/_setup.php
+++ b/inc/ui/_setup.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 require(JVB_DIR.'/inc/ui/Modal.php');
 require(JVB_DIR.'/inc/ui/Navigation.php');
 require(JVB_DIR.'/inc/ui/Tabs.php');
diff --git a/inc/ui/index.php b/inc/ui/index.php
index 4f2541d..6220032 100644
--- a/inc/ui/index.php
+++ b/inc/ui/index.php
@@ -1,3 +1,2 @@
 <?php
-//Nothing to see here
-
+// Silence is golden.
diff --git a/inc/utility/helpers.php b/inc/utility/helpers.php
index b3d9bbc..ea6680d 100644
--- a/inc/utility/helpers.php
+++ b/inc/utility/helpers.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/inc/utility/setup.php b/inc/utility/setup.php
index 188786d..ee17a9c 100644
--- a/inc/utility/setup.php
+++ b/inc/utility/setup.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 require(JVB_DIR.'/inc/utility/helpers.php');
 require(JVB_DIR.'/inc/utility/Image.php');
 require(JVB_DIR.'/inc/utility/Video.php');
diff --git a/jvb.php b/jvb.php
index 47f2f35..6494ea5 100644
--- a/jvb.php
+++ b/jvb.php
@@ -9,17 +9,36 @@
 Textdomain: jvb
 */
 
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
+use JVBase\admin\SEOAdmin;
+use JVBase\base\SchemaHelper;
+use JVBase\blocks\CustomBlocks;
 use JVBase\JVB;
+use JVBase\managers\AdminPages;
+use JVBase\managers\Approvals;
+use JVBase\managers\Dashboard\Dashboard;
+use JVBase\managers\DirectoryManager;
+use JVBase\managers\Emails;
+use JVBase\managers\Favourites;
 use JVBase\managers\IconsManager;
+use JVBase\managers\InvitationsManager;
+use JVBase\managers\Login;
+use JVBase\managers\MagicLinks;
+use JVBase\managers\NotificationManager;
+use JVBase\managers\Notifications\Notifications;
+use JVBase\managers\queue\Queue;
+use JVBase\managers\Referrals;
+use JVBase\managers\Responses;
+use JVBase\managers\Roles;
+use JVBase\managers\SEO\render\SchemaOutput;
+use JVBase\managers\TaxonomyRelationships;
+use JVBase\managers\UserTermsManager;
 use JVBase\registrar\Registrar;
 
 use JVBase\base\Site;
 
-//security
-if (!defined('ABSPATH')) {
-    exit;
-}
-
 //add_action('admin_enqueue_scripts', function() {
 //	global $wp_scripts;
 //	add_action('admin_print_scripts', function() use ($wp_scripts) {
@@ -119,11 +138,13 @@
 define('JVB_LOCKOUT_DURATION', 15 * MINUTE_IN_SECONDS);
 
 
+require(JVB_DIR.'/inc/Instance.php');
 require(JVB_DIR.'/base/_setup.php');
 
 require(JVB_DIR.'/inc/utility/setup.php');
 require(JVB_DIR.'/checks.php');
 
+require(JVB_DIR . '/inc/integrations/ServiceMap.php');
 require(JVB_DIR . '/inc/registrar/_setup.php');
 require(JVB_DIR . '/activate.php');
 
@@ -212,7 +233,7 @@
         'display' => __('Every minute', 'jvb')
     ];
     $schedules[ 'monthly' ] = [
-        'interval' => 604800 * 4,
+        'interval' => MONTH_IN_SECONDS,
         'display' => __('Once Monthly', 'jvb')
     ];
 
@@ -223,20 +244,6 @@
 register_deactivation_hook(JVB_DIR.'/jvb.php', 'jvbDeactivatePlugin');
 
 
-
-
-
-
-
-add_action('init', 'jvbUserCheck');
-function jvbUserCheck():void
-{
-    if (is_admin() && isOurPeople()) {
-        wp_redirect(get_home_url(null, '/dash'));
-        exit;
-    }
-}
-
 //require(JVB_DIR . '/inc/users/UserSettings.php');
 
 
@@ -287,7 +294,61 @@
 
 function jvbLoadBase():void
 {
-	JVB::getInstance();
+	do_action('jvbloaded');
+	Emails::getInstance();
+	Queue::getInstance();
+	Roles::getInstance();
+	Login::getInstance();
+	SEOAdmin::getInstance();
+	SchemaOutput::getInstance();
+	SchemaHelper::getInstance();
+	CustomBlocks::getInstance();
+
+	AdminPages::getInstance();
+
+	if (Site::has('magic_link')) {
+		MagicLinks::getInstance();
+	}
+	if (Site::has('dashboard')) {
+		Dashboard::getInstance();
+	}
+	if (Site::has('referrals')) {
+		Referrals::getInstance();
+	}
+
+
+	if (Site::has('is_directory')) {
+		DirectoryManager::getInstance();
+	}
+
+	if (Site::has('feed_block')) {
+		TaxonomyRelationships::getInstance();
+	}
+
+	if (Site::hasAll(['dashboard', 'membership', 'feed_block'])) {
+		UserTermsManager::getInstance();
+	}
+
+	if (Site::has('favourites')) {
+		Favourites::getInstance();
+	}
+
+	if (!empty(Registrar::withFeature('has_responses'))) {
+		Responses::getInstance();
+	}
+
+	$membership = Site::membership();
+	if ($membership && $membership->has('notifications')) {
+		NotificationManager::getInstance();
+	}
+	if ($membership && $membership->has('invitable')) {
+		InvitationsManager::getInstance();
+	}
+
+	if (!empty(Registrar::withFeature('approve_new'))) {
+		Approvals::getInstance();
+	}
+	jvbLoadRoutes();
 }
 
 function jvb_integration_definitions():void
@@ -301,12 +362,92 @@
 	Registrar::maybeBuildSections();
 }
 
+add_action('wp_footer', 'jvbAdditionalActions');
+function jvbAdditionalActions():void
+{
+	if (Login::isLogin()) {
+		return;
+	}
+	$extras = apply_filters('jvbAdditionalActions', []);
+	$extras = array_filter($extras, function ($extra) {
+		return is_array($extra) && array_key_exists('button', $extra) && array_key_exists('content', $extra);
+	});
+	if (empty ($extras)) {
+		return;
+	}
+	$buttons = array_map(function ($extra) {
+		return $extra['button'];
+	}, $extras);
+	$contents = array_map(function($extra) {
+		return $extra['content'];
+	}, $extras);
+
+	if (!empty ($buttons)) {
+		?>
+		<section class="main-actions">
+			<div class="buttons col">
+				<?= implode('', $buttons); ?>
+			</div>
+			<div class="actions">
+				<?= implode('', $contents); ?>
+			</div>
+		</section>
+		<?php
+	}
+}
+
 
 function JVB(): JVB
 {
 	return JVB::getInstance();
 }
 
+use JVBase\rest\routes\AdminRoutes;
+use JVBase\rest\routes\ContentRoutes;
+use JVBase\rest\routes\ContentTermsRoutes;
+use JVBase\rest\routes\ErrorRoutes;
+use JVBase\rest\routes\FeedRoutes;
+use JVBase\rest\routes\FormRoutes;
+use JVBase\rest\routes\NewsRoutes;
+use JVBase\rest\routes\OptionsRoutes;
+use JVBase\rest\routes\SEORoutes;
+use JVBase\rest\routes\SettingsRoutes;
+use JVBase\rest\routes\TermRoutes;
+use JVBase\rest\routes\UploadRoutes;
+
+function jvbLoadRoutes():void
+{
+	SEORoutes::getInstance();
+	SettingsRoutes::getInstance();
+	UploadRoutes::getInstance();
+	if (!empty(apply_filters('jvb_register_forms', []))) {
+		FormRoutes::getInstance();
+	}
+	UserRoutes::getInstance();
+
+	if (Site::has('feed_block')) {
+		FeedRoutes::getInstance();
+	}
+
+	if (Site::has('feed_block') || Site::has('dashboard')) {
+		TermRoutes::getInstance();
+	}
+
+	if (Site::has('dashboard')) {
+		ErrorRoutes::getInstance();
+		AdminRoutes::getInstance();
+		ContentRoutes::getInstance();
+		ContentTermsRoutes::getInstance();
+		OptionsRoutes::getInstance();
+	}
+
+	$membership = Site::membership();
+	if ($membership && $membership->has('forum'))
+	{
+		NewsRoutes::getInstance();
+	}
+}
+
 
 
 
@@ -340,192 +481,7 @@
 }
 
 
-/**
- * Scripts
- */
-add_action('wp_enqueue_scripts', 'jvbScripts', 10);
 
-function jvbScripts():void
-{
-    add_action('wp_head', 'jvbInlineNavStyles');
-
-	if (Site::has('dashboard')) {
-		wp_enqueue_script('jvb-queue');
-	}
-
-	wp_enqueue_script('jvb-auth');
-	wp_enqueue_script('jvb-settings');
-    wp_enqueue_script('jvb-navigation');
-//    wp_enqueue_script('jvb-ui');
-//    wp_enqueue_script('jvb-media');
-	wp_enqueue_script('jvb-gallery');
-    wp_enqueue_script('jvb-cache');
-
-
-	$interactions = [];
-	if (Site::has('favourites')) {
-		$interactions[] = 'favourites';
-	}
-	if (!empty(Registrar::withFeature('karma'))) {
-		$interactions[] = 'karma';
-	}
-	if (Site::has('notifications')) {
-		$interactions[] = 'notifications';
-	}
-
-	if (!empty($interactions)) {
-		foreach($interactions as $interaction) {
-			wp_enqueue_script('jvb-'.$interaction);
-		}
-	}
-
-
-	$queue = [
-		'api' => rest_url('jvb/v1/'),
-		'redirect' => get_home_url(null, '/login/'),
-		'labels' => Registrar::getLabels(),
-	];
-
-    wp_localize_script('jvb-auth', 'jvbSettings', $queue);
-
-
-	$initUserSettings = 'async function initUserItems() {
-	if (!jvbSettings.currentUser) return;
-
-	window.userFavourites = {};
-	window.userVotes = {};
-	';
-	if (Site::has('favourites')) {
-		wp_enqueue_script('jvb-favourites');
-		$initUserSettings .= '
-		//Fetch user favourites
-		try {
-			const result = await window.jvbCache.fetchWithCache(
-				`${jvbSettings.api}favourites?all=true`,
-                {
-                    method: \'GET\',
-                    headers: {\'X-WP-Nonce\': jvbSettings.nonce}
-                }
-            );
-
-            if (result && result.favourites) {
-				for (const key in result.favourites) {
-					window.userFavourites[key] = new Set();
-					result.favourites[key].forEach(id => {
-						window.userFavourites[key].add(parseInt(id));
-					});
-                }
-            }
-        } catch (error) {
-			console.error(\'Failed to fetch user favourites:\', error);
-		}';
-	}
-
-	if (!empty(Registrar::withFeature('karma'))) {
-		wp_enqueue_script('jvb-votes');
-		$initUserSettings .= '// Fetch user votes
-        try {
-			const result = await window.jvbCache.fetchWithCache(
-				`${jvbSettings.api}vote?user=${jvbSettings.currentUser}`,
-                {
-                    method: \'GET\',
-                    headers: {\'X-WP-Nonce\': jvbSettings.nonce}
-                },
-                {forceRefresh: true}
-            );
-
-            window.userVotes = {};
-            if (result) {
-				for (const key in result) {
-					window.userVotes[key] = new Map();
-					for (const [id, vote] of Object.entries(result[key])) {
-						window.userVotes[key].set(parseInt(id), vote);
-					}
-                }
-            }
-        } catch (error) {
-			console.error(\'Failed to fetch user votes:\', error);
-		}';
-	}
-
-	$initUserSettings .= '} initUserItems();';
-
-//	wp_add_inline_script('jvb-queue', $initUserSettings, 'after');
-
-
-    if (is_search() ||
-        is_tax(BASE.'shop') ||
-        is_singular(BASE.'artist') ||
-        jvbIsDirectory()
-    ) {
-        wp_enqueue_script('jvb-page-nav');
-    }
-
-	if (has_block('jvb/summaryBlock')) {
-		wp_enqueue_script('jvb-page-nav');
-	}
-
-        // Only load on single shop pages or other relevant pages
-    if (is_tax(BASE.'shop') ||
-        is_singular(BASE.'partner')) {
-            $ID = get_queried_object_id();
-        if (is_tax()) {
-            $check = get_term_meta($ID, BASE.'has_map', true);
-        } else {
-            $check = get_post_meta($ID, BASE.'has_map', true);
-        }
-
-//        if ($check && $check !== '') {
-////            wp_enqueue_script(
-////                'google-maps-frontend',
-////                'https://maps.googleapis.com/maps/api/js?key=' . GOOGLE_API . '&callback=googleMapsLoaded',
-////                [],
-////                null,
-////                ['strategy' => 'async',
-////                 'in_footer' => true]
-////            );
-//
-//            // Add the callback function
-//            wp_add_inline_script('google-maps-frontend', '
-//            function googleMapsLoaded() {
-//                if (window.googleMapsCallbacks && window.googleMapsCallbacks.length) {
-//                    window.googleMapsCallbacks.forEach(callback => callback());
-//                }
-//            }
-//        ');
-//        }
-    }
-    if (is_user_logged_in() && Site::has('notifications')) {
-        wp_enqueue_script('jvb-notifications');
-
-        wp_localize_script('jvb-notifications', 'notificationSettings', array(
-            'homeUrl' => home_url(),
-            'strings' => array(
-                'loading' => 'Loading...',
-                'error' => 'Failed to load notifications',
-                'noNotifications' => 'No new notifications',
-                'viewAll' => 'View All Notifications',
-                'markRead' => 'Mark as read',
-                'review' => 'Review'
-            )
-        ));
-    }
-
-//    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-queue');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-selector');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-data-store');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-cache');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-a11y');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-utility');
-    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-gallery');
-//    jvbAddScriptDependency('jvb-feed-view-script', 'jvb-loading');
-	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-queue');
-	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-quill');
-	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-form');
-	jvbAddScriptDependency('jvb-forms-view-script', 'jvb-tabs');
-
-	jvbAddScriptDependency('jvb-timeline-view-script', 'jvb-gallery');
-}
 
 function jvbAdminMap():void
 {
@@ -550,21 +506,7 @@
 //    ');
 }
 
-function jvbAddScriptDependency(string $handle, string $dep):bool
-{
-    global $wp_scripts;
 
-    $script = $wp_scripts->query($handle);
-    if (!$script) {
-        return false;
-    }
-
-    if (!in_array($dep, $script->deps)) {
-        $script->deps[] = $dep;
-    }
-
-    return true;
-}
 
 
 add_action('wp_body_open', 'jvbAccessibility');
@@ -573,19 +515,15 @@
     echo '<div class="screen-reader-text live-region" aria-live="polite" role="status"></div>';
 }
 
-add_action('wp_head', 'jvbFrontendBase',1);
-function jvbFrontendBase():void
-{
-	?>
-	<script type="text/javascript">window.jvbBase = '<?= BASE ?>';</script>
-	<?php
-}
+//add_action('wp_head', 'jvbFrontendBase',1);
+//function jvbFrontendBase():void
+//{
+//	?>
+<!--	<script type="text/javascript">window.jvbBase = '--><?php //= BASE ?>//';</script>
+//	<?php
+//}
 
-//add_action('wp_head', 'jvbDumpIt');
-function jvbDumpIt()
-{
-//	jvbDump(Registrar::getInstance('foodie')->getFields());
-}
+
 
 add_action('after_setup_theme', 'jvbImageSize');
 function jvbImageSize():void
diff --git a/src/drawer-menu/render.php b/src/drawer-menu/render.php
index b3d9bbc..ea6680d 100644
--- a/src/drawer-menu/render.php
+++ b/src/drawer-menu/render.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/src/faq/render.php b/src/faq/render.php
index e69de29..ea6680d 100644
--- a/src/faq/render.php
+++ b/src/faq/render.php
@@ -0,0 +1,5 @@
+<?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/src/glossary/render.php b/src/glossary/render.php
index 52aded4..44c57fb 100644
--- a/src/glossary/render.php
+++ b/src/glossary/render.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 /**
  * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
  */
diff --git a/src/gmbreviews/render.php b/src/gmbreviews/render.php
index b3d9bbc..ea6680d 100644
--- a/src/gmbreviews/render.php
+++ b/src/gmbreviews/render.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/src/menu/render.php b/src/menu/render.php
index 52aded4..44c57fb 100644
--- a/src/menu/render.php
+++ b/src/menu/render.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 /**
  * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
  */
diff --git a/src/simple-calendar/render.php b/src/simple-calendar/render.php
index b3d9bbc..ea6680d 100644
--- a/src/simple-calendar/render.php
+++ b/src/simple-calendar/render.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/src/timeline/render.php b/src/timeline/render.php
index 52aded4..44c57fb 100644
--- a/src/timeline/render.php
+++ b/src/timeline/render.php
@@ -1,4 +1,8 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
 /**
  * @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
  */
diff --git a/src/video/render.php b/src/video/render.php
index b3d9bbc..ea6680d 100644
--- a/src/video/render.php
+++ b/src/video/render.php
@@ -1 +1,5 @@
 <?php
+
+if (!defined('ABSPATH')) {
+	exit; // Exit if accessed directly
+}
diff --git a/templates/dashboard/sections/notifications.php b/templates/dashboard/sections/notifications.php
index 18b672a..d82c702 100644
--- a/templates/dashboard/sections/notifications.php
+++ b/templates/dashboard/sections/notifications.php
@@ -1,4 +1,7 @@
 <?php
+
+use JVBase\managers\NotificationManager;
+
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
@@ -11,7 +14,7 @@
     true
 );
 
-$handler = JVB()->notification();
+$handler = NotificationManager::getInstance();
 $user = wp_get_current_user();
 $role = array_values($user->roles)[0];
 

--
Gitblit v1.10.0