From 0e4b986e81f8132a44e61fa8df18860301cc3468 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 01 Jan 2026 20:31:10 +0000
Subject: [PATCH] =JakeVan preliminary additions

---
 assets/js/concise/TaxonomySelector.js |  563 +++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 393 insertions(+), 170 deletions(-)

diff --git a/assets/js/concise/TaxonomySelector.js b/assets/js/concise/TaxonomySelector.js
index 3262ea8..032c749 100644
--- a/assets/js/concise/TaxonomySelector.js
+++ b/assets/js/concise/TaxonomySelector.js
@@ -12,27 +12,34 @@
 		this.isInitializing = true;
 		this.taxonomiesToFetch = new Set();
 
-		this.store = new window.jvbStore({
-			name: `taxonomies`,
-			storeName: `terms`,
-			keyPath: 'id',
-			showLoading: false,
-			indexes: [
-				{name: 'taxonomy', keyPath: 'taxonomy'},
-				{name: 'parent', keyPath: 'parent'},
-				{name: 'slug', keyPath: 'slug', unique: true},
-				{name: 'count', keyPath: 'count'},
-			],
-			endpoint: 'terms',
-			TTL: 7200000, //2 hours
-			filters: {
-				taxonomy: '',
-				page: 1,
-				search: '',
-				parent: 0
-			},
-			required: 'taxonomy'
-		});
+		this.triggers = new Set(['.taxonomy-toggle']);
+
+		this.subscribers = new Set();
+
+		const store = window.jvbStore.register(
+			'taxonomies',
+			{
+				storeName: `terms`,
+				keyPath: 'id',
+				showLoading: false,
+				indexes: [
+					{name: 'taxonomy', keyPath: 'taxonomy'},
+					{name: 'parent', keyPath: 'parent'},
+					{name: 'slug', keyPath: 'slug', unique: true},
+					{name: 'count', keyPath: 'count'},
+				],
+				endpoint: 'terms',
+				TTL: 2 * 60 * 1000, //2 hours
+				filters: {
+					taxonomy: '',
+					page: 1,
+					search: '',
+					parent: 0
+				},
+				required: 'taxonomy',
+				delayFetch: true,
+			});
+		this.store = store.terms;
 
 		// Central field management
 		this.fields = new Map();
@@ -43,7 +50,6 @@
 		this.currentConfig = null;
 		this.currentSingular = null;
 		this.currentPlural = null;
-		this.activeStore = null;
 
 		// Modal state
 		this.disabled = false;
@@ -64,6 +70,9 @@
 		this.scanExistingFields();
 		this.initGlobalListeners();
 
+		if (this.hasAutocomplete && window.jvbTaxCreator) {
+			this.creator = new window.jvbTaxCreator(this);
+		}
 		this.store.subscribe(this.handleStoreEvent.bind(this));
 		// Complete initialization
 		this.isInitializing = false;
@@ -76,11 +85,28 @@
 	handleStoreEvent(event, data) {
 		switch (event) {
 			case 'data-loaded':
-				// Only render if modal is open OR if it's an autocomplete request
+				const taxonomy = this.store.filters.taxonomy;
+				// Handle batch taxonomy loading (comma-separated)
+				if (taxonomy?.includes(',')) {
+					this.handleBatchDataLoaded(taxonomy, data);
+				}
+				// Update button states for this taxonomy (or taxonomies)
+				if (taxonomy) {
+					// Handle comma-separated taxonomies from batch fetch
+					const taxonomies = taxonomy.includes(',')
+						? taxonomy.split(',').map(t => t.trim())
+						: [taxonomy];
+
+					taxonomies.forEach(tax => {
+						this.updateFieldsForTaxonomy(tax);
+					});
+				}
+
+				// Only render if modal is open OR autocomplete active
 				if (this.modal?.open) {
 					this.handleTermsLoaded(data);
 				}
-				// Handle autocomplete results
+
 				if (this.isAutocompleteActive && this.activeField) {
 					const field = this.fields.get(this.activeField);
 					const terms = data.data?.items || [];
@@ -91,7 +117,6 @@
 				break;
 
 			case 'filters-changed':
-				// Modal UI updates happen here if needed
 				if (this.modal?.open) {
 					this.showLoading();
 				}
@@ -112,10 +137,12 @@
 	 */
 	handleTermsLoaded(data) {
 		this.hideLoading();
-		const terms = data.data?.items || [];
-		const pagination = data.data?.pagination || {};
+		const terms = this.store.getFiltered();  // Use getFiltered() instead of getFilteredItems()
+		const response = this.store.lastResponse?.page || {};
 		const isSearch = data.filters?.search && data.filters.search.length > 0;
-		const append = data.filters?.page > 1;
+		const append = response.page > 1;
+
+		this.notify('terms-loaded', { terms, filters: data.filters });
 
 		if (terms.length === 0) {
 			if (!append) {
@@ -124,9 +151,9 @@
 			this.observer.unobserve(this.ui.sentinel);
 		} else {
 			this.renderTerms(terms, append, isSearch);
-			this.currentTerms = terms;
+
 			// Handle pagination
-			if (pagination.has_more) {
+			if (response.has_more) {
 				this.observer.observe(this.ui.sentinel);
 			} else {
 				this.observer.unobserve(this.ui.sentinel);
@@ -154,28 +181,45 @@
 		}
 	}
 
+
+	/**
+	 * Check if taxonomy has terms and update button states
+	 */
+	updateFieldButtonState(fieldId) {
+		const field = this.fields.get(fieldId);
+		if (!field) return;
+
+		// Check store for items of this specific taxonomy
+		const hasTerms = Array.from(this.store.data.values())
+			.some(term => term.taxonomy === field.taxonomy);
+
+		if (field.toggle) {
+			field.toggle.disabled = !hasTerms && !field.canCreate;
+			field.toggle.title = !hasTerms
+				? `No ${this.getSingular(field.taxonomy)} available`
+				: `Select ${this.getPlural(field.taxonomy)}`;
+		}
+	}
 	/**
 	 * Update fields when taxonomy items are updated
 	 */
-	updateFieldsForTaxonomy(taxonomy, items) {
-		this.fields.forEach(field => {
-			if (field.taxonomy === taxonomy && field.selectedTerms.size > 0) {
-				// Update display with fresh term data
-				field.selectedTerms.forEach(termId => {
-					const term = items.find(item => item.id === termId);
-					if (term) {
-						const selectedItem = field.selectedContainer.querySelector(`[data-id="${termId}"]`);
-						if (selectedItem) {
-							selectedItem.dataset.path = term.path;
-							selectedItem.querySelector('span').textContent = term.path;
-						}
-					}
-				});
-			}
+	updateFieldsForTaxonomy(taxonomy) {
+		this.getFieldsForTaxonomy(taxonomy).forEach(field => {
+			this.updateFieldButtonState(field.id);
 		});
 	}
 
 	/**
+	 * Get fields for a specific taxonomy
+	 */
+	getFieldsForTaxonomy(taxonomy) {
+		return Array.from(this.fields.values())
+			.filter(field => field.taxonomy === taxonomy);
+	}
+
+
+
+	/**
 	 * Scan page for existing taxonomy fields and register them
 	 */
 	scanExistingFields(container = null) {
@@ -203,14 +247,18 @@
 	registerField(field, options = {}) {
 		let input = field.querySelector('input[type=hidden]');
 		if (!input) {
-			return;
+			return false;
 		}
-
 		if (!('fieldId' in field.dataset)) {
 			field.dataset.fieldId = this.createFieldId(field);
 		}
 		let fieldId = field.dataset.fieldId;
-		let button = field.querySelector('button.taxonomy-toggle');
+
+		let button = (Object.hasOwn(options, 'button')) ? options.button : field.querySelector('button.taxonomy-toggle');
+
+		if (Object.hasOwn(options, 'buttonSelector')) {
+			this.triggers.add(options.buttonSelector);
+		}
 
 		let config = {
 			id: fieldId,
@@ -226,7 +274,7 @@
 			isRequired: 'required' in button.dataset,
 			selectedTerms: new Set(),
 			toggle: button,
-			selectedContainer: field.querySelector('.selected-items'),
+			selectedContainer: (Object.hasOwn(options, 'selected')) ? options.selected : field.querySelector('.selected-items'),
 			...options
 		};
 
@@ -244,13 +292,19 @@
 			selectedIds.forEach(id => config.selectedTerms.add(id));
 		}
 
+		if (Object.hasOwn(options, 'selectedItems')) {
+			options.selectedItems.forEach(id => {
+				config.selectedTerms.add(id);
+			});
+		}
+
 		this.fields.set(fieldId, config);
 
 		// Ensure store exists for this taxonomy
 		if (this.isInitializing) {
 			this.taxonomiesToFetch.add(config.taxonomy);
 		} else {
-			this.store.setFilter('taxonomy', config.taxonomy);
+			// this.store.setFilter('taxonomy', config.taxonomy);
 		}
 
 		// Initialize display for any pre-selected values
@@ -262,32 +316,43 @@
 	}
 
 	/**
-	 * Batch fetch all unique taxonomies collected during init
+	 * Register a filter button (simplified registration for feed blocks)
 	 */
-	async batchFetchTaxonomies() {
-		if (this.taxonomiesToFetch.size === 0) return;
+	registerFilterButton(button, options = {}) {
+		const fieldId = this.createFieldId(button);
+		button.dataset.fieldId = fieldId;
 
-		const taxonomies = Array.from(this.taxonomiesToFetch);
-		this.taxonomiesToFetch.clear();
-
-		console.log(`Batch fetching ${taxonomies.length} unique taxonomies:`, taxonomies);
-
-		// Fetch each taxonomy sequentially (cache will prevent duplicates)
-		for (const taxonomy of taxonomies) {
-			await this.store.setFilters({
-				taxonomy: taxonomy,
-				page: 1,
-				search: '',
-				parent: 0
-			});
+		if (options.buttonSelector) {
+			this.triggers.add(options.buttonSelector);
 		}
 
-		// Now initialize field displays
-		this.fields.forEach((config, fieldId) => {
-			if (config.selectedTerms.size > 0) {
-				this.initFieldDisplay(fieldId);
-			}
-		});
+		const config = {
+			id: fieldId,
+			input: null,
+			container: options.container || button.closest('.filters') || button.parentElement,
+			taxonomy: button.dataset.taxonomy,
+			name: `filter_${button.dataset.taxonomy}`,
+			maxSelection: parseInt(button.dataset.max) || 0,
+			canSearch: 'search' in button.dataset,
+			hasAutocomplete: false,
+			canCreate: false,
+			isRequired: false,
+			selectedTerms: new Set(options.selectedItems || []),
+			toggle: button,
+			selectedContainer: options.selected || null,
+			isFilterMode: true,
+			...options
+		};
+
+		this.fields.set(fieldId, config);
+
+		if (this.isInitializing) {
+			this.taxonomiesToFetch.add(config.taxonomy);
+		} else {
+			this.store.setFilter('taxonomy', config.taxonomy);
+		}
+
+		return fieldId;
 	}
 
 	/**
@@ -306,21 +371,13 @@
 		if (!field || field.selectedTerms.size === 0) return;
 
 		const selectedIds = Array.from(field.selectedTerms);
-		const cachedTerms = [];
 
 		selectedIds.forEach(termId => {
-			const term = this.store.data.get(termId);
+			const term = this.store.get(termId);  // Changed from getItem
 			if (term) {
-				cachedTerms.push(term);
+				this.addTermToDisplay(fieldId, term.id, term.name, term.path);
 			}
 		});
-
-		// Display all found terms
-		cachedTerms.forEach(term => {
-			this.addTermToDisplay(fieldId, term.id, term.name, term.path);
-		});
-
-		// Don't fetch missing terms here - they should be loaded by batchFetchTaxonomies
 	}
 
 	/**
@@ -346,7 +403,6 @@
 		this.modalInstance.subscribe((event, data) => {
 			switch (event) {
 				case 'modal-open':
-					console.log(data);
 					this.openModal(data);
 					break;
 				case 'modal-close':
@@ -401,7 +457,7 @@
 		// Initialize intersection observer for infinite scroll
 		this.observer = new IntersectionObserver((entries) => {
 			entries.forEach(entry => {
-				if (entry.isIntersecting && this.activeStore) {
+				if (entry.isIntersecting) {
 					this.loadMoreTerms();
 				}
 			});
@@ -424,10 +480,29 @@
 
 	initAutocomplete()
 	{
-		console.log('Autocomplete init');
-		this.autocompleteHandler = window.debounce((e) => this.handleAutocomplete(e), 300);
+		this.autocompleteHandler = (e) => {
+			window.debouncer.schedule(
+				'taxonomy-autocomplete',
+				() => this.handleAutocomplete(e),
+				300
+			);
+		};
 		document.addEventListener('input', this.autocompleteHandler);
 		document.addEventListener('blur', this.cleanupAutocomplete.bind(this));
+		// Preload taxonomy data on focus
+		document.addEventListener('focus', (e) => {
+			if (!('autocomplete' in e.target.dataset)) {
+				return;
+			}
+
+			const fieldId = this.getFieldId(e.target);
+			const field = this.fields.get(fieldId);
+
+			if (!field) return;
+
+			// Preload this taxonomy's data
+			this.preloadTaxonomy(field.taxonomy);
+		}, true); // Use capture phase
 	}
 
 	/**
@@ -435,7 +510,8 @@
 	 */
 	handleClick(e) {
 		// Handle taxonomy toggle buttons
-		const toggleButton = window.targetCheck(e, '.taxonomy-toggle');
+		const toggleButton = window.targetCheck(e, Array.from(this.triggers));
+
 		if (toggleButton) {
 			e.preventDefault();
 			this.handleToggleClick(toggleButton);
@@ -496,65 +572,53 @@
 				return;
 			}
 
-			this.setActiveField(fieldId);
-			this.modalInstance.handleOpen();
+
+			this.setActiveField(fieldId, true);
 
 		} catch (error) {
 			console.error('Error handling toggle click:', error);
-			this.error?.handleError(error, {
-				component: 'TaxonomySelector',
-				action: 'handleToggleClick'
-			});
+			if (this.error?.log) {
+				this.error.log(error, {
+					component: 'TaxonomySelector',
+					action: 'handleToggleClick'
+				});
+			}
 		}
 	}
 
 	/**
 	 * Set the active field for modal operations
 	 */
-	setActiveField(fieldId) {
+	setActiveField(fieldId, openModal = false) {
 		this.activeField = fieldId;
 		this.currentConfig = this.fields.get(fieldId);
 
-		this.currentSingular = jvbSettings.labels[this.currentConfig.taxonomy].single;
-		this.currentPlural = jvbSettings.labels[this.currentConfig.taxonomy].plural;
+		this.currentSingular = this.getSingular(this.currentConfig.taxonomy);
+		this.currentPlural = this.getPlural(this.currentConfig.taxonomy);
 
-		// Get or create store for this taxonomy
+		if (openModal) {
+			this.modalInstance.handleOpen();
+		}
+
+		// Set taxonomy filter - store handles the rest
 		this.store.setFilter('taxonomy', this.currentConfig.taxonomy);
 
 		// Clear modal selection state
 		this.selectedTerms.clear();
 
 		// Copy field's current selections to modal state
-		if (this.currentConfig.selectedTerms) {
-			let termsToFetch = [];
-			this.currentConfig.selectedTerms.forEach(termId => {
-				const term = this.store.getItem(termId);
-				if (term) {
-					this.selectedTerms.set(termId, {
-						id: termId,
-						name: term.name,
-						path: term.path
-					});
-				} else {
-					termsToFetch.push(termId);
-				}
-			});
-			if (termsToFetch.length > 0) {
-				let terms = this.fetchSpecificTerms(termsToFetch);
-				terms.forEach(term => {
-					this.selectedTerms.set(term.id, {
-						id: term.id,
-						name: term.name,
-						path: term.path
-					});
+		this.currentConfig.selectedTerms.forEach(termId => {
+			const term = this.store.get(termId);
+			if (term) {
+				this.selectedTerms.set(termId, {
+					id: termId,
+					name: term.name,
+					path: term.path
 				});
 			}
-		}
+		});
 	}
 
-	fetchSpecificTerms(terms) {
-		return [];
-	}
 
 	/**
 	 * Handle clicks within modal
@@ -627,43 +691,70 @@
 			filterCallback: callback // Store the callback
 		});
 
-		this.setActiveField(virtualFieldId);
+		this.setActiveField(virtualFieldId, true);
 		this.modalInstance.handleOpen();
 	}
 
 	/**
 	 * Open modal and initialize
 	 */
-	openModal(config) {
-		this.activeField = config.fieldId;
-		this.currentConfig = config;
+	openModal() {
+		if (!this.currentConfig) {
+			console.error('No active field set');
+			return;
+		}
+
 		// Initialize creator if available
-		if (config.canCreate && 'jvbTaxCreator' in window) {
+		if (!this.creator && this.currentConfig.canCreate && 'jvbTaxCreator' in window) {
 			this.creator = new window.jvbTaxCreator(this);
-		} else if (this.creator) {
-			delete this.creator;
 		}
 
-		// Load selected terms into modal state
-		this.selectedTerms = new Set(config.selectedTerms);
+		// Update modal UI
+		this.updateModalForTaxonomy();
 
-		// Only fetch if taxonomy changed
-		const currentTaxonomy = this.store.filters.taxonomy;
-		if (currentTaxonomy !== config.taxonomy) {
-			this.store.setFilters({
-				taxonomy: config.taxonomy,
-				page: 1,
-				search: '',
-				parent: 0
-			});
-		}
-
-		// Reset UI
-		window.removeChildren(this.ui.termsList);
-		this.ui.search.value = '';
+		// Load selected terms display
+		this.updateModalSelections();
 		this.updateSelectionCount();
 
-		this.modalInstance.open();
+		// Clear terms list and show loading
+		window.removeChildren(this.ui.termsList);
+		this.showLoading();
+	}
+
+	/**
+	 * Update selection count display in modal
+	 */
+	updateSelectionCount() {
+		if (!this.currentConfig) return;
+
+		const count = this.selectedTerms.size;
+		const max = this.currentConfig.maxSelection;
+
+		// Update any count display elements
+		const countElement = this.modal?.querySelector('.selection-count');
+		if (countElement) {
+			if (max > 0) {
+				countElement.textContent = `${count} of ${max} selected`;
+			} else {
+				countElement.textContent = `${count} selected`;
+			}
+		}
+	}
+
+
+
+	/**
+	 * Get singular label for taxonomy
+	 */
+	getSingular(taxonomy) {
+		return jvbSettings.labels[taxonomy]?.single || taxonomy;
+	}
+
+	/**
+	 * Get plural label for taxonomy
+	 */
+	getPlural(taxonomy) {
+		return jvbSettings.labels[taxonomy]?.plural || taxonomy;
 	}
 
 	/**
@@ -673,12 +764,17 @@
 		this.observer.unobserve(this.ui.sentinel);
 		window.removeChildren(this.ui.termsList);
 
+		this.notify('selected-terms', {
+			terms: this.selectedTerms,
+			taxonomy: this.currentConfig.taxonomy
+		});
+
 		if (this.currentConfig?.isFilterMode) {
 			if (this.currentConfig.filterCallback) {
 				const selectedIds = Array.from(this.selectedTerms.keys());
 				this.currentConfig.filterCallback(selectedIds, this.currentConfig.taxonomy);
 			}
-			this.fields.delete(this.activeField);
+			// this.fields.delete(this.activeField);
 		} else if (this.activeField) {
 			this.saveSelectionsToField(this.activeField);
 		}
@@ -688,7 +784,7 @@
 			this.ui.search.input.removeEventListener('input', this.searchHandler);
 		}
 
-		if (this.creator) {
+		if (!this.hasAutocomplete && this.creator) {
 			delete this.creator;
 		}
 
@@ -960,8 +1056,9 @@
 
 		if (!field) return;
 
-
+		// Store current value immediately (fixes fast typing issue)
 		const query = e.target.value.trim();
+		field.currentAutocompleteQuery = query;
 
 		if (query.length < 2) {
 			if (field.autocompleteDropdown) {
@@ -972,12 +1069,6 @@
 		}
 
 		this.activeField = fieldId;
-		this.currentConfig = field;
-
-
-		if (field.canCreate && ! this.creator) {
-			this.creator = new window.jvbTaxCreator(this);
-		}
 		this.isAutocompleteActive = true;
 
 		if (field.autocompleteDropdown) {
@@ -1041,15 +1132,26 @@
 			});
 		}
 
-		// Offer to create new term if creator is available
-		if (this.creator) {
-			const createOption = this.creator.createAutocompleteOption(query, field);
+		// Use stored current query instead of debounced one
+		const currentQuery = field.currentAutocompleteQuery || query;
+		if (field.canCreate && currentQuery && window.jvbTaxCreator) {
+			const createOption = this.createNewTermOption(currentQuery);
 			dropdown.appendChild(createOption);
 		}
 
 		dropdown.hidden = false;
 	}
 
+	createNewTermOption(query) {
+		const button = document.createElement('button');
+		button.type = 'button';
+		button.className = 'autocomplete-item create-term';
+		button.dataset.query = query;
+		button.innerHTML = `<strong>Create:</strong> "${query}"`;
+
+		return button;
+	}
+
 	createAutocompleteTermElement(field, term) {
 		const item = document.createElement('button');
 		item.type = 'button';
@@ -1081,7 +1183,7 @@
 	 * Navigate to parent term
 	 */
 	navigateToParent() {
-		// Single call instead of two setFilter + manual fetch
+		// Store handles fetch automatically
 		this.store.setFilters({
 			parent: 0,
 			page: 1
@@ -1095,7 +1197,7 @@
 	 * Navigate to child term
 	 */
 	navigateToChild(termId, termName) {
-		// Single call - auto-fetches
+		// Store handles fetch automatically
 		this.store.setFilters({
 			parent: termId,
 			page: 1
@@ -1112,7 +1214,7 @@
 	navigateToPath(pathLevel) {
 		const parentId = parseInt(pathLevel.dataset.id) || 0;
 
-		// Single call - auto-fetches
+		// Store handles fetch automatically
 		this.store.setFilters({
 			parent: parentId,
 			page: 1
@@ -1126,17 +1228,19 @@
 	 * Load more terms (pagination)
 	 */
 	loadMoreTerms() {
-		if (!this.activeStore) return;
-
-		const currentPage = this.activeStore.filters.page || 1;
+		const currentPage = this.store.filters.page || 1;
 		this.store.setFilter('page', currentPage + 1);
-
 	}
 
 	/**
 	 * Render terms list
 	 */
-	renderTerms(terms, append = false, showPath = false) {
+	renderTerms(terms = null, append = false, showPath = false) {
+		// If no terms provided, get from store
+		if (!terms) {
+			terms = this.store.getFiltered();
+		}
+
 		if (!append) {
 			window.removeChildren(this.ui.termsList);
 		}
@@ -1148,10 +1252,10 @@
 			return;
 		}
 
-		// Use this.store instead of this.activeStore
 		const currentParent = this.store.filters.parent || 0;
 		this.ui.breadcrumbs.back.hidden = currentParent === 0;
 
+		const fragment = document.createDocumentFragment();
 		terms.forEach(term => {
 			const element = this.createTermElement({
 				id: parseInt(term.id),
@@ -1162,9 +1266,11 @@
 			});
 
 			if (element) {
-				this.ui.termsList.appendChild(element);
+				fragment.appendChild(element);
 			}
 		});
+
+		this.ui.termsList.appendChild(fragment);
 	}
 
 	/**
@@ -1308,6 +1414,117 @@
 
 		return null;
 	}
+	/********************************************
+	BATCH FETCH: fetches first page for all taxonomies in one call
+	 ********************************************/
+	async batchFetchTaxonomies() {
+		if (this.taxonomiesToFetch.size === 0) return;
+
+		const taxonomies = Array.from(this.taxonomiesToFetch);
+		this.taxonomiesToFetch.clear();
+
+		// Single fetch - the data-loaded event will handle cache splitting
+		this.store.setFilters({
+			taxonomy: taxonomies.join(','),
+			page: 1,
+			search: '',
+			parent: 0
+		});
+	}
+	handleBatchDataLoaded(taxonomyString, data) {
+		const taxonomies = taxonomyString.split(',').map(t => t.trim());
+		const storeInstance = this.store.getStore(); // Access actual store instance
+
+		taxonomies.forEach(taxonomy => {
+			const filters = {
+				taxonomy: taxonomy,
+				page: 1,
+				search: '',
+				parent: 0
+			};
+
+			// Use the internal generateCacheKey method via store instance
+			const cacheKey = this.generateCacheKeyForFilters(filters);
+
+			// Filter items for this specific taxonomy
+			const items = Array.from(this.store.data.values())
+				.filter(item => item.taxonomy === taxonomy)
+				.map(item => item.id);
+
+			const cacheEntry = {
+				key: cacheKey,
+				items: items,
+				timestamp: Date.now(),
+				endpoint: storeInstance.config.endpoint,
+				filters: filters
+			};
+
+			// Set in both memory and IndexedDB cache
+			storeInstance.cache.set(cacheKey, cacheEntry);
+
+			// Persist to IndexedDB (if available)
+			if (storeInstance.db?.objectStoreNames.contains('cache')) {
+				const tx = storeInstance.db.transaction(['cache'], 'readwrite');
+				const objectStore = tx.objectStore('cache');
+				objectStore.put(cacheEntry);
+			}
+
+			// Update button states for this taxonomy
+			this.updateFieldsForTaxonomy(taxonomy);
+		});
+
+		// Initialize field displays
+		this.fields.forEach((config, fieldId) => {
+			if (config.selectedTerms.size > 0) {
+				this.initFieldDisplay(fieldId);
+			}
+		});
+	}
+
+	/**
+	 * Generate cache key for given filters (matching DataStore's internal logic)
+	 */
+	generateCacheKeyForFilters(filters) {
+		const normalized = Object.keys(filters)
+			.sort()
+			.reduce((acc, key) => {
+				acc[key] = filters[key];
+				return acc;
+			}, {});
+
+		return JSON.stringify(normalized);
+	}
+
+	/**
+	 * Preload taxonomy data on hover
+	 */
+	async preloadTaxonomy(taxonomy) {
+		// Trigger fetch for this taxonomy
+		this.store.setFilters({
+			taxonomy: taxonomy,
+			page: 1,
+			search: '',
+			parent: 0
+		});
+	}
+	/*****************************************
+	SUBSCRIBERS
+	 *****************************************/
+
+	subscribe(callback) {
+		this.subscribers.add(callback);
+		return () => this.subscribers.delete(callback);
+	}
+
+	notify(event, data = {}) {
+		this.subscribers.forEach( callback => {
+			try {
+				callback(event, data);
+			} catch (error) {
+				console.error('Subscriber error:', error);
+			}
+		});
+	}
 
 	/**
 	 * Clean up
@@ -1323,6 +1540,7 @@
 		// Destroy all stores
 		this.store.destroy();
 
+		this.subscribers.clear();
 		// Clear all maps
 		this.fields.clear();
 		this.selectedTerms.clear();
@@ -1333,5 +1551,10 @@
  * Initialize singleton
  */
 document.addEventListener('DOMContentLoaded', function() {
-	window.jvbSelector = new TaxonomySelector();
+	window.auth.subscribe((event) => {
+		if (event === 'auth-loaded') {
+			window.jvbSelector = new TaxonomySelector();
+		}
+	});
+
 });

--
Gitblit v1.10.0