@@ -30,6 +30,10 @@ export interface ProjectsCardProps {
3030 /** Optional: link template for developer chips. Receives login, returns href.
3131 * If omitted, chips are not links. */
3232 developerHref ?: ( login : string ) => string ;
33+ /** When provided, an "Other (not in top N)" row is appended showing the
34+ * activity not captured by the project clusters. Shown as a peer row with
35+ * the same bar format so users can compare scale directly. */
36+ actualTotals ?: { commits : number ; prs : number ; jiras : number } ;
3337 /** Collapsible mode (GLOOK-11): header becomes a toggle button styled to
3438 * match <TeamPulseCard>, body hidden when collapsed. Controlled — parent
3539 * owns `expanded` state via `onExpandedChange`. */
@@ -56,12 +60,14 @@ function ProjectsBody({
5660 emptyMessage,
5761 developerHref,
5862 variant,
63+ actualTotals,
5964} : {
6065 projects : ProjectsCardItem [ ] | TeamProject [ ] ;
6166 loading ?: boolean ;
6267 emptyMessage : string ;
6368 developerHref ?: ( login : string ) => string ;
6469 variant : 'standalone' | 'collapsible' ;
70+ actualTotals ?: { commits : number ; prs : number ; jiras : number } ;
6571} ) {
6672 if ( loading ) {
6773 return (
@@ -86,17 +92,34 @@ function ProjectsBody({
8692 [ projects ] ,
8793 ) ;
8894
95+ // "Other" row: activity not attributed to any named project cluster.
96+ // Only shown when actualTotals is provided (home page passes org totals from devStats).
97+ const other = useMemo ( ( ) => {
98+ if ( ! actualTotals ) return null ;
99+ const sumCommits = sorted . reduce ( ( s , p ) => s + p . estimated_commits , 0 ) ;
100+ const sumPrs = sorted . reduce ( ( s , p ) => s + p . estimated_prs , 0 ) ;
101+ const sumJiras = sorted . reduce ( ( s , p ) => s + p . jira_count , 0 ) ;
102+ const o = {
103+ commits : Math . max ( 0 , actualTotals . commits - sumCommits ) ,
104+ prs : Math . max ( 0 , actualTotals . prs - sumPrs ) ,
105+ jiras : Math . max ( 0 , actualTotals . jiras - sumJiras ) ,
106+ } ;
107+ return ( o . commits + o . prs + o . jiras ) > 0 ? o : null ;
108+ } , [ sorted , actualTotals ] ) ;
109+
89110 // Bar width = total volume (PRs + Jiras + Commits) / max across all projects.
90111 // Commits are intentionally included here even though they are excluded from the
91112 // sort key — the bar shows the full activity footprint of each project (sort rank
92113 // reflects quality output, bar width reflects overall size).
93- const maxVolume = useMemo (
94- ( ) => sorted . reduce ( ( max , p ) => Math . max ( max , p . estimated_prs + p . jira_count + p . estimated_commits ) , 1 ) ,
95- [ sorted ] ,
96- ) ;
114+ // If "Other" is present, include it in maxVolume so bars are comparable.
115+ const maxVolume = useMemo ( ( ) => {
116+ const projectMax = sorted . reduce ( ( max , p ) => Math . max ( max , p . estimated_prs + p . jira_count + p . estimated_commits ) , 1 ) ;
117+ const otherTotal = other ? other . commits + other . prs + other . jiras : 0 ;
118+ return Math . max ( projectMax , otherTotal , 1 ) ;
119+ } , [ sorted , other ] ) ;
97120
98121 // Hide Jira legend entry when Jira is disabled (all counts are 0).
99- const hasJira = useMemo ( ( ) => sorted . some ( p => p . jira_count > 0 ) , [ sorted ] ) ;
122+ const hasJira = useMemo ( ( ) => sorted . some ( p => p . jira_count > 0 ) || ( other ?. jiras ?? 0 ) > 0 , [ sorted , other ] ) ;
100123
101124 return (
102125 < div className = { `space-y-3 ${ variant === 'collapsible' ? 'mt-3' : 'mt-4' } ` } >
@@ -174,6 +197,47 @@ function ProjectsBody({
174197 </ div >
175198 ) ;
176199 } ) }
200+
201+ { /* "Other" row — same visual style as a project, italic label, no summary/devs */ }
202+ { other && ( ( ) => {
203+ const otherTotal = other . commits + other . prs + other . jiras ;
204+ const otherBarPct = ( otherTotal / maxVolume ) * 100 ;
205+ return (
206+ < div
207+ className = "rounded-lg p-3"
208+ style = { { background : 'rgba(255,255,255,0.01)' , border : '1px solid rgba(255,255,255,0.06)' } }
209+ >
210+ < div className = "flex items-start justify-between gap-3 mb-1" >
211+ < div className = "flex items-center gap-2 min-w-0" >
212+ < span className = "text-xs text-gray-700 w-4 shrink-0 text-right" > ~</ span >
213+ < span className = "text-sm text-gray-500 italic" > Other (not in top { sorted . length } )</ span >
214+ </ div >
215+ < div className = "flex items-center gap-3 shrink-0 text-[11px] text-gray-600" >
216+ < span > ~{ other . jiras } jiras</ span >
217+ < span > ~{ other . commits } commits</ span >
218+ < span > ~{ other . prs } PRs</ span >
219+ </ div >
220+ </ div >
221+ { otherTotal > 0 && (
222+ < div className = "pl-6 mb-1.5" >
223+ < div
224+ className = "h-[5px] rounded-sm overflow-hidden"
225+ style = { { background : 'rgba(255,255,255,0.05)' } }
226+ role = "img"
227+ aria-label = { `Other: ${ other . prs } PRs, ${ other . jiras } Jiras, ${ other . commits } commits not attributed to a named project` }
228+ >
229+ < div className = "h-full flex" style = { { width : `${ otherBarPct } %` } } >
230+ < div style = { { flex : other . prs , background : SEGMENT_COLORS . prs } } />
231+ < div style = { { flex : other . jiras , background : SEGMENT_COLORS . jiras } } />
232+ < div style = { { flex : other . commits , background : SEGMENT_COLORS . commits } } />
233+ </ div >
234+ </ div >
235+ </ div >
236+ ) }
237+ < div className = "text-[10px] text-gray-700 pl-6" > Activity not attributed to a named project</ div >
238+ </ div >
239+ ) ;
240+ } ) ( ) }
177241 </ div >
178242 ) ;
179243}
@@ -185,6 +249,7 @@ export default function ProjectsCard({
185249 subtitle,
186250 emptyMessage = 'No active projects in this window.' ,
187251 developerHref,
252+ actualTotals,
188253 collapsible = false ,
189254 expanded = true ,
190255 onExpandedChange,
@@ -227,6 +292,7 @@ export default function ProjectsCard({
227292 emptyMessage = { emptyMessage }
228293 developerHref = { developerHref }
229294 variant = "collapsible"
295+ actualTotals = { actualTotals }
230296 />
231297 </ div >
232298 ) }
@@ -251,6 +317,7 @@ export default function ProjectsCard({
251317 emptyMessage = { emptyMessage }
252318 developerHref = { developerHref }
253319 variant = "standalone"
320+ actualTotals = { actualTotals }
254321 />
255322 </ div >
256323 ) ;
0 commit comments