@@ -6,7 +6,7 @@ import { pushNotification } from "../lib/errors";
66export const VIEW_STORAGE_KEY = "github-tracker:view" ;
77const IGNORED_ITEMS_CAP = 500 ;
88const TRACKED_ITEMS_CAP = 200 ;
9- const LOCKED_REPOS_CAP = 50 ;
9+ export const LOCKED_REPOS_CAP = 50 ;
1010
1111export const TrackedItemSchema = z . object ( {
1212 id : z . number ( ) ,
@@ -102,6 +102,8 @@ export const ViewStateSchema = z.object({
102102export type ViewState = z . infer < typeof ViewStateSchema > ;
103103export type IgnoredItem = ViewState [ "ignoredItems" ] [ number ] ;
104104
105+ const REPO_STATE_TAB_IDS = [ "issues" , "pullRequests" , "actions" ] as const ;
106+
105107export function migrateLockedRepos ( raw : unknown ) : unknown {
106108 if ( raw == null ) return { issues : [ ] , pullRequests : [ ] , actions : [ ] } ;
107109 if ( Array . isArray ( raw ) ) {
@@ -110,7 +112,7 @@ export function migrateLockedRepos(raw: unknown): unknown {
110112 return { issues : [ ...arr ] , pullRequests : [ ...arr ] , actions : [ ...arr ] } ;
111113 }
112114 if ( typeof raw === "object" ) {
113- // Object → pass through as-is (already per-tab record shape)
115+ // Object → pass through as-is; loadViewState cap-guard sanitizes malformed entries
114116 return raw ;
115117 }
116118 return { issues : [ ] , pullRequests : [ ] , actions : [ ] } ;
@@ -156,15 +158,15 @@ export function resetViewState(): void {
156158 produce ( ( draft ) => {
157159 // Delete dynamic custom tab keys that Object.assign wouldn't clear
158160 for ( const key of Object . keys ( draft . expandedRepos ) ) {
159- if ( ! [ "issues" , "pullRequests" , "actions" ] . includes ( key ) ) {
161+ if ( ! ( REPO_STATE_TAB_IDS as readonly string [ ] ) . includes ( key ) ) {
160162 delete draft . expandedRepos [ key ] ;
161163 }
162164 }
163165 for ( const key of Object . keys ( draft . customTabFilters ) ) {
164166 delete draft . customTabFilters [ key ] ;
165167 }
166168 for ( const key of Object . keys ( draft . lockedRepos ) ) {
167- if ( ! [ "issues" , "pullRequests" , "actions" ] . includes ( key ) ) {
169+ if ( ! ( REPO_STATE_TAB_IDS as readonly string [ ] ) . includes ( key ) ) {
168170 delete draft . lockedRepos [ key ] ;
169171 }
170172 }
@@ -371,38 +373,44 @@ export function removeCustomTabState(tabId: string): void {
371373}
372374
373375export function lockRepo ( tabKey : string , repoFullName : string ) : void {
374- setViewState ( produce ( ( draft ) => {
375- if ( ! draft . lockedRepos [ tabKey ] ) draft . lockedRepos [ tabKey ] = [ ] ;
376- const arr = draft . lockedRepos [ tabKey ] ;
377- if ( ! arr . includes ( repoFullName ) && arr . length < LOCKED_REPOS_CAP ) {
378- arr . push ( repoFullName ) ;
379- }
380- } ) ) ;
376+ setViewState (
377+ produce ( ( draft ) => {
378+ if ( ! draft . lockedRepos [ tabKey ] ) draft . lockedRepos [ tabKey ] = [ ] ;
379+ const arr = draft . lockedRepos [ tabKey ] ;
380+ if ( ! arr . includes ( repoFullName ) && arr . length < LOCKED_REPOS_CAP ) {
381+ arr . push ( repoFullName ) ;
382+ }
383+ } )
384+ ) ;
381385}
382386
383387export function unlockRepo ( tabKey : string , repoFullName : string ) : void {
384- setViewState ( produce ( ( draft ) => {
385- if ( ! draft . lockedRepos [ tabKey ] ) return ;
386- draft . lockedRepos [ tabKey ] = draft . lockedRepos [ tabKey ] . filter ( r => r !== repoFullName ) ;
387- } ) ) ;
388+ setViewState (
389+ produce ( ( draft ) => {
390+ if ( ! draft . lockedRepos [ tabKey ] ) return ;
391+ draft . lockedRepos [ tabKey ] = draft . lockedRepos [ tabKey ] . filter ( ( r ) => r !== repoFullName ) ;
392+ } )
393+ ) ;
388394}
389395
390396export function moveLockedRepo (
391397 tabKey : string ,
392398 repoFullName : string ,
393399 direction : "up" | "down"
394400) : void {
395- setViewState ( produce ( ( draft ) => {
396- if ( ! draft . lockedRepos [ tabKey ] ) return ;
397- const arr = draft . lockedRepos [ tabKey ] ;
398- const idx = arr . indexOf ( repoFullName ) ;
399- if ( idx === - 1 ) return ;
400- const targetIdx = direction === "up" ? idx - 1 : idx + 1 ;
401- if ( targetIdx < 0 || targetIdx >= arr . length ) return ;
402- const tmp = arr [ idx ] ;
403- arr [ idx ] = arr [ targetIdx ] ;
404- arr [ targetIdx ] = tmp ;
405- } ) ) ;
401+ setViewState (
402+ produce ( ( draft ) => {
403+ if ( ! draft . lockedRepos [ tabKey ] ) return ;
404+ const arr = draft . lockedRepos [ tabKey ] ;
405+ const idx = arr . indexOf ( repoFullName ) ;
406+ if ( idx === - 1 ) return ;
407+ const targetIdx = direction === "up" ? idx - 1 : idx + 1 ;
408+ if ( targetIdx < 0 || targetIdx >= arr . length ) return ;
409+ const tmp = arr [ idx ] ;
410+ arr [ idx ] = arr [ targetIdx ] ;
411+ arr [ targetIdx ] = tmp ;
412+ } )
413+ ) ;
406414}
407415
408416export function pruneLockedRepos (
@@ -412,11 +420,13 @@ export function pruneLockedRepos(
412420 const current = untrack ( ( ) => viewState . lockedRepos [ tabKey ] ?? [ ] ) ;
413421 if ( current . length === 0 ) return ;
414422 const activeSet = new Set ( activeRepoNames ) ;
415- const filtered = current . filter ( name => activeSet . has ( name ) ) ;
423+ const filtered = current . filter ( ( name ) => activeSet . has ( name ) ) ;
416424 if ( filtered . length === current . length ) return ;
417- setViewState ( produce ( ( draft ) => {
418- draft . lockedRepos [ tabKey ] = filtered ;
419- } ) ) ;
425+ setViewState (
426+ produce ( ( draft ) => {
427+ draft . lockedRepos [ tabKey ] = filtered ;
428+ } )
429+ ) ;
420430}
421431
422432export function trackItem ( item : TrackedItem ) : void {
@@ -451,16 +461,18 @@ export function moveTrackedItem(
451461 type : "issue" | "pullRequest" ,
452462 direction : "up" | "down"
453463) : void {
454- setViewState ( produce ( ( draft ) => {
455- const arr = draft . trackedItems ;
456- const idx = arr . findIndex ( ( i ) => i . id === id && i . type === type ) ;
457- if ( idx === - 1 ) return ;
458- const targetIdx = direction === "up" ? idx - 1 : idx + 1 ;
459- if ( targetIdx < 0 || targetIdx >= arr . length ) return ;
460- const tmp = arr [ idx ] ;
461- arr [ idx ] = arr [ targetIdx ] ;
462- arr [ targetIdx ] = tmp ;
463- } ) ) ;
464+ setViewState (
465+ produce ( ( draft ) => {
466+ const arr = draft . trackedItems ;
467+ const idx = arr . findIndex ( ( i ) => i . id === id && i . type === type ) ;
468+ if ( idx === - 1 ) return ;
469+ const targetIdx = direction === "up" ? idx - 1 : idx + 1 ;
470+ if ( targetIdx < 0 || targetIdx >= arr . length ) return ;
471+ const tmp = arr [ idx ] ;
472+ arr [ idx ] = arr [ targetIdx ] ;
473+ arr [ targetIdx ] = tmp ;
474+ } )
475+ ) ;
464476}
465477
466478export function pruneClosedTrackedItems ( pruneKeys : Set < string > ) : void {
0 commit comments