// INITIALIZATION - FIXED VERSION
// =============================================================================
/**
* Helper function to create dummy DOM elements to prevent null reference errors
* @returns {Object} A dummy element object with basic properties
*/
function createDummyElement() {
return {
textContent: "",
innerHTML: "",
style: {},
value: "",
checked: false,
disabled: false,
addEventListener: () => {},
removeEventListener: () => {},
focus: () => {},
click: () => {},
querySelector: () => null,
querySelectorAll: () => [],
};
}
/**
* Safely retrieves a DOM element by ID with fallback to dummy element
* @param {string} id - Element ID
* @param {boolean} required - Whether to log warning if element missing
* @returns {HTMLElement|Object} Element or dummy element
*/
function safeGetElement(id, required = false) {
const element = document.getElementById(id);
if (!element && required) {
console.warn(`Required element '${id}' not found in DOM`);
}
return element || createDummyElement();
}
/**
* Main application initialization function
*
* This function coordinates the complete application startup process with proper
* error handling, DOM element validation, and event binding.
*
* @returns {void} Fully initializes the application interface
*/
document.addEventListener("DOMContentLoaded", async () => {
debugLog(`=== APPLICATION INITIALIZATION STARTED (v${APP_VERSION}) ===`);
try {
// Phase 0: Apply domain-based logo branding
const brandName = typeof getBrandingName === 'function' ? getBrandingName() : BRANDING_TITLE;
const logoSplit = BRANDING_DOMAIN_OPTIONS.logoSplit[brandName];
if (logoSplit) {
document.querySelectorAll('.logo-silver').forEach(el => { el.textContent = logoSplit[0]; });
document.querySelectorAll('.logo-gold').forEach(el => { el.textContent = logoSplit[1]; });
// Adjust SVG viewBox for longer brand names
if (logoSplit[2]) {
const logoSvg = document.querySelector('.stackr-logo');
if (logoSvg) logoSvg.setAttribute('viewBox', `0 0 ${logoSplit[2]} 200`);
}
}
const appLogo = document.getElementById('appLogo');
if (appLogo) appLogo.setAttribute('aria-label', brandName);
const footerBrand = document.getElementById('footerBrand');
if (footerBrand) footerBrand.textContent = brandName;
// Update About modal site link to match current domain
const siteDomain = typeof getFooterDomain === 'function' ? getFooterDomain() : 'staktrakr.com';
const aboutSiteLink = document.getElementById('aboutSiteLink');
const aboutSiteDomain = document.getElementById('aboutSiteDomain');
if (aboutSiteLink) aboutSiteLink.href = `https://www.${siteDomain}`;
if (aboutSiteDomain) aboutSiteDomain.textContent = siteDomain;
// Phase 1: Initialize Core DOM Elements
debugLog("Phase 1: Initializing core DOM elements...");
// Core form elements
elements.inventoryForm = safeGetElement("inventoryForm", true);
const inventoryTableEl = safeGetElement("inventoryTable", true);
const tbody = inventoryTableEl && inventoryTableEl.querySelector ? inventoryTableEl.querySelector("tbody") : null;
elements.inventoryTable = tbody;
elements.itemMetal = safeGetElement("itemMetal", true);
elements.itemName = safeGetElement("itemName", true);
elements.itemQty = safeGetElement("itemQty", true);
elements.itemType = safeGetElement("itemType", true);
elements.itemWeight = safeGetElement("itemWeight", true);
elements.itemWeightUnit = safeGetElement("itemWeightUnit", true);
elements.itemGbDenom = safeGetElement("itemGbDenom");
elements.itemPrice = safeGetElement("itemPrice", true);
elements.itemMarketValue = safeGetElement("itemMarketValue");
elements.marketValueField = safeGetElement("marketValueField");
elements.dateField = safeGetElement("dateField");
elements.purchaseLocation = safeGetElement("purchaseLocation", true);
elements.storageLocation = safeGetElement("storageLocation");
elements.itemNotes = safeGetElement("itemNotes");
elements.itemDate = safeGetElement("itemDate", true);
elements.itemSpotPrice = safeGetElement("itemSpotPrice");
elements.itemCatalog = safeGetElement("itemCatalog");
elements.itemYear = safeGetElement("itemYear");
elements.itemGrade = safeGetElement("itemGrade");
elements.itemGradingAuthority = safeGetElement("itemGradingAuthority");
elements.itemCertNumber = safeGetElement("itemCertNumber");
elements.itemObverseImageUrl = safeGetElement("itemObverseImageUrl");
elements.itemReverseImageUrl = safeGetElement("itemReverseImageUrl");
elements.itemPcgsNumber = safeGetElement("itemPcgsNumber");
elements.itemSerialNumber = safeGetElement("itemSerialNumber");
elements.searchNumistaBtn = safeGetElement("searchNumistaBtn");
elements.lookupPcgsBtn = safeGetElement("lookupPcgsBtn");
elements.spotLookupBtn = safeGetElement("spotLookupBtn");
elements.itemPuritySelect = safeGetElement("itemPuritySelect");
elements.itemPurity = safeGetElement("itemPurity");
elements.purityCustomWrapper = safeGetElement("purityCustomWrapper");
// Header buttons - CRITICAL
debugLog("Phase 2: Initializing header buttons...");
elements.appLogo = safeGetElement("appLogo");
elements.settingsBtn = safeGetElement("settingsBtn", true);
elements.aboutBtn = safeGetElement("aboutBtn");
// STACK-54 header toggles
elements.headerThemeBtn = safeGetElement("headerThemeBtn");
elements.headerCurrencyBtn = safeGetElement("headerCurrencyBtn");
// STACK-54 layout sections
elements.spotPricesSection = safeGetElement("spotPricesSection");
elements.totalsSectionEl = safeGetElement("totalsSectionEl");
elements.searchSectionEl = safeGetElement("searchSectionEl");
elements.tableSectionEl = safeGetElement("tableSectionEl");
// Check if critical buttons exist
debugLog(
"Settings Button found:",
!!document.getElementById("settingsBtn"),
);
// Import/Export elements
debugLog("Phase 3: Initializing import/export elements...");
elements.importCsvFile = safeGetElement("importCsvFile");
elements.importCsvOverride = safeGetElement("importCsvOverride");
elements.importCsvMerge = safeGetElement("importCsvMerge");
elements.importJsonFile = safeGetElement("importJsonFile");
elements.importJsonOverride = safeGetElement("importJsonOverride");
elements.importJsonMerge = safeGetElement("importJsonMerge");
elements.importProgress = safeGetElement("importProgress");
elements.importProgressText = safeGetElement("importProgressText");
elements.numistaImportBtn = safeGetElement("numistaImportBtn");
elements.numistaImportFile = safeGetElement("numistaImportFile");
elements.numistaOverride = safeGetElement("numistaOverride");
elements.numistaMerge = safeGetElement("numistaMerge");
elements.numistaImportOptions = safeGetElement("numistaImportOptions");
elements.exportCsvBtn = safeGetElement("exportCsvBtn");
elements.exportJsonBtn = safeGetElement("exportJsonBtn");
elements.exportPdfBtn = safeGetElement("exportPdfBtn");
elements.cloudSyncBtn = safeGetElement("cloudSyncBtn");
elements.syncAllBtn = safeGetElement("syncAllBtn");
elements.numistaApiKey = safeGetElement("numistaApiKey");
elements.removeInventoryDataBtn = safeGetElement("removeInventoryDataBtn");
elements.boatingAccidentBtn = safeGetElement("boatingAccidentBtn");
elements.vaultExportBtn = safeGetElement("vaultExportBtn");
elements.vaultImportBtn = safeGetElement("vaultImportBtn");
elements.vaultImportFile = safeGetElement("vaultImportFile");
// Modal elements
debugLog("Phase 4: Initializing modal elements...");
elements.settingsModal = safeGetElement("settingsModal");
elements.apiInfoModal = safeGetElement("apiInfoModal");
elements.apiHistoryModal = safeGetElement("apiHistoryModal");
elements.goldbackHistoryModal = safeGetElement("goldbackHistoryModal");
elements.cloudSyncModal = safeGetElement("cloudSyncModal");
elements.cloudSyncConflictModal = safeGetElement("cloudSyncConflictModal");
elements.vaultModal = safeGetElement("vaultModal");
elements.apiQuotaModal = safeGetElement("apiQuotaModal");
elements.aboutModal = safeGetElement("aboutModal");
elements.ackModal = safeGetElement("ackModal");
elements.ackAcceptBtn = safeGetElement("ackAcceptBtn");
// Unified item modal elements (add/edit)
elements.itemModal = safeGetElement("itemModal");
elements.itemCloseBtn = safeGetElement("itemCloseBtn");
elements.cancelItemBtn = safeGetElement("cancelItem");
elements.itemModalTitle = safeGetElement("itemModalTitle");
elements.itemModalSubmit = safeGetElement("itemModalSubmit");
elements.itemSerial = safeGetElement("itemSerial");
elements.undoChangeBtn = safeGetElement("undoChangeBtn");
// Show acknowledgment modal immediately and set up modal events
if (typeof setupAckModalEvents === "function") {
setupAckModalEvents();
}
if (typeof setupAboutModalEvents === "function") {
setupAboutModalEvents();
}
if (typeof setupFaqModalEvents === "function") {
setupFaqModalEvents();
}
// Notes modal elements
elements.notesModal = safeGetElement("notesModal");
elements.notesTextarea = safeGetElement("notesTextarea");
elements.saveNotesBtn = safeGetElement("saveNotes");
elements.cancelNotesBtn = safeGetElement("cancelNotes");
elements.notesCloseBtn = safeGetElement("notesCloseBtn");
// View item modal elements
elements.viewItemModal = safeGetElement("viewItemModal");
elements.viewModalCloseBtn = safeGetElement("viewModalCloseBtn");
// Debug modal elements
elements.debugModal = safeGetElement("debugModal");
elements.debugCloseBtn = safeGetElement("debugCloseBtn");
// Bulk edit modal elements
elements.bulkEditModal = safeGetElement("bulkEditModal");
elements.bulkEditBtn = safeGetElement("bulkEditBtn");
elements.bulkEditCloseBtn = safeGetElement("bulkEditCloseBtn");
// Settings change log panel
elements.settingsChangeLogClearBtn = safeGetElement("settingsChangeLogClearBtn");
// Settings Activity Log sub-tab elements (STACK-44)
elements.settingsSpotHistoryClearBtn = safeGetElement("settingsSpotHistoryClearBtn");
elements.settingsCatalogHistoryClearBtn = safeGetElement("settingsCatalogHistoryClearBtn");
elements.settingsPriceHistoryClearBtn = safeGetElement("settingsPriceHistoryClearBtn");
elements.settingsCloudActivityClearBtn = safeGetElement("settingsCloudActivityClearBtn");
elements.priceHistoryFilterInput = safeGetElement("priceHistoryFilterInput");
// Pagination elements
debugLog("Phase 5: Initializing pagination elements...");
elements.itemsPerPage = safeGetElement("itemsPerPage");
elements.itemCount = safeGetElement("itemCount");
elements.changeLogBtn = safeGetElement("changeLogBtn");
elements.backupReminder = safeGetElement("backupReminder");
elements.changeLogModal = safeGetElement("changeLogModal");
elements.changeLogCloseBtn = safeGetElement("changeLogCloseBtn");
elements.changeLogClearBtn = safeGetElement("changeLogClearBtn");
elements.changeLogTable = safeGetElement("changeLogTable");
elements.storageUsage = safeGetElement("storageUsage");
elements.storageReportLink = safeGetElement("storageReportLink");
// Search elements
debugLog("Phase 6: Initializing search elements...");
elements.searchInput = safeGetElement("searchInput");
elements.clearBtn = safeGetElement("clearBtn");
elements.newItemBtn = safeGetElement("newItemBtn");
elements.searchResultsInfo = safeGetElement("searchResultsInfo");
elements.activeFilters = safeGetElement("activeFilters");
// Ensure chipMinCount has a sensible default for new installs
try {
const chipMinEl = document.getElementById('chipMinCount');
const saved = localStorage.getItem('chipMinCount');
if (!saved) {
localStorage.setItem('chipMinCount', '3');
}
if (chipMinEl) {
chipMinEl.value = localStorage.getItem('chipMinCount') || '3';
}
} catch (e) {
// ignore storage errors
}
// Details modal elements
debugLog("Phase 7: Initializing details modal elements...");
elements.detailsModal = safeGetElement("detailsModal");
elements.detailsModalTitle = safeGetElement("detailsModalTitle");
elements.typeBreakdown = safeGetElement("typeBreakdown");
elements.locationBreakdown = safeGetElement("locationBreakdown");
elements.detailsCloseBtn = safeGetElement("detailsCloseBtn");
elements.totalTitles = document.querySelectorAll(".total-title");
// Chart elements
debugLog("Phase 8: Initializing chart elements...");
elements.typeChart = safeGetElement("typeChart");
elements.locationChart = safeGetElement("locationChart");
// Phase 9: Initialize Metal-Specific Elements
debugLog("Phase 9: Initializing metal-specific elements...");
// Initialize nested objects for spot price cards
elements.spotPriceDisplay = {};
elements.spotSyncIcon = {};
elements.spotRangeSelect = {};
elements.spotSparkline = {};
Object.values(METALS).forEach((metalConfig) => {
const metalKey = metalConfig.key;
const metalName = metalConfig.name;
debugLog(` Setting up ${metalName} elements...`);
elements.spotPriceDisplay[metalKey] = safeGetElement(
`spotPriceDisplay${metalName}`,
);
elements.spotSyncIcon[metalKey] = safeGetElement(
`syncIcon${metalName}`,
);
elements.spotRangeSelect[metalKey] = safeGetElement(
`spotRange${metalName}`,
);
elements.spotSparkline[metalKey] = safeGetElement(
`sparkline${metalName}`,
);
debugLog(` - ${metalName} display element:`, !!document.getElementById(`spotPriceDisplay${metalName}`));
debugLog(` - ${metalName} sparkline canvas:`, !!document.getElementById(`sparkline${metalName}`));
});
// Phase 10: Initialize Totals Elements
debugLog("Phase 10: Initializing totals elements...");
if (!elements.totals) {
elements.totals = {};
}
Object.values(METALS).forEach((metalConfig) => {
const metalKey = metalConfig.key;
const metalName = metalConfig.name;
elements.totals[metalKey] = {
items: safeGetElement(`totalItems${metalName}`),
weight: safeGetElement(`totalWeight${metalName}`),
value: safeGetElement(`currentValue${metalName}`),
purchased: safeGetElement(`totalPurchased${metalName}`),
retailValue: safeGetElement(`retailValue${metalName}`),
lossProfit: safeGetElement(`lossProfit${metalName}`),
avgCostPerOz: safeGetElement(`avgCostPerOz${metalName}`),
};
});
// Initialize "All" totals
elements.totals.all = {
items: safeGetElement("totalItemsAll"),
weight: safeGetElement("totalWeightAll"),
value: safeGetElement("currentValueAll"),
purchased: safeGetElement("totalPurchasedAll"),
retailValue: safeGetElement("retailValueAll"),
lossProfit: safeGetElement("lossProfitAll"),
avgCostPerOz: safeGetElement("avgCostPerOzAll"),
};
// Phase 11: Version Management
debugLog("Phase 11: Updating version information...");
document.title = getAppTitle();
// COMMENTED OUT: This was overriding the SVG logo in the header
// const appHeader = document.querySelector(".app-header h1");
// if (appHeader) {
// const headerBrand = getBrandingName();
// appHeader.textContent = headerBrand;
// }
const aboutVersion = document.getElementById("aboutVersion");
if (aboutVersion) {
aboutVersion.textContent = `v${APP_VERSION}`;
}
const footerDomainEl = document.getElementById("footerDomain");
if (footerDomainEl) {
footerDomainEl.textContent = getFooterDomain();
}
if (typeof loadAnnouncements === "function") {
loadAnnouncements();
}
// Phase 12: Data Initialization
debugLog("Phase 12: Loading application data...");
// Set default date
if (elements.itemDate && elements.itemDate.value !== undefined) {
elements.itemDate.value = todayStr();
}
// Load data
loadInventory();
// Migrate: existing users keep header theme button visible
if (inventory.length > 0 && localStorage.getItem('headerThemeBtnVisible') === null) {
localStorage.setItem('headerThemeBtnVisible', 'true');
}
// Load seed rule toggles before seed inventory (so migration sees real user data)
if (typeof NumistaLookup !== 'undefined' && typeof NumistaLookup.loadEnabledSeedRules === 'function') {
NumistaLookup.loadEnabledSeedRules();
}
// Seed sample inventory for first-time users
if (typeof loadSeedInventory === 'function') {
loadSeedInventory();
}
if (typeof sanitizeTablesOnLoad === "function") {
sanitizeTablesOnLoad();
}
inventory.forEach((i) => addCompositionOption(i.composition || i.metal));
refreshCompositionOptions();
loadSpotHistory();
// Load per-item price history (STACK-43)
if (typeof loadItemPriceHistory === 'function') {
loadItemPriceHistory();
}
// Load item tags (STAK-126)
if (typeof loadItemTags === 'function') {
loadItemTags();
debugLog(`Loaded tags for ${Object.keys(itemTags).length} items`);
}
// Load Goldback denomination pricing (STACK-45)
if (typeof loadGoldbackPrices === 'function') loadGoldbackPrices();
if (typeof loadGoldbackPriceHistory === 'function') loadGoldbackPriceHistory();
if (typeof loadGoldbackEnabled === 'function') loadGoldbackEnabled();
if (typeof loadGoldbackEstimateEnabled === 'function') loadGoldbackEstimateEnabled();
if (typeof loadGoldbackEstimateModifier === 'function') loadGoldbackEstimateModifier();
// Load display currency preference and cached exchange rates (STACK-50)
if (typeof loadDisplayCurrency === 'function') loadDisplayCurrency();
if (typeof loadExchangeRates === 'function') loadExchangeRates();
// Seed spot history for first-time users
if (typeof loadSeedSpotHistory === 'function') {
await loadSeedSpotHistory();
}
// Initialize API system
apiConfig = loadApiConfig();
apiCache = loadApiCache();
// Apply saved desktop card view setting (STAK-118)
const _isCardOnInit = localStorage.getItem(DESKTOP_CARD_VIEW_KEY) === 'true';
if (_isCardOnInit) {
document.body.classList.add('force-card-view');
}
// Load persisted items-per-page setting (view-aware defaults: card=3, table=24)
try {
const savedIpp = localStorage.getItem(ITEMS_PER_PAGE_KEY);
if (savedIpp) {
if (savedIpp === 'all') {
itemsPerPage = Infinity;
if (elements.itemsPerPage) elements.itemsPerPage.value = 'all';
} else {
const parsed = parseInt(savedIpp, 10);
if ([3, 6, 12, 24, 48, 96, 128, 512].includes(parsed)) {
itemsPerPage = parsed;
if (elements.itemsPerPage) elements.itemsPerPage.value = String(parsed);
}
}
} else {
// No saved preference — default to all
itemsPerPage = Infinity;
if (elements.itemsPerPage) elements.itemsPerPage.value = 'all';
}
} catch (e) { /* ignore */ }
// Apply saved theme attribute early so CSS variables resolve correctly
// before renderActiveFilters() computes contrast colors in Phase 13
const earlyTheme = localStorage.getItem(THEME_KEY);
if (earlyTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
} else if (earlyTheme === 'sepia') {
document.documentElement.setAttribute('data-theme', 'sepia');
}
// Initialize IndexedDB image cache (COIN_IMAGES feature)
if (typeof imageCache !== 'undefined' && featureFlags.isEnabled('COIN_IMAGES')) {
try {
await imageCache.init();
debugLog('ImageCache available:', imageCache.isAvailable());
} catch (e) {
console.warn('ImageCache init failed:', e);
}
}
// Backfill CDN image URLs from local catalog cache (no API calls).
// Items viewed before URL persistence was added may have cached Numista
// data in the Local provider but no URLs on the inventory item itself.
try {
const catalogCache = JSON.parse(localStorage.getItem('staktrakr.catalog.cache') || '{}');
const cacheKeys = Object.keys(catalogCache);
const itemsWithNid = inventory.filter(i => i.numistaId).length;
debugLog(`[CDN Backfill] Catalog cache has ${cacheKeys.length} entries, ${itemsWithNid} items have numistaId`);
let backfilled = 0;
for (const item of inventory) {
const catId = item.numistaId;
if (!catId) continue;
const cached = catalogCache[catId];
if (!cached) {
debugLog(`[CDN Backfill] No cache entry for ${catId} (${(item.name || '').slice(0, 30)})`);
continue;
}
if (!item.obverseImageUrl && cached.imageUrl) {
item.obverseImageUrl = cached.imageUrl;
backfilled++;
}
if (!item.reverseImageUrl && cached.reverseImageUrl) {
item.reverseImageUrl = cached.reverseImageUrl;
backfilled++;
}
}
if (backfilled > 0) {
saveInventory();
debugLog(`[CDN Backfill] Backfilled ${backfilled} CDN image URLs from catalog cache`);
} else {
debugLog(`[CDN Backfill] Nothing to backfill (items may already have URLs or cache is empty)`);
}
} catch (e) { debugLog('[CDN Backfill] Error:', e); }
// Clean up stale localStorage keys from removed systems
try { localStorage.removeItem('seedImagesVersion'); } catch (_) { /* ignore */ }
// Wire view modal close button
if (elements.viewModalCloseBtn) {
elements.viewModalCloseBtn.addEventListener('click', () => {
if (typeof closeViewModal === 'function') closeViewModal();
});
}
// Background click dismiss for view modal
if (elements.viewItemModal) {
elements.viewItemModal.addEventListener('click', (e) => {
if (e.target === elements.viewItemModal && typeof closeViewModal === 'function') closeViewModal();
});
}
// Apply header toggle & layout visibility from saved prefs (STACK-54)
if (typeof applyHeaderToggleVisibility === 'function') applyHeaderToggleVisibility();
if (typeof applyLayoutOrder === 'function') applyLayoutOrder();
if (typeof applyMetalOrder === 'function') applyMetalOrder();
// Phase 13: Initial Rendering
debugLog("Phase 13: Rendering initial display...");
renderTable();
if (typeof renderActiveFilters === 'function') {
renderActiveFilters();
}
fetchSpotPrice();
if (typeof updateAllSparklines === "function") {
updateAllSparklines();
}
updateSyncButtonStates();
if (typeof updateStorageStats === "function") {
updateStorageStats();
}
// STAK-149: Initialize cloud auto-sync (starts poller if previously enabled)
if (typeof initCloudSync === 'function') {
initCloudSync();
}
// Load Numista search lookup custom rules
if (typeof NumistaLookup !== 'undefined' && typeof NumistaLookup.loadCustomRules === 'function') {
NumistaLookup.loadCustomRules();
}
// Load seed custom pattern rules + images for first-time users
// Must run after loadCustomRules() so addRule() doesn't clobber existing rules
if (typeof loadSeedImages === 'function') {
await loadSeedImages();
}
// STACK-62: Initialize autocomplete/fuzzy search system
if (typeof initializeAutocomplete === 'function') {
initializeAutocomplete(inventory);
}
// Automatically sync prices if cache is stale and API keys are available
if (typeof autoSyncSpotPrices === "function") {
autoSyncSpotPrices();
}
// Fetch fresh exchange rates in the background (STACK-50)
if (typeof fetchExchangeRates === 'function') {
fetchExchangeRates().then(updated => {
if (updated && displayCurrency !== 'USD') {
// Re-render with fresh rates
if (typeof renderTable === 'function') renderTable();
if (typeof updateSummary === 'function') updateSummary();
}
}).catch(() => {});
}
// Phase 14: Event Listeners Setup (Delayed)
debugLog("Phase 14: Setting up event listeners...");
// Use a small delay to ensure all DOM manipulation is complete
setTimeout(() => {
try {
setupEventListeners();
setupPagination();
setupBulkEditControls();
setupThemeToggle();
if (typeof setupSettingsEventListeners === 'function') {
setupSettingsEventListeners();
}
setupColumnResizing();
// Purity select ↔ custom input toggle
if (elements.itemPuritySelect) {
elements.itemPuritySelect.addEventListener('change', () => {
const wrapper = elements.purityCustomWrapper || document.getElementById('purityCustomWrapper');
const input = elements.itemPurity || document.getElementById('itemPurity');
const isCustom = elements.itemPuritySelect.value === 'custom';
if (wrapper) wrapper.style.display = isCustom ? '' : 'none';
if (input && !isCustom) input.value = '';
});
}
// Weight unit ↔ denomination picker toggle (STACK-45)
if (elements.itemWeightUnit) {
elements.itemWeightUnit.addEventListener('change', () => {
if (typeof toggleGbDenomPicker === 'function') toggleGbDenomPicker();
});
}
if (elements.itemGbDenom) {
elements.itemGbDenom.addEventListener('change', () => {
if (elements.itemWeight) {
elements.itemWeight.value = elements.itemGbDenom.value;
}
});
}
// Setup Edit header toggle functionality
const editHeader = document.querySelector('th[data-column="actions"]');
if (editHeader) {
editHeader.addEventListener('click', (event) => {
if (event.shiftKey) {
// Shift + Click = Toggle all items edit mode
if (typeof toggleAllItemsEdit === 'function') {
toggleAllItemsEdit();
}
} else {
// Regular Click = Toggle edit mode (quick/modal)
if (typeof toggleEditMode === 'function') {
toggleEditMode();
}
}
});
editHeader.title = 'Click to toggle edit mode • Shift+Click to toggle all items edit';
debugLog("✓ Edit header toggle initialized");
}
debugLog("✓ All event listeners setup complete");
} catch (eventError) {
console.error("❌ Error setting up event listeners:", eventError);
// Try basic event setup as fallback
setupBasicEventListeners();
}
// Always set up search listeners
setupSearch();
}, 200); // Increased delay for better compatibility
// Phase 15: Completion
debugLog("=== INITIALIZATION COMPLETE ===");
debugLog("✓ Version:", APP_VERSION);
debugLog("✓ API configured:", !!apiConfig);
debugLog("✓ Inventory items:", inventory.length);
debugLog("✓ Critical elements check:");
debugLog(" - Settings button:", !!elements.settingsBtn);
debugLog(" - Inventory form:", !!elements.inventoryForm);
debugLog(" - Inventory table:", !!elements.inventoryTable);
// Phase 16: Storage optimization pass
if (typeof optimizeStoragePhase1C === 'function') { optimizeStoragePhase1C(); }
// Phase 17: Hash deep-link handling (runs after event listeners are wired)
// Supports privacy.html redirect shim and any direct #privacy / #faq links.
setTimeout(() => { // nosemgrep: javascript.lang.security.detect-eval-with-expression.detect-eval-with-expression
const hash = window.location.hash;
if (hash === '#privacy') {
window.location.hash = '';
if (window.openModalById) openModalById('privacyModal');
} else if (hash === '#faq') {
window.location.hash = '';
if (typeof showSettingsModal === 'function') showSettingsModal('faq');
}
}, 250);
} catch (error) {
console.error("=== CRITICAL INITIALIZATION ERROR ===");
console.error("Error:", error.message);
console.error("Stack:", error.stack);
// Try to show a user-friendly error message
setTimeout(() => {
alert(
`Application initialization failed: ${error.message}\n\nPlease refresh the page and try again. If the problem persists, check the browser console for more details.`,
);
}, 100);
}
});
/**
* Basic event listener setup as fallback
*/
function setupBasicEventListeners() {
debugLog("Setting up basic event listeners as fallback...");
// Settings button
const settingsBtn = document.getElementById("settingsBtn");
if (settingsBtn) {
settingsBtn.onclick = function () {
if (typeof showSettingsModal === "function") {
showSettingsModal();
}
};
}
debugLog("Basic event listeners setup complete");
}
// Make functions available globally for inline event handlers
window.showDetailsModal = showDetailsModal;
window.closeDetailsModal = closeDetailsModal;
window.showViewModal = typeof showViewModal !== 'undefined' ? showViewModal : () => {};
window.closeViewModal = typeof closeViewModal !== 'undefined' ? closeViewModal : () => {};
window.editItem = editItem;
window.deleteItem = deleteItem;
window.showNotes = showNotes;
window.applyColumnFilter = applyColumnFilter;
// Register service worker for PWA support (HTTP/HTTPS only, skip file://)
if ('serviceWorker' in navigator && location.protocol !== 'file:') {
navigator.serviceWorker.register('./sw.js').catch(() => {});
}