55import { openStore , CorsError } from "../src/store.js" ;
66import { buildTree , buildTreeFromV3 , buildTreeFromCrawl , openNode , insertNode } from "../src/hierarchy.js" ;
77import { detectConventions } from "../src/conventions.js" ;
8+ import { validateStore } from "../src/report.js" ;
89import { renderTree , highlightNode } from "./tree.js" ;
910import { renderDetail } from "./detail-panel.js" ;
11+ import { renderReport , renderProgressUI } from "./report-panel.js" ;
1012
1113// DOM elements
1214const form = document . getElementById ( "open-form" ) ;
@@ -22,6 +24,8 @@ const addPathBtn = document.getElementById("add-path-btn");
2224// App state
2325let currentStore = null ;
2426let currentTree = null ;
27+ let currentReport = null ;
28+ let currentAbortController = null ;
2529
2630// --- URL state ---
2731
@@ -95,6 +99,7 @@ async function openStoreFromUrl(url, autoSelectNode) {
9599 }
96100
97101 updateUrlParams ( url , null ) ;
102+ injectValidateAllButton ( ) ;
98103
99104 // Auto-select node from URL if provided
100105 if ( autoSelectNode && currentTree ) {
@@ -174,12 +179,78 @@ backBtn.addEventListener("click", showTreeView);
174179
175180function onNodeSelect ( node ) {
176181 highlightNode ( treeContainer , node . path ) ;
177- const conventions = detectConventions ( node . attrs ) ;
178- renderDetail ( node , conventions , detailPanel ) ;
182+
183+ // If we have a cached report, show a "Back to Report" link
184+ if ( currentReport ) {
185+ const backLink = document . createElement ( "button" ) ;
186+ backLink . className = "report-back-link" ;
187+ backLink . textContent = "\u2190 Back to Report" ;
188+ backLink . addEventListener ( "click" , ( ) => showReport ( currentReport ) ) ;
189+ detailPanel . innerHTML = "" ;
190+ detailPanel . appendChild ( backLink ) ;
191+
192+ // Render detail below the back link
193+ const detailWrapper = document . createElement ( "div" ) ;
194+ detailPanel . appendChild ( detailWrapper ) ;
195+ const conventions = detectConventions ( node . attrs ) ;
196+ renderDetail ( node , conventions , detailWrapper ) ;
197+ } else {
198+ const conventions = detectConventions ( node . attrs ) ;
199+ renderDetail ( node , conventions , detailPanel ) ;
200+ }
201+
179202 updateUrlParams ( urlInput . value . trim ( ) , node . path ) ;
180203 showDetailView ( ) ;
181204}
182205
206+ // --- Validate All ---
207+
208+ function injectValidateAllButton ( ) {
209+ // Remove existing button if present
210+ const existing = document . getElementById ( "validate-all-btn" ) ;
211+ if ( existing ) existing . remove ( ) ;
212+
213+ const btn = document . createElement ( "button" ) ;
214+ btn . id = "validate-all-btn" ;
215+ btn . className = "validate-all-btn" ;
216+ btn . textContent = "Validate All" ;
217+ btn . addEventListener ( "click" , runValidateAll ) ;
218+ statusEl . appendChild ( btn ) ;
219+ }
220+
221+ async function runValidateAll ( ) {
222+ if ( ! currentTree ) return ;
223+
224+ // Cancel any in-progress validation
225+ if ( currentAbortController ) {
226+ currentAbortController . abort ( ) ;
227+ }
228+ currentAbortController = new AbortController ( ) ;
229+ currentReport = null ;
230+
231+ const progressUI = renderProgressUI ( detailPanel ) ;
232+ showDetailView ( ) ;
233+
234+ try {
235+ const report = await validateStore ( currentTree , {
236+ onProgress : ( { completed, total } ) => progressUI . update ( completed , total ) ,
237+ signal : currentAbortController . signal ,
238+ } ) ;
239+ currentReport = report ;
240+ showReport ( report ) ;
241+ } catch ( err ) {
242+ if ( err . name !== "AbortError" ) {
243+ detailPanel . innerHTML = `<div class="error-banner">Validation failed: ${ escapeHtml ( err . message ) } </div>` ;
244+ }
245+ } finally {
246+ currentAbortController = null ;
247+ }
248+ }
249+
250+ function showReport ( report ) {
251+ renderReport ( report , onNodeSelect , detailPanel ) ;
252+ }
253+
183254// --- Manual path addition ---
184255
185256addPathBtn . addEventListener ( "click" , addPath ) ;
0 commit comments