Implement interactive execution plan and strict HTML validation

Features:
- **Interactive Checklist**: 'Review Changes' modal now updates in real-time as save tasks complete.
- **Signal Logging**: Backend emits [SIGNAL] logs for deletions, adoptions, uploads, and reorders.
- **UI Cleanup**: Removed redundant textual 'Execute Progress' log pane.

Build & Quality:
- **HTML Validation**: Added 	ools/validate_html.ts to build pipeline to prevent syntax errors in embedded JS.
- **Strict Build**:
pm run build now runs alidate:html first.
This commit is contained in:
Ben Miller
2026-01-02 00:23:01 -07:00
parent ee5fd782fe
commit 1068c912dc
9 changed files with 1021 additions and 327 deletions

99
tools/validate_html.ts Normal file
View File

@ -0,0 +1,99 @@
import * as fs from 'fs';
import * as path from 'path';
import * as cheerio from 'cheerio';
import * as ts from 'typescript';
import { glob } from 'glob';
// Configuration
const SRC_DIR = 'src';
async function validateHtmlFiles() {
console.log(`[HTML Validator] Scanning ${SRC_DIR} for HTML files...`);
// Find all HTML files
const htmlFiles = glob.sync(`${SRC_DIR}/**/*.html`);
let hasErrors = false;
for (const file of htmlFiles) {
const absolutPath = path.resolve(file);
const content = fs.readFileSync(absolutPath, 'utf-8');
// Load with source location info enabled
// Cast options to any to avoid TS version mismatches with cheerio types
const options: any = { sourceCodeLocationInfo: true };
const $ = cheerio.load(content, options);
const scripts = $('script').toArray();
for (const element of scripts) {
// Cast to any to access startIndex safely
const node = element as any;
// Skip external scripts
if ($(element).attr('src')) continue;
const scriptContent = $(element).html();
if (!scriptContent) continue;
// Determine start line of the script tag in the original file
// Cheerio (htmlparser2) location info:
const loc = node.startIndex !== undefined ?
getLineNumber(content, node.startIndex) : 1;
// Validate Syntax using TypeScript Compiler API
const sourceFile = ts.createSourceFile(
'virtual.js',
scriptContent,
ts.ScriptTarget.ES2020,
true, // setParentNodes
ts.ScriptKind.JS
);
// Cast to any because parseDiagnostics might not be in the public interface depending on version
const sf: any = sourceFile;
if (sf.parseDiagnostics && sf.parseDiagnostics.length > 0) {
hasErrors = true;
console.error(`\n❌ Syntax Error in ${file}`);
sf.parseDiagnostics.forEach((diag: any) => {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(diag.start!);
const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
// Adjust line number: Script Start line + Error line inside script
// Note: 'line' is 0-indexed relative to script start
const visualLine = loc + line;
console.error(` Line ${visualLine}: ${message}`);
// Show snippet
const lines = scriptContent.split('\n');
if (lines[line]) {
console.error(` > ${lines[line].trim()}\n`);
}
});
}
}
}
if (hasErrors) {
console.error(`\n[HTML Validator] Failed. Syntax errors detected.`);
process.exit(1);
} else {
console.log(`[HTML Validator] Passed. All HTML scripts are valid.`);
}
}
// Helper to calculate line number from char index
function getLineNumber(fullText: string, index: number): number {
return fullText.substring(0, index).split('\n').length;
}
// Check if run directly
if (require.main === module) {
validateHtmlFiles().catch(err => {
console.error("Validator crashed:", err);
process.exit(1);
});
}