@@ -35,6 +35,25 @@ const overlayStyle = /*css*/ `
3535 overflow-y: scroll;
3636 margin: 0;
3737 background: rgba(0, 0, 0, 0.66);
38+ scrollbar-width: thin;
39+ scrollbar-color: #444 #1a1a1a;
40+ }
41+
42+ .backdrop::-webkit-scrollbar {
43+ width: 8px;
44+ }
45+
46+ .backdrop::-webkit-scrollbar-track {
47+ background: #1a1a1a;
48+ }
49+
50+ .backdrop::-webkit-scrollbar-thumb {
51+ background: #444;
52+ border-radius: 4px;
53+ }
54+
55+ .backdrop::-webkit-scrollbar-thumb:hover {
56+ background: #666;
3857}
3958
4059.window {
@@ -61,31 +80,44 @@ pre {
6180 margin-top: 0;
6281 margin-bottom: 1em;
6382 overflow-x: scroll;
64- scrollbar-width: none;
83+ scrollbar-width: thin;
84+ scrollbar-color: #444 transparent;
6585}
6686
6787pre::-webkit-scrollbar {
68- display: none ;
88+ height: 6px ;
6989}
7090
71- pre.frame::-webkit-scrollbar {
72- display: block;
73- height: 5px;
91+ pre::-webkit-scrollbar-track {
92+ background: transparent;
7493}
7594
76- pre.frame ::-webkit-scrollbar-thumb {
77- background: #999 ;
78- border-radius: 5px ;
95+ pre::-webkit-scrollbar-thumb {
96+ background: #444 ;
97+ border-radius: 3px ;
7998}
8099
81- pre.frame {
82- scrollbar-width: thin;
100+ pre::-webkit-scrollbar-thumb:hover {
101+ background: #666;
102+ }
103+
104+ .header {
105+ display: flex;
106+ align-items: center;
107+ justify-content: space-between;
108+ gap: 1em;
109+ margin-bottom: 1em;
83110}
84111
85112.message {
86113 line-height: 1.3;
87114 font-weight: 600;
88115 white-space: pre-wrap;
116+ margin: 0;
117+ padding: 0;
118+ flex: 1;
119+ min-width: 0;
120+ overflow-x: hidden;
89121}
90122
91123.message-body {
@@ -156,6 +188,35 @@ code {
156188 color: var(--yellow);
157189}
158190
191+ .copy-btn {
192+ flex-shrink: 0;
193+ padding: 6px 12px;
194+ font-family: var(--monospace);
195+ font-size: 12px;
196+ font-weight: 600;
197+ color: var(--dim);
198+ background: #2a2a2a;
199+ border: 1px solid #444;
200+ border-radius: 4px;
201+ cursor: pointer;
202+ transition: background 0.2s, color 0.2s, border-color 0.2s;
203+ }
204+
205+ .copy-btn:hover {
206+ background: #333;
207+ color: #fff;
208+ border-color: #666;
209+ }
210+
211+ .copy-btn:active {
212+ background: #444;
213+ }
214+
215+ .copy-btn.copied {
216+ color: #50fa7b;
217+ border-color: #50fa7b;
218+ }
219+
159220kbd {
160221 line-height: 1.5;
161222 font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
@@ -173,12 +234,42 @@ kbd {
173234` ;
174235
175236export function setupErrorOverlay ( instance : ModuleTSX ) : void {
237+ /** If the error originates from a module-tsx blob URL, show the overlay and return true. */
238+ const handleRuntimeError = ( error : Error ) : boolean => {
239+ const blobLocation = extractBlobLocation ( error . stack || "" , instance ) ;
240+ if ( ! blobLocation ) return false ;
241+
242+ const sourceUrl = instance . getSourceUrlByBlob ( blobLocation . blobUrl ) ;
243+ if ( ! sourceUrl ) return false ;
244+
245+ showErrorOverlay ( sourceUrl , error , instance ) ;
246+ return true ;
247+ } ;
248+
176249 instance . addEventListener ( "*" , ( event ) => {
177250 const { type, payload } = ( event as CustomEvent ) . detail ;
178251 if ( ! type . endsWith ( ":error" ) ) return ;
179252
180253 showErrorOverlay ( payload . id || payload . sourceUrl , payload . error , instance ) ;
181254 } ) ;
255+
256+ // Catch runtime errors (e.g. from event handlers, timeouts) originating from module-tsx modules
257+ window . addEventListener ( "error" , ( event ) => {
258+ if ( event . error instanceof Error && handleRuntimeError ( event . error ) ) {
259+ event . preventDefault ( ) ;
260+ }
261+ } ) ;
262+
263+ // Catch unhandled promise rejections from module-tsx modules
264+ window . addEventListener ( "unhandledrejection" , ( event ) => {
265+ const error = event . reason instanceof Error
266+ ? event . reason
267+ : new Error ( String ( event . reason ) ) ;
268+
269+ if ( handleRuntimeError ( error ) ) {
270+ event . preventDefault ( ) ;
271+ }
272+ } ) ;
182273}
183274
184275const overlayId = "module-tsx-error-overlay" ;
@@ -617,7 +708,30 @@ async function showErrorOverlay(id: string, error: unknown, instance: ModuleTSX)
617708 innerHTML : "Click outside, press <kbd>Esc</kbd> key, or fix the code to dismiss." ,
618709 } ) ;
619710
620- window_ . appendChild ( messagePre ) ;
711+ // Copy button
712+ const copyBtn = Object . assign ( document . createElement ( "button" ) , {
713+ className : "copy-btn" ,
714+ textContent : "Copy" ,
715+ } ) ;
716+ copyBtn . addEventListener ( "click" , ( ) => {
717+ const text = [ message , id , stack ] . filter ( Boolean ) . join ( "\n\n" ) ;
718+ navigator . clipboard . writeText ( text ) . then ( ( ) => {
719+ copyBtn . textContent = "Copied!" ;
720+ copyBtn . classList . add ( "copied" ) ;
721+ setTimeout ( ( ) => {
722+ copyBtn . textContent = "Copy" ;
723+ copyBtn . classList . remove ( "copied" ) ;
724+ } , 2000 ) ;
725+ } ) ;
726+ } ) ;
727+
728+ const header = Object . assign ( document . createElement ( "div" ) , {
729+ className : "header" ,
730+ } ) ;
731+ header . appendChild ( messagePre ) ;
732+ header . appendChild ( copyBtn ) ;
733+
734+ window_ . appendChild ( header ) ;
621735 window_ . appendChild ( filePre ) ;
622736 if ( codeFrameEl ) {
623737 window_ . appendChild ( codeFrameEl ) ;
0 commit comments