From d7dbe7fee362d587dfc334135d9581b6216a4295 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 23 Nov 2025 04:13:56 +0000
Subject: [PATCH] =Timeline block, and feed block updated. DataStore.js refactored to not block rendering

---
 assets/js/concise/UploadManager.js | 3920 ++++++++++++++++++++++++++++-------------------------------
 1 files changed, 1,871 insertions(+), 2,049 deletions(-)

diff --git a/assets/js/concise/UploadManager.js b/assets/js/concise/UploadManager.js
index b59101c..59a0d5f 100644
--- a/assets/js/concise/UploadManager.js
+++ b/assets/js/concise/UploadManager.js
@@ -1,42 +1,51 @@
+/**
+ * UploadManager - Refactored for clarity
+ *
+ * Architecture:
+ * - DataStores (fieldStore, uploadStore) = Recovery cache only, cleared after successful upload
+ * - Maps (uploadElements, fieldElements) = Runtime DOM references
+ * - Upload data flows: File → Process → Queue → Server → Clean up stores
+ */
 class UploadManager {
 	constructor() {
-		//Load dependencies
+		// Load dependencies
 		this.queue = window.jvbQueue;
 		this.a11y = window.jvbA11y;
 		this.error = window.jvbError;
 
-		//Load Datastore
-		this.fieldStore = new window.jvbStore({
-			name: 'upload_fields',
-			storeName: 'fieldStates',
-			keyPath: 'id',
-			version: 2,
+		// RECOVERY STORES - Cleared after successful upload
+		this.fieldStore = window.jvbStore.register(
+			'upload_fields',
+			{
+				storeName: 'fieldStates',
+				keyPath: 'id',
+				version: 2,
+				indexes: [
+					{ name: 'fieldId', keyPath: 'fieldId' },
+					{ name: 'timestamp', keyPath: 'timestamp' },
+					{ name: 'content', keyPath: 'content' },
+					{ name: 'itemId', keyPath: 'itemId' },
+					{ name: 'status', keyPath: 'status' }
+				],
+				TTL: 86400000 * 7, // 1 week
+				delayFetch: true
+			});
 
-			indexes: [
-				{ name: 'fieldId', keyPath: 'fieldId' },
-				{ name: 'timestamp', keyPath: 'timestamp' },
-				{ name: 'content', keyPath: 'content' },
-				{ name: 'itemId', keyPath: 'itemId' },
-				{ name: 'status', keyPath: 'status' }
-			],
-
-			stripDOMReferences: true,
-			TTL: 86400000*7 // 24 hours -> 1 week
-		});
-
-		this.uploadStore = new window.jvbStore({
-			name: 'uploads',
-			storeName: 'uploads',
-			keyPath: 'id',
-			storeBlobs: true,
-
-			indexes: [
-				{ name: 'fieldId', keyPath: 'fieldId' },
-				{ name: 'status', keyPath: 'status' },
-				{ name: 'groupId', keyPath: 'groupId' },
-				{ name: 'attachmentId', keyPath: 'attachmentId' }
-			],
-		});
+		this.uploadStore = window.jvbStore.register(
+			'uploads',
+			{
+				name: 'uploads',
+				storeName: 'uploads',
+				keyPath: 'id',
+				storeBlobs: true,
+				indexes: [
+					{ name: 'fieldId', keyPath: 'fieldId' },
+					{ name: 'status', keyPath: 'status' },
+					{ name: 'groupId', keyPath: 'groupId' },
+					{ name: 'attachmentId', keyPath: 'attachmentId' }
+				],
+				delayFetch: true
+			});
 
 		window.jvbUploadBlobs = this.uploadStore;
 
@@ -44,20 +53,24 @@
 		this.fieldStore.subscribe(this.handleFieldStoreEvent.bind(this));
 		this.uploadStore.subscribe(this.handleUploadStoreEvent.bind(this));
 
-		//Load Worker
-		this.initWorker();
+		// RUNTIME DATA - DOM references and ephemeral state
+		this.uploadElements = new Map();  // uploadId → { element, preview, location }
+		this.fieldElements = new Map();   // fieldId → { element, ui, config }
+		this.groupElements = new Map();   // groupId → { element, grid, fieldId }
 
-		// Core data structures
-		this.fields = new Map();
-		this.uploads = new Map();
-		this.groups = new Map();
+		// Selection and UI state
 		this.selected = new Map();
 		this.selectionHandlers = new Map();
 		this.previewUrls = new Set();
-		//Notification and Subscribers
+		this.sortableInstances = new Map();
+
+		// Worker for image processing
+		this.initWorker();
+
+		// Notification subscribers
 		this.subscribers = new Set();
 
-		// Controllers (will be initialized based on features)
+		// Controllers
 		this.dragController = null;
 
 		// Selectors
@@ -65,7 +78,6 @@
 			field: {
 				field: '[data-upload-field]',
 				input: 'input[type="file"]',
-				hiddenValue: 'input[type="hidden"]',
 				dropZone: '.file-upload-container',
 				preview: '.item-grid.preview',
 				progress: '.image-progress'
@@ -86,7 +98,6 @@
 			}
 		};
 
-
 		this.statusMapping = {
 			'received': 'Image Received',
 			'local_processing': 'Processing Image...',
@@ -100,66 +111,48 @@
 		};
 
 		// Sortable configuration
-		this.sortableInstances = new Map();
 		this.sortableConfig = {
 			animation: 150,
 			draggable: '.item',
-			handle: '.select-item-label, img', // Can drag by image or checkbox label
+			handle: '.select-item, img',
 			ghostClass: 'sortable-ghost',
 			chosenClass: 'sortable-chosen',
 			dragClass: 'sortable-drag',
-			onEnd: (evt) => {
-				this.handleReorder(evt);
-			}
+			onEnd: (evt) => this.handleReorder(evt)
 		};
 
 		this.init();
 	}
 
 	async init() {
-		// Load existing data
-		await this.loadFields();
-		await this.loadUploads();
-		// Initialize fields
 		this.initializeFields();
-
-		// Set up core listeners
 		this.initListeners();
 
+		// Queue integration - handle completion/failure
 		this.queue.subscribe((event, operation) => {
-			if (operation.endpoint !== 'uploads' && operation.endpoint !== 'uploads/meta') {
+			if (!['uploads', 'uploads/meta', 'uploads/groups'].includes(operation.endpoint)) {
 				return;
 			}
+
 			const fieldId = operation.data instanceof FormData
 				? operation.data.get('fieldId')
-				: operation.data.fieldId;
+				: operation.data?.fieldId;
+
 			switch(event) {
 				case 'cancel-operation':
-					if (fieldId) {
-						this.clearField(fieldId);
-					}
+					if (fieldId) this.handleOperationCancelled(fieldId);
 					break;
 				case 'operation-status':
-					if (fieldId) {
-						this.updateFieldStatus(fieldId, operation.status);
-					}
+					if (fieldId) this.updateFieldStatus(fieldId, operation.status);
 					break;
 				case 'operation-complete':
-					const results = operation.result?.data || [];
-					results.forEach(result => {
-						const upload = this.uploads.get(result.upload_id);
-						if (upload) {
-							upload.attachmentId = result.attachment_id;
-							upload.status = 'completed';
-							this.uploads.set(upload.id, upload);
-						}
-					});
-					if (fieldId) {
-						this.cleanField(fieldId);
-					}
+					this.handleOperationComplete(operation, fieldId);
+					break;
+				case 'operation-failed':
+				case 'operation-failed-permanent':
+					this.handleOperationFailed(operation, fieldId);
 					break;
 			}
-
 		});
 
 		window.addEventListener('beforeunload', () => {
@@ -172,12 +165,9 @@
 			worker: null,
 			timeout: null,
 			tasks: new Map(),
-			restart: {
-				count: 0,
-				max: 3,
-			},
+			restart: { count: 0, max: 3 },
 			settings: {
-				timeout: 10000, //10 seconds per image
+				timeout: 10000,
 				batchSize: 1,
 				maxConcurrent: 3,
 				restartAfterTimeout: true
@@ -185,71 +175,68 @@
 		};
 	}
 
-	/**
-	 * Initialize all upload fields on the page
-	 */
+	/*******************************************************************************
+	 * FIELD MANAGEMENT
+	 *******************************************************************************/
+
 	initializeFields() {
 		const fields = document.querySelectorAll(this.selectors.field.field);
-		fields.forEach(uploader => {
-			this.registerUploader(uploader);
-		});
+		fields.forEach(uploader => this.registerUploader(uploader));
 	}
 
 	scanFields(container) {
 		const fields = container.querySelectorAll(this.selectors.field.field);
-		fields.forEach(uploader => {
-			this.registerUploader(uploader);
-		});
+		fields.forEach(uploader => this.registerUploader(uploader));
 	}
 
 	registerUploader(uploader) {
 		const fieldId = this.determineFieldId(uploader);
 		const config = this.extractFieldConfig(uploader);
+		const ui = this.buildFieldUI(uploader);
 
-		// Create field data structure
-		const field = {
+		// Store field data with Sets for runtime
+		const fieldData = {
 			id: fieldId,
 			config: config,
-			element: uploader,
-			ui: this.buildFieldUI(uploader),
 			uploads: new Set(),
-			groups: new Set(),
+			groups: [],
 			state: 'ready',
+			timestamp: Date.now()
 		};
 
-		this.fields.set(fieldId, field);
+		// Save to store (will convert Sets to Arrays automatically)
+		this.fieldStore.save(fieldData);
+
+		// Store DOM references separately
+		this.fieldElements.set(fieldId, { element: uploader, ui, config });
+
 		uploader.dataset.uploader = fieldId;
 		this.addFieldSelectionHandler(fieldId);
 
 		if (config.destination === 'post_group' && !this.dragController) {
 			this.initGroupFeatures();
 		}
+
 		if (config.type !== 'single') {
-			this.initSortable(field);
+			this.initSortable(fieldId);
 		}
 
 		return fieldId;
 	}
 
-	/**
-	 * Extract configuration from field element
-	 */
 	extractFieldConfig(fieldElement) {
 		return {
 			destination: fieldElement.dataset.destination || 'meta',
 			content: fieldElement.dataset.content || null,
 			mode: fieldElement.dataset.mode || 'direct',
 			type: fieldElement.dataset.type || 'single',
-			name: fieldElement.dataset.field,  // Field name for meta
-			itemID: fieldElement.dataset.itemId || 0,  // Post/term/user ID
+			name: fieldElement.dataset.field,
+			itemID: fieldElement.dataset.itemId || 0,
 			maxFiles: parseInt(fieldElement.dataset.maxFiles) || 999,
 			subtype: fieldElement.dataset.subtype || 'image'
 		};
 	}
 
-	/**
-	 * Build UI element references for a field
-	 */
 	buildFieldUI(fieldElement) {
 		let UI = {
 			field: fieldElement,
@@ -279,92 +266,129 @@
 		return UI;
 	}
 
-	/**
-	 * Set up core event listeners
-	 */
-	initListeners() {
-		this.clickHandler = this.handleClick.bind(this);
-		this.changeHandler = this.handleChange.bind(this);
+	/*******************************************************************************
+	 * SORTABLE INITIALIZATION
+	 *******************************************************************************/
 
-		document.addEventListener('click', this.clickHandler);
-		document.addEventListener('change', this.changeHandler);
+	initSortable(fieldId) {
+		if (!window.Sortable) return;
 
-		// External file drops
-		this.dragEnterHandler 	= this.handleExternalDragEnter.bind(this);
-		this.dragLeaveHandler 	= this.handleExternalDragLeave.bind(this);
-		this.dragOverHandler 	= this.handleExternalDragOver.bind(this);
-		this.dropHandler 		= this.handleExternalDrop.bind(this);
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldEl) return;
 
-		document.addEventListener('dragenter', this.dragEnterHandler);
-		document.addEventListener('dragleave', this.dragLeaveHandler);
-		document.addEventListener('dragover', this.dragOverHandler);
-		document.addEventListener('drop', this.dropHandler);
+		// Find all sortable grids (preview + group grids, but not restore grids)
+		const grids = fieldEl.element.querySelectorAll('.item-grid.preview, .item-grid.group');
+
+		grids.forEach((grid) => {
+			// Skip if already initialized
+			if (grid.sortableInstance) return;
+
+			const isGroupGrid = grid.classList.contains('group');
+			const gridId = isGroupGrid
+				? `${fieldId}-group-${grid.closest('.upload-group')?.dataset.groupId}`
+				: `${fieldId}-preview`;
+
+			const sortableInstance = new Sortable(grid, {
+				...this.sortableConfig,
+				group: {
+					name: fieldId,
+					pull: true,
+					put: true
+				},
+
+				onAdd: (evt) => {
+					// Re-enable when items added
+					this.updateSortableState(evt.to);
+				},
+				onRemove: (evt) => {
+					// Disable source if now empty
+					this.updateSortableState(evt.from);
+				}
+			});
+
+			// Store reference on element for easy access
+			grid.sortableInstance = sortableInstance;
+			this.sortableInstances.set(gridId, sortableInstance);
+		});
 	}
 
 	/**
-	 * Initialize group-specific features (drag & drop for rearranging)
+	 * Update sortable enabled/disabled state based on item count
 	 */
+	updateSortableState(grid) {
+		const sortable = grid?.sortableInstance;
+		if (!sortable) return;
+
+		const hasItems = grid.querySelectorAll('.item').length > 0;
+		sortable.option('disabled', !hasItems);
+	}
+
+	/**
+	 * Refresh sortable for a field (call after adding/removing items dynamically)
+	 */
+	refreshSortable(fieldId) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldEl) return;
+
+		const grids = fieldEl.element.querySelectorAll('.item-grid.preview, .item-grid.group');
+		grids.forEach(grid => this.updateSortableState(grid));
+	}
+
+	handleReorder(evt) {
+		const grid = evt.to;
+		const fieldWrapper = grid.closest('.field, .upload');
+		if (!fieldWrapper) return;
+
+		let hiddenInput = fieldWrapper.querySelector('input[type="hidden"]');
+		let items = Array.from(fieldWrapper.querySelectorAll('.item')).map(upload => upload.dataset.id);
+		console.log(items);
+		hiddenInput.value = items.join(',');
+
+		if (window.jvbA11y) {
+			window.jvbA11y.announce('Item reordered');
+		}
+
+		fieldWrapper.dispatchEvent(new CustomEvent('jvb-items-reordered', {
+			detail: { from: evt.from, to: evt.to, oldIndex: evt.oldIndex, newIndex: evt.newIndex },
+			bubbles: true
+		}));
+	}
+
+	/*******************************************************************************
+	 * DRAG & DROP INITIALIZATION
+	 *******************************************************************************/
+
 	initGroupFeatures() {
-		// Initialize drag controller for rearranging items
 		this.dragController = new window.jvbDragHandler({
-			// What can be dragged
 			draggableSelector: this.selectors.items.item,
-
-			// Where items can be dropped
 			dropTargetSelector: `${this.selectors.field.preview}, ${this.selectors.groups.grid}, .empty-group`,
-
-			// Don't start drag on interactive elements
 			ignoreSelector: 'input:not(.upload-select), button, select, textarea, details, summary, a',
 			previewElement: 'img, video, .icon',
-
-			// Extract upload ID from element
-			getItemId: (element) => {
-				return element.dataset.uploadId;
-			},
-
-			// Get selected items for multi-drag
+			getItemId: (element) => element.dataset.uploadId,
 			getSelectedItems: (element) => {
 				const fieldId = this.getFieldIdFromElement(element);
 				const uploadId = element.dataset.uploadId;
 				const selected = this.getCurrentSelection(fieldId);
-
-				if (selected && selected.includes(uploadId)) {
-					return selected;
-				}
-
-				return [uploadId];
+				return (selected && selected.includes(uploadId)) ? selected : [uploadId];
 			},
-
-			// Validate drop location
 			validateDrop: (itemIds, targetElement) => {
 				const targetFieldId = this.getFieldIdFromElement(targetElement);
 				const itemElement = document.querySelector(`[data-upload-id="${itemIds[0]}"]`);
 				const itemFieldId = this.getFieldIdFromElement(itemElement);
-
 				return targetFieldId === itemFieldId;
 			},
-
-			// Handle successful drop
 			onDrop: (itemIds, targetElement) => {
 				this.handleItemDrop(itemIds, targetElement);
 				targetElement.scrollIntoView({behavior:'smooth', block:'center'});
 			},
-
-			// Optional callbacks
-			onDragStart: (itemIds) => {
-			},
-
 			onDragEnd: (itemIds, success) => {
 				if (success) {
-					// Clear selection after successful move
 					const itemElement = document.querySelector(`[data-upload-id="${itemIds[0]}"]`);
 					const fieldId = this.getFieldIdFromElement(itemElement);
 					const handler = this.selectionHandlers.get(fieldId);
 					handler?.clearSelection();
 				}
 			},
-
-			// Preview options
 			previewOptions: {
 				multiOffset: { x: -60, y: -80 },
 				singleOffset: { x: -50, y: -60 },
@@ -373,93 +397,38 @@
 		});
 	}
 
-	initSortable(field) {
-		if (!window.Sortable) return;
-
-		// Main grid
-		const mainGrid = field.element.querySelector('.item-grid:not(.group)');
-		if (mainGrid) {
-			this.sortableInstances.set(`${field.id}-main`,
-				new Sortable(mainGrid, {
-					...this.sortableConfig,
-					group: {
-						name: field.id,
-						pull: true,
-						put: true
-					}
-				})
-			);
-		}
-
-		// Group grids (for selection mode with grouping)
-		const groupGrids = field.element.querySelectorAll('.item-grid.group');
-		groupGrids.forEach((grid, index) => {
-			this.sortableInstances.set(`${field.id}-group-${index}`,
-				new Sortable(grid, {
-					...this.sortableConfig,
-					group: {
-						name: field.id,
-						pull: true,
-						put: true
-					}
-				})
-			);
-		});
-	}
-
-// Add reorder handler
-	handleReorder(evt) {
-		const grid = evt.to;
-		const fieldWrapper = grid.closest('.field, .upload');
-		if (!fieldWrapper) return;
-
-		const form = fieldWrapper.closest('form');
-		if (!form) return;
-
-		// Get form config if available
-		const formId = form.dataset.formId;
-		if (formId && window.jvbForms) {
-			const formConfig = window.jvbForms.forms?.get(formId);
-			if (formConfig?.options.autosave) {
-				// Trigger autosave after reordering
-				window.jvbForms.scheduleSave(formConfig, 1000);
-			}
-		}
-
-		// Announce for accessibility
-		if (window.jvbA11y) {
-			window.jvbA11y.announce('Item reordered');
-		}
-
-		// Trigger custom event
-		fieldWrapper.dispatchEvent(new CustomEvent('jvb-items-reordered', {
-			detail: {
-				from: evt.from,
-				to: evt.to,
-				oldIndex: evt.oldIndex,
-				newIndex: evt.newIndex
-			},
-			bubbles: true
-		}));
-	}
-
 	/*******************************************************************************
-	 * EXTERNAL FILE DROP HANDLERS (for new uploads from desktop)
+	 * FILE DROP HANDLERS
 	 *******************************************************************************/
 
+	initListeners() {
+		this.clickHandler = this.handleClick.bind(this);
+		this.changeHandler = this.handleChange.bind(this);
+
+		document.addEventListener('click', this.clickHandler);
+		document.addEventListener('change', this.changeHandler);
+
+		this.dragEnterHandler = this.handleExternalDragEnter.bind(this);
+		this.dragLeaveHandler = this.handleExternalDragLeave.bind(this);
+		this.dragOverHandler = this.handleExternalDragOver.bind(this);
+		this.dropHandler = this.handleExternalDrop.bind(this);
+
+		document.addEventListener('dragenter', this.dragEnterHandler);
+		document.addEventListener('dragleave', this.dragLeaveHandler);
+		document.addEventListener('dragover', this.dragOverHandler);
+		document.addEventListener('drop', this.dropHandler);
+	}
+
 	handleExternalDragLeave(e) {
 		const dropZone = e.target.closest(this.selectors.field.dropZone);
 		if (dropZone && !dropZone.contains(e.relatedTarget)) {
 			dropZone.classList.remove('dragover');
 		}
 	}
+
 	handleExternalDragEnter(e) {
-		if (!e.dataTransfer.types.includes('Files')) {
-			return;
-		}
-
+		if (!e.dataTransfer.types.includes('Files')) return;
 		const dropZone = e.target.closest(this.selectors.field.dropZone);
-
 		if (dropZone) {
 			e.preventDefault();
 			dropZone.classList.add('dragover');
@@ -468,7 +437,6 @@
 
 	handleExternalDragOver(e) {
 		if (!e.dataTransfer.types.includes('Files')) return;
-
 		const dropZone = e.target.closest(this.selectors.field.dropZone);
 		if (dropZone) {
 			e.preventDefault();
@@ -478,33 +446,25 @@
 
 	handleExternalDrop(e) {
 		const dropZone = e.target.closest(this.selectors.field.dropZone);
-
 		if (!dropZone) return;
 
 		e.preventDefault();
 		dropZone.classList.remove('dragover');
 
 		const files = Array.from(e.dataTransfer.files);
-
 		if (files.length === 0) return;
 
 		const fieldId = this.getFieldIdFromElement(dropZone);
-
 		if (fieldId) {
 			this.processFiles(fieldId, files);
 			this.a11y.announce(`${files.length} file(s) dropped for upload`);
-		} else {
-			console.error('No field ID found for drop zone');
 		}
 	}
 
 	/*******************************************************************************
-	 * ITEM DROP HANDLER (for rearranging existing uploads)
+	 * ITEM DROP HANDLER (for rearranging)
 	 *******************************************************************************/
 
-	/**
-	 * Handle items being dropped (called by DragController)
-	 */
 	handleItemDrop(itemIds, targetElement) {
 		const isPreviewDrop = targetElement.classList.contains('preview');
 		let actualTarget = targetElement;
@@ -513,43 +473,32 @@
 		if (targetElement.classList.contains('empty-group')) {
 			const fieldId = this.getFieldIdFromElement(targetElement);
 			const group = this.createGroup(fieldId);
-
-			if (!group) {
-				console.error('Failed to create group');
-				return;
-			}
-
+			if (!group) return;
 			actualTarget = group.grid;
 		}
 
-		// Move each item to target
+		// Move each item
 		itemIds.forEach(uploadId => {
 			if (isPreviewDrop) {
-				// Moving back to preview (ungrouping)
 				this.removeFromGroup(uploadId);
 			} else {
-				// Moving to a group
 				this.addToGroup(uploadId, actualTarget);
 			}
 		});
 
-		// Persist state
 		const fieldId = this.getFieldIdFromElement(targetElement);
 		this.schedulePersistance(fieldId);
 
-		// Announce for accessibility
-		const message = itemIds.length > 1
-			? `Moved ${itemIds.length} items`
-			: 'Moved item';
+		const message = itemIds.length > 1 ? `Moved ${itemIds.length} items` : 'Moved item';
 		this.a11y.announce(message);
 	}
 
 	/*******************************************************************************
-	 * CLICK HANDLERS
+	 * CLICK & CHANGE HANDLERS
 	 *******************************************************************************/
 
 	handleClick(e) {
-		// File input triggers
+		// Trigger file input
 		if (e.target.matches(this.selectors.field.dropZone) ||
 			e.target.closest(this.selectors.field.dropZone)) {
 			const dropZone = e.target.closest(this.selectors.field.dropZone);
@@ -568,11 +517,10 @@
 
 	handleChange(e) {
 		const fieldId = this.getFieldIdFromElement(e.target);
+
 		// File input change
 		if (e.target.matches(this.selectors.field.input)) {
-			const fieldId = this.getFieldIdFromElement(e.target);
 			const files = Array.from(e.target.files);
-
 			if (files.length > 0 && fieldId) {
 				this.processFiles(fieldId, files);
 			}
@@ -580,7 +528,8 @@
 
 		// Meta field changes
 		if (fieldId) {
-			if (this.fields.get(fieldId).config.destination === 'post_group') {
+			const fieldData = this.getFieldData(fieldId);
+			if (fieldData?.config.destination === 'post_group') {
 				this.handleGroupMetaChange(e.target);
 			} else {
 				this.queueUploadMeta(e);
@@ -588,363 +537,39 @@
 		}
 	}
 
-	/********************************************************************************
-	 UTILITY
-	********************************************************************************/
-	getCurrentSelection(fieldId) {
-		let selected = [];
-		for (let [key, handler] of this.selectionHandlers) {
-			if ((fieldId === key || key.includes(fieldId)) && handler.selectedItems.size > 0) {
-				selected = selected.concat([... handler.selectedItems]);
-			}
-		}
-		return selected;
-	}
-
-	getSubtypeFromMime(mimeType) {
-		if (mimeType.startsWith('image/')) return 'image';
-		if (mimeType.startsWith('video/')) return 'video';
-		return 'document';
-	}
-
-	getStatusText(status) {
-		return this.statusMapping[status] || status;
-	}
-
-	getStatusIcon(status) {
-		return window.getIcon(this.queue.icons[status]);
-	}
-	getStatusProgress(status) {
-		switch (status) {
-			case 'local_processing':
-				return 28;
-			case 'queued':
-				return 50;
-			case 'uploading':
-				return 66;
-			case 'pending':
-				return 75;
-			case 'processing':
-				return 89;
-			case 'completed':
-				return 100;
-			default:
-				return 0;
-		}
-	}
-
-	getModalType(field) {
-		// Return cached value if available
-		if (field._cachedModalType !== undefined) {
-			return field._cachedModalType;
-		}
-
-		// Safety check for field.element
-		if (!field || !field.element) {
-			field._cachedModalType = null;
-			return null;
-		}
-
-		const dialog = field.element.closest('dialog');
-		if (!dialog) {
-			field._cachedModalType = null;
-			return null;
-		}
-
-		let modalType = null;
-		if (dialog.classList.contains('edit')) modalType = 'edit';
-		else if (dialog.classList.contains('create')) modalType = 'create';
-		else if (dialog.classList.contains('bulkEdit')) modalType = 'bulkEdit';
-		else modalType = dialog.className;
-
-		// Cache the result
-		field._cachedModalType = modalType;
-		return modalType;
-	}
-	/*******************************************************************************
-	 * GROUP ACTIONS
-	 *******************************************************************************/
-
-	handleAction(button) {
-		const action = button.dataset.action;
-		const fieldId = this.getFieldIdFromElement(button);
-		switch(action) {
-			case 'add-to-group':
-				this.handleAddToGroup(button);
-				break;
-			case 'delete-group':
-				this.handleDeleteGroup(button);
-				break;
-			case 'delete-upload':
-			case 'remove-from-group':
-				this.handleRemoveItem(button);
-				break;
-			case 'upload':
-				//upload groups
-				let field = this.fields.get(fieldId);
-				field.element.closest('details').open = false;
-				document.body.classList.add('uploading');
-
-				this.submitUploads(fieldId);
-				break;
-			case 'restore':
-				this.handleRestoreUploads().then(()=>{});
-				break;
-			case 'clear-cache':
-				if (!confirm(`Save these uploads for later?`)) {
-					this.cleanupStoredUploads();
-				}
-				this.cleanupRestore();
-				break;
-		}
-	}
-
-	handleAddToGroup(button) {
-		const fieldElement = button.closest(this.selectors.field.field);
-		const fieldId = fieldElement?.dataset.uploader;
-
-		if (!fieldId) return;
-
-		const selected = this.selected.get(fieldId);
-
-		if (!selected || selected.size === 0) {
-			// Create empty group
-			this.createGroup(fieldId);
-		} else {
-			// Create group with selected items
-			const group = this.createGroup(fieldId);
-			if (!group) return;
-
-			selected.forEach(uploadId => {
-				this.addToGroup(uploadId, group.grid);
-			});
-
-			// Clear selection
-			const handler = this.selectionHandlers.get(fieldId);
-			handler?.clearSelection();
-
-			this.a11y.announce(`Created group with ${selected.size} items`);
-		}
-
-		this.schedulePersistance(fieldId);
-	}
-
-	handleDeleteGroup(button) {
-		const group = button.closest(this.selectors.groups.container);
-		if (!group) return;
-
-		const groupId = group.dataset.groupId;
-		const fieldId = this.getFieldIdFromElement(group);
-
-		if (!confirm('Delete this group? Items will be moved back to the upload area.')) {
-			return;
-		}
-
-		// Move items back to preview
-		const items = group.querySelectorAll(this.selectors.items.item);
-		items.forEach(item => {
-			const uploadId = item.dataset.uploadId;
-			this.removeFromGroup(uploadId);
-		});
-
-		// Remove group
-		this.deleteGroup(groupId);
-
-		this.a11y.announce('Group deleted, items returned to upload area');
-		this.schedulePersistance(fieldId);
-	}
-
-	handleRemoveItem(button) {
-		const item = button.closest(this.selectors.items.item);
-		if (!item) return;
-
-		const uploadId = item.dataset.uploadId;
-		const fieldId = this.getFieldIdFromElement(item);
-
-		if (!confirm('Remove this item?')) {
-			return;
-		}
-
-		this.removeUpload(fieldId, uploadId);
-		this.a11y.announce('Item removed');
-		this.schedulePersistance(fieldId);
-	}
-
-	/*******************************************************************************
-	 * SELECTION MANAGEMENT
-	 *******************************************************************************/
-
-	/**
-	 * Add selection handler for a field
-	 */
-	addFieldSelectionHandler(fieldId) {
-		if (this.selectionHandlers.has(fieldId)) {
-			return this.selectionHandlers.get(fieldId);
-		}
-
-		const field = this.fields.get(fieldId);
-		if (!field) return;
-
-		const container = field.ui.field;
-		if (!container) return;
-
-		const handler = new window.jvbHandleSelection({
-			container: container,
-			ui: {
-				selectAll: container.querySelector('[name="select-all-uploads"]'),
-				bulkControls: container.querySelector('.selection-actions'),
-				count: container.querySelector('.selection-count')
-			},
-			itemSelector: '[data-upload-id]',
-			checkboxSelector: '[name*="select-item"]'
-		});
-
-		// Subscribe to selection changes
-		handler.subscribe((event, data) => {
-			switch(event) {
-				case 'item-selected':
-				case 'item-deselected':
-				case 'range-selected':
-					this.selected.set(fieldId, data.selectedItems);
-					break;
-				case 'select-all':
-					this.handleSelectAll(data.container, data.selected);
-					break;
-			}
-		});
-
-		this.selectionHandlers.set(fieldId, handler);
-		return handler;
-	}
-
-	/**
-	 * Add selection handler for a group
-	 */
-	addGroupSelectionHandler(fieldId, groupId) {
-		const handlerKey = `${fieldId}_${groupId}`;
-
-		if (this.selectionHandlers.has(handlerKey)) {
-			return this.selectionHandlers.get(handlerKey);
-		}
-
-		const group = this.groups.get(groupId);
-		if (!group) return;
-
-		const handler = new window.jvbHandleSelection({
-			container: group.element,
-			ui: {
-				selectAll: group.element.querySelector(this.selectors.groups.selectAll),
-				bulkControls: group.element.querySelector(this.selectors.groups.actions),
-				count: group.element.querySelector(this.selectors.groups.count)
-			},
-			itemSelector: '[data-upload-id]',
-			checkboxSelector: '[name*="select-item"]'
-		});
-
-		handler.subscribe((event, data) => {
-			switch(event) {
-				case 'item-selected':
-				case 'item-deselected':
-				case 'range-selected':
-					this.selected.set(fieldId, data.selectedItems);
-					break;
-				case 'select-all':
-					this.handleSelectAll(data.container, data.selected);
-					break;
-			}
-		});
-
-		this.selectionHandlers.set(handlerKey, handler);
-		return handler;
-	}
-
-	handleSelectAll(container, selected) {
-	}
-
-	/*******************************************************************************
-	 * HELPER METHODS
-	 *******************************************************************************/
-
-	determineFieldId(fieldElement) {
-		const content = fieldElement.dataset.content ||
-			fieldElement.closest('dialog')?.dataset.content ||
-			fieldElement.closest('form')?.dataset.save || '';
-		const itemID = fieldElement.dataset.itemId ||
-			fieldElement.closest('dialog')?.dataset.itemId || '';
-		const field = fieldElement.dataset.field || '';
-
-		return `${content}_${itemID}_${field}`;
-	}
-
-	getFromElement(element, type) {
-		const map = {
-			'field': { selector: this.selectors.field.field, key: 'uploader', store: this.fields },
-			'upload': { selector: this.selectors.items.item, key: 'uploadId', store: this.uploads },
-			'group': { selector: this.selectors.groups.container, key: 'groupId', store: this.groups }
-		};
-
-		const config = map[type];
-		if (!config) return null;
-
-		const el = element.closest(config.selector);
-		if (!el) return null;
-
-		const id = el.dataset[config.key];
-		return config.store.get(id);
-	}
-	getFieldFromElement(el) { return this.getFromElement(el, 'field'); }
-	getUploadFromElement(el) { return this.getFromElement(el, 'upload'); }
-	getGroupFromElement(el) { return this.getFromElement(el, 'group'); }
-
-	getFieldIdFromElement(el) { return this.getFromElement(el, 'field')?.id ?? null};
-	getUploadIdFromElement(el) {return this.getFromElement(el, 'upload')?.id ?? null};
-	getGroupIdFromElement(el) {return this.getFromElement(el, 'group')?.id ?? null};
-
-
 	/*******************************************************************************
 	 * FILE PROCESSING
 	 *******************************************************************************/
-	async processFiles(fieldId, files) {
-		const field = this.fields.get(fieldId);
-		if (!field) return;
 
-		// Hide upload container, show group display
-		if (field.ui.dropZone) {
-			field.ui.dropZone.hidden = true;
+	async processFiles(fieldId, files) {
+		const fieldData = this.getFieldData(fieldId);
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldData || !fieldEl) return;
+
+		// Show group display, hide upload zone
+		if (fieldEl.ui.dropZone) {
+			fieldEl.ui.dropZone.hidden = true;
 		}
-		if (field.ui.groups.display) {
-			field.ui.groups.display.hidden = false;
+		if (fieldEl.ui.groups?.display) {
+			fieldEl.ui.groups.display.hidden = false;
 		}
 
 		const totalFiles = files.length;
 		let processedCount = 0;
 
-		// Show initial progress
 		this.updateUploadProgress(fieldId, 0, totalFiles, 'Processing files...');
 
-		// Initialize field uploads set if needed
-		if (!field.uploads) {
-			field.uploads = new Set();
-		}
-
-		// Process files
-		const processPromises = Array.from(files).map(async (file, index) => {
+		const processPromises = Array.from(files).map(async (file) => {
 			try {
-
-				// Create upload ID
 				const uploadId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
 
-				// Create upload data
+				// Create upload data (without blob initially)
 				const uploadData = {
 					id: uploadId,
-					attachment_id: null,
+					attachmentId: null,
 					fieldId: fieldId,
-					originalFile: file,
-					processedFile: null,
-					preview: null,
 					status: 'local_processing',
-					element: null,
-					location: null,
+					groupId: null,
 					meta: {
 						originalName: file.name,
 						size: file.size,
@@ -952,50 +577,55 @@
 					}
 				};
 
-				// Create preview URL
-				uploadData.preview = this.createPreviewUrl(file);
+				// Process file
+				const preview = this.createPreviewUrl(file);
+				const processedFile = file.type.startsWith('image/')
+					? await this.processImage(file, fieldData.config.subtype)
+					: file;
 
-				// Process the file (resize if image)
-				if (file.type.startsWith('image/')) {
-					uploadData.processedFile = await this.processImage(file, field.subtype);
-				} else {
-					uploadData.processedFile = file;
-				}
-
-				// Store blob data separately in IndexedDB
-				await this.uploadStore.saveBlob(uploadId, uploadData.processedFile || file);
+				// Store blob data as ArrayBuffer
+				await this.saveBlobData(uploadId, processedFile || file);
 
 				// Create DOM element
 				const subtype = this.getSubtypeFromMime(file.type);
-				uploadData.element = this.createUploadElement({
-					...uploadData,
+				const element = this.createUploadElement({
+					id: uploadId,
+					preview: preview,
+					meta: uploadData.meta,
 					subtype: subtype
-				}, field.config.destination === 'post_group');
+				}, fieldData.config.destination === 'post_group');
 
-				// Show progress on the item
+				// Show progress
 				this.showUploadProgress(uploadId, true);
 				this.updateUploadItemProgress(uploadId, 50, 'local_processing');
 
 				// Add to preview grid
-				if (field.ui.preview) {
-					field.ui.preview.appendChild(uploadData.element);
-					uploadData.location = field.ui.preview;
+				if (fieldEl.ui.preview) {
+					fieldEl.ui.preview.appendChild(element);
+
+					// Store runtime element data
+					this.uploadElements.set(uploadId, {
+						element: element,
+						preview: preview,
+						location: fieldEl.ui.preview
+					});
 				}
 
-				// Store upload
-				this.uploads.set(uploadId, uploadData);
-				field.uploads.add(uploadId);
+				// Store persistent data
+				uploadData.status = 'processed';
+				await this.uploadStore.save(uploadData);
+
+				// Add to field
+				fieldData.uploads.add(uploadId);
+				await this.saveFieldData(fieldData);
 
 				// Update progress
 				processedCount++;
 				this.updateUploadProgress(fieldId, processedCount, totalFiles, 'Processing files...');
 				this.updateUploadItemProgress(uploadId, 100, 'processed');
-				uploadData.status = 'processed';
 
-				// Fade out item progress after a moment
-				setTimeout(() => {
-					this.showUploadProgress(uploadId, false);
-				}, 1000);
+				// Fade out progress
+				setTimeout(() => this.showUploadProgress(uploadId, false), 1000);
 
 				return uploadId;
 
@@ -1007,192 +637,21 @@
 			}
 		});
 
-		// Wait for all files to process
 		await Promise.all(processPromises);
 
 		this.updateFieldState(fieldId);
-		// Cache the state (now without DOM references)
-		await this.schedulePersistance(fieldId);
+		this.refreshSortable(fieldId);
 
 		// Queue for upload if in direct mode
-		if (field.config.destination !== 'post_group') {
+		if (fieldData.config.destination !== 'post_group') {
 			await this.queueUpload(fieldId);
-			// Lock uploads if max reached
 			this.maybeLockUploads(fieldId);
 		}
-
 	}
 
-	updateFieldState(fieldId) {
-		const field = this.fields.get(fieldId);
-		if (!field || !field.ui.field) return;
-
-		const container = field.ui.field;
-		const uploadCount = field.uploads?.size || 0;
-		const hasGroups = field.ui.groups?.container?.querySelectorAll('.upload-group').length > 0;
-
-		// Set data attributes for CSS targeting
-		container.dataset.hasUploads = uploadCount > 0 ? 'true' : 'false';
-		container.dataset.uploadCount = uploadCount.toString();
-		container.dataset.hasGroups = hasGroups ? 'true' : 'false';
-
-		// Update ARIA labels for accessibility
-		if (field.ui.preview) {
-			field.ui.preview.setAttribute('aria-label',
-				`Upload preview area with ${uploadCount} item${uploadCount !== 1 ? 's' : ''}`
-			);
-		}
-	}
-
-	updateUploadProgress(fieldId, current, total, message) {
-		const field = this.fields.get(fieldId);
-		if (!field?.ui?.progress?.progress) return;
-
-		const progress = field.ui.progress;
-		const percent = total > 0 ? (current / total) * 100 : 0;
-
-		if (progress.fill) {
-			progress.fill.style.width = `${percent}%`;
-		}
-		if (progress.text) {
-			progress.text.textContent = message;
-		}
-		if (progress.count) {
-			progress.count.textContent = `${current}/${total}`;
-		}
-
-		progress.progress.hidden = (current === total);
-	}
-
-	updateFieldStatus(fieldId, status) {
-		const field = this.fields.get(fieldId);
-		if (!field) return;
-
-		field.state = status;
-		// Update UI based on status
-	}
-
-	updateUploadStatus(uploadId, status) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload) return;
-
-		upload.status = status;
-		this.updateUploadUI(uploadId);
-	}
-
-	updateUploadUI(uploadId) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload?.element) return;
-
-		// Update status classes
-		upload.element.className = upload.element.className.replace(/status-[\w-]+/g, '');
-		upload.element.classList.add(`status-${upload.status}`);
-
-		// Update progress if showing
-		const progress = upload.element.querySelector('.progress');
-		if (progress) {
-			this.updateUploadItemProgress(uploadId,
-				this.getStatusProgress(upload.status),
-				upload.status
-			);
-		}
-	}
-
-	/**
-	 * Show/hide progress indicator on individual upload items
-	 */
-	showUploadProgress(uploadId, show = true) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload || !upload.element) return;
-
-		const progressEl = upload.element.querySelector('.progress');
-		if (progressEl) {
-			if (show) {
-				progressEl.style.removeProperty('animation');
-				progressEl.hidden = false;
-			} else {
-				progressEl.style.animation = 'fadeOut var(--transition-base)';
-				setTimeout(() => {
-					progressEl.hidden = true;
-				}, 300);
-			}
-		}
-	}
-
-	/**
-	 * Update individual upload progress bar
-	 */
-	updateUploadItemProgress(uploadId, percent, status = null) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload || !upload.element) return;
-
-		const progressEl = upload.element.querySelector('.progress');
-		if (!progressEl) return;
-
-		const fill = progressEl.querySelector('.fill');
-		const details = progressEl.querySelector('.details');
-		const icon = progressEl.querySelector('.icon');
-
-		if (fill) {
-			fill.style.width = `${percent}%`;
-		}
-
-		if (status && details) {
-			details.textContent = this.getStatusText(status);
-		}
-
-		if (status && icon) {
-			icon.innerHTML = this.getStatusIcon(status).outerHTML;
-		}
-	}
-	checkFieldLimits(fieldId, additionalFiles) {
-		const field = this.fields.get(fieldId);
-		if (!field) return false;
-
-		const currentCount = field.uploads?.size || 0;
-		const totalCount = currentCount + additionalFiles;
-
-		return totalCount <= field.maxFiles;
-
-
-	}
-	validateFile(file, field) {
-		// Type validation
-		if (!this.settings.allowedTypes.includes(file.type)) {
-			this.notify(`Invalid file type: ${file.type}`, 'error');
-			return false;
-		}
-
-		// Size validation
-		if (file.size > this.settings.maxFileSize) {
-			this.notify(`File too large: ${this.formatBytes(file.size)}`, 'error');
-			return false;
-		}
-
-		return true;
-	}
-
-	formatBytes(bytes, decimals = 2) {
-		if (bytes === 0) return '0 Bytes';
-
-		const k = 1024;
-		const dm = decimals < 0 ? 0 : decimals;
-		const sizes = ['Bytes', 'KB', 'MB', 'GB'];
-
-		const i = Math.floor(Math.log(bytes) / Math.log(k));
-
-		return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-	}
-
-	shouldProcessClientSide(file, subtype) {
-		// Only process images client-side
-		if (subtype === 'image' && file.type.startsWith('image/')) {
-			return true;
-		}
-
-		// Videos and documents go straight to server
-		return false;
-	}
+	/*******************************************************************************
+	 * IMAGE PROCESSING (simplified - keeping existing logic)
+	 *******************************************************************************/
 
 	async processImage(file, uploadId) {
 		const timeout = this.worker.settings.timeout;
@@ -1201,27 +660,19 @@
 			let timeoutId;
 			let taskCompleted = false;
 
-			// Set timeout
 			timeoutId = setTimeout(() => {
 				if (!taskCompleted) {
 					taskCompleted = true;
-
-					// Remove from active tasks
 					this.worker.tasks.delete(uploadId);
-
-					// Maybe restart worker if configured
 					if (this.worker.settings.restartAfterTimeout) {
 						this.restartCompressionWorker();
 					}
-
 					reject(new Error(`Processing timeout for ${file.name}`));
 				}
 			}, timeout);
 
-			// Track this task
 			this.worker.tasks.set(uploadId, { file, timeoutId });
 
-			// Process image
 			this.handleProcess(file, uploadId)
 				.then(result => {
 					if (!taskCompleted) {
@@ -1243,7 +694,6 @@
 	}
 
 	async handleProcess(file, uploadId) {
-		// Skip non-images
 		if (!file.type.startsWith('image/')) {
 			return file;
 		}
@@ -1251,14 +701,11 @@
 		const maxDimension = this.getMaxDimension();
 		const quality = 0.85;
 
-		// Try worker first if available
 		if (this.shouldUseWorker(file)) {
 			try {
-				// Ensure worker is initialized
 				if (!this.worker.worker) {
 					this.initCompressionWorker();
 				}
-
 				if (this.worker.worker) {
 					return await this.processWithWorker(file, uploadId, maxDimension, quality);
 				}
@@ -1267,13 +714,9 @@
 			}
 		}
 
-		// Fallback to main thread
 		return await this.processOnMainThread(file, maxDimension, quality);
 	}
 
-	/**
-	 * Process image on main thread with better error handling
-	 */
 	async processOnMainThread(file, maxDimension, quality) {
 		return new Promise((resolve, reject) => {
 			const img = new Image();
@@ -1288,7 +731,6 @@
 					URL.revokeObjectURL(objectUrl);
 					objectUrl = null;
 				}
-				// Explicitly clean up canvas
 				canvas.width = 1;
 				canvas.height = 1;
 				ctx.clearRect(0, 0, 1, 1);
@@ -1300,7 +742,6 @@
 					canvas.width = width;
 					canvas.height = height;
 
-					// Enhanced image smoothing
 					ctx.imageSmoothingEnabled = true;
 					ctx.imageSmoothingQuality = 'high';
 					ctx.drawImage(img, 0, 0, width, height);
@@ -1347,67 +788,41 @@
 		});
 	}
 
-	/**
-	 * Get optimal output format
-	 */
 	getOptimalFormat(file) {
-		// Keep original format for certain types
 		if (file.type === 'image/gif' || file.type === 'image/svg+xml') {
 			return file.type;
 		}
-
-		// Use WebP if supported, otherwise JPEG
 		return this.supportsWebP() ? 'image/webp' : 'image/jpeg';
 	}
 
-	/**
-	 * Get optimal quality setting
-	 */
 	getOptimalQuality(file, requestedQuality) {
-		// Higher quality for smaller files
 		if (file.size < 500 * 1024) return Math.max(requestedQuality, 0.9);
 		if (file.size < 2 * 1024 * 1024) return requestedQuality;
-
-		// Lower quality for very large files
 		return Math.min(requestedQuality, 0.8);
 	}
 
-	/**
-	 * Generate processed file name
-	 */
 	getProcessedFileName(originalFile, outputFormat) {
 		const baseName = originalFile.name.replace(/\.[^/.]+$/, '');
-
 		const extensions = {
 			'image/webp': '.webp',
 			'image/jpeg': '.jpg',
 			'image/png': '.png',
 			'image/gif': '.gif'
 		};
-
 		return baseName + (extensions[outputFormat] || '.jpg');
 	}
 
-	/**
-	 * Get maximum dimension based on device capabilities
-	 */
 	getMaxDimension() {
 		const screenWidth = window.screen.width;
 		const devicePixelRatio = window.devicePixelRatio || 1;
-
-		// Scale based on device capabilities
 		if (screenWidth * devicePixelRatio > 2560) return 2400;
 		if (screenWidth * devicePixelRatio > 1920) return 1920;
 		return 1200;
 	}
 
-	/**
-	 * Determine if we should use Web Worker
-	 */
 	shouldUseWorker(file) {
-		// Use worker for large files or when available
 		return this.worker.worker &&
-			file.size > 1024 * 1024 && // > 1MB
+			file.size > 1024 * 1024 &&
 			typeof OffscreenCanvas !== 'undefined';
 	}
 
@@ -1418,14 +833,11 @@
 				return;
 			}
 
-			// Create unique message ID for this task
 			const messageId = `${uploadId}_${Date.now()}`;
 
-			// Handler for this specific message
 			const messageHandler = (e) => {
 				if (e.data.messageId !== messageId) return;
 
-				// Remove handler
 				this.worker.worker.removeEventListener('message', messageHandler);
 				this.worker.worker.removeEventListener('error', errorHandler);
 
@@ -1447,11 +859,9 @@
 				reject(new Error(`Worker error: ${error.message}`));
 			};
 
-			// Add handlers
 			this.worker.worker.addEventListener('message', messageHandler);
 			this.worker.worker.addEventListener('error', errorHandler);
 
-			// Send message to worker
 			this.worker.worker.postMessage({
 				messageId,
 				file,
@@ -1462,85 +872,45 @@
 		});
 	}
 
-	/**
-	 * Restart compression worker
-	 */
 	restartCompressionWorker() {
-		// Terminate existing worker
 		if (this.worker.worker) {
 			this.worker.worker.terminate();
 			this.worker.worker = null;
 		}
-
-		// Clear active tasks
 		this.worker.tasks.clear();
-
-		// Check restart limit
 		if (this.worker.restart.count >= this.worker.restart.max) {
 			console.error('Max worker restarts reached, disabling worker');
 			return;
 		}
-
 		this.worker.restart.count++;
-
-		// Reinitialize
 		this.initCompressionWorker();
 	}
 
-	/**
-	 * Initialize Web Worker for image compression
-	 */
 	initCompressionWorker() {
 		if (this.worker.worker || typeof Worker === 'undefined') return;
 
 		try {
 			const workerScript = `
-            self.onmessage = async function(e) {
-                const { messageId, file, maxDimension, quality, outputFormat } = e.data;
-
-                try {
-                    // Create ImageBitmap from file
-                    const bitmap = await createImageBitmap(file);
-
-                    // Calculate dimensions
-                    const scale = Math.min(maxDimension / bitmap.width, maxDimension / bitmap.height, 1);
-                    const width = Math.round(bitmap.width * scale);
-                    const height = Math.round(bitmap.height * scale);
-
-                    // Create OffscreenCanvas
-                    const canvas = new OffscreenCanvas(width, height);
-                    const ctx = canvas.getContext('2d');
-
-                    // Draw and resize
-                    ctx.imageSmoothingEnabled = true;
-                    ctx.imageSmoothingQuality = 'high';
-                    ctx.drawImage(bitmap, 0, 0, width, height);
-
-                    // Clean up bitmap
-                    bitmap.close();
-
-                    // Convert to blob
-                    const blob = await canvas.convertToBlob({
-                        type: outputFormat,
-                        quality: quality
-                    });
-
-                    self.postMessage({
-                        messageId,
-                        success: true,
-                        blob: blob,
-                        format: outputFormat
-                    });
-
-                } catch (error) {
-                    self.postMessage({
-                        messageId,
-                        success: false,
-                        error: error.message
-                    });
-                }
-            };
-        `;
+				self.onmessage = async function(e) {
+					const { messageId, file, maxDimension, quality, outputFormat } = e.data;
+					try {
+						const bitmap = await createImageBitmap(file);
+						const scale = Math.min(maxDimension / bitmap.width, maxDimension / bitmap.height, 1);
+						const width = Math.round(bitmap.width * scale);
+						const height = Math.round(bitmap.height * scale);
+						const canvas = new OffscreenCanvas(width, height);
+						const ctx = canvas.getContext('2d');
+						ctx.imageSmoothingEnabled = true;
+						ctx.imageSmoothingQuality = 'high';
+						ctx.drawImage(bitmap, 0, 0, width, height);
+						bitmap.close();
+						const blob = await canvas.convertToBlob({ type: outputFormat, quality: quality });
+						self.postMessage({ messageId, success: true, blob: blob, format: outputFormat });
+					} catch (error) {
+						self.postMessage({ messageId, success: false, error: error.message });
+					}
+				};
+			`;
 
 			const blob = new Blob([workerScript], { type: 'application/javascript' });
 			this.worker.worker = new Worker(this.createPreviewUrl(blob));
@@ -1551,30 +921,18 @@
 		}
 	}
 
-	/**
-	 * Calculate optimal dimensions with aspect ratio preservation
-	 */
 	calculateOptimalDimensions(img, maxDimension) {
 		let { width, height } = img;
-
-		// Don't upscale
 		if (width <= maxDimension && height <= maxDimension) {
 			return { width, height };
 		}
-
-		// Calculate scale factor
 		const scale = Math.min(maxDimension / width, maxDimension / height);
-
 		return {
 			width: Math.round(width * scale),
 			height: Math.round(height * scale)
 		};
 	}
 
-
-	/**
-	 * Check WebP support
-	 */
 	supportsWebP() {
 		const canvas = document.createElement('canvas');
 		return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
@@ -1582,7 +940,6 @@
 
 	createPreviewUrl(file) {
 		const url = URL.createObjectURL(file);
-		// Track for cleanup
 		if (!this.previewUrls) this.previewUrls = new Set();
 		this.previewUrls.add(url);
 		return url;
@@ -1595,131 +952,19 @@
 		}
 	}
 
-	maybeLockUploads(fieldId) {
-		const field = this.fields.get(fieldId);
-		if (!field?.ui?.dropZone) return;
-
-		if (field.config.destination === 'post_group') {
-			return;
-		}
-
-		const uploadCount = field.uploads?.size || 0;
-		const maxFiles = field.config?.maxFiles || 999;
-
-		// Hide dropzone if at max files
-		field.ui.dropZone.hidden = uploadCount >= maxFiles;
-
-		// Update field state
-		field.element.classList.toggle('at-max-uploads', uploadCount >= maxFiles);
-	}
-	createUploadElement(upload, draggable = false) {
-		let image = window.getTemplate('uploadItem');
-		if (!image) {
-			console.error('Image template not found');
-			return;
-		}
-		image.dataset.uploadId = upload.id;
-		if (upload.originalFile) {
-			image.dataset.subtype = this.getSubtypeFromMime(upload.originalFile.type);
-		}
-
-
-		image.querySelector('[name="featured"]').value = upload.id;
-		let [
-			featured,
-			img,
-			video,
-			preview,
-			details
-		] = [
-			image.querySelector('[name="featured"]'),
-			image.querySelector('img'),
-			image.querySelector('video'),
-			image.querySelector('label > span'),
-			image.querySelector('details')
-		];
-		[
-			featured.value,
-			img.src,
-			img.alt
-		] = [
-			upload.id,
-			upload.preview,
-			upload.originalFile?.name ?? upload.meta?.originalName ?? '',
-		];
-
-		switch (image.dataset.subtype) {
-			case 'image':
-				[
-					img.src,
-					img.alt
-				] = [
-					upload.preview,
-					upload.originalFile?.name ?? upload.meta?.originalName?? ''
-				];
-				video.remove();
-				preview.remove();
-				break;
-			case 'video':
-				video.src = upload.preview;
-				img.remove();
-				preview.remove();
-				break;
-			case 'document':
-				const fileName = upload.originalFile?.name ?? upload.meta?.originalName ?? '';
-				const extension = fileName.split('.').pop()?.toLowerCase() ?? '';
-				const iconMap = {
-					'pdf': 'file-pdf',
-					'csv': 'file-csv',
-					'doc': 'file-doc',
-					'docx': 'file-doc',
-					'txt': 'file-txt',
-					'xls': 'file-xls',
-					'xlsx': 'file-xls'
-				};
-
-				const icon = window.getIcon(iconMap[extension] || 'file');
-
-				preview.innerText = upload.originalFile.name;
-				preview.prepend(icon);
-				img.remove();
-				video.remove();
-				break;
-		}
-		if (details) {
-			let template = window.getTemplate('uploadMeta');
-			if (template){
-				details.append(template);
-			}
-		}
-		image.draggable = draggable;
-
-		// Update input IDs safely
-		image.querySelectorAll('input').forEach(input => {
-			let id = input.id;
-			if (id) {
-				let newId = id + upload.id;
-				let label = input.parentNode.querySelector(`label[for="${id}"]`);
-				input.id = newId;
-				if (label) {
-					label.htmlFor = newId;
-				}
-			}
-		});
-
-		return image;
-	}
 	/*******************************************************************************
 	 * QUEUE INTEGRATION
 	 *******************************************************************************/
+
 	async submitUploads(fieldId) {
-		const field = this.fields.get(fieldId);
-		if (!field?.uploads || field.uploads.size === 0) {
+		const fieldData = this.getFieldData(fieldId);
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldData?.uploads || fieldData.uploads.size === 0) {
 			return;
 		}
 
-		let uploads = Array.from(field.uploads);
-		if (uploads.length === 0) {
+		let uploadIds = Array.from(fieldData.uploads);
+		if (uploadIds.length === 0) {
 			this.error.log('No uploads to upload', {
 				component: 'UploadManager',
 				action: 'submitGroupedUploads',
@@ -1744,108 +989,88 @@
 		const formData = new FormData();
 		let uploadMap = [];
 
-		uploads = uploads.map((upload) => {
-			return this.uploads.get(upload);
-		});
-
-		fieldGroups.forEach((group, groupIndex) => {
+		// Process each group
+		for (const group of fieldGroups) {
 			const post = {
 				images: [],
 				fields: {}
 			};
+
+			// Add group metadata
 			for (let [name, value] of Object.entries(group.changes)) {
 				post.fields[name] = value;
 			}
 
-			let groupUploads = uploads.filter((upload) => {
-				return upload['groupId'] === group.id;
+			// Get uploads for this group
+			const groupUploadIds = uploadIds.filter(uploadId => {
+				const upload = this.uploadStore.get(uploadId);
+				return upload?.groupId === group.id;
 			});
 
-			groupUploads.forEach((upload) => {
-				if (upload) {
-					const fileToUpload = upload.processedFile || upload.originalFile;
-					if (fileToUpload) {
-						formData.append('files[]', fileToUpload);
-
-						const imageData = {
-							upload_id: upload.id,
-							index: uploadMap.length
-						};
-						post.images.push(imageData);
-						uploadMap.push(upload.id);
-					}
-				}
-			});
-			// Add images for this group
-			// group.uploads.forEach(uploadId => {
-			// 	const upload = this.uploads.get(uploadId);
-			// 	if (upload) {
-			// 		const fileToUpload = upload.processedFile || upload.originalFile;
-			// 		if (fileToUpload) {
-			// 			formData.append('files[]', fileToUpload);
-			//
-			// 			const imageData = {
-			// 				upload_id: upload.id,
-			// 				index: uploadMap.length
-			// 			};
-			//
-			// 			// Check if this is the featured image
-			// 			const radioInput = upload.element?.querySelector('[name="featured"]');
-			// 			if (radioInput?.checked) {
-			// 				post.fields.featured = upload.id;
-			// 			}
-			//
-			// 			post.images.push(imageData);
-			// 			uploadMap.push(upload.id);
-			// 		}
-			// 	}
-			// });
-
-			posts.push(post);
-		});
-
-		//Each remaining upload (without a groupId) becomes its own post
-		let remainingUploads = uploads.filter((upload) => {
-			return !Object.hasOwn(upload, 'groupId');
-		});
-
-		remainingUploads.forEach((upload) => {
-			if (upload) {
-
-				const post = {
-					images: [],
-					fields: {}
-				};
-				const fileToUpload = upload.processedFile || upload.originalFile;
-				if (fileToUpload) {
-					formData.append('files[]', fileToUpload);
+			// Add files for this group
+			for (const uploadId of groupUploadIds) {
+				const file = await this.getBlobData(uploadId);
+				if (file) {
+					formData.append('files[]', file);
 
 					const imageData = {
-						upload_id: upload.id,
+						upload_id: uploadId,
 						index: uploadMap.length
 					};
+
+					// Check if featured
+					const uploadEl = this.uploadElements.get(uploadId);
+					const radioInput = uploadEl?.element?.querySelector('[name="featured"]');
+					if (radioInput?.checked) {
+						post.fields.featured = uploadId;
+					}
+
 					post.images.push(imageData);
-					uploadMap.push(upload.id);
+					uploadMap.push(uploadId);
 				}
-				posts.push(post);
 			}
+
+			posts.push(post);
+		}
+
+		// Handle remaining uploads (without groupId) - each becomes its own post
+		const remainingUploadIds = uploadIds.filter(uploadId => {
+			const upload = this.uploadStore.get(uploadId);
+			return !upload?.groupId;
 		});
 
+		for (const uploadId of remainingUploadIds) {
+			const post = {
+				images: [],
+				fields: {}
+			};
+
+			const file = await this.getBlobData(uploadId);
+			if (file) {
+				formData.append('files[]', file);
+
+				const imageData = {
+					upload_id: uploadId,
+					index: uploadMap.length
+				};
+				post.images.push(imageData);
+				uploadMap.push(uploadId);
+			}
+
+			posts.push(post);
+		}
 
 		// Add metadata to FormData
-		formData.append('content', field.config.content);
-		formData.append('user', field.config.itemID); // Assuming itemID is user ID
+		formData.append('content', fieldData.config.content);
+		formData.append('user', fieldData.config.itemID);
 		formData.append('posts', JSON.stringify(posts));
 		formData.append('upload_ids', JSON.stringify(uploadMap));
 
-		for (const [key, value] of formData.entries()) {
-			console.log(key, value);
-		}
 		const operation = {
 			endpoint: 'uploads/groups',
 			method: 'POST',
 			data: formData,
-			title: `Creating ${posts.length} ${field.config.content}${posts.length > 1 ? 's' : ''} from uploads...`,
+			title: `Creating ${posts.length} ${fieldData.config.content}${posts.length > 1 ? 's' : ''} from uploads...`,
 			popup: `Creating ${posts.length} post${posts.length > 1 ? 's' : ''}...`,
 			canMerge: false,
 			headers: {
@@ -1857,15 +1082,20 @@
 		try {
 			const operationId = await this.queue.addToQueue(operation);
 
-			uploads.forEach(uploadId => {
-				let upload = this.uploads.get(uploadId);
+			// Update upload statuses
+			uploadIds.forEach(uploadId => {
+				const upload = this.uploadStore.get(uploadId);
 				if (upload) {
 					upload.operationId = operationId;
+					upload.status = 'queued';
+					this.uploadStore.save(upload);
 					this.updateUploadStatus(uploadId, 'queued');
 				}
 			});
 
-			field.operationId = operationId;
+			fieldData.operationId = operationId;
+			await this.saveFieldData(fieldData);
+
 			this.a11y.announce(`Creating ${posts.length} post${posts.length > 1 ? 's' : ''} from your uploads`);
 
 			return operationId;
@@ -1876,181 +1106,107 @@
 				fieldId: fieldId
 			});
 			throw error;
-		} finally {
-			this.schedulePersistance(field.id);
 		}
 	}
 
 	async queueUpload(fieldId) {
-		const field = this.fields.get(fieldId);
-		if (!field?.uploads) return;
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldData?.uploads || fieldData.uploads.size === 0) return;
 
-		const uploads = Array.from(field.uploads);
-		if (uploads.length === 0) {
-			return;
-		}
+		const uploads = Array.from(fieldData.uploads);
+		const data = this.prepareUploadData(fieldData, uploads);
 
-		const data = this.prepareUploadData(field, uploads);
 		this.a11y.announce('Queuing for upload');
-		let img = (uploads.length === 1) ? 'file' : 'files';
+
 		const operation = {
 			endpoint: 'uploads',
 			method: 'POST',
 			data: data,
-			title: `Uploading ${uploads.length} ${img} to server...`,
-			popup: `Uploading ${uploads.length} ${img}...`,
+			title: `Uploading ${uploads.length} file${uploads.length > 1 ? 's' : ''} to server...`,
+			popup: `Uploading ${uploads.length} file${uploads.length > 1 ? 's' : ''}...`,
 			canMerge: false,
-			headers: {
-				'action_nonce': jvbSettings.dash
-			},
+			headers: { 'action_nonce': jvbSettings.dash },
 			append: '_upload'
-		}
+		};
+
 		try {
 			const operationId = await this.queue.addToQueue(operation);
 
+			// Update upload statuses
 			uploads.forEach(uploadId => {
-				let upload = this.uploads.get(uploadId);
-				if (!upload) {
-					return;
+				const upload = this.uploadStore.get(uploadId);
+				if (upload) {
+					upload.operationId = operationId;
+					upload.status = 'queued';
+					this.uploadStore.save(upload);
+					this.updateUploadStatus(uploadId, 'queued');
 				}
-				upload.operationId = operationId;
-				this.updateUploadStatus(uploadId, 'queued');
 			});
-			field.operationId = operationId;
+
+			fieldData.operationId = operationId;
+			await this.saveFieldData(fieldData);
 
 			return operationId;
 		} catch (error) {
 			throw error;
-		} finally {
-			this.schedulePersistance(field.id);
 		}
 	}
 
-	prepareUploadData(field, uploads) {
-
+	async prepareUploadData(fieldData, uploads) {
 		const formData = new FormData();
-		formData.append('content', field.config.content);
-		formData.append('mode', field.config.mode);
-		formData.append('field_name', field.config.name);
-		formData.append('fieldId', field.id);
-		formData.append('field_type', field.config.type);
-		formData.append('subtype', field.config.subtype);
-		formData.append('item_id', field.config.itemID);		//post, term, or user id
-		formData.append('destination', field.config.destination || 'meta'); //meta, post, post_group
+		formData.append('content', fieldData.config.content);
+		formData.append('mode', fieldData.config.mode);
+		formData.append('field_name', fieldData.config.name);
+		formData.append('fieldId', fieldData.id);
+		formData.append('field_type', fieldData.config.type);
+		formData.append('subtype', fieldData.config.subtype);
+		formData.append('item_id', fieldData.config.itemID);
+		formData.append('destination', fieldData.config.destination || 'meta');
+
 		let uploadMap = [];
 
-		const fieldGroups = this.getFieldGroups(field.id);
-		if (field.config.destination === 'post_group' && fieldGroups.length > 0) {
-			// User has created groups
-			let groups = [];
-			let titles = [];
-			let featuredImages = [];
 
-			fieldGroups.forEach(group => {
-				let groupUploadIndices = [];
-				let featuredIndex = null;
+		const blobPromises = uploads.map(async (uploadId) => {
+			const upload = this.uploadStore.get(uploadId);
+			if (!upload) return;
 
-				group.uploads.forEach(uploadId => {
-					let upload = this.uploads.get(uploadId);
-					if (upload) {
-						const fileToUpload = upload.processedFile || upload.originalFile;
-						if (fileToUpload) {
-							formData.append('files[]', fileToUpload);
-							const fileIndex = uploadMap.length;
-							uploadMap.push(upload.id);
-							groupUploadIndices.push(upload.id);
-
-							// Check if this is the featured image
-							const radioInput = upload.element?.querySelector('[name="featured"]');
-							if (radioInput?.checked) {
-								featuredIndex = upload.id;
-							}
-						}
-					}
-				});
-
-				groups.push(groupUploadIndices);
-				titles.push(group.title || '');
-				featuredImages.push(featuredIndex);
-			});
-
-			formData.append('groups', JSON.stringify(groups));
-			formData.append('group_titles', JSON.stringify(titles));
-			formData.append('featured_images', JSON.stringify(featuredImages));
-		} else {
-			// No groups - just append all files
-			uploads.forEach(uploadId => {
-				let upload = this.uploads.get(uploadId);
-				if (upload) {
-					const fileToUpload = upload.processedFile || upload.originalFile;
-					if (fileToUpload) {
-						formData.append('files[]', fileToUpload);
-						uploadMap.push(upload.id);
-					}
-				}
-			});
-		}
-		formData.append('upload_ids', JSON.stringify(uploadMap));
-
-		// console.log('Final FormData:');
-		// for (let pair of formData.entries()) {
-		// 	console.log(pair[0], pair[1]);
-		// }
-
-		return formData;
-	}
-
-	getFieldGroups(fieldId) {
-		const groups = [];
-
-		this.groups.forEach((groupData, groupId) => {
-			if (groupData.fieldId === fieldId) {
-				const field = this.fields.get(fieldId);
-				const groupElement = field?.ui?.groups?.groups?.get(groupId);
-
-				groups.push({
-					id: groupId,
-					uploads: Array.from(groupData.uploads || new Set()),
-					changes: groupData.changes || {},
-					element: groupElement || null
-				});
+			const file = await this.getBlobData(uploadId);
+			if (file) {
+				formData.append('files[]', file);
+				uploadMap.push(upload.id);
 			}
 		});
 
-		return groups;
+		await Promise.all(blobPromises);
+
+		formData.append('upload_ids', JSON.stringify(uploadMap));
+		return formData;
 	}
 
 	async queueUploadMeta(e) {
-		const upload = this.getUploadFromElement(e.target);
+		const uploadId = this.getUploadIdFromElement(e.target);
+		const upload = this.uploadStore.get(uploadId);
 		if (!upload) return;
 
-		const field = this.fields.get(upload.fieldId);
-		if (!field) return;
-
-		const container = e.target.closest('.upload-meta');
-		if (!container) return;
+		const fieldData = this.getFieldData(upload.fieldId);
+		if (!fieldData) return;
 
 		let data = {};
 		data[e.target.name] = e.target.value;
 
-		upload.meta = {
-			...upload.meta,
-			... data
-		};
+		upload.meta = { ...upload.meta, ...data };
+		await this.uploadStore.save(upload);
 
 		let queueData = {};
-		//If there is an attachment ID, use that: else, use our generated upload id
-		queueData[upload.attachmentId??upload.id] = upload.meta;
+		queueData[upload.attachmentId ?? upload.id] = upload.meta;
 
 		const operation = {
-			endpoint:	'uploads/meta',
-			method:		'POST',
-			data:		queueData,
-			title:		`Updating meta`,
-			canMerge:	true,
-			headers:	{
-				'action_nonce': jvbSettings.dash
-			}
+			endpoint: 'uploads/meta',
+			method: 'POST',
+			data: queueData,
+			title: 'Updating meta',
+			canMerge: true,
+			headers: { 'action_nonce': jvbSettings.dash }
 		};
 
 		try {
@@ -2063,441 +1219,109 @@
 			});
 		}
 	}
+
 	/*******************************************************************************
-	 * GROUP MANAGEMENT
+	 * QUEUE EVENT HANDLERS - CLEANUP AFTER SUCCESS
 	 *******************************************************************************/
 
-	createGroup(fieldKey, groupId = null) {
-		const field = this.fields.get(fieldKey);
-		if (!field) {
-			console.error('Field not found:', fieldKey);
-			return null;
-		}
+	/**
+	 * Handle successful operation completion - CLEAR STORES
+	 */
+	async handleOperationComplete(operation, fieldId) {
+		const results = operation.result?.data || operation.serverData?.data || [];
 
-		if (!groupId) {
-			groupId = `group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
-		}
-
-		const groupElement = this.createGroupElement(groupId, fieldKey);
-		if (!groupElement) {
-			console.error('Failed to create group element');
-			return null;
-		}
-
-		// Store in field UI Map
-		if (!field.ui.groups) {
-			field.ui.groups = {
-				groups: new Map(),
-				container: null,
-				empty: null,
-				display: null
-			};
-		}
-
-		field.ui.groups.groups.set(groupId, groupElement);
-
-		// Insert into DOM
-		if (field.ui.groups.container && field.ui.groups.empty) {
-			field.ui.groups.container.insertBefore(groupElement, field.ui.groups.empty);
-		} else if (field.ui.groups.container) {
-			field.ui.groups.container.appendChild(groupElement);
-		}
-
-		// Create group object
-		const group = {
-			id: groupId,
-			fieldId: fieldKey,
-			element: groupElement,
-			grid: groupElement.querySelector('.item-grid.group'),
-			uploads: new Set(),
-			changes: {}
-		};
-
-		// Store group
-		this.groups.set(groupId, group);
-
-		// Initialize selection handler for this group
-		this.addGroupSelectionHandler(fieldKey, groupId);
-
-		// Persist state
-		this.schedulePersistance(fieldKey);
-
-		return group;
-	}
-
-	createGroupElement(groupId, fieldId) {
-		let groupElement = window.getTemplate('imageGroup');
-		if (!groupElement) return;
-
-		groupElement.dataset.groupId = groupId;
-		groupElement.dataset.fieldId = fieldId;
-
-		let fields = window.getTemplate('groupMetadata');
-		const fieldsContainer = groupElement.querySelector('.fields');
-		if (fieldsContainer && fields) {
-			fieldsContainer.append(fields);
-
-			// Set unique IDs and names for form fields
-			const titleInput = fieldsContainer.querySelector('[name="post_title"]');
-			const excerptInput = fieldsContainer.querySelector('[name="post_excerpt"]');
-
-			if (titleInput) {
-				titleInput.id = `${groupId}_title`;
-				titleInput.name = `${groupId}[post_title]`;
+		// Update upload statuses with attachment IDs
+		results.forEach(result => {
+			const upload = this.uploadStore.get(result.upload_id);
+			if (upload) {
+				upload.attachmentId = result.attachment_id;
+				upload.status = 'completed';
+				this.uploadStore.save(upload);
+				this.updateUploadStatus(result.upload_id, 'completed');
 			}
-			if (excerptInput) {
-				excerptInput.id = `${groupId}_excerpt`;
-				excerptInput.name = `${groupId}[post_excerpt]`;
-			}
-			let field = this.fields.get(fieldId);
-			if (field.config.content !== '') {
-				let summary = groupElement.querySelector('summary');
-				summary.textContent = field.config.content + ' Fields';
-			}
+		});
+
+		if (!fieldId) return;
+
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldData) return;
+
+		// Clean up completed uploads from stores
+		const completedUploads = Array.from(fieldData.uploads).filter(uploadId => {
+			const upload = this.uploadStore.get(uploadId);
+			return upload?.status === 'completed';
+		});
+
+		for (const uploadId of completedUploads) {
+			await this.clearUpload(uploadId, false);
+			fieldData.uploads.delete(uploadId);
+		}
+
+		// If all uploads complete, clear entire field from stores
+		if (fieldData.uploads.size === 0) {
+			await this.clearFieldFromStores(fieldId);
+			this.a11y.announce('All uploads completed successfully');
 		} else {
-			groupElement.querySelector('details').remove();
+			// Otherwise just update field state
+			await this.saveFieldData(fieldData);
 		}
 
-		const gridContainer = groupElement.querySelector('.item-grid.group');
-		if (gridContainer) {
-			gridContainer.dataset.groupId = groupId;
-		}
-
-		return groupElement;
-	}
-
-	deleteGroup(groupId, confirm = true) {
-		let group = this.groups.get(groupId);
-		if (!group) {
-			return;
-		}
-
-		let keepUploads = true;
-		if (confirm && group.uploads && group.uploads.size > 0) {
-			keepUploads = !window.confirm('Delete uploads in group?');
-		}
-
-		if (confirm && keepUploads) {
-			// Move any remaining uploads back to preview
-			if (group.uploads && group.uploads.size > 0) {
-				Array.from(group.uploads).forEach(uploadId => {
-					this.addImageToGroup(uploadId, null, false);
-				});
-			}
-		}
-
-		// Remove from groups Map
-		this.groups.delete(groupId);
-
-		// Remove DOM element
-		let groupElement = group.element;
-		if (groupElement) {
-			groupElement.remove();
-			this.a11y.announce('Group removed');
-		}
-
-		this.schedulePersistance(group.fieldId);
-	}
-
-	addToGroup(uploadId, target = null, persist = true) {
-		let upload = this.uploads.get(uploadId);
-		if(!upload) {
-			return;
-		}
-		let field = this.fields.get(upload.fieldId);
-		if (!field) {
-			return;
-		}
-
-		//Already in the Preview Grid, or already in the group we're moving to
-		if ((!target && upload.location === field.ui.preview) || target === upload.location) {
-			return;
-		}
-
-		// Remove from previous location
-		if (upload.location) {
-			let groupId = upload.location.dataset.groupId;
-			if (groupId) {
-				let group = this.groups.get(groupId);
-				if (group && group.uploads) {
-					group.uploads.delete(uploadId);
-
-					if (group.uploads.size === 0) {
-						this.deleteGroup(groupId);
-					}
-				}
-			}
-		}
-
-		const checkbox = upload.element.querySelector('[name*="select-item"]');
-		if (checkbox) {
-			checkbox.checked = false;
-		}
-
-		let featured = upload.element.querySelector('[name="featured"]');
-		featured.hidden = !target;
-
-
-		//If no target, it's going to the preview grid
-		if (!target) {
-			target = field.ui.preview;
-			upload.groupId = null;
-		} else if (!target.classList.contains('item-grid') || !target.classList.contains('preview')) {
-			// It's a group target
-			let groupId = target.dataset.groupId;
-			featured.name = groupId+'_'+featured.name;
-			let group = this.groups.get(groupId);
-			if (!group) {
-				group = this.createGroup(upload.fieldId);
-				target = group.grid;
-				groupId = group.id;
-			}
-			if (group) {
-				group.uploads.add(uploadId);
-				upload.groupId = groupId;
-			}
-
-		}
-
-		upload.location = target;
-		target.append(upload.element);
-
-		if (persist) {
-			this.schedulePersistance(field.id);
-		}
-	}
-
-	removeFromGroup(uploadId) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload) return;
-
-		const field = this.fields.get(upload.fieldId);
-		if (!field) return;
-
-		// Remove from current group if in one
-		if (upload.groupId) {
-			const group = this.groups.get(upload.groupId);
-			if (group?.uploads) {
-				group.uploads.delete(uploadId);
-
-				// Delete empty group
-				if (group.uploads.size === 0) {
-					this.deleteGroup(upload.groupId, false);
-				}
-			}
-			upload.groupId = null;
-		}
-
-		// Move back to preview
-		if (field.ui?.preview) {
-			field.ui.preview.appendChild(upload.element);
-			upload.location = field.ui.preview;
-		}
-
-		// Hide featured radio
-		const featured = upload.element.querySelector('[name="featured"]');
-		if (featured) {
-			featured.hidden = true;
-			featured.checked = false;
-		}
-	}
-
-	removeUpload(fieldId, uploadId) {
-		const field = this.fields.get(fieldId);
-		const upload = this.uploads.get(uploadId);
-
-		if (!field || !upload) return;
-
-		// Remove from field
-		field.uploads?.delete(uploadId);
-
-		// Remove from group if grouped
-		if (upload.groupId) {
-			const group = this.groups.get(upload.groupId);
-			if (group && group.uploads) {
-				group.uploads.delete(uploadId);
-
-				if (group.uploads.size === 0) {
-					this.removeGroup(upload.groupId);
-				}
-			}
-		}
-
-		// Clean up element
-		upload.element?.remove();
-
-		// Clean up memory
-		this.clearUpload(uploadId);
-
-		// Update field state after removal
 		this.updateFieldState(fieldId);
-
-		// Update UI
-		this.maybeLockUploads(fieldId);
-		const handler = this.selectionHandlers.get(field.id);
-		if (handler) {
-			handler.deselect(uploadId);
-		}
-
-		this.a11y.announce('Upload removed');
 	}
 
-	/*******************************************************************************
-	 * STATE MANAGEMENT
-	 *******************************************************************************/
-	schedulePersistance(fieldId) {
-		const key = `persist_${fieldId}`;
-		window.debouncer.schedule(
-			key,
-			() => this.persistFieldState(fieldId),
-			1000
-		);
-	}
+	/**
+	 * Handle operation failure
+	 */
+	handleOperationFailed(operation, fieldId) {
+		const uploadIds = operation.data instanceof FormData
+			? JSON.parse(operation.data.get('upload_ids') || '[]')
+			: operation.data.upload_ids || [];
 
-	async persistFieldState(fieldId) {
-		const field = this.fields.get(fieldId);
-		if (!field) return;
+		uploadIds.forEach(uploadId => {
+			const upload = this.uploadStore.get(uploadId);
+			if (upload) {
+				upload.status = operation.status === 'operation-failed-permanent'
+					? 'failed_permanent'
+					: 'failed';
+				this.uploadStore.save(upload);
+				this.updateUploadStatus(uploadId, upload.status);
+			}
+		});
 
-		// Convert Sets to Arrays for storage
-		const fieldData = {
-			...field,
-			id: fieldId, // Use as primary key
-			fieldId: fieldId,
-			uploads: Array.from(field.uploads || []).map(uploadId => {
-				return this.uploads.get(uploadId);;
-			}),
-			groups: Array.from(this.groups.entries())
-				.filter(([id, data]) => data.fieldId === fieldId && data.uploads && data.uploads.size > 0)
-				.map(([id, data]) => ({
-					id: data.id,
-					uploads: Array.from(data.uploads),
-					changes: data.changes || {}
-				})),
-
-			// Context for restoration
-			context: {
-				url: this.normalizeUrl(window.location.href),
-				fullUrl: window.location.href,
-				modalType: this.getModalType(field),
-				formId: field.formId,
-				fieldSelector: `.field.upload[data-field="${field.config.name}"]`
-			},
-			timestamp: Date.now()
-		};
-
-		// Save to store
-		await this.fieldStore.save(fieldData);
-	}
-	normalizeUrl(url) {
-		try {
-			const urlObj = new URL(url);
-			// Return just the origin + pathname (no query string or hash)
-			return urlObj.origin + urlObj.pathname;
-		} catch (e) {
-			return url;
+		if (fieldId) {
+			this.updateFieldState(fieldId);
 		}
 	}
 
 	/**
-	 * Get uploads for a field, optionally cleaned for storage
-	 * @param {string} fieldId
-	 * @param {boolean} clean - Remove DOM references for IndexedDB storage
-	 * @returns {Array}
+	 * Handle operation cancellation
 	 */
-	getFieldUploads(fieldId, clean = false) {
-		const field = this.fields.get(fieldId);
-		if (!field || !field.uploads) return [];
+	async handleOperationCancelled(fieldId) {
+		const fieldData = this.getFieldData(fieldId);  // ✅
+		if (!fieldData) return;
 
-		return Array.from(field.uploads)
-			.map(uploadId => {
-				const upload = this.uploads.get(uploadId);
-				if (!upload) return null;
+		const uploadsArray = fieldData.uploads instanceof Set
+			? Array.from(fieldData.uploads)
+			: fieldData.uploads;
 
-				if (clean) {
-					// Return cleaned version without DOM references or blob URLs
-					return {
-						id: upload.id,
-						fieldId: upload.fieldId,
-						status: upload.status,
-						// DON'T include preview (blob URL)
-						// DON'T include originalFile or processedFile (in blob storage)
-						attachmentId: upload.attachmentId,
-						operationId: upload.operationId,
-						groupId: upload.groupId || null,
-						changes: upload.changes || {}, // ← ADD: Include changes
-						meta: {
-							originalName: upload.meta?.originalName || upload.originalFile?.name,
-							size: upload.meta?.size || upload.originalFile?.size,
-							type: upload.meta?.type || upload.originalFile?.type,
-							title: upload.meta?.title,
-							alt: upload.meta?.alt,
-							caption: upload.meta?.caption
-						}
-					};
-				}
-
-				// Return full upload object
-				return upload;
-			})
-			.filter(Boolean);
-	}
-
-	async checkForStoredUploads() {
-		if (!this.db) return;
-
-		const tx = this.db.transaction(['fieldStates'], 'readonly');
-		const fieldStore = tx.objectStore('fieldStates');
-
-		const allFieldStates = await new Promise(resolve => {
-			const request = fieldStore.getAll();
-			request.onsuccess = () => resolve(request.result);
-		});
-
-		//
-		// allFieldStates.forEach(field => {
-		// 	console.log(`Field ${field.fieldId} has ${field.uploads.length} uploads:`);
-		// 	field.uploads.forEach((upload, idx) => {
-		// 		console.log(`  Upload ${idx}:`, {
-		// 			id: upload.id,
-		// 			status: upload.status,
-		// 			operationId: upload.operationId,
-		// 			hasOperationId: !!upload.operationId
-		// 		});
-		// 	});
-		// });
-
-		// Filter for pending uploads (not yet sent to server)
-		const pendingFields = allFieldStates.filter(field =>
-			field.uploads.some(upload =>
-				// If no operationId, it hasn't been sent to server yet
-				!upload.operationId &&
-				// And it's been processed locally
-				(upload.status === 'completed' ||
-					upload.status === 'processed' ||
-					upload.status === 'local_processing' ||
-					upload.status === 'processed-original')
-			)
-		);
-
-		if (pendingFields.length === 0) return;
-
-		// Show recovery notification
-		this.showRecoveryNotification(pendingFields);
-	}
-
-	async handleRestoreUploads() {
-		let notification = document.querySelector('dialog.restore-uploads');
-		if (!notification) {
-			return;
+		for (const uploadId of uploadsArray) {
+			await this.clearUpload(uploadId, false);
 		}
 
-		const selectedUploads = this.getSelectedRestorationUploads(notification);
-		if (selectedUploads.length === 0) {
-			return;
-		}
-		await this.restoreSelectedUploads(selectedUploads);
+		await this.clearFieldFromStores(fieldId);
+		this.updateFieldState(fieldId);
+		this.a11y.announce('Upload cancelled');
+	}
 
-		this.cleanupRestore();
+	getFieldGroups(fieldId) {
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldData?.groups) return [];
+
+		return fieldData.groups.map(group => ({
+			id: group.id,
+			uploads: group.uploads || [],
+			changes: group.changes || {}
+		}));
 	}
 
 	getSelectedRestorationUploads(notificationEl) {
@@ -2517,29 +1341,1347 @@
 		return selected;
 	}
 
-	handleGroupMetaChange(input) {
-		let group = this.getGroupFromElement(input);
-		if (!group) {
-			return;
+	async restoreSelectedUploads(selectedUploads) {
+		const byField = new Map();
+		selectedUploads.forEach(item => {
+			if (!byField.has(item.fieldId)) {
+				byField.set(item.fieldId, []);
+			}
+			byField.get(item.fieldId).push(item.uploadId);
+		});
+
+		for (const [fieldId, uploadIds] of byField.entries()) {
+			const fieldState = this.fieldStore.get(fieldId);
+			if (fieldState) {
+				fieldState.uploads = uploadIds;
+				await this.restoreField(fieldState);
+			}
 		}
-		if (!Object.hasOwn(group, 'changes')) {
-			group.changes = {};
-		}
-		let name = input.name;
-		if (name.includes('group')) {
-			let replace = group.id+'_';
-			let replace2 = group.id+'[';
-			name = name.replace(replace, '').replace(replace2,'').replace(']', '');
-		}
-		group.changes[`${name}`] = input.value;
-		this.groups.set(group.id, group);
-		this.schedulePersistance(group.fieldId);
 	}
 
+	async restoreField(fieldState) {
+		const { config, context, uploads, groups, id } = fieldState;
+
+		// If in a modal, open it first
+		if (context?.modalType) {
+			await this.openModalForRestore(context);
+		}
+
+		// Find field element
+		let fieldElement = document.querySelector(`.field.upload[data-field="${config.name}"]`);
+
+		if (!fieldElement) {
+			const uploaderKey = `${config.content}_${config.itemID}_${config.name}`;
+			fieldElement = document.querySelector(`.field.upload[data-uploader="${uploaderKey}"]`);
+		}
+
+		if (!fieldElement) {
+			console.warn(`Field ${config.name} not found for restoration`, config);
+			return;
+		}
+
+		// Register the field if not already registered
+		let fieldKey = fieldElement.dataset.uploader;
+		if (!fieldKey || !this.fieldElements.has(fieldKey)) {
+			fieldKey = this.registerUploader(fieldElement);
+		}
+
+		const fieldEl = this.fieldElements.get(fieldKey);
+		const fieldData = this.getFieldData(fieldKey);
+
+		if (!fieldEl || !fieldData) {
+			console.error('Failed to register field for restoration');
+			return;
+		}
+
+		// Merge saved state back into field
+		fieldData.state = fieldState.state || 'ready';
+
+		// Rebuild UI references if needed
+		if (!fieldEl.ui) {
+			fieldEl.ui = this.buildFieldUI(fieldElement);
+		}
+
+		if (fieldEl.ui.groups?.display) {
+			fieldEl.ui.groups.display.hidden = false;
+		}
+		if (fieldEl.ui.dropZone) {
+			fieldEl.ui.dropZone.hidden = true;
+		}
+
+		// Restore groups first
+		if (groups && groups.length > 0) {
+			await this.restoreGroups(fieldKey, groups);
+		}
+
+		// Handle both Array and Set for uploads
+		const uploadsArray = uploads instanceof Set
+			? Array.from(uploads)
+			: Array.isArray(uploads)
+				? uploads
+				: [];
+
+		// Restore uploads
+		for (const uploadId of uploadsArray) {
+			// Get upload data from store
+			const uploadData = this.uploadStore.get(uploadId);
+			if (uploadData) {
+				await this.restoreUpload(fieldKey, uploadData);
+			}
+		}
+
+		// Update field state
+		await this.saveFieldData(fieldData);
+		this.updateFieldState(fieldKey);
+		this.maybeLockUploads(fieldKey);
+		this.refreshSortable(fieldKey);
+
+		// Queue for upload if needed
+		if (config.mode === 'direct' && config.destination !== 'post_group') {
+			await this.queueUpload(fieldKey);
+		}
+	}
+
+	async restoreUpload(fieldId, uploadData) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		const fieldData = this.getFieldData(fieldId);
+
+		if (!fieldEl || !fieldData) {
+			console.error('Field not found for upload restoration:', fieldId);
+			return;
+		}
+
+		// Get reconstructed File from blob data
+		const file = await this.getBlobData(uploadData.id);
+
+		if (!file) {
+			console.warn('Blob data not found for upload:', uploadData.id);
+			return;
+		}
+
+		// Create preview URL
+		const previewUrl = this.createPreviewUrl(file);
+
+		// Recreate DOM element
+		const subtype = this.getSubtypeFromMime(file.type);
+		const element = this.createUploadElement({
+			id: uploadData.id,
+			preview: previewUrl,
+			meta: uploadData.meta || {
+				originalName: file.name,
+				size: file.size,
+				type: file.type
+			},
+			subtype: subtype
+		}, fieldData.config.destination === 'post_group');
+
+		// Determine correct location
+		let location;
+		if (uploadData.groupId) {
+			// Check if group exists
+			const groupEl = this.groupElements.get(uploadData.groupId);
+			if (groupEl?.grid) {
+				location = groupEl.grid;
+
+				// Add to group's upload list
+				const group = fieldData.groups?.find(g => g.id === uploadData.groupId);
+				if (group) {
+					if (!group.uploads) group.uploads = [];
+					if (!group.uploads.includes(uploadData.id)) {
+						group.uploads.push(uploadData.id);
+					}
+				}
+			} else {
+				// Group doesn't exist, add to preview
+				location = fieldEl.ui.preview;
+				uploadData.groupId = null;
+			}
+		} else {
+			// No group, add to preview
+			location = fieldEl.ui.preview;
+		}
+
+		// Add element to DOM
+		if (location) {
+			location.appendChild(element);
+		} else if (fieldEl.ui.preview) {
+			fieldEl.ui.preview.appendChild(element);
+			location = fieldEl.ui.preview;
+		}
+
+		// Store runtime element data
+		this.uploadElements.set(uploadData.id, {
+			element: element,
+			preview: previewUrl,
+			location: location
+		});
+
+		// Add to field uploads
+		if (!fieldData.uploads) fieldData.uploads = new Set();
+		fieldData.uploads.add(uploadData.id);
+
+		// Update upload data in store
+		uploadData.status = 'processed';
+		await this.uploadStore.save(uploadData);
+
+		// Update sortable state for the grid
+		if (location) {
+			this.updateSortableState(location);
+		}
+	}
+
+	async restoreGroups(fieldId, groups) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		const fieldData = this.getFieldData(fieldId);
+
+		if (!fieldEl || !fieldData) {
+			console.error('Field not found for group restoration:', fieldId);
+			return;
+		}
+
+		for (const groupData of groups) {
+			// Create group using existing method which handles all initialization
+			const group = this.createGroup(fieldId, groupData.id);
+
+			if (!group) {
+				console.warn('Failed to create group:', groupData.id);
+				continue;
+			}
+
+			// Find the group in fieldData (createGroup already added it)
+			const storedGroup = fieldData.groups?.find(g => g.id === groupData.id);
+
+			if (storedGroup) {
+				// Restore metadata
+				if (groupData.changes) {
+					storedGroup.changes = { ...groupData.changes };
+				}
+
+				if (groupData.uploads) {
+					storedGroup.uploads = [...groupData.uploads];
+				}
+
+				// Restore form field values if they exist
+				if (groupData.changes) {
+					const titleInput = group.element.querySelector('[name*="post_title"]');
+					const excerptInput = group.element.querySelector('[name*="post_excerpt"]');
+
+					if (titleInput && groupData.changes.post_title) {
+						titleInput.value = groupData.changes.post_title;
+					}
+
+					if (excerptInput && groupData.changes.post_excerpt) {
+						excerptInput.value = groupData.changes.post_excerpt;
+					}
+				}
+			}
+		}
+
+		// Save updated field data
+		await this.saveFieldData(fieldData);
+	}
+
+	async openModalForRestore(context) {
+		if (!context) return;
+
+		const { modalType, itemId } = context;
+
+		// Find and click the appropriate button to open the modal
+		let trigger = null;
+
+		switch(modalType) {
+			case 'create':
+				trigger = document.querySelector('[data-action="create"]');
+				break;
+			case 'edit':
+				// Need to find the specific edit button
+				if (itemId) {
+					trigger = document.querySelector(`[data-action="edit"][data-id="${itemId}"]`);
+				}
+				break;
+			case 'bulkEdit':
+				trigger = document.querySelector('[data-action="bulk-edit"]');
+				break;
+		}
+
+		if (trigger) {
+			trigger.click();
+
+			// Wait for modal to open and render
+			await new Promise(resolve => setTimeout(resolve, 300));
+		} else {
+			console.warn('Modal trigger not found for restoration:', context);
+		}
+	}
+
+	formatBytes(bytes, decimals = 2) {
+		if (bytes === 0) return '0 Bytes';
+		const k = 1024;
+		const dm = decimals < 0 ? 0 : decimals;
+		const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+		const i = Math.floor(Math.log(bytes) / Math.log(k));
+		return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+	}
 
 	/*******************************************************************************
-	 * RESTORING UPLOADS
+	 * CLEANUP METHODS - AGGRESSIVE CLEANUP AFTER SUCCESS
 	 *******************************************************************************/
+
+	/**
+	 * Clear individual upload from stores (called after successful upload)
+	 */
+	async clearUpload(uploadId, persist = true) {
+		const uploadEl = this.uploadElements.get(uploadId);
+		if (uploadEl) {
+			this.revokePreviewUrl(uploadEl.preview);
+			if (uploadEl.element) {
+				const previewUrl = uploadEl.element.dataset.previewUrl;
+				this.revokePreviewUrl(previewUrl);
+				delete uploadEl.element.dataset.previewUrl;
+			}
+		}
+
+		// Remove from runtime memory
+		this.uploadElements.delete(uploadId);
+
+		// Remove from store (no separate blob store - it's part of the upload object)
+		await this.uploadStore.delete(uploadId);
+
+		// Update field if needed
+		if (persist) {
+			const upload = this.uploadStore.get(uploadId);
+			if (upload?.fieldId) {
+				await this.schedulePersistance(upload.fieldId);
+			}
+		}
+	}
+
+	/**
+	 * Clear entire field from stores (called when all uploads complete)
+	 */
+	async clearFieldFromStores(fieldId) {
+		const fieldData = this.getFieldData(fieldId);
+
+		// Clear all related uploads
+		if (fieldData?.uploads) {
+			const uploadsArray = fieldData.uploads instanceof Set
+				? Array.from(fieldData.uploads)
+				: fieldData.uploads;
+
+			for (const uploadId of uploadsArray) {
+				await this.uploadStore.delete(uploadId);
+			}
+		}
+
+		// Clear field from store
+		await this.fieldStore.delete(fieldId);
+
+		// Keep runtime references (fieldElements, etc) intact for reuse
+	}
+
+	cleanupAllPreviewUrls() {
+		if (this.previewUrls) {
+			this.previewUrls.forEach(url => {
+				try {
+					URL.revokeObjectURL(url);
+				} catch (e) {
+					// Ignore errors during cleanup
+				}
+			});
+			this.previewUrls.clear();
+		}
+	}
+
+	/*******************************************************************************
+	 * UI UPDATE METHODS
+	 *******************************************************************************/
+
+	updateFieldState(fieldId) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldEl || !fieldData) return;
+
+		const container = fieldEl.element;
+		const uploadCount = fieldData.uploads?.size || 0;
+		const hasGroups = fieldEl.ui.groups?.container?.querySelectorAll('.upload-group').length > 0;
+
+		container.dataset.hasUploads = uploadCount > 0 ? 'true' : 'false';
+		container.dataset.uploadCount = uploadCount.toString();
+		container.dataset.hasGroups = hasGroups ? 'true' : 'false';
+
+		if (fieldEl.ui.preview) {
+			fieldEl.ui.preview.setAttribute('aria-label',
+				`Upload preview area with ${uploadCount} item${uploadCount !== 1 ? 's' : ''}`
+			);
+		}
+	}
+
+	updateUploadProgress(fieldId, current, total, message) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldEl?.ui?.progress?.progress) return;
+
+		const progress = fieldEl.ui.progress;
+		const percent = total > 0 ? (current / total) * 100 : 0;
+
+		if (progress.fill) progress.fill.style.width = `${percent}%`;
+		if (progress.text) progress.text.textContent = message;
+		if (progress.count) progress.count.textContent = `${current}/${total}`;
+
+		progress.progress.hidden = (current === total);
+	}
+
+	updateFieldStatus(fieldId, status) {
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldData) return;
+
+		fieldData.state = status;
+		this.saveFieldData(fieldData);
+	}
+
+	updateUploadStatus(uploadId, status) {
+		const upload = this.uploadStore.get(uploadId);
+		if (!upload) return;
+
+		upload.status = status;
+		this.uploadStore.save(upload);
+		this.updateUploadUI(uploadId);
+	}
+
+	updateUploadUI(uploadId) {
+		const uploadEl = this.uploadElements.get(uploadId);
+		const upload = this.uploadStore.get(uploadId);
+		if (!upload || !uploadEl?.element) return;
+
+		uploadEl.element.className = uploadEl.element.className.replace(/status-[\w-]+/g, '');
+		uploadEl.element.classList.add(`status-${upload.status}`);
+
+		const progress = uploadEl.element.querySelector('.progress');
+		if (progress) {
+			this.updateUploadItemProgress(uploadId,
+				this.getStatusProgress(upload.status),
+				upload.status
+			);
+		}
+	}
+
+	showUploadProgress(uploadId, show = true) {
+		const uploadEl = this.uploadElements.get(uploadId);
+		if (!uploadEl?.element) return;
+
+		const progressEl = uploadEl.element.querySelector('.progress');
+		if (progressEl) {
+			if (show) {
+				progressEl.style.removeProperty('animation');
+				progressEl.hidden = false;
+			} else {
+				progressEl.style.animation = 'fadeOut var(--transition-base)';
+				setTimeout(() => { progressEl.hidden = true; }, 300);
+			}
+		}
+	}
+
+	updateUploadItemProgress(uploadId, percent, status = null) {
+		const uploadEl = this.uploadElements.get(uploadId);
+		if (!uploadEl?.element) return;
+
+		const progressEl = uploadEl.element.querySelector('.progress');
+		if (!progressEl) return;
+
+		const fill = progressEl.querySelector('.fill');
+		const details = progressEl.querySelector('.details');
+		const icon = progressEl.querySelector('.icon');
+
+		if (fill) fill.style.width = `${percent}%`;
+		if (status && details) details.textContent = this.getStatusText(status);
+		if (status && icon) icon.innerHTML = this.getStatusIcon(status).outerHTML;
+	}
+
+	maybeLockUploads(fieldId) {
+		const fieldEl = this.fieldElements.get(fieldId);
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldEl?.ui?.dropZone || !fieldData) return;
+
+		if (fieldData.config.destination === 'post_group') return;
+
+		const uploadCount = fieldData.uploads?.size || 0;
+		const maxFiles = fieldData.config?.maxFiles || 999;
+
+		fieldEl.ui.dropZone.hidden = uploadCount >= maxFiles;
+		fieldEl.element.classList.toggle('at-max-uploads', uploadCount >= maxFiles);
+	}
+
+	/*******************************************************************************
+	 * GROUP MANAGEMENT
+	 *******************************************************************************/
+
+	createGroup(fieldId, groupId = null) {
+		const fieldData = this.getFieldData(fieldId);
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldData || !fieldEl) return null;
+
+		if (!groupId) {
+			groupId = `group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+		}
+
+		const groupElement = this.createGroupElement(groupId, fieldId);
+		if (!groupElement) return null;
+
+		// Store in field UI Map
+		if (!fieldEl.ui.groups) {
+			fieldEl.ui.groups = {
+				groups: new Map(),
+				container: null,
+				empty: null,
+				display: null
+			};
+		}
+
+		fieldEl.ui.groups.groups.set(groupId, groupElement);
+
+		// Insert into DOM
+		if (fieldEl.ui.groups.container && fieldEl.ui.groups.empty) {
+			fieldEl.ui.groups.container.insertBefore(groupElement, fieldEl.ui.groups.empty);
+		} else if (fieldEl.ui.groups.container) {
+			fieldEl.ui.groups.container.appendChild(groupElement);
+		}
+
+		// Store group element reference
+		const grid = groupElement.querySelector('.item-grid.group');
+		this.groupElements.set(groupId, {
+			element: groupElement,
+			grid: grid,
+			fieldId: fieldId
+		});
+
+		// Add to field groups
+		if (!fieldData.groups) fieldData.groups = [];
+		fieldData.groups.push({
+			id: groupId,
+			uploads: [],
+			changes: {}
+		});
+		this.saveFieldData(fieldData);
+
+		// Initialize selection handler and sortable
+		this.addGroupSelectionHandler(fieldId, groupId);
+
+		// Initialize sortable for this new group grid
+		if (grid) {
+			const sortableInstance = new Sortable(grid, {
+				...this.sortableConfig,
+				group: { name: fieldId, pull: true, put: true },
+				disabled: true, // Empty initially
+				onAdd: (evt) => this.updateSortableState(evt.to),
+				onRemove: (evt) => this.updateSortableState(evt.from)
+			});
+			grid.sortableInstance = sortableInstance;
+			this.sortableInstances.set(`${fieldId}-group-${groupId}`, sortableInstance);
+		}
+
+		return { id: groupId, element: groupElement, grid: grid };
+	}
+
+	createGroupElement(groupId, fieldId) {
+		let groupElement = window.getTemplate('imageGroup');
+		if (!groupElement) return;
+
+		groupElement.dataset.groupId = groupId;
+		groupElement.dataset.fieldId = fieldId;
+
+		let fields = window.getTemplate('groupMetadata');
+		const fieldsContainer = groupElement.querySelector('.fields');
+		if (fieldsContainer && fields) {
+			fieldsContainer.append(fields);
+
+			const titleInput = fieldsContainer.querySelector('[name="post_title"]');
+			const excerptInput = fieldsContainer.querySelector('[name="post_excerpt"]');
+
+			if (titleInput) {
+				titleInput.id = `${groupId}_title`;
+				titleInput.name = `${groupId}[post_title]`;
+			}
+			if (excerptInput) {
+				excerptInput.id = `${groupId}_excerpt`;
+				excerptInput.name = `${groupId}[post_excerpt]`;
+			}
+
+			const fieldData = this.getFieldData(fieldId);
+			if (fieldData && fieldData.config.content !== '') {
+				let summary = groupElement.querySelector('summary');
+				if (summary) summary.textContent = fieldData.config.content + ' Fields';
+			}
+		} else {
+			groupElement.querySelector('details')?.remove();
+		}
+
+		const gridContainer = groupElement.querySelector('.item-grid.group');
+		if (gridContainer) {
+			gridContainer.dataset.groupId = groupId;
+		}
+
+		return groupElement;
+	}
+
+	deleteGroup(groupId, confirm = true) {
+		const groupEl = this.groupElements.get(groupId);
+		if (!groupEl) return;
+
+		const fieldData = this.getFieldData(groupEl.fieldId);
+		if (!fieldData) return;
+
+		const group = fieldData.groups?.find(g => g.id === groupId);
+		let keepUploads = true;
+
+		if (confirm && group?.uploads?.length > 0) {
+			keepUploads = !window.confirm('Delete uploads in group?');
+		}
+
+		if (confirm && keepUploads && group?.uploads) {
+			// Move uploads back to preview
+			group.uploads.forEach(uploadId => {
+				this.removeFromGroup(uploadId);
+			});
+		}
+
+		// Remove from field groups
+		if (fieldData.groups) {
+			fieldData.groups = fieldData.groups.filter(g => g.id !== groupId);
+			this.saveFieldData(fieldData);
+		}
+
+		// Remove DOM element
+		if (groupEl.element) {
+			groupEl.element.remove();
+			this.a11y.announce('Group removed');
+		}
+
+		// Remove from maps
+		this.groupElements.delete(groupId);
+
+		// Clean up sortable
+		const sortableKey = `${groupEl.fieldId}-group-${groupId}`;
+		const sortable = this.sortableInstances.get(sortableKey);
+		if (sortable?.destroy) {
+			sortable.destroy();
+		}
+		this.sortableInstances.delete(sortableKey);
+
+		this.schedulePersistance(groupEl.fieldId);
+	}
+
+	addToGroup(uploadId, target = null, persist = true) {
+		const upload = this.uploadStore.get(uploadId);
+		const uploadEl = this.uploadElements.get(uploadId);
+		if (!upload || !uploadEl) return;
+
+		const fieldData = this.getFieldData(upload.fieldId);
+		const fieldEl = this.fieldElements.get(upload.fieldId);
+		if (!fieldData || !fieldEl) return;
+
+		// Already in correct location
+		if ((!target && uploadEl.location === fieldEl.ui.preview) || target === uploadEl.location) {
+			return;
+		}
+
+		// Remove from previous group
+		if (upload.groupId) {
+			const group = fieldData.groups?.find(g => g.id === upload.groupId);
+			if (group) {
+				group.uploads = group.uploads.filter(id => id !== uploadId);
+				if (group.uploads.length === 0) {
+					this.deleteGroup(upload.groupId);
+				}
+			}
+		}
+
+		// Clear selection checkbox
+		const checkbox = uploadEl.element.querySelector('[name*="select-item"]');
+		if (checkbox) checkbox.checked = false;
+
+		let featured = uploadEl.element.querySelector('[name="featured"]');
+		if (featured) featured.hidden = !target;
+
+		// Moving to preview or to group
+		if (!target || target.classList.contains('preview')) {
+			target = fieldEl.ui.preview;
+			upload.groupId = null;
+		} else {
+			// Moving to group
+			const groupId = target.dataset.groupId;
+			if (featured) featured.name = groupId + '_' + featured.name;
+
+			const group = fieldData.groups?.find(g => g.id === groupId);
+			if (group) {
+				if (!group.uploads) group.uploads = [];
+				group.uploads.push(uploadId);
+				upload.groupId = groupId;
+			}
+		}
+
+		// Update location
+		uploadEl.location = target;
+		target.append(uploadEl.element);
+
+		// Update stores
+		this.uploadStore.save(upload);
+		if (persist) {
+			this.saveFieldData(fieldData);
+		}
+
+		// Update sortable state
+		this.updateSortableState(target);
+		if (uploadEl.location && uploadEl.location !== target) {
+			this.updateSortableState(uploadEl.location);
+		}
+	}
+
+	removeFromGroup(uploadId) {
+		const upload = this.uploadStore.get(uploadId);
+		const uploadEl = this.uploadElements.get(uploadId);
+		if (!upload || !uploadEl) return;
+
+		const fieldData = this.getFieldData(upload.fieldId);
+		const fieldEl = this.fieldElements.get(upload.fieldId);
+		if (!fieldData || !fieldEl) return;
+
+		// Remove from current group
+		if (upload.groupId) {
+			const group = fieldData.groups?.find(g => g.id === upload.groupId);
+			if (group) {
+				group.uploads = group.uploads.filter(id => id !== uploadId);
+				if (group.uploads.length === 0) {
+					this.deleteGroup(upload.groupId, false);
+				}
+			}
+			upload.groupId = null;
+		}
+
+		// Move back to preview
+		if (fieldEl.ui?.preview) {
+			fieldEl.ui.preview.appendChild(uploadEl.element);
+			uploadEl.location = fieldEl.ui.preview;
+		}
+
+		// Hide featured radio
+		const featured = uploadEl.element.querySelector('[name="featured"]');
+		if (featured) {
+			featured.hidden = true;
+			featured.checked = false;
+		}
+
+		this.uploadStore.save(upload);
+		this.updateSortableState(fieldEl.ui.preview);
+	}
+
+	removeUpload(fieldId, uploadId) {
+		const fieldData = this.getFieldData(fieldId);
+		const upload = this.uploadStore.get(uploadId);
+		const uploadEl = this.uploadElements.get(uploadId);
+
+		if (!fieldData || !upload) return;
+
+		// Remove from field
+		fieldData.uploads?.delete(uploadId);
+
+		// Remove from group if grouped
+		if (upload.groupId) {
+			const group = fieldData.groups?.find(g => g.id === upload.groupId);
+			if (group) {
+				group.uploads = group.uploads.filter(id => id !== uploadId);
+				if (group.uploads.length === 0) {
+					this.deleteGroup(upload.groupId);
+				}
+			}
+		}
+
+		// Clean up element
+		uploadEl?.element?.remove();
+
+		// Clean up memory
+		this.clearUpload(uploadId);
+
+		// Update field state
+		this.saveFieldData(fieldData);
+		this.updateFieldState(fieldId);
+		this.maybeLockUploads(fieldId);
+
+		const handler = this.selectionHandlers.get(fieldId);
+		if (handler) {
+			handler.deselect(uploadId);
+		}
+
+		this.a11y.announce('Upload removed');
+	}
+
+	handleGroupMetaChange(input) {
+		const groupEl = this.getGroupFromElement(input);
+		if (!groupEl) return;
+
+		const fieldData = this.getFieldData(groupEl.fieldId);
+		const group = fieldData?.groups?.find(g => g.id === groupEl.element.dataset.groupId);
+		if (!group) return;
+
+		if (!group.changes) group.changes = {};
+
+		let name = input.name;
+		if (name.includes('group')) {
+			name = name.replace(`${group.id}_`, '').replace(`${group.id}[`, '').replace(']', '');
+		}
+
+		group.changes[name] = input.value;
+		this.saveFieldData(fieldData);
+		this.schedulePersistance(groupEl.fieldId);
+	}
+
+	/*******************************************************************************
+	 * ACTION HANDLERS
+	 *******************************************************************************/
+
+	handleAction(button) {
+		const action = button.dataset.action;
+		const fieldId = this.getFieldIdFromElement(button);
+
+		switch(action) {
+			case 'add-to-group':
+				this.handleAddToGroup(button);
+				break;
+			case 'delete-group':
+				this.handleDeleteGroup(button);
+				break;
+			case 'delete-upload':
+			case 'remove-from-group':
+				this.handleRemoveItem(button);
+				break;
+			case 'upload':
+				const fieldEl = this.fieldElements.get(fieldId);
+				if (fieldEl) {
+					fieldEl.element.closest('details').open = false;
+					document.body.classList.add('uploading');
+					this.submitUploads(fieldId);
+				}
+				break;
+			case 'restore':
+				this.handleRestoreUploads().then(() => {});
+				break;
+			case 'clear-cache':
+				if (!confirm('Save these uploads for later?')) {
+					this.cleanupStoredUploads();
+				}
+				this.cleanupRestore();
+				break;
+		}
+	}
+
+	handleAddToGroup(button) {
+		const fieldElement = button.closest(this.selectors.field.field);
+		const fieldId = fieldElement?.dataset.uploader;
+		if (!fieldId) return;
+
+		const selected = this.selected.get(fieldId);
+
+		if (!selected || selected.size === 0) {
+			this.createGroup(fieldId);
+		} else {
+			const group = this.createGroup(fieldId);
+			if (!group) return;
+
+			selected.forEach(uploadId => {
+				this.addToGroup(uploadId, group.grid);
+			});
+
+			const handler = this.selectionHandlers.get(fieldId);
+			handler?.clearSelection();
+
+			this.a11y.announce(`Created group with ${selected.size} items`);
+		}
+
+		this.schedulePersistance(fieldId);
+	}
+
+	handleDeleteGroup(button) {
+		const group = button.closest(this.selectors.groups.container);
+		if (!group) return;
+
+		const groupId = group.dataset.groupId;
+		const fieldId = this.getFieldIdFromElement(group);
+
+		if (!confirm('Delete this group? Items will be moved back to the upload area.')) {
+			return;
+		}
+
+		const items = group.querySelectorAll(this.selectors.items.item);
+		items.forEach(item => {
+			const uploadId = item.dataset.uploadId;
+			this.removeFromGroup(uploadId);
+		});
+
+		this.deleteGroup(groupId);
+		this.a11y.announce('Group deleted, items returned to upload area');
+		this.schedulePersistance(fieldId);
+	}
+
+	handleRemoveItem(button) {
+		const item = button.closest(this.selectors.items.item);
+		if (!item) return;
+
+		const uploadId = item.dataset.uploadId;
+		const fieldId = this.getFieldIdFromElement(item);
+
+		if (!confirm('Remove this item?')) return;
+
+		this.removeUpload(fieldId, uploadId);
+		this.a11y.announce('Item removed');
+		this.schedulePersistance(fieldId);
+	}
+
+	/*******************************************************************************
+	 * SELECTION MANAGEMENT
+	 *******************************************************************************/
+
+	addFieldSelectionHandler(fieldId) {
+		if (this.selectionHandlers.has(fieldId)) {
+			return this.selectionHandlers.get(fieldId);
+		}
+
+		const fieldEl = this.fieldElements.get(fieldId);
+		if (!fieldEl?.element) return;
+
+		const handler = new window.jvbHandleSelection({
+			container: fieldEl.element,
+			ui: {
+				selectAll: fieldEl.element.querySelector('[name="select-all-uploads"]'),
+				bulkControls: fieldEl.element.querySelector('.selection-actions'),
+				count: fieldEl.element.querySelector('.selection-count')
+			},
+			itemSelector: '[data-upload-id]',
+			checkboxSelector: '[name*="select-item"]'
+		});
+
+		handler.subscribe((event, data) => {
+			switch(event) {
+				case 'item-selected':
+				case 'item-deselected':
+				case 'range-selected':
+					this.selected.set(fieldId, data.selectedItems);
+					break;
+				case 'select-all':
+					this.handleSelectAll(data.container, data.selected);
+					break;
+			}
+		});
+
+		this.selectionHandlers.set(fieldId, handler);
+		return handler;
+	}
+
+	addGroupSelectionHandler(fieldId, groupId) {
+		const handlerKey = `${fieldId}_${groupId}`;
+		if (this.selectionHandlers.has(handlerKey)) {
+			return this.selectionHandlers.get(handlerKey);
+		}
+
+		const groupEl = this.groupElements.get(groupId);
+		if (!groupEl?.element) return;
+
+		const handler = new window.jvbHandleSelection({
+			container: groupEl.element,
+			ui: {
+				selectAll: groupEl.element.querySelector(this.selectors.groups.selectAll),
+				bulkControls: groupEl.element.querySelector(this.selectors.groups.actions),
+				count: groupEl.element.querySelector(this.selectors.groups.count)
+			},
+			itemSelector: '[data-upload-id]',
+			checkboxSelector: '[name*="select-item"]'
+		});
+
+		handler.subscribe((event, data) => {
+			switch(event) {
+				case 'item-selected':
+				case 'item-deselected':
+				case 'range-selected':
+					this.selected.set(fieldId, data.selectedItems);
+					break;
+				case 'select-all':
+					this.handleSelectAll(data.container, data.selected);
+					break;
+			}
+		});
+
+		this.selectionHandlers.set(handlerKey, handler);
+		return handler;
+	}
+
+	handleSelectAll(container, selected) {
+		// Can add custom logic here if needed
+	}
+
+	getCurrentSelection(fieldId) {
+		let selected = [];
+		for (let [key, handler] of this.selectionHandlers) {
+			if ((fieldId === key || key.includes(fieldId)) && handler.selectedItems.size > 0) {
+				selected = selected.concat([...handler.selectedItems]);
+			}
+		}
+		return selected;
+	}
+
+	/*******************************************************************************
+	 * HELPER METHODS
+	 *******************************************************************************/
+
+	/**
+	 * Get field data from store and normalize it
+	 * Always use this instead of directly accessing fieldStore.get()
+	 */
+	getFieldData(fieldId) {
+		const fieldData = this.fieldStore.get(fieldId);
+		if (!fieldData) return null;
+
+		// Only convert uploads back to Set (DataStore returns Arrays)
+		if (Array.isArray(fieldData.uploads)) {
+			fieldData.uploads = new Set(fieldData.uploads);
+		} else if (!fieldData.uploads) {
+			fieldData.uploads = new Set();
+		}
+
+		// Ensure groups is an array
+		if (!Array.isArray(fieldData.groups)) {
+			fieldData.groups = [];
+		}
+
+		return fieldData;
+	}
+
+	/**
+	 * Save field data to store, converting Sets to Arrays
+	 */
+	async saveFieldData(fieldData) {
+		await this.fieldStore.save({
+			...fieldData,
+			timestamp: Date.now()
+		});
+	}
+
+	determineFieldId(fieldElement) {
+		const content = fieldElement.dataset.content ||
+			fieldElement.closest('dialog')?.dataset.content ||
+			fieldElement.closest('form')?.dataset.save || '';
+		const itemID = fieldElement.dataset.itemId ||
+			fieldElement.closest('dialog')?.dataset.itemId || '';
+		const field = fieldElement.dataset.field || '';
+
+		return `${content}_${itemID}_${field}`;
+	}
+
+	getFromElement(element, type) {
+		const map = {
+			'field': {
+				selector: this.selectors.field.field,
+				key: 'uploader',
+				getRuntimeData: (id) => this.fieldElements.get(id),
+				getStoreData: (id) => this.getFieldData(id)
+			},
+			'upload': {
+				selector: this.selectors.items.item,
+				key: 'uploadId',
+				getRuntimeData: (id) => this.uploadElements.get(id),
+				getStoreData: (id) => this.uploadStore.get(id)
+			},
+			'group': {
+				selector: this.selectors.groups.container,
+				key: 'groupId',
+				getRuntimeData: (id) => this.groupElements.get(id),
+				getStoreData: (id) => {
+					// Groups are stored in field.groups array
+					const groupEl = this.groupElements.get(id);
+					if (!groupEl) return null;
+					const fieldData = this.getFieldData(groupEl.fieldId);
+					return fieldData?.groups?.find(g => g.id === id);
+				}
+			}
+		};
+
+		const config = map[type];
+		if (!config) return null;
+
+		const el = element.closest(config.selector);
+		if (!el) return null;
+
+		const id = el.dataset[config.key];
+
+		// Return combined runtime + store data for convenience
+		const runtime = config.getRuntimeData(id);
+		const store = config.getStoreData(id);
+
+		return { ...runtime, ...store };
+	}
+
+	getFieldFromElement(el) { return this.getFromElement(el, 'field'); }
+	getUploadFromElement(el) { return this.getFromElement(el, 'upload'); }
+	getGroupFromElement(el) { return this.getFromElement(el, 'group'); }
+
+	getFieldIdFromElement(el) {
+		const field = this.getFromElement(el, 'field');
+		return field?.id ?? null;
+	}
+	getUploadIdFromElement(el) {
+		const upload = this.getFromElement(el, 'upload');
+		return upload?.id ?? null;
+	}
+	getGroupIdFromElement(el) {
+		const group = this.getFromElement(el, 'group');
+		return group?.id ?? null;
+	}
+
+	getSubtypeFromMime(mimeType) {
+		if (mimeType.startsWith('image/')) return 'image';
+		if (mimeType.startsWith('video/')) return 'video';
+		return 'document';
+	}
+
+	getStatusText(status) {
+		return this.statusMapping[status] || status;
+	}
+
+	getStatusIcon(status) {
+		return window.getIcon(this.queue.icons[status]);
+	}
+
+	getStatusProgress(status) {
+		const progress = {
+			'local_processing': 28,
+			'queued': 50,
+			'uploading': 66,
+			'pending': 75,
+			'processing': 89,
+			'completed': 100
+		};
+		return progress[status] || 0;
+	}
+
+	getModalType(fieldEl) {
+		if (!fieldEl?.element) return null;
+		if (fieldEl._cachedModalType !== undefined) {
+			return fieldEl._cachedModalType;
+		}
+
+		const dialog = fieldEl.element.closest('dialog');
+		if (!dialog) {
+			fieldEl._cachedModalType = null;
+			return null;
+		}
+
+		let modalType = null;
+		if (dialog.classList.contains('edit')) modalType = 'edit';
+		else if (dialog.classList.contains('create')) modalType = 'create';
+		else if (dialog.classList.contains('bulkEdit')) modalType = 'bulkEdit';
+		else modalType = dialog.className;
+
+		fieldEl._cachedModalType = modalType;
+		return modalType;
+	}
+
+	createUploadElement(upload, draggable = false) {
+		let image = window.getTemplate('uploadItem');
+		if (!image) return;
+
+		image.dataset.uploadId = upload.id;
+		image.dataset.subtype = upload.subtype || 'image';
+
+		let [featured, img, video, preview, details] = [
+			image.querySelector('[name="featured"]'),
+			image.querySelector('img'),
+			image.querySelector('video'),
+			image.querySelector('label > span'),
+			image.querySelector('details')
+		];
+
+		if (featured) featured.value = upload.id;
+
+		switch (upload.subtype) {
+			case 'image':
+				if (img) {
+					img.src = upload.preview;
+					img.alt = upload.meta?.originalName || '';
+				}
+				video?.remove();
+				preview?.remove();
+				break;
+			case 'video':
+				if (video) video.src = upload.preview;
+				img?.remove();
+				preview?.remove();
+				break;
+			case 'document':
+				const fileName = upload.meta?.originalName || '';
+				const extension = fileName.split('.').pop()?.toLowerCase() || '';
+				const iconMap = {
+					'pdf': 'file-pdf', 'csv': 'file-csv',
+					'doc': 'file-doc', 'docx': 'file-doc',
+					'txt': 'file-txt', 'xls': 'file-xls', 'xlsx': 'file-xls'
+				};
+				const icon = window.getIcon(iconMap[extension] || 'file');
+				if (preview) {
+					preview.innerText = fileName;
+					preview.prepend(icon);
+				}
+				img?.remove();
+				video?.remove();
+				break;
+		}
+
+		if (details) {
+			let template = window.getTemplate('uploadMeta');
+			if (template) details.append(template);
+		}
+
+		image.draggable = draggable;
+
+		// Update input IDs
+		image.querySelectorAll('input').forEach(input => {
+			let id = input.id;
+			if (id) {
+				let newId = id + upload.id;
+				let label = input.parentNode.querySelector(`label[for="${id}"]`);
+				input.id = newId;
+				if (label) label.htmlFor = newId;
+			}
+		});
+
+		return image;
+	}
+
+	/*******************************************************************************
+	 * PERSISTENCE
+	 *******************************************************************************/
+
+	/**
+	 * Normalize field data loaded from IndexedDB
+	 * Converts Arrays back to Sets, handles missing properties
+	 */
+	normalizeFieldData(fieldData) {
+		if (!fieldData) return null;
+
+		// Convert uploads array back to Set
+		if (Array.isArray(fieldData.uploads)) {
+			fieldData.uploads = new Set(fieldData.uploads);
+		} else if (!fieldData.uploads) {
+			fieldData.uploads = new Set();
+		}
+
+		// Convert groups array, ensure proper structure
+		if (!Array.isArray(fieldData.groups)) {
+			fieldData.groups = [];
+		}
+
+		// Ensure each group has uploads array
+		fieldData.groups = fieldData.groups.map(group => ({
+			...group,
+			uploads: Array.isArray(group.uploads) ? group.uploads : []
+		}));
+
+		return fieldData;
+	}
+
+	schedulePersistance(fieldId) {
+		const key = `persist_${fieldId}`;
+		window.debouncer.schedule(
+			key,
+			() => this.persistFieldState(fieldId),
+			1000
+		);
+	}
+
+	async persistFieldState(fieldId) {
+		const fieldData = this.getFieldData(fieldId);
+		if (!fieldData) return;
+
+		// Save with updated timestamp
+		await this.saveFieldData(fieldData);
+	}
+
+	// In UploadManager, add blob conversion helpers
+	async saveBlobData(uploadId, file) {
+		const arrayBuffer = await file.arrayBuffer();
+
+		const uploadData = this.uploadStore.get(uploadId) || { id: uploadId };
+
+		// Store blob data as ArrayBuffer with metadata
+		uploadData.blobData = {
+			buffer: arrayBuffer,
+			name: file.name,
+			type: file.type,
+			size: file.size,
+			lastModified: file.lastModified || Date.now()
+		};
+
+		await this.uploadStore.save(uploadData);
+	}
+
+	async getBlobData(uploadId) {
+		const upload = this.uploadStore.get(uploadId);
+		if (!upload?.blobData) return null;
+
+		// Reconstruct File from ArrayBuffer
+		const blob = new Blob([upload.blobData.buffer], { type: upload.blobData.type });
+		return new File([blob], upload.blobData.name, {
+			type: upload.blobData.type,
+			lastModified: upload.blobData.lastModified
+		});
+	}
+
+	/*******************************************************************************
+	 * RECOVERY & RESTORATION
+	 *******************************************************************************/
+
+	handleFieldStoreEvent(event, data) {
+		switch(event) {
+			case 'data-loaded':
+				// Check for pending uploads to restore
+				this.checkForStoredUploads();
+				break;
+		}
+	}
+
+	handleUploadStoreEvent(event, data) {
+		switch(event) {
+			case 'data-loaded':
+				break;
+			case 'item-saved':
+				this.showSaveIndicator(data.key);
+				break;
+		}
+	}
+
+	async checkForStoredUploads() {
+		const allFieldStates = this.fieldStore.getAll();
+
+		console.log('Checking for stored uploads...', {
+			fieldStates: allFieldStates.length,
+			uploadStoreSize: this.uploadStore.data.size
+		});
+		const pendingFields = allFieldStates.filter(field => {
+			if (!field.uploads) return false;
+
+			// Handle both Set and Array (from IndexedDB)
+			const uploadsArray = field.uploads instanceof Set
+				? Array.from(field.uploads)
+				: Array.isArray(field.uploads)
+					? field.uploads
+					: [];
+
+			return uploadsArray.some(uploadId => {
+				const upload = this.uploadStore.get(uploadId);
+				return upload && !upload.operationId &&
+					['completed', 'processed', 'local_processing', 'processed-original'].includes(upload.status);
+			});
+		});
+		console.log('Found pending fields:', pendingFields.length);
+		if (pendingFields.length === 0) return;
+
+		this.showRecoveryNotification(pendingFields);
+	}
+
 	async showRecoveryNotification(pendingFields) {
 		const totalUploads = pendingFields.reduce((sum, field) => sum + field.uploads.length, 0);
 		const totalGroups = pendingFields.reduce((sum, field) =>
@@ -2588,14 +2730,12 @@
 				// 	const imgEl = uploadItem.querySelector('img');
 				// 	const placeholderEl = uploadItem.querySelector('.image-placeholder');
 				//
-				const blobData = await this.uploadStore.getBlob(upload.id);
+				const file = await this.getBlobData(upload.id);
+				if (file) {
 
-
-				if (blobData) {
 					try {
 						// Create new blob URL from stored data
-						const blob = new Blob([blobData.data], { type: blobData.type });
-						const previewUrl = this.createPreviewUrl(blob);
+						const previewUrl = this.createPreviewUrl(file);
 
 						let [
 							featured,
@@ -2616,7 +2756,7 @@
 
 						uploadItem.dataset.fieldId = field.id;
 
-						let subtype = this.getSubtypeFromMime(blobData.type);
+						let subtype = this.getSubtypeFromMime(file.type);
 						uploadItem.dataset.subtype = subtype;
 						switch (subtype) {
 							case 'image':
@@ -2625,7 +2765,7 @@
 									img.alt
 								] = [
 									previewUrl,
-									upload.originalFile?.name ?? upload.meta?.originalName?? ''
+									file.name ?? upload.meta?.originalName ?? ''
 								];
 								video.remove();
 								preview.remove();
@@ -2720,336 +2860,23 @@
 
 	}
 
-	async restoreSelectedUploads(selectedUploads) {
-		// Group by field
-		const byField = new Map();
-		selectedUploads.forEach(item => {
-			if (!byField.has(item.fieldId)) {
-				byField.set(item.fieldId, []);
-			}
-			byField.get(item.fieldId).push(item.uploadId);
-		});
-
-		// Get full field states from IndexedDB
-		if (!this.db) {
-			// this.notifications.add('Cannot restore: Database not available', 'error');
+	async handleRestoreUploads() {
+		let notification = document.querySelector('dialog.restore-uploads');
+		if (!notification) {
 			return;
 		}
 
-		const tx = this.db.transaction(['fieldStates'], 'readonly');
-		const store = tx.objectStore('fieldStates');
-
-		for (const [fieldId, uploadIds] of byField.entries()) {
-			const request = store.get(fieldId);
-			const fieldState = await new Promise(resolve => {
-				request.onsuccess = () => resolve(request.result);
-				request.onerror = () => resolve(null);
-			});
-
-			if (fieldState) {
-				// Filter to only selected uploads
-				fieldState.uploads = fieldState.uploads.filter(u => uploadIds.includes(u.id));
-				await this.restoreField(fieldState);
-			}
-		}
-
-		// this.notifications.add(`Restored ${selectedUploads.length} upload(s)`, 'success');
-	}
-
-	async restoreField(fieldState) {
-		const { config, context, uploads, groups, id } = fieldState;  // ← Use 'id'
-
-		// If in a modal, open it first
-		if (context.modalType) {
-			await this.openModalForRestore(context);
-		}
-
-		// Find field element
-		let fieldElement = document.querySelector(`.field.upload[data-field="${config.name}"]`);
-
-		if (!fieldElement) {
-			const uploaderKey = `${config.content}_${config.itemID}_${config.name}`;
-			fieldElement = document.querySelector(`.field.upload[data-uploader="${uploaderKey}"]`);
-		}
-
-		if (!fieldElement) {
-			console.warn(`Field ${config.name} not found for restoration`, config);
+		const selectedUploads = this.getSelectedRestorationUploads(notification);
+		if (selectedUploads.length === 0) {
 			return;
 		}
+		await this.restoreSelectedUploads(selectedUploads);
 
-		// Register the field if not already registered
-		let fieldKey = fieldElement.dataset.uploader;
-		if (!fieldKey || !this.fields.has(fieldKey)) {
-			fieldKey = this.registerUploader(fieldElement, config);
-		}
-
-		const field = this.fields.get(fieldKey);
-		if (!field) {
-			console.error('Failed to register field for restoration');
-			return;
-		}
-
-		// Merge saved state back into field
-		field.state = fieldState.state || 'ready';
-
-		// Rebuild UI references
-		field.ui = this.buildFieldUI(fieldElement);
-
-		if (field.ui.groups?.display) {
-			field.ui.groups.display.hidden = false;
-		}
-
-		// Restore groups
-		if (groups && groups.length > 0) {
-			await this.restoreGroups(fieldKey, groups);
-		}
-
-		// Restore uploads
-		for (const uploadData of uploads) {
-			await this.restoreUpload(field, uploadData);
-		}
-
-		// Update UI
-		this.updateFieldState(fieldKey);
-		this.maybeLockUploads(fieldKey);
-
-		// Queue for upload if needed
-		if (config.mode === 'direct' && config.destination !== 'post_group') {
-			await this.queueUpload(fieldKey);
-		}
+		this.cleanupRestore();
 	}
 
-	async restoreUpload(field, uploadData) {
-		// Try to get blob data from IndexedDB
-		const blobData = await this.uploadStore.getBlob(uploadData.id);
-
-		if (blobData) {
-			const file = blobData.data instanceof File
-				? blobData.data
-				: new File(
-					[blobData.data],
-					blobData.name,
-					{ type: blobData.type, lastModified: blobData.lastModified }
-				);
-
-			uploadData.originalFile = file;
-			uploadData.processedFile = file;
-			uploadData.preview = this.createPreviewUrl(file);
-		} else {
-			console.warn('Blob data not found for upload:', uploadData.id);
-			return; // Skip this upload if we can't restore the file
-		}
-
-		// Add to field
-		if (!field.uploads) field.uploads = new Set();
-		field.uploads.add(uploadData.id);
-
-		// Recreate DOM element
-		const subtype = this.getSubtypeFromMime(uploadData.originalFile.type);
-		uploadData.element = this.createUploadElement({
-			...uploadData,
-			subtype: subtype
-		}, field.config.destination === 'post_group');
-
-		// Restore to correct location
-		let location;
-		if (uploadData.groupId && field.ui.groups.groups.has(uploadData.groupId)) {
-			location = field.ui.groups.groups.get(uploadData.groupId).querySelector('.item-grid');
-		} else {
-			location = field.ui.preview;
-		}
-
-		if (location) {
-			location.appendChild(uploadData.element);
-			uploadData.location = location;
-		}
-
-		// Store in memory
-		this.uploads.set(uploadData.id, uploadData);
-		if (uploadData.groupId) {
-			const group = this.groups.get(uploadData.groupId);
-			if (group && group.uploads) {
-				group.uploads.add(uploadData.id);
-			}
-		}
-	}
-
-	async restoreGroups(fieldKey, groups) {
-		for (const groupData of groups) {
-			// Use createGroup which properly initializes EVERYTHING including selection handlers
-			const group = this.createGroup(fieldKey, groupData.id);
-
-			if (group) {
-				// Update the group metadata from saved state
-				if (groupData.meta) {
-					group.meta = { ...groupData.meta };
-				}
-				if (groupData.changes) {
-					group.changes = { ...groupData.changes };
-				}
-
-
-				// If you saved group titles, restore them
-				if (groupData.title) {
-					const titleInput = group.element.querySelector('[name*="post_title"]');
-					if (titleInput) {
-						titleInput.value = groupData.title;
-					}
-				}
-			}
-		}
-	}
-
-	async openModalForRestore(context) {
-		const { modalType, formId } = context;
-
-		// Find and click the appropriate button to open the modal
-		let trigger = null;
-
-		switch(modalType) {
-			case 'create':
-				trigger = document.querySelector('[data-action="create"]');
-				break;
-			case 'edit':
-				// Need to find the specific edit button
-				trigger = document.querySelector(`[data-action="edit"][data-id="${context.itemId}"]`);
-				break;
-			case 'bulkEdit':
-				trigger = document.querySelector('[data-action="bulk-edit"]');
-				break;
-		}
-
-		if (trigger) {
-			trigger.click();
-
-			// Wait for modal to open
-			await new Promise(resolve => setTimeout(resolve, 300));
-		}
-	}
-
-	/*******************************************************************************
-	 INDEXEDDB CACHE FUNCTIONALITY
-	 *******************************************************************************/
-	handleFieldStoreEvent(event, data) {
-		switch(event) {
-			case 'data-loaded':
-
-				break;
-			case 'item-saved':
-				console.log(`Field state saved: ${data.key}`);
-				break;
-		}
-	}
-
-	handleUploadStoreEvent(event, data) {
-		switch(event) {
-			case 'data-loaded':
-				this.checkForStoredUploads();
-				break;
-			case 'item-saved':
-				this.showSaveIndicator(data.key);
-				break;
-		}
-	}
-	async saveUpload(upload) {
-		// Use the processed file if available, otherwise original
-		const fileToStore = upload.processedFile || upload.originalFile || upload.file;
-
-		if (fileToStore instanceof File || fileToStore instanceof Blob) {
-			await this.uploadStore.saveBlob(upload.id, fileToStore);
-
-			// Don't store file objects in main store
-			const { file, originalFile, processedFile, ...cleanUpload } = upload;
-			await this.uploadStore.save(cleanUpload);
-		} else {
-			await this.uploadStore.save(upload);
-		}
-	}
-
-	async loadFields() {
-		// Load all field states from the store
-		const fields = await this.fieldStore.getAll();
-
-		fields.forEach(field => {
-			// Reconstruct upload sets
-			if (field.uploads && Array.isArray(field.uploads)) {
-				field.uploads = new Set(field.uploads.map(u => u.id));
-			}
-			this.fields.set(field.fieldId, field);
-		});
-	}
-
-	async loadUploads() {
-		const uploads = await this.uploadStore.getAll();
-		uploads.forEach(upload => {
-			this.uploads.set(upload.id, upload);
-		});
-	}
-
-	/**************************************************************************
-	 SUBSCRIBERS
-	 **************************************************************************/
-	/**
-	 * Event system
-	 */
-	subscribe(callback) {
-		this.subscribers.add(callback);
-		return () => this.subscribers.delete(callback);
-	}
-
-	notify(event, data) {
-		this.subscribers.forEach(cb => cb(event, data));
-	}
-	/*******************************************************************************
-	 * CLEANUP
-	 *******************************************************************************/
-
-	destroy() {
-		// Remove core listeners
-		document.removeEventListener('click', this.clickHandler);
-		document.removeEventListener('change', this.changeHandler);
-		document.removeEventListener('dragenter', this.dragEnterHandler);
-		document.removeEventListener('dragleave', this.dragLeaveHandler);
-		document.removeEventListener('dragover', this.dragOverHandler);
-		document.removeEventListener('drop', this.dropHandler);
-
-		// Destroy drag controller
-		if (this.dragController) {
-			this.dragController.destroy();
-		}
-
-		// Destroy selection handlers
-		this.selectionHandlers.forEach(handler => handler.destroy());
-		this.selectionHandlers.clear();
-
-		this.cleanupAllPreviewUrls();
-		this.sortableInstances.forEach(instance => {
-			if (instance?.destroy) {
-				instance.destroy();
-			}
-		});
-		this.sortableInstances.clear();
-
-		// Clear data
-		this.fields.clear();
-		this.uploads.clear();
-		this.groups.clear();
-		this.selected.clear();
-		this.subscribers.clear();
-	}
-
-	destroySortable(fieldName) {
-		// Destroy all sortable instances for this field
-		const instances = Array.from(this.sortableInstances.keys())
-			.filter(key => key.startsWith(fieldName));
-
-		instances.forEach(key => {
-			const instance = this.sortableInstances.get(key);
-			if (instance?.destroy) {
-				instance.destroy();
-			}
-			this.sortableInstances.delete(key);
-		});
+	showSaveIndicator(key) {
+		// Optional: show user that state is being saved
 	}
 
 	cleanupRestore() {
@@ -3062,65 +2889,60 @@
 	}
 
 	async cleanupStoredUploads() {
-		this.fieldStore.clear();
-		this.uploadStore.clear();
+		await this.fieldStore.clear();
+		await this.uploadStore.clear();
 	}
 
-	/**
-	 * Clear all uploads for a field and cleanup resources
-	 */
-	async clearField(fieldId) {
-		// Clear from stores
-		await this.fieldStore.delete(fieldId);
+	/*******************************************************************************
+	 * EVENT SYSTEM
+	 *******************************************************************************/
 
-		// Clear related uploads
-		const field = this.fields.get(fieldId);
-		if (field?.uploads) {
-			for (const uploadId of field.uploads) {
-				await this.uploadStore.delete(uploadId);
+	subscribe(callback) {
+		this.subscribers.add(callback);
+		return () => this.subscribers.delete(callback);
+	}
+
+	notify(event, data = {}) {
+		this.subscribers.forEach(cb => {
+			try {
+				cb(event, data);
+			} catch (error) {
+				console.error('Subscriber error:', error);
 			}
-		}
-
-		// Clear from memory
-		this.fields.delete(fieldId);
+		});
 	}
 
-	async clearUpload(uploadId, persist = true) {
-		const upload = this.uploads.get(uploadId);
-		if (!upload) return;
+	/*******************************************************************************
+	 * DESTROY & CLEANUP
+	 *******************************************************************************/
 
-		// Clean up preview URL using helper
-		this.revokePreviewUrl(upload.preview);
+	destroy() {
+		document.removeEventListener('click', this.clickHandler);
+		document.removeEventListener('change', this.changeHandler);
+		document.removeEventListener('dragenter', this.dragEnterHandler);
+		document.removeEventListener('dragleave', this.dragLeaveHandler);
+		document.removeEventListener('dragover', this.dragOverHandler);
+		document.removeEventListener('drop', this.dropHandler);
 
-		// Clean up element preview URL
-		if (upload.element) {
-			const previewUrl = upload.element.dataset.previewUrl;
-			this.revokePreviewUrl(previewUrl);
-			delete upload.element.dataset.previewUrl;
+		if (this.dragController) {
+			this.dragController.destroy();
 		}
 
-		if (persist) {
-			await this.schedulePersistance(upload.fieldId);
-		}
+		this.selectionHandlers.forEach(handler => handler.destroy());
+		this.selectionHandlers.clear();
 
-		// Remove from memory
-		this.uploads.delete(uploadId);
+		this.cleanupAllPreviewUrls();
 
-		// Remove from IndexedDB
-		this.uploadStore.delete(uploadId);
-		this.uploadStore.delete(uploadId, 'blobs');
-	}
-	cleanupAllPreviewUrls() {
-		if (this.previewUrls) {
-			this.previewUrls.forEach(url => {
-				try {
-					URL.revokeObjectURL(url);
-				} catch (e) {
-					// Ignore errors during cleanup
-				}
-			});
-			this.previewUrls.clear();
-		}
+		this.sortableInstances.forEach(instance => {
+			if (instance?.destroy) instance.destroy();
+		});
+		this.sortableInstances.clear();
+
+		this.uploadElements.clear();
+		this.fieldElements.clear();
+		this.groupElements.clear();
+		this.selected.clear();
+		this.subscribers.clear();
 	}
 }
 

--
Gitblit v1.10.0