fix(build): frontend build determinism across line endings (DC-119) [glm-grade=A]
The deploy host showed phantom dist drift on every git pull: committed
dist bundles could not be reproduced on DNS2. Root cause (verified with
esbuild 0.25.12 probes): the production transform uses sourcemap:'both',
whose inline map base64-embeds RAW source bytes as sourcesContent — a CRLF
working copy (Windows dev, core.autocrlf=true) vs an LF checkout produces
different dist bytes and a different sw.js cache tag.
- build.js: normalizeSource (\r\n -> \n) on every source read (bundle
inputs + sw.js read); exported + require.main guard so tests can import
it without triggering a build
- .gitattributes: * text=auto eol=lf (git-layer kill of the CRLF vector)
+ binary exclusions; 9 CRLF-in-index asset files renormalized
- tests/build-determinism.test.js: 3-case pin (byte-identity after
normalization, divergence pre-normalization, CR-strip contract) importing
the ACTUAL normalizeSource from build.js
- package.json: declare jsdom devDependency — 2 committed test files
require('jsdom') but it was never declared, so fresh-checkout
npm test failed (it only passed where a stray ancestor node_modules
happened to contain it)
- dist/features.js + sw.js: canonical deterministic rebuild
(cache tag 3354f5fd96 -> d39ab69dd4)
Verified: status 54/54, API 2858/2858 (128 suites); CRLF-sim tree and LF
tree of same HEAD produce byte-identical dist artifacts (sha256-equal).
Judge: glm-5.3 cold round 1 = A, round 2 (post-fold) = A clean, 0 blocking.
This commit is contained in:
+177
-177
@@ -1,177 +1,177 @@
|
||||
/**
|
||||
* DashCaddy User Onboarding System
|
||||
* Main entry point for the tooltip-based onboarding experience
|
||||
*
|
||||
* This file initializes the onboarding system and coordinates between
|
||||
* the various components (TourManager, ProgressTracker, ThemeAdapter, etc.)
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let progressTracker;
|
||||
let themeAdapter;
|
||||
let tourManager;
|
||||
let dnsTemplateSelector;
|
||||
let errorHandler;
|
||||
|
||||
/**
|
||||
* Initialize the onboarding system
|
||||
*/
|
||||
async function initializeOnboarding() {
|
||||
try {
|
||||
console.log('[Onboarding] Initializing system...');
|
||||
|
||||
// Initialize Error Handler first
|
||||
errorHandler = new ErrorHandler();
|
||||
console.log('[Onboarding] Error Handler initialized');
|
||||
|
||||
// Initialize Progress Tracker
|
||||
progressTracker = new ProgressTracker('dashcaddy_onboarding');
|
||||
console.log('[Onboarding] Progress Tracker initialized');
|
||||
|
||||
// Initialize Theme Adapter
|
||||
themeAdapter = new ThemeAdapter();
|
||||
console.log('[Onboarding] Theme Adapter initialized');
|
||||
|
||||
// Initialize DNS Template Selector
|
||||
dnsTemplateSelector = new DnsTemplateSelector(progressTracker);
|
||||
console.log('[Onboarding] DNS Template Selector initialized');
|
||||
|
||||
// Initialize Tour Manager
|
||||
tourManager = new TourManager(progressTracker, themeAdapter, dnsTemplateSelector);
|
||||
console.log('[Onboarding] Tour Manager initialized');
|
||||
|
||||
// Check if tour should auto-start
|
||||
if (tourManager.shouldAutoStart()) {
|
||||
console.log('[Onboarding] Auto-starting tour for first-time user');
|
||||
// Wait a bit for page to fully load
|
||||
setTimeout(() => {
|
||||
tourManager.startTour();
|
||||
}, 1000);
|
||||
} else {
|
||||
const tourCompleted = progressTracker.isTourCompleted();
|
||||
const currentStep = progressTracker.getCurrentStep();
|
||||
console.log(`[Onboarding] Tour not auto-starting (completed: ${tourCompleted}, step: ${currentStep})`);
|
||||
|
||||
// If tour is in progress, offer to resume
|
||||
if (!tourCompleted && currentStep > 0) {
|
||||
console.log('[Onboarding] Tour in progress, can be resumed manually');
|
||||
}
|
||||
}
|
||||
|
||||
// Add restart tour button to tools row
|
||||
addRestartTourButton();
|
||||
|
||||
// Expose to global scope for manual triggering
|
||||
window.DashCaddyOnboarding = {
|
||||
startTour: () => tourManager.startTour(),
|
||||
restartTour: () => tourManager.restartTour(),
|
||||
showTooltip: (id) => tourManager.showTooltip(id),
|
||||
showWhatsNew: () => tourManager.showWhatsNew(),
|
||||
resetProgress: () => progressTracker.resetProgress(),
|
||||
getErrors: () => errorHandler.getErrors(),
|
||||
getErrorStats: () => errorHandler.getStatistics()
|
||||
};
|
||||
|
||||
console.log('[Onboarding] System initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('[Onboarding] Initialization error:', error);
|
||||
|
||||
// Use error handler if available
|
||||
if (errorHandler) {
|
||||
errorHandler.logError('Initialization', error);
|
||||
}
|
||||
|
||||
// Graceful degradation - don't break the dashboard
|
||||
console.warn('[Onboarding] System failed to initialize, dashboard will continue without onboarding');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add restart tour button to tools row
|
||||
*/
|
||||
function addRestartTourButton() {
|
||||
const toolsRow = document.querySelector('.tools');
|
||||
if (!toolsRow) return;
|
||||
|
||||
const clickHandler = () => {
|
||||
if (tourManager) {
|
||||
console.log('[Onboarding] Starting tour via button click');
|
||||
tourManager.restartTour();
|
||||
} else {
|
||||
console.error('[Onboarding] Tour manager not initialized');
|
||||
alert('Tour is not available. Check browser console for errors.\n\nPossible issues:\n- Driver.js library failed to load\n- JavaScript errors during initialization');
|
||||
}
|
||||
};
|
||||
|
||||
// If button already exists in the HTML, just attach the handler
|
||||
const existing = document.getElementById('restart-tour-btn');
|
||||
if (existing) {
|
||||
existing.onclick = clickHandler;
|
||||
return;
|
||||
}
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.id = 'restart-tour-btn';
|
||||
button.textContent = 'Help Tour';
|
||||
button.title = 'Restart the onboarding tour';
|
||||
button.onclick = clickHandler;
|
||||
toolsRow.appendChild(button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Driver.js is loaded
|
||||
*/
|
||||
function checkDriverLoaded() {
|
||||
// Driver.js v1.x IIFE: window.driver.js.driver is the factory function
|
||||
const driverFactory = window.driver?.js?.driver || window.driver?.driver || window.driver;
|
||||
if (typeof driverFactory !== 'function') {
|
||||
console.warn('[Onboarding] Driver.js not loaded yet, will retry... window.driver:', window.driver);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Driver.js to load, then initialize
|
||||
*/
|
||||
function waitForDriver() {
|
||||
let retries = 0;
|
||||
const maxRetries = 10;
|
||||
|
||||
function attemptInit() {
|
||||
if (checkDriverLoaded()) {
|
||||
initializeOnboarding();
|
||||
} else {
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
// Retry after a short delay
|
||||
setTimeout(attemptInit, 500);
|
||||
} else {
|
||||
// Max retries reached, show fallback
|
||||
console.error('[Onboarding] Driver.js failed to load after multiple attempts');
|
||||
if (errorHandler) {
|
||||
errorHandler.handleDriverLoadFailure();
|
||||
} else {
|
||||
// Create temporary error handler for fallback
|
||||
const tempHandler = new ErrorHandler();
|
||||
tempHandler.handleDriverLoadFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attemptInit();
|
||||
}
|
||||
|
||||
// Start initialization when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', waitForDriver);
|
||||
} else {
|
||||
waitForDriver();
|
||||
}
|
||||
|
||||
console.log('[Onboarding] System loaded');
|
||||
|
||||
})();
|
||||
/**
|
||||
* DashCaddy User Onboarding System
|
||||
* Main entry point for the tooltip-based onboarding experience
|
||||
*
|
||||
* This file initializes the onboarding system and coordinates between
|
||||
* the various components (TourManager, ProgressTracker, ThemeAdapter, etc.)
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let progressTracker;
|
||||
let themeAdapter;
|
||||
let tourManager;
|
||||
let dnsTemplateSelector;
|
||||
let errorHandler;
|
||||
|
||||
/**
|
||||
* Initialize the onboarding system
|
||||
*/
|
||||
async function initializeOnboarding() {
|
||||
try {
|
||||
console.log('[Onboarding] Initializing system...');
|
||||
|
||||
// Initialize Error Handler first
|
||||
errorHandler = new ErrorHandler();
|
||||
console.log('[Onboarding] Error Handler initialized');
|
||||
|
||||
// Initialize Progress Tracker
|
||||
progressTracker = new ProgressTracker('dashcaddy_onboarding');
|
||||
console.log('[Onboarding] Progress Tracker initialized');
|
||||
|
||||
// Initialize Theme Adapter
|
||||
themeAdapter = new ThemeAdapter();
|
||||
console.log('[Onboarding] Theme Adapter initialized');
|
||||
|
||||
// Initialize DNS Template Selector
|
||||
dnsTemplateSelector = new DnsTemplateSelector(progressTracker);
|
||||
console.log('[Onboarding] DNS Template Selector initialized');
|
||||
|
||||
// Initialize Tour Manager
|
||||
tourManager = new TourManager(progressTracker, themeAdapter, dnsTemplateSelector);
|
||||
console.log('[Onboarding] Tour Manager initialized');
|
||||
|
||||
// Check if tour should auto-start
|
||||
if (tourManager.shouldAutoStart()) {
|
||||
console.log('[Onboarding] Auto-starting tour for first-time user');
|
||||
// Wait a bit for page to fully load
|
||||
setTimeout(() => {
|
||||
tourManager.startTour();
|
||||
}, 1000);
|
||||
} else {
|
||||
const tourCompleted = progressTracker.isTourCompleted();
|
||||
const currentStep = progressTracker.getCurrentStep();
|
||||
console.log(`[Onboarding] Tour not auto-starting (completed: ${tourCompleted}, step: ${currentStep})`);
|
||||
|
||||
// If tour is in progress, offer to resume
|
||||
if (!tourCompleted && currentStep > 0) {
|
||||
console.log('[Onboarding] Tour in progress, can be resumed manually');
|
||||
}
|
||||
}
|
||||
|
||||
// Add restart tour button to tools row
|
||||
addRestartTourButton();
|
||||
|
||||
// Expose to global scope for manual triggering
|
||||
window.DashCaddyOnboarding = {
|
||||
startTour: () => tourManager.startTour(),
|
||||
restartTour: () => tourManager.restartTour(),
|
||||
showTooltip: (id) => tourManager.showTooltip(id),
|
||||
showWhatsNew: () => tourManager.showWhatsNew(),
|
||||
resetProgress: () => progressTracker.resetProgress(),
|
||||
getErrors: () => errorHandler.getErrors(),
|
||||
getErrorStats: () => errorHandler.getStatistics()
|
||||
};
|
||||
|
||||
console.log('[Onboarding] System initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('[Onboarding] Initialization error:', error);
|
||||
|
||||
// Use error handler if available
|
||||
if (errorHandler) {
|
||||
errorHandler.logError('Initialization', error);
|
||||
}
|
||||
|
||||
// Graceful degradation - don't break the dashboard
|
||||
console.warn('[Onboarding] System failed to initialize, dashboard will continue without onboarding');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add restart tour button to tools row
|
||||
*/
|
||||
function addRestartTourButton() {
|
||||
const toolsRow = document.querySelector('.tools');
|
||||
if (!toolsRow) return;
|
||||
|
||||
const clickHandler = () => {
|
||||
if (tourManager) {
|
||||
console.log('[Onboarding] Starting tour via button click');
|
||||
tourManager.restartTour();
|
||||
} else {
|
||||
console.error('[Onboarding] Tour manager not initialized');
|
||||
alert('Tour is not available. Check browser console for errors.\n\nPossible issues:\n- Driver.js library failed to load\n- JavaScript errors during initialization');
|
||||
}
|
||||
};
|
||||
|
||||
// If button already exists in the HTML, just attach the handler
|
||||
const existing = document.getElementById('restart-tour-btn');
|
||||
if (existing) {
|
||||
existing.onclick = clickHandler;
|
||||
return;
|
||||
}
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.id = 'restart-tour-btn';
|
||||
button.textContent = 'Help Tour';
|
||||
button.title = 'Restart the onboarding tour';
|
||||
button.onclick = clickHandler;
|
||||
toolsRow.appendChild(button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Driver.js is loaded
|
||||
*/
|
||||
function checkDriverLoaded() {
|
||||
// Driver.js v1.x IIFE: window.driver.js.driver is the factory function
|
||||
const driverFactory = window.driver?.js?.driver || window.driver?.driver || window.driver;
|
||||
if (typeof driverFactory !== 'function') {
|
||||
console.warn('[Onboarding] Driver.js not loaded yet, will retry... window.driver:', window.driver);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Driver.js to load, then initialize
|
||||
*/
|
||||
function waitForDriver() {
|
||||
let retries = 0;
|
||||
const maxRetries = 10;
|
||||
|
||||
function attemptInit() {
|
||||
if (checkDriverLoaded()) {
|
||||
initializeOnboarding();
|
||||
} else {
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
// Retry after a short delay
|
||||
setTimeout(attemptInit, 500);
|
||||
} else {
|
||||
// Max retries reached, show fallback
|
||||
console.error('[Onboarding] Driver.js failed to load after multiple attempts');
|
||||
if (errorHandler) {
|
||||
errorHandler.handleDriverLoadFailure();
|
||||
} else {
|
||||
// Create temporary error handler for fallback
|
||||
const tempHandler = new ErrorHandler();
|
||||
tempHandler.handleDriverLoadFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attemptInit();
|
||||
}
|
||||
|
||||
// Start initialization when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', waitForDriver);
|
||||
} else {
|
||||
waitForDriver();
|
||||
}
|
||||
|
||||
console.log('[Onboarding] System loaded');
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user