@@ -19,6 +19,56 @@ async function initPopup() {
1919 const explainRefs = document . getElementById ( "explainRefs" ) ;
2020 const closeExplainBtn = document . getElementById ( "closeExplainBtn" ) ;
2121
22+ // Suggestion modal wiring (needs to work even when we only
23+ // use manual validation on non-GitHub pages).
24+ if ( closeSuggestionBtn ) {
25+ closeSuggestionBtn . addEventListener ( "click" , ( ) => {
26+ if ( suggestionModal ) { suggestionModal . style . display = "none" ; }
27+ } ) ;
28+ }
29+ if ( copyPatchBtn ) {
30+ copyPatchBtn . addEventListener ( "click" , async ( ) => {
31+ try {
32+ const text = suggestionPre . textContent || "" ;
33+ await navigator . clipboard . writeText ( text ) ;
34+ showToast ( "Patched YAML copied" ) ;
35+ } catch ( e ) {
36+ showToast ( "Copy failed" , { background : "#b91c1c" } ) ;
37+ }
38+ } ) ;
39+ }
40+ if ( downloadPatchBtn ) {
41+ downloadPatchBtn . addEventListener ( "click" , ( ) => {
42+ try {
43+ const text = suggestionPre . textContent || "" ;
44+ const blob = new Blob ( [ text ] , { type : "text/yaml" } ) ;
45+ const url = URL . createObjectURL ( blob ) ;
46+ const a = document . createElement ( "a" ) ;
47+ a . href = url ;
48+ a . download = "patched.yaml" ;
49+ document . body . appendChild ( a ) ;
50+ a . click ( ) ;
51+ a . remove ( ) ;
52+ URL . revokeObjectURL ( url ) ;
53+ showToast ( "Downloaded patched YAML" ) ;
54+ } catch ( e ) {
55+ showToast ( "Download failed" , { background : "#b91c1c" } ) ;
56+ }
57+ } ) ;
58+ }
59+
60+ // Explain modal close button wiring (also needed for manual flow).
61+ if ( closeExplainBtn ) {
62+ closeExplainBtn . addEventListener ( "click" , ( ) => {
63+ if ( explainModal ) { explainModal . style . display = "none" ; }
64+ } ) ;
65+ }
66+
67+ // Track the YAML text currently being validated so that
68+ // suggestion previews work consistently for both GitHub
69+ // and manual-paste validation flows.
70+ let currentYamlText = "" ;
71+
2272 // Dynamically import the rules engine so we can show an error in the UI
2373 // if it fails to load (instead of a silent module load error).
2474 let validateYaml = null ;
@@ -252,6 +302,11 @@ async function initPopup() {
252302 return ;
253303 }
254304
305+ // Remember the YAML we successfully fetched so that
306+ // suggestion previews and copied snippets can use the
307+ // same base document.
308+ currentYamlText = yamlText ;
309+
255310 function showManualPasteUI ( ) {
256311 noYaml . style . display = "block" ;
257312 statusBadge . textContent = "NO YAML" ;
@@ -273,6 +328,9 @@ async function initPopup() {
273328 }
274329 const content = ( document . getElementById ( "manualYaml" ) || { value : "" } ) . value ;
275330 if ( ! content ) { return ; }
331+ // For manual validations, keep the pasted YAML available
332+ // for suggestion preview helpers.
333+ currentYamlText = content ;
276334 try {
277335 // Run Guardon rules
278336 const { customRules } = await storageGet ( "customRules" ) ;
@@ -315,18 +373,25 @@ async function initPopup() {
315373 if ( ! diagEl ) {
316374 diagEl = document . createElement ( "div" ) ;
317375 diagEl . id = "schemaDiagnostic" ;
318- diagEl . style . cssText = "margin:8px 0;padding:8px;border:1px solid #eee;background:#f9f9f9; font-size:12px;white-space:pre-wrap;" ;
376+ diagEl . style . cssText = "margin:8px 0;padding:8px;font-size:12px;white-space:pre-wrap;" ;
319377 summary . parentNode . insertBefore ( diagEl , summary . nextSibling ) ;
320378 }
321379 diagEl . textContent = schemaDiagnostic ;
322380 let errEl = document . getElementById ( "schemaErrorSection" ) ;
323381 if ( ! errEl ) {
324382 errEl = document . createElement ( "ul" ) ;
325383 errEl . id = "schemaErrorSection" ;
326- errEl . style . cssText = "margin:8px 0;padding:8px;border:1px solid #fbb;background:#fff0f0; font-size:13px;" ;
384+ errEl . style . cssText = "margin:8px 0;padding:8px;font-size:13px;" ;
327385 diagEl . parentNode . insertBefore ( errEl , diagEl . nextSibling ) ;
328386 }
329387 errEl . innerHTML = schemaErrorSection ;
388+ // Hide the schema error section entirely when there are no
389+ // schema errors so we don't show an empty red box.
390+ if ( ! schemaErrorSection ) {
391+ errEl . style . display = "none" ;
392+ } else {
393+ errEl . style . display = "block" ;
394+ }
330395
331396 // --- OPA WASM evaluation for manual YAML ---
332397 let opaResults = [ ] ;
@@ -499,7 +564,7 @@ async function initPopup() {
499564 if ( ! diagEl ) {
500565 diagEl = document . createElement ( "div" ) ;
501566 diagEl . id = "schemaDiagnostic" ;
502- diagEl . style . cssText = "margin:8px 0;padding:8px;border:1px solid #eee;background:#f9f9f9; font-size:12px;white-space:pre-wrap;" ;
567+ diagEl . style . cssText = "margin:8px 0;padding:8px;font-size:12px;white-space:pre-wrap;" ;
503568 summary . parentNode . insertBefore ( diagEl , summary . nextSibling ) ;
504569 }
505570 diagEl . textContent = schemaDiagnostic ;
@@ -508,10 +573,17 @@ async function initPopup() {
508573 if ( ! errEl ) {
509574 errEl = document . createElement ( "ul" ) ;
510575 errEl . id = "schemaErrorSection" ;
511- errEl . style . cssText = "margin:8px 0;padding:8px;border:1px solid #fbb;background:#fff0f0; font-size:13px;" ;
576+ errEl . style . cssText = "margin:8px 0;padding:8px;font-size:13px;" ;
512577 diagEl . parentNode . insertBefore ( errEl , diagEl . nextSibling ) ;
513578 }
514579 errEl . innerHTML = schemaErrorSection ;
580+ // Hide the schema error section box entirely when there are no
581+ // schema errors so the UI doesn't show an empty panel.
582+ if ( ! schemaErrorSection ) {
583+ errEl . style . display = "none" ;
584+ } else {
585+ errEl . style . display = "block" ;
586+ }
515587 } catch ( err ) {
516588 // Validation engine threw an error
517589 showValidationUnavailable ( "Validation failed — see console for details." ) ;
@@ -667,7 +739,7 @@ async function initPopup() {
667739 return ;
668740 }
669741 try {
670- const patched = await previewPatchedYaml ( yamlText , r . docIndex , r . suggestion , { fullStream : true } ) ;
742+ const patched = await previewPatchedYaml ( currentYamlText , r . docIndex , r . suggestion , { fullStream : true } ) ;
671743 suggestionHint . textContent = r . suggestion . hint || ( r . message || "Suggested fix" ) ;
672744 suggestionPre . textContent = patched || "Failed to generate preview" ;
673745 suggestionModal . style . display = "flex" ;
@@ -757,28 +829,6 @@ async function initPopup() {
757829 copyBtn . textContent = "✅ Copied!" ;
758830 setTimeout ( ( ) => ( copyBtn . textContent = "📋 Copy Report" ) , 1500 ) ;
759831 } ;
760- // Suggestion modal wiring
761- if ( closeSuggestionBtn ) { closeSuggestionBtn . addEventListener ( "click" , ( ) => { if ( suggestionModal ) { suggestionModal . style . display = "none" ; } } ) ; }
762- if ( copyPatchBtn ) { copyPatchBtn . addEventListener ( "click" , async ( ) => {
763- try {
764- const text = suggestionPre . textContent || "" ;
765- await navigator . clipboard . writeText ( text ) ;
766- showToast ( "Patched YAML copied" ) ;
767- } catch ( e ) { showToast ( "Copy failed" , { background : "#b91c1c" } ) ; }
768- } ) ; }
769- if ( downloadPatchBtn ) { downloadPatchBtn . addEventListener ( "click" , ( ) => {
770- try {
771- const text = suggestionPre . textContent || "" ;
772- const blob = new Blob ( [ text ] , { type : "text/yaml" } ) ;
773- const url = URL . createObjectURL ( blob ) ;
774- const a = document . createElement ( "a" ) ;
775- a . href = url ; a . download = "patched.yaml" ; document . body . appendChild ( a ) ; a . click ( ) ; a . remove ( ) ; URL . revokeObjectURL ( url ) ;
776- showToast ( "Downloaded patched YAML" ) ;
777- } catch ( e ) { showToast ( "Download failed" , { background : "#b91c1c" } ) ; }
778- } ) ; }
779- // Explain modal wiring
780- if ( closeExplainBtn ) { closeExplainBtn . addEventListener ( "click" , ( ) => { if ( explainModal ) { explainModal . style . display = "none" ; } } ) ; }
781-
782832 // renderResults helper used by manual validation
783833 function renderResults ( results ) {
784834 if ( ! results || results . length === 0 ) {
@@ -811,21 +861,25 @@ async function initPopup() {
811861 tdSeverity . innerHTML = `<span class="severity-icon">${ icon } </span>${ r . severity . toUpperCase ( ) } ` ;
812862
813863 const tdRule = document . createElement ( "td" ) ; tdRule . textContent = r . ruleId ;
814- const tdSource = document . createElement ( "td" ) ; tdSource . textContent = getSourceLabel ( r ) ;
815- const tdMessage = document . createElement ( "td" ) ; setMessageCellContent ( tdMessage , r . message ) ;
816- const tdActions = document . createElement ( "td" ) ;
864+ const tdSource = document . createElement ( "td" ) ; tdSource . textContent = getSourceLabel ( r ) ;
865+ const tdMessage = document . createElement ( "td" ) ; setMessageCellContent ( tdMessage , r . message ) ;
866+ const tdActions = document . createElement ( "td" ) ;
867+ tdActions . className = "actions-cell" ;
817868
818869 if ( r . suggestion ) {
819870 const previewBtn = document . createElement ( "button" ) ;
820871 previewBtn . type = "button" ;
821- previewBtn . textContent = "Preview Patch" ;
872+ previewBtn . className = "action-btn icon-btn preview" ;
873+ previewBtn . title = "Preview patch" ;
874+ previewBtn . setAttribute ( "aria-label" , "Preview patch" ) ;
875+ previewBtn . innerHTML = "🔧" ;
822876 previewBtn . addEventListener ( "click" , async ( ) => {
823877 if ( ! previewAvailable ) {
824878 alert ( "Patch preview not available" ) ;
825879 return ;
826880 }
827881 try {
828- const patched = await previewPatchedYaml ( yamlText , r . docIndex , r . suggestion , { fullStream : true } ) ;
882+ const patched = await previewPatchedYaml ( currentYamlText , r . docIndex , r . suggestion , { fullStream : true } ) ;
829883 suggestionHint . textContent = r . suggestion . hint || ( r . message || "Suggested fix" ) ;
830884 suggestionPre . textContent = patched || "Failed to generate preview" ;
831885 suggestionModal . style . display = "flex" ;
@@ -838,7 +892,10 @@ async function initPopup() {
838892
839893 const copySnippetBtn = document . createElement ( "button" ) ;
840894 copySnippetBtn . type = "button" ;
841- copySnippetBtn . textContent = "Copy Snippet" ;
895+ copySnippetBtn . className = "action-btn icon-btn copy" ;
896+ copySnippetBtn . title = "Copy snippet" ;
897+ copySnippetBtn . setAttribute ( "aria-label" , "Copy snippet" ) ;
898+ copySnippetBtn . innerHTML = "📋" ;
842899 copySnippetBtn . addEventListener ( "click" , async ( ) => {
843900 try {
844901 const j = globalThis . jsyaml ;
0 commit comments