feat(wizard): re-validate disk artefacts on stage entry, app start and window focus (fixes #151) #156
@@ -1405,4 +1405,95 @@ mod tests {
|
|||||||
let candidates_exe = get_binary_candidates("targen.exe");
|
let candidates_exe = get_binary_candidates("targen.exe");
|
||||||
assert_eq!(candidates_exe, vec!["targen.exe"]);
|
assert_eq!(candidates_exe, vec!["targen.exe"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_stage_artefacts_empty_input() {
|
||||||
|
let status = verify_stage_artefacts("".to_string(), "".to_string());
|
||||||
|
assert!(!status.stage1_complete);
|
||||||
|
assert!(!status.stage2_complete);
|
||||||
|
assert!(!status.stage3_complete);
|
||||||
|
assert!(!status.stage4_complete);
|
||||||
|
assert!(status.profile_path.is_none());
|
||||||
|
|
||||||
|
let status2 = verify_stage_artefacts(" ".to_string(), " ".to_string());
|
||||||
|
assert!(!status2.stage1_complete);
|
||||||
|
assert!(!status2.stage2_complete);
|
||||||
|
assert!(!status2.stage3_complete);
|
||||||
|
assert!(!status2.stage4_complete);
|
||||||
|
assert!(status2.profile_path.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_stage_artefacts_file_progression() {
|
||||||
|
let temp_dir = std::env::temp_dir().join(format!("iccery_test_gating_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()));
|
||||||
|
std::fs::create_dir_all(&temp_dir).unwrap();
|
||||||
|
let cwd = temp_dir.to_string_lossy().to_string();
|
||||||
|
let basename = "test_target".to_string();
|
||||||
|
|
||||||
|
// 0. No files exist
|
||||||
|
let status0 = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(!status0.stage1_complete);
|
||||||
|
assert!(!status0.stage2_complete);
|
||||||
|
assert!(!status0.stage3_complete);
|
||||||
|
assert!(!status0.stage4_complete);
|
||||||
|
assert!(status0.profile_path.is_none());
|
||||||
|
|
||||||
|
// 1. .ti1 created
|
||||||
|
std::fs::write(temp_dir.join("test_target.ti1"), b"ti1 content").unwrap();
|
||||||
|
let status1 = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(status1.stage1_complete);
|
||||||
|
assert!(!status1.stage2_complete);
|
||||||
|
assert!(!status1.stage3_complete);
|
||||||
|
assert!(!status1.stage4_complete);
|
||||||
|
assert!(status1.profile_path.is_none());
|
||||||
|
|
||||||
|
// 2. .ti2 created
|
||||||
|
std::fs::write(temp_dir.join("test_target.ti2"), b"ti2 content").unwrap();
|
||||||
|
let status2 = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(status2.stage1_complete);
|
||||||
|
assert!(status2.stage2_complete);
|
||||||
|
assert!(!status2.stage3_complete);
|
||||||
|
assert!(!status2.stage4_complete);
|
||||||
|
|
||||||
|
// 3. .ti3 created
|
||||||
|
std::fs::write(temp_dir.join("test_target.ti3"), b"ti3 content").unwrap();
|
||||||
|
let status3 = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(status3.stage1_complete);
|
||||||
|
assert!(status3.stage2_complete);
|
||||||
|
assert!(status3.stage3_complete);
|
||||||
|
assert!(!status3.stage4_complete);
|
||||||
|
|
||||||
|
// 4. Profile created (.icc / .icm)
|
||||||
|
let (prof_ext, _) = resolve_profile_extension(&cwd, &basename);
|
||||||
|
std::fs::write(temp_dir.join(format!("test_target.{}", prof_ext)), b"icc profile content").unwrap();
|
||||||
|
let status4 = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(status4.stage1_complete);
|
||||||
|
assert!(status4.stage2_complete);
|
||||||
|
assert!(status4.stage3_complete);
|
||||||
|
assert!(status4.stage4_complete);
|
||||||
|
assert!(status4.profile_path.is_some());
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_stage_artefacts_missing_intermediate() {
|
||||||
|
let temp_dir = std::env::temp_dir().join(format!("iccery_test_missing_ti2_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()));
|
||||||
|
std::fs::create_dir_all(&temp_dir).unwrap();
|
||||||
|
let cwd = temp_dir.to_string_lossy().to_string();
|
||||||
|
let basename = "gap_target".to_string();
|
||||||
|
|
||||||
|
// Create .ti1 and .ti3, but NOT .ti2
|
||||||
|
std::fs::write(temp_dir.join("gap_target.ti1"), b"ti1").unwrap();
|
||||||
|
std::fs::write(temp_dir.join("gap_target.ti3"), b"ti3").unwrap();
|
||||||
|
|
||||||
|
let status = verify_stage_artefacts(cwd.clone(), basename.clone());
|
||||||
|
assert!(status.stage1_complete);
|
||||||
|
assert!(!status.stage2_complete, ".ti2 is missing");
|
||||||
|
assert!(status.stage3_complete, ".ti3 exists");
|
||||||
|
assert!(!status.stage4_complete);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,13 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="content">
|
<main class="content">
|
||||||
|
<!-- Global Wizard Notification Banner -->
|
||||||
|
<div id="wizardNotification" class="notification-banner warning hidden" role="alert">
|
||||||
|
<span class="notification-icon" id="wizardNotificationIcon">⚠️</span>
|
||||||
|
<div class="notification-text" id="wizardNotificationText"></div>
|
||||||
|
<button type="button" id="wizardNotificationClose" class="notification-close" title="Dismiss notice">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stage 1: targen -->
|
<!-- Stage 1: targen -->
|
||||||
<section id="stage-1" class="stage active">
|
<section id="stage-1" class="stage active">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
|||||||
+21
-26
@@ -11,37 +11,32 @@ import { wizardState } from './state.js';
|
|||||||
const { invoke } = window.__TAURI__.core;
|
const { invoke } = window.__TAURI__.core;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Wizard navigation
|
// Wizard stepper navigation with re-validation
|
||||||
const steps = document.querySelectorAll('.step');
|
const steps = document.querySelectorAll('.step');
|
||||||
const stages = document.querySelectorAll('.stage');
|
steps.forEach(step => {
|
||||||
|
step.addEventListener('click', async () => {
|
||||||
|
const targetStep = parseInt(step.getAttribute('data-step'), 10);
|
||||||
|
if (isNaN(targetStep)) return;
|
||||||
|
await wizardState.navigateToStage(targetStep);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global wizard notification close button
|
||||||
|
const wizardNotificationClose = document.getElementById('wizardNotificationClose');
|
||||||
|
if (wizardNotificationClose) {
|
||||||
|
wizardNotificationClose.addEventListener('click', () => {
|
||||||
|
wizardState.hideNotice();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-validate gating on window focus (e.g. when returning after modifying files in Explorer/Finder)
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
wizardState.updateGating();
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize gating on load
|
// Initialize gating on load
|
||||||
wizardState.updateGating();
|
wizardState.updateGating();
|
||||||
|
|
||||||
steps.forEach(step => {
|
|
||||||
step.addEventListener('click', () => {
|
|
||||||
if (step.classList.contains('disabled')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetStep = step.getAttribute('data-step');
|
|
||||||
|
|
||||||
// Update UI
|
|
||||||
steps.forEach(s => s.classList.remove('active'));
|
|
||||||
stages.forEach(s => {
|
|
||||||
s.classList.remove('active');
|
|
||||||
s.classList.add('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
step.classList.add('active');
|
|
||||||
const targetStage = document.getElementById(`stage-${targetStep}`);
|
|
||||||
if (targetStage) {
|
|
||||||
targetStage.classList.remove('hidden');
|
|
||||||
targetStage.classList.add('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// About modal
|
// About modal
|
||||||
const aboutDialog = document.getElementById('aboutDialog');
|
const aboutDialog = document.getElementById('aboutDialog');
|
||||||
const openAboutBtn = document.getElementById('openAboutBtn');
|
const openAboutBtn = document.getElementById('openAboutBtn');
|
||||||
|
|||||||
+7
-20
@@ -476,7 +476,8 @@ export function initChartread() {
|
|||||||
basename: basename,
|
basename: basename,
|
||||||
});
|
});
|
||||||
logPre.textContent += `\n[SUCCESS] Promoted ${recordedPasses[0].filename} → ${basename}.ti3 (single pass, average not invoked).\n`;
|
logPre.textContent += `\n[SUCCESS] Promoted ${recordedPasses[0].filename} → ${basename}.ti3 (single pass, average not invoked).\n`;
|
||||||
acceptStage3(basename, cwd);
|
setStage3Result(basename, cwd);
|
||||||
|
advanceToStage4();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
btnFinishAndAverage.disabled = false;
|
btnFinishAndAverage.disabled = false;
|
||||||
setPrompt(`Failed to restore ${basename}.ti3: ${e}`);
|
setPrompt(`Failed to restore ${basename}.ti3: ${e}`);
|
||||||
@@ -503,7 +504,8 @@ export function initChartread() {
|
|||||||
if (event.payload.code === 0) {
|
if (event.payload.code === 0) {
|
||||||
setPrompt(`✅ Successfully averaged ${recordedPasses.length} measurement passes into ${basename}.ti3!`);
|
setPrompt(`✅ Successfully averaged ${recordedPasses.length} measurement passes into ${basename}.ti3!`);
|
||||||
logPre.textContent += `\n[SUCCESS] average wrote ${basename}.ti3\n`;
|
logPre.textContent += `\n[SUCCESS] average wrote ${basename}.ti3\n`;
|
||||||
acceptStage3(basename, cwd);
|
setStage3Result(basename, cwd);
|
||||||
|
advanceToStage4();
|
||||||
} else {
|
} else {
|
||||||
setPrompt(`⚠️ Argyll average exited with code ${event.payload.code}. Promoting pass 1 as fallback.`);
|
setPrompt(`⚠️ Argyll average exited with code ${event.payload.code}. Promoting pass 1 as fallback.`);
|
||||||
logPre.textContent += `\n[ERROR] average exited ${event.payload.code}. Promoting ${recordedPasses[0].filename}.\n`;
|
logPre.textContent += `\n[ERROR] average exited ${event.payload.code}. Promoting ${recordedPasses[0].filename}.\n`;
|
||||||
@@ -513,7 +515,8 @@ export function initChartread() {
|
|||||||
source: recordedPasses[0].filename,
|
source: recordedPasses[0].filename,
|
||||||
basename: basename,
|
basename: basename,
|
||||||
});
|
});
|
||||||
acceptStage3(basename, cwd);
|
setStage3Result(basename, cwd);
|
||||||
|
advanceToStage4();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
btnFinishAndAverage.disabled = false;
|
btnFinishAndAverage.disabled = false;
|
||||||
setPrompt(`Average failed and fallback promote failed: ${e}`);
|
setPrompt(`Average failed and fallback promote failed: ${e}`);
|
||||||
@@ -609,21 +612,5 @@ export function initChartread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function advanceToStage4() {
|
function advanceToStage4() {
|
||||||
const steps = document.querySelectorAll('.step');
|
wizardState.navigateToStage(4);
|
||||||
const stages = document.querySelectorAll('.stage');
|
|
||||||
|
|
||||||
steps.forEach(s => s.classList.remove('active'));
|
|
||||||
if (steps[3]) {
|
|
||||||
steps[3].classList.add('active');
|
|
||||||
steps[3].classList.remove('disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
stages.forEach(s => {
|
|
||||||
s.classList.remove('active');
|
|
||||||
s.classList.add('hidden');
|
|
||||||
});
|
|
||||||
if (stages[3]) {
|
|
||||||
stages[3].classList.remove('hidden');
|
|
||||||
stages[3].classList.add('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-14
@@ -164,18 +164,5 @@ async function triggerGamutExtraction(basename, cwd, profilePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function advanceToStage5() {
|
function advanceToStage5() {
|
||||||
const steps = document.querySelectorAll('.step');
|
wizardState.navigateToStage(5);
|
||||||
const stages = document.querySelectorAll('.stage');
|
|
||||||
|
|
||||||
steps.forEach(s => s.classList.remove('active'));
|
|
||||||
if (steps[4]) steps[4].classList.add('active');
|
|
||||||
|
|
||||||
stages.forEach(s => {
|
|
||||||
s.classList.remove('active');
|
|
||||||
s.classList.add('hidden');
|
|
||||||
});
|
|
||||||
if (stages[4]) {
|
|
||||||
stages[4].classList.remove('hidden');
|
|
||||||
stages[4].classList.add('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-14
@@ -652,18 +652,5 @@ export function initPrinttarg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function advanceToStage3() {
|
function advanceToStage3() {
|
||||||
const steps = document.querySelectorAll('.step');
|
wizardState.navigateToStage(3);
|
||||||
const stages = document.querySelectorAll('.stage');
|
|
||||||
|
|
||||||
steps.forEach(s => s.classList.remove('active'));
|
|
||||||
if (steps[2]) steps[2].classList.add('active');
|
|
||||||
|
|
||||||
stages.forEach(s => {
|
|
||||||
s.classList.remove('active');
|
|
||||||
s.classList.add('hidden');
|
|
||||||
});
|
|
||||||
if (stages[2]) {
|
|
||||||
stages[2].classList.remove('hidden');
|
|
||||||
stages[2].classList.add('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-14
@@ -4,6 +4,7 @@ export const wizardState = {
|
|||||||
currentStage: 1,
|
currentStage: 1,
|
||||||
basename: "",
|
basename: "",
|
||||||
cwd: "",
|
cwd: "",
|
||||||
|
noticeTimer: null,
|
||||||
|
|
||||||
setTarget(basename, cwd) {
|
setTarget(basename, cwd) {
|
||||||
if (basename) this.basename = basename;
|
if (basename) this.basename = basename;
|
||||||
@@ -11,8 +12,48 @@ export const wizardState = {
|
|||||||
return this.updateGating();
|
return this.updateGating();
|
||||||
},
|
},
|
||||||
|
|
||||||
navigateToStage(stageNumber) {
|
showNotice(message, type = "warning", durationMs = 5000) {
|
||||||
this.currentStage = stageNumber;
|
const banner = document.getElementById("wizardNotification");
|
||||||
|
const textEl = document.getElementById("wizardNotificationText");
|
||||||
|
const iconEl = document.getElementById("wizardNotificationIcon");
|
||||||
|
|
||||||
|
if (!banner || !textEl) return;
|
||||||
|
|
||||||
|
if (this.noticeTimer) {
|
||||||
|
clearTimeout(this.noticeTimer);
|
||||||
|
this.noticeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
banner.className = `notification-banner ${type}`;
|
||||||
|
banner.classList.remove("hidden");
|
||||||
|
textEl.textContent = message;
|
||||||
|
|
||||||
|
if (iconEl) {
|
||||||
|
if (type === "success") iconEl.textContent = "✓";
|
||||||
|
else if (type === "error") iconEl.textContent = "✕";
|
||||||
|
else if (type === "info") iconEl.textContent = "ℹ️";
|
||||||
|
else iconEl.textContent = "⚠️";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (durationMs > 0) {
|
||||||
|
this.noticeTimer = setTimeout(() => {
|
||||||
|
this.hideNotice();
|
||||||
|
}, durationMs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hideNotice() {
|
||||||
|
const banner = document.getElementById("wizardNotification");
|
||||||
|
if (banner) {
|
||||||
|
banner.classList.add("hidden");
|
||||||
|
}
|
||||||
|
if (this.noticeTimer) {
|
||||||
|
clearTimeout(this.noticeTimer);
|
||||||
|
this.noticeTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
applyStageDOM(stageNumber) {
|
||||||
const steps = document.querySelectorAll('.step');
|
const steps = document.querySelectorAll('.step');
|
||||||
const stages = document.querySelectorAll('.stage');
|
const stages = document.querySelectorAll('.stage');
|
||||||
|
|
||||||
@@ -36,9 +77,30 @@ export const wizardState = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async navigateToStage(stageNumber) {
|
||||||
|
const targetNum = parseInt(stageNumber, 10);
|
||||||
|
if (isNaN(targetNum) || targetNum < 1 || targetNum > 5) return false;
|
||||||
|
|
||||||
|
if (targetNum === 1) {
|
||||||
|
this.currentStage = 1;
|
||||||
|
this.applyStageDOM(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gating = await this.updateGating();
|
||||||
|
if (gating && targetNum <= gating.maxValidStage) {
|
||||||
|
this.currentStage = targetNum;
|
||||||
|
this.applyStageDOM(targetNum);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.showNotice(`Stage ${targetNum} is locked because required prerequisite files are missing on disk.`, "warning");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async updateGating() {
|
async updateGating() {
|
||||||
const steps = document.querySelectorAll('.step');
|
const steps = document.querySelectorAll('.step');
|
||||||
if (!steps || steps.length === 0) return;
|
if (!steps || steps.length === 0) return null;
|
||||||
|
|
||||||
if (!this.basename || !this.cwd) {
|
if (!this.basename || !this.cwd) {
|
||||||
steps.forEach((step, idx) => {
|
steps.forEach((step, idx) => {
|
||||||
@@ -48,7 +110,20 @@ export const wizardState = {
|
|||||||
step.classList.add('disabled');
|
step.classList.add('disabled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
|
if (this.currentStage > 1) {
|
||||||
|
this.currentStage = 1;
|
||||||
|
this.applyStageDOM(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stage1_complete: false,
|
||||||
|
stage2_complete: false,
|
||||||
|
stage3_complete: false,
|
||||||
|
stage4_complete: false,
|
||||||
|
maxValidStage: 1,
|
||||||
|
unlocked: [true, false, false, false, false],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -57,19 +132,32 @@ export const wizardState = {
|
|||||||
basename: this.basename,
|
basename: this.basename,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 1: always accessible
|
// Strict sequential gating:
|
||||||
// Step 2: unlocked if .ti1 exists
|
// Stage 1: always accessible
|
||||||
// Step 3: unlocked if .ti2 exists
|
// Stage 2: unlocked if .ti1 exists (stage1_complete)
|
||||||
// Step 4: unlocked if .ti3 exists
|
// Stage 3: unlocked if .ti1 + .ti2 exist
|
||||||
// Step 5: unlocked if profile exists
|
// Stage 4: unlocked if .ti1 + .ti2 + .ti3 exist
|
||||||
|
// Stage 5: unlocked if .ti1 + .ti2 + .ti3 + profile exist
|
||||||
|
const stage1Valid = true;
|
||||||
|
const stage2Valid = !!status.stage1_complete;
|
||||||
|
const stage3Valid = stage2Valid && !!status.stage2_complete;
|
||||||
|
const stage4Valid = stage3Valid && !!status.stage3_complete;
|
||||||
|
const stage5Valid = stage4Valid && !!status.stage4_complete;
|
||||||
|
|
||||||
const unlocked = [
|
const unlocked = [
|
||||||
true,
|
stage1Valid,
|
||||||
status.stage1_complete,
|
stage2Valid,
|
||||||
status.stage2_complete,
|
stage3Valid,
|
||||||
status.stage3_complete,
|
stage4Valid,
|
||||||
status.stage4_complete,
|
stage5Valid,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let maxValidStage = 1;
|
||||||
|
if (stage2Valid) maxValidStage = 2;
|
||||||
|
if (stage3Valid) maxValidStage = 3;
|
||||||
|
if (stage4Valid) maxValidStage = 4;
|
||||||
|
if (stage5Valid) maxValidStage = 5;
|
||||||
|
|
||||||
steps.forEach((step, idx) => {
|
steps.forEach((step, idx) => {
|
||||||
if (unlocked[idx]) {
|
if (unlocked[idx]) {
|
||||||
step.classList.remove('disabled');
|
step.classList.remove('disabled');
|
||||||
@@ -77,8 +165,22 @@ export const wizardState = {
|
|||||||
step.classList.add('disabled');
|
step.classList.add('disabled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If current stage is no longer valid, automatically navigate back to maxValidStage
|
||||||
|
if (this.currentStage > maxValidStage) {
|
||||||
|
this.currentStage = maxValidStage;
|
||||||
|
this.applyStageDOM(maxValidStage);
|
||||||
|
this.showNotice(`Target files changed on disk — returned to Stage ${maxValidStage}.`, "warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...status,
|
||||||
|
maxValidStage,
|
||||||
|
unlocked,
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Could not verify stage artefacts:", e);
|
console.warn("Could not verify stage artefacts:", e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-16
@@ -189,6 +189,7 @@ export function initTargen() {
|
|||||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||||
}
|
}
|
||||||
updateGenerateButton();
|
updateGenerateButton();
|
||||||
|
wizardState.setTarget(basename, currentWorkingDir);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to open file dialog:", err);
|
console.error("Failed to open file dialog:", err);
|
||||||
@@ -358,20 +359,5 @@ export function initTargen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function advanceToStage2() {
|
function advanceToStage2() {
|
||||||
const steps = document.querySelectorAll('.step');
|
wizardState.navigateToStage(2);
|
||||||
const stages = document.querySelectorAll('.stage');
|
|
||||||
|
|
||||||
// Update stepper
|
|
||||||
steps.forEach((s) => s.classList.remove('active'));
|
|
||||||
if (steps[1]) steps[1].classList.add('active');
|
|
||||||
|
|
||||||
// Update sections
|
|
||||||
stages.forEach((s) => {
|
|
||||||
s.classList.remove('active');
|
|
||||||
s.classList.add('hidden');
|
|
||||||
});
|
|
||||||
if (stages[1]) {
|
|
||||||
stages[1].classList.remove('hidden');
|
|
||||||
stages[1].classList.add('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1099,12 +1099,35 @@ button.danger:hover {
|
|||||||
color: #a5d6a7;
|
color: #a5d6a7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification-banner.warning {
|
||||||
|
background: rgba(255, 152, 0, 0.12);
|
||||||
|
border: 1px solid rgba(255, 152, 0, 0.3);
|
||||||
|
color: #ffcc80;
|
||||||
|
}
|
||||||
|
|
||||||
.notification-banner.error {
|
.notification-banner.error {
|
||||||
background: rgba(244, 67, 54, 0.12);
|
background: rgba(244, 67, 54, 0.12);
|
||||||
border: 1px solid rgba(244, 67, 54, 0.3);
|
border: 1px solid rgba(244, 67, 54, 0.3);
|
||||||
color: #ef9a9a;
|
color: #ef9a9a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.print-actions-row {
|
.print-actions-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user