@@ -5,16 +5,44 @@ class ActivityFeedPanel {
55 this . eventIds = new Set ( ) ;
66 this . socket = null ;
77 this . serverUrl = window . location . origin ;
8- this . filterText = '' ;
9- this . groupFilter = 'all' ;
10- this . paused = false ;
8+ this . filterText = this . loadPref ( 'activityFeed.filterText' , '' ) ;
9+ this . groupFilter = this . loadPref ( 'activityFeed.groupFilter' , 'all' ) ;
10+ this . paused = this . loadPrefBool ( 'activityFeed.paused' , false ) ;
11+ this . errorsOnly = this . loadPrefBool ( 'activityFeed.errorsOnly' , false ) ;
1112 this . unseenCount = 0 ;
1213
1314 this . _dismissPointerHandler = null ;
1415 this . _dismissKeyHandler = null ;
1516 this . _socketHandler = null ;
1617 }
1718
19+ loadPref ( key , fallback ) {
20+ try {
21+ const v = localStorage . getItem ( key ) ;
22+ return v === null ? fallback : v ;
23+ } catch {
24+ return fallback ;
25+ }
26+ }
27+
28+ loadPrefBool ( key , fallback ) {
29+ try {
30+ const v = localStorage . getItem ( key ) ;
31+ if ( v === null ) return fallback ;
32+ return v === 'true' ;
33+ } catch {
34+ return fallback ;
35+ }
36+ }
37+
38+ savePref ( key , value ) {
39+ try {
40+ localStorage . setItem ( key , String ( value ) ) ;
41+ } catch {
42+ // ignore
43+ }
44+ }
45+
1846 isOpen ( ) {
1947 const modal = document . getElementById ( 'activity-feed-modal' ) ;
2048 return ! ! modal && ! modal . classList . contains ( 'hidden' ) ;
@@ -105,6 +133,11 @@ class ActivityFeedPanel {
105133 Pause live
106134 </label>
107135
136+ <label class="option-toggle" title="Show only failing/error events">
137+ <input type="checkbox" id="activity-errors-only">
138+ Errors only
139+ </label>
140+
108141 <button class="btn-secondary" id="activity-refresh-btn">Refresh</button>
109142 <button class="btn-secondary" id="activity-clear-btn" title="Clear only this UI list (does not delete server history)">Clear</button>
110143 </div>
@@ -126,6 +159,7 @@ class ActivityFeedPanel {
126159 textInput . value = this . filterText ;
127160 textInput . oninput = ( ) => {
128161 this . filterText = String ( textInput . value || '' ) ;
162+ this . savePref ( 'activityFeed.filterText' , this . filterText ) ;
129163 this . renderList ( ) ;
130164 } ;
131165 }
@@ -135,6 +169,7 @@ class ActivityFeedPanel {
135169 groupSelect . value = this . groupFilter ;
136170 groupSelect . onchange = ( ) => {
137171 this . groupFilter = String ( groupSelect . value || 'all' ) ;
172+ this . savePref ( 'activityFeed.groupFilter' , this . groupFilter ) ;
138173 this . renderList ( ) ;
139174 } ;
140175 }
@@ -144,10 +179,21 @@ class ActivityFeedPanel {
144179 pauseCb . checked = ! ! this . paused ;
145180 pauseCb . onchange = ( ) => {
146181 this . paused = ! ! pauseCb . checked ;
182+ this . savePref ( 'activityFeed.paused' , this . paused ? 'true' : 'false' ) ;
147183 this . renderStats ( ) ;
148184 } ;
149185 }
150186
187+ const errorsCb = document . getElementById ( 'activity-errors-only' ) ;
188+ if ( errorsCb ) {
189+ errorsCb . checked = ! ! this . errorsOnly ;
190+ errorsCb . onchange = ( ) => {
191+ this . errorsOnly = ! ! errorsCb . checked ;
192+ this . savePref ( 'activityFeed.errorsOnly' , this . errorsOnly ? 'true' : 'false' ) ;
193+ this . renderList ( ) ;
194+ } ;
195+ }
196+
151197 document . getElementById ( 'activity-refresh-btn' ) ?. addEventListener ( 'click' , ( ) => this . refresh ( ) ) ;
152198 document . getElementById ( 'activity-clear-btn' ) ?. addEventListener ( 'click' , ( ) => {
153199 this . events = [ ] ;
@@ -260,12 +306,24 @@ class ActivityFeedPanel {
260306 const kind = String ( ev ?. kind || '' ) ;
261307 const groupOk = group === 'all' ? true : this . getGroup ( kind ) === group ;
262308 if ( ! groupOk ) return false ;
309+ if ( this . errorsOnly && ! this . isErrorEvent ( ev ) ) return false ;
263310 if ( ! text ) return true ;
264311 const hay = `${ kind } ${ JSON . stringify ( ev ?. data || { } ) } ` . toLowerCase ( ) ;
265312 return hay . includes ( text ) ;
266313 } ) ;
267314 }
268315
316+ isErrorEvent ( ev ) {
317+ const kind = String ( ev ?. kind || '' ) ;
318+ const data = ev ?. data && typeof ev . data === 'object' ? ev . data : { } ;
319+ if ( data . ok === false ) return true ;
320+ if ( kind . includes ( 'failed' ) ) return true ;
321+ if ( kind . includes ( '.error' ) ) return true ;
322+ if ( kind . endsWith ( '.failed' ) ) return true ;
323+ if ( kind . includes ( 'close.failed' ) ) return true ;
324+ return false ;
325+ }
326+
269327 getGroup ( kind ) {
270328 const k = String ( kind || '' ) ;
271329 const head = k . split ( '.' ) [ 0 ] || '' ;
@@ -281,9 +339,10 @@ class ActivityFeedPanel {
281339 const dataJson = this . escapeHtml ( this . compactJson ( ev ?. data ) ) ;
282340 const group = this . getGroup ( kind ) ;
283341 const actions = this . renderEventActions ( ev ) ;
342+ const failedClass = this . isErrorEvent ( ev ) ? ' activity-failed' : '' ;
284343
285344 return `
286- <div class="activity-event">
345+ <div class="activity-event${ failedClass } ">
287346 <div class="activity-meta">
288347 <span class="activity-time">${ this . escapeHtml ( time ) } </span>
289348 <span class="activity-kind activity-kind-${ this . escapeHtml ( group ) } ">${ this . escapeHtml ( kind ) } </span>
0 commit comments