@@ -107,6 +107,8 @@ type FilterMode =
107107
108108// Special constant for "All Repos" global filter
109109const ALL_REPOS_KEY = "__all_repos__" ;
110+ // Prefix used to encode an org-scoped filter in the `name` field, e.g. `__org__:anthropic`.
111+ const ORG_FILTER_PREFIX = "__org__:" ;
110112
111113// Repository with its filter mode
112114interface RepoFilter {
@@ -142,6 +144,20 @@ function isAllReposFilter(filter: RepoFilter): boolean {
142144 return filter . name === ALL_REPOS_KEY ;
143145}
144146
147+ function orgFilterKey ( org : string ) : string {
148+ return `${ ORG_FILTER_PREFIX } ${ org } ` ;
149+ }
150+
151+ function isOrgFilter ( filter : RepoFilter ) : boolean {
152+ return filter . name . startsWith ( ORG_FILTER_PREFIX ) ;
153+ }
154+
155+ function getOrgFromFilter ( filter : RepoFilter ) : string | null {
156+ return isOrgFilter ( filter )
157+ ? filter . name . slice ( ORG_FILTER_PREFIX . length )
158+ : null ;
159+ }
160+
145161// ============================================================================
146162// Storage Helpers
147163// ============================================================================
@@ -330,9 +346,12 @@ function buildSearchQueries(
330346 ? `updated:<${ stalledThreshold } `
331347 : undefined ;
332348
333- // Separate "All Repos" filters from specific repo filters
349+ // Separate "All Repos", org-scoped, and specific repo filters.
334350 const allReposFilters = enabledRepos . filter ( isAllReposFilter ) ;
335- const specificRepos = enabledRepos . filter ( ( r ) => ! isAllReposFilter ( r ) ) ;
351+ const orgFilters = enabledRepos . filter ( isOrgFilter ) ;
352+ const specificRepos = enabledRepos . filter (
353+ ( r ) => ! isAllReposFilter ( r ) && ! isOrgFilter ( r )
354+ ) ;
336355
337356 // Handle "All Repos" global filters (one query per mode)
338357 for ( const filter of allReposFilters ) {
@@ -364,6 +383,59 @@ function buildSearchQueries(
364383 }
365384 }
366385
386+ // Group org filters by mode+authoredBy. Multiple `org:` qualifiers OR just
387+ // like `repo:` qualifiers.
388+ if ( orgFilters . length > 0 ) {
389+ const byOrgModeKey = new Map <
390+ string ,
391+ { mode : FilterMode ; authoredBy ?: string ; orgs : string [ ] }
392+ > ( ) ;
393+ for ( const filter of orgFilters ) {
394+ const org = getOrgFromFilter ( filter ) ;
395+ if ( ! org ) continue ;
396+ const key =
397+ filter . mode === "authored-by"
398+ ? `${ filter . mode } :${ filter . authoredBy || "" } `
399+ : filter . mode ;
400+ const existing = byOrgModeKey . get ( key ) ;
401+ if ( existing ) {
402+ existing . orgs . push ( org ) ;
403+ } else {
404+ byOrgModeKey . set ( key , {
405+ mode : filter . mode ,
406+ authoredBy : filter . authoredBy ,
407+ orgs : [ org ] ,
408+ } ) ;
409+ }
410+ }
411+
412+ for ( const [ , { mode, authoredBy, orgs } ] of byOrgModeKey ) {
413+ if ( mode === "authored-by" && ! authoredBy ) continue ;
414+
415+ const parts = [ "is:pr" , "archived:false" ] ;
416+ if ( stateFilter ) parts . push ( stateFilter ) ;
417+ if ( stalledFilter ) parts . push ( stalledFilter ) ;
418+ parts . push ( ...orgs . map ( ( o ) => `org:${ o } ` ) ) ;
419+ const modeFilter = getModeFilter ( mode , authoredBy ) ;
420+ if ( modeFilter ) parts . push ( modeFilter ) ;
421+ queries . push ( parts . join ( " " ) ) ;
422+
423+ if ( mode === "involves" ) {
424+ const teams = getCachedTeams ( ) ;
425+ if ( teams . length > 0 ) {
426+ const teamParts = [ "is:pr" , "archived:false" ] ;
427+ if ( stateFilter ) teamParts . push ( stateFilter ) ;
428+ if ( stalledFilter ) teamParts . push ( stalledFilter ) ;
429+ teamParts . push ( ...orgs . map ( ( o ) => `org:${ o } ` ) ) ;
430+ for ( const team of teams ) {
431+ teamParts . push ( `team-review-requested:${ team . org } /${ team . slug } ` ) ;
432+ }
433+ queries . push ( teamParts . join ( " " ) ) ;
434+ }
435+ }
436+ }
437+ }
438+
367439 // Group specific repos by mode+authoredBy (for authored-by, different authors need separate queries)
368440 if ( specificRepos . length > 0 ) {
369441 // Use a composite key: mode + authoredBy for authored-by mode
@@ -1123,12 +1195,15 @@ export function Home() {
11231195 ) }
11241196 { config . repos . map ( ( repo ) => {
11251197 const isAllRepos = isAllReposFilter ( repo ) ;
1198+ const orgName = getOrgFromFilter ( repo ) ;
1199+ const isOrg = orgName !== null ;
11261200 const modeOption = MODE_OPTIONS . find (
11271201 ( m ) => m . value === repo . mode
11281202 ) ! ;
11291203 const isOpen = openRepoDropdown === repo . name ;
11301204 const isEnabled = repo . enabled !== false ;
1131- // For "All Repos", exclude the "All PRs" mode since it would be too broad
1205+ // For "All Repos", exclude the "All PRs" mode since it would be too broad.
1206+ // Org filters keep "all" — `org:foo` is already scoped.
11321207 const availableModes = isAllRepos
11331208 ? MODE_OPTIONS . filter ( ( m ) => m . value !== "all" )
11341209 : MODE_OPTIONS ;
@@ -1165,7 +1240,7 @@ export function Home() {
11651240 "inline-flex items-center gap-1.5 pl-2 pr-1.5 py-1 rounded-md text-xs transition-colors border cursor-pointer" ,
11661241 isOpen
11671242 ? "bg-muted border-border"
1168- : isAllRepos
1243+ : isAllRepos || isOrg
11691244 ? isEnabled
11701245 ? "bg-primary/10 border-primary/30 hover:bg-primary/20 hover:border-primary/50"
11711246 : "bg-muted/30 border-border/50 opacity-50"
@@ -1196,7 +1271,7 @@ export function Home() {
11961271 < modeOption . icon
11971272 className = { cn (
11981273 "w-3 h-3" ,
1199- isAllRepos
1274+ isAllRepos || isOrg
12001275 ? isEnabled
12011276 ? "text-primary"
12021277 : "text-muted-foreground/50"
@@ -1209,7 +1284,11 @@ export function Home() {
12091284 ! isEnabled && "line-through"
12101285 ) }
12111286 >
1212- { isAllRepos ? modeOption . label : repo . name }
1287+ { isAllRepos
1288+ ? modeOption . label
1289+ : isOrg
1290+ ? `org:${ orgName } `
1291+ : repo . name }
12131292 </ span >
12141293 { repo . mode === "authored-by" && repo . authoredBy && (
12151294 < span
@@ -1520,6 +1599,37 @@ export function Home() {
15201599 </ div >
15211600 </ button >
15221601 ) }
1602+ { /* "All repos in ORG" suggestion — shown while the query
1603+ looks like an org name (non-empty, no slash).
1604+ Deduped against any org filter already added. */ }
1605+ { ( ( ) => {
1606+ const trimmed = searchQuery . trim ( ) ;
1607+ if ( ! trimmed || trimmed . includes ( "/" ) ) return null ;
1608+ const orgKey = orgFilterKey ( trimmed ) ;
1609+ if ( config . repos . some ( ( r ) => r . name === orgKey ) )
1610+ return null ;
1611+ return (
1612+ < button
1613+ onMouseDown = { ( ) => {
1614+ handleAddRepo ( orgKey ) ;
1615+ setShowAddRepo ( false ) ;
1616+ } }
1617+ className = "w-full flex items-center gap-2 px-3 py-2.5 hover:bg-primary/10 transition-colors text-left border-b border-border bg-primary/5"
1618+ >
1619+ < div className = "w-4 h-4 rounded bg-primary/20 flex items-center justify-center shrink-0" >
1620+ < Users className = "w-3 h-3 text-primary" />
1621+ </ div >
1622+ < div className = "flex-1 min-w-0" >
1623+ < span className = "font-medium text-xs" >
1624+ All repos in "{ trimmed } "
1625+ </ span >
1626+ < span className = "text-[10px] text-muted-foreground ml-1.5" >
1627+ org:{ trimmed }
1628+ </ span >
1629+ </ div >
1630+ </ button >
1631+ ) ;
1632+ } ) ( ) }
15231633 { searchResults . length > 0 ? (
15241634 searchResults . map ( ( repo ) => (
15251635 < button
0 commit comments