@@ -11,7 +11,7 @@ import PersonalSummaryStrip from "./PersonalSummaryStrip";
1111import { config , setConfig , getCustomTab , isBuiltinTab , isActionsBasedTab , updateJiraConfig , type TrackedUser } from "../../stores/config" ;
1212import { viewState , updateViewState , setSortPreference , pruneClosedTrackedItems , removeCustomTabState , untrackJiraItem , setTabFilter , IssueFiltersSchema , PullRequestFiltersSchema , ActionsFiltersSchema } from "../../stores/view" ;
1313import DependenciesTab from "./DependenciesTab" ;
14- import { isDependencyPr , expandBotLogins , needsBodyFallback } from "../../lib/dependency-detection" ;
14+ import { isDependencyPr , expandBotLogins , needsBodyFallback , parseRenovateBody , type VersionInfo } from "../../lib/dependency-detection" ;
1515import { findDashboardIssues , parseAbandonedSection , resetAbandonedPatternCache , type AbandonedDependency } from "../../lib/dependency-dashboard" ;
1616import { fetchDashboardIssueBodies , fetchDepPRBodies } from "../../services/api" ;
1717import type { SortOption } from "../shared/SortDropdown" ;
@@ -28,7 +28,7 @@ import {
2828 fetchAllData ,
2929 type DashboardData ,
3030} from "../../services/poll" ;
31- import { expireToken , user , onAuthCleared , DASHBOARD_STORAGE_KEY , jiraAuth , setJiraAuth , isJiraAuthenticated , clearJiraAuth } from "../../stores/auth" ;
31+ import { expireToken , user , onAuthCleared , DASHBOARD_STORAGE_KEY , DEP_META_STORAGE_KEY , jiraAuth , setJiraAuth , isJiraAuthenticated , clearJiraAuth } from "../../stores/auth" ;
3232import { JiraApiError } from "../../services/jira-client" ;
3333import { createJiraClient , mergeCustomFields , jiraJqlForScope } from "../../lib/jira-utils" ;
3434import type { JiraIssue } from "../../../shared/jira-types" ;
@@ -144,7 +144,33 @@ let _jiraFetching = false;
144144// Dependency dashboard state — module-level for same reasons as jira state above
145145const [ abandonedDepsMap , setAbandonedDepsMap ] = createSignal < Map < string , AbandonedDependency [ ] > > ( new Map ( ) ) ;
146146const [ dashboardIssueUrls , setDashboardIssueUrls ] = createSignal < Map < string , string > > ( new Map ( ) ) ;
147- const [ depBodies , setDepBodies ] = createSignal < ReadonlyMap < number , string > > ( new Map ( ) ) ;
147+ function loadDepMetaCache ( ) : Map < number , VersionInfo > {
148+ try {
149+ const raw = localStorage . getItem ?.( DEP_META_STORAGE_KEY ) ;
150+ if ( ! raw ) return new Map ( ) ;
151+ const parsed = JSON . parse ( raw ) as Record < string , unknown > ;
152+ const map = new Map < number , VersionInfo > ( ) ;
153+ for ( const [ k , v ] of Object . entries ( parsed ) ) {
154+ const id = Number ( k ) ;
155+ if ( ! isNaN ( id ) && v && typeof v === "object" ) map . set ( id , v as VersionInfo ) ;
156+ }
157+ return map ;
158+ } catch {
159+ return new Map ( ) ;
160+ }
161+ }
162+
163+ function persistDepMeta ( meta : ReadonlyMap < number , VersionInfo > ) : void {
164+ try {
165+ const obj : Record < number , VersionInfo > = { } ;
166+ for ( const [ k , v ] of meta ) obj [ k ] = v ;
167+ localStorage . setItem ( DEP_META_STORAGE_KEY , JSON . stringify ( obj ) ) ;
168+ } catch {
169+ // Non-critical — storage may be full
170+ }
171+ }
172+
173+ const [ depMeta , setDepMeta ] = createSignal < ReadonlyMap < number , VersionInfo > > ( loadDepMetaCache ( ) ) ;
148174let _fetchingDashboardBodies = false ;
149175let _fetchingDepBodies = false ;
150176
@@ -158,7 +184,8 @@ onAuthCleared(() => {
158184 _jiraFetching = false ;
159185 setAbandonedDepsMap ( new Map ( ) ) ;
160186 setDashboardIssueUrls ( new Map ( ) ) ;
161- setDepBodies ( new Map ( ) ) ;
187+ setDepMeta ( new Map ( ) ) ;
188+ localStorage . removeItem ?.( DEP_META_STORAGE_KEY ) ;
162189 _fetchingDashboardBodies = false ;
163190 resetAbandonedPatternCache ( ) ;
164191 const coord = _coordinator ( ) ;
@@ -1160,18 +1187,17 @@ export default function DashboardPage() {
11601187 ) ) ;
11611188
11621189 // Fetch PR bodies for dependency PRs where title parsing can't determine update type.
1163- // Uses a separate reactive signal (depBodies) rather than mutating store PR objects,
1164- // because dependencyPullRequests() returns filtered plain objects that lose store
1165- // proxy tracking — produce() mutations to pr.body wouldn't trigger recomputation.
1190+ // Parses bodies immediately into VersionInfo and persists to localStorage so
1191+ // classification survives page refresh without visual jank.
11661192 createEffect ( ( ) => {
11671193 if ( ! config . dependencies . enabled ) return ;
11681194 if ( _fetchingDepBodies ) return ;
11691195 const octokit = getClient ( ) ;
11701196 if ( ! octokit ) return ;
11711197
1172- const bodies = depBodies ( ) ;
1198+ const meta = depMeta ( ) ;
11731199 const depPrs = dependencyPullRequests ( ) ;
1174- const toFetch = depPrs . filter ( ( pr ) => ! bodies . has ( pr . id ) && needsBodyFallback ( pr ) ) ;
1200+ const toFetch = depPrs . filter ( ( pr ) => ! meta . has ( pr . id ) && needsBodyFallback ( pr ) ) ;
11751201 if ( toFetch . length === 0 ) return ;
11761202
11771203 _fetchingDepBodies = true ;
@@ -1181,9 +1207,18 @@ export default function DashboardPage() {
11811207 const bodyMap = await fetchDepPRBodies ( octokit , nodeIds ) ;
11821208 if ( bodyMap . size === 0 ) return ;
11831209
1184- const merged = new Map ( bodies ) ;
1185- for ( const [ id , body ] of bodyMap ) merged . set ( id , body ) ;
1186- setDepBodies ( merged ) ;
1210+ const merged = new Map ( meta ) ;
1211+ for ( const [ id , body ] of bodyMap ) {
1212+ const parsed = parseRenovateBody ( body ) ;
1213+ if ( parsed ) merged . set ( id , parsed ) ;
1214+ }
1215+ // Prune entries for PRs no longer in the dependency set
1216+ const depPrIds = new Set ( depPrs . map ( ( pr ) => pr . id ) ) ;
1217+ for ( const k of [ ...merged . keys ( ) ] ) {
1218+ if ( ! depPrIds . has ( k ) ) merged . delete ( k ) ;
1219+ }
1220+ setDepMeta ( merged ) ;
1221+ setTimeout ( ( ) => persistDepMeta ( merged ) , 0 ) ;
11871222 } finally {
11881223 _fetchingDepBodies = false ;
11891224 }
@@ -1284,7 +1319,7 @@ export default function DashboardPage() {
12841319 < Match when = { activeTab ( ) === "dependencies" } >
12851320 < DependenciesTab
12861321 pullRequests = { dependencyPullRequests ( ) }
1287- depBodies = { depBodies ( ) }
1322+ depMeta = { depMeta ( ) }
12881323 loading = { dashboardData . loading }
12891324 abandonedDepsMap = { abandonedDepsMap ( ) }
12901325 dashboardIssueUrls = { dashboardIssueUrls ( ) }
0 commit comments