@@ -1283,24 +1283,34 @@ export async function InstallSkyBridge(): Promise<void> {
12831283 return [ ] ;
12841284 }
12851285 } ;
1286- const AttachDataProvider = ( ViewId : string , Retries : number ) : void => {
1286+ // Pending attaches: views whose extension contributes the
1287+ // `viewsRegistry` registration AFTER our `cel:tree-view:create`
1288+ // event fires (gitlens, clangd, dependencies all hit this -
1289+ // their views activate ~3-5 s into boot, well after the original
1290+ // 750 ms retry window expired). Each attempt-attach call adds to
1291+ // this set on miss and removes on success; whenever ANY view
1292+ // successfully attaches, we replay the pending set (the
1293+ // workbench tree-views service is now wired - other pending
1294+ // views likely just have to be looked up).
1295+ const PendingAttaches = new Set < string > ( ) ;
1296+
1297+ const RetryPendingAttaches = ( ) : void => {
1298+ if ( PendingAttaches . size === 0 ) return ;
12871299 const Services = GetServices ( ) ;
1288- const GetTreeView = Services ?. TreeViewByViewId ;
1289- const TreeView =
1290- typeof GetTreeView === "function" ? GetTreeView ( ViewId ) : null ;
1291- if ( ! TreeView ) {
1292- if ( Retries <= 0 ) {
1293- invoke ( "RenderDevLog" , {
1294- Tag : "tree-view" ,
1295- Message : `[TreeView] attach-give-up view=${ ViewId } (no workbench tree descriptor)` ,
1296- tag : "tree-view" ,
1297- message : `[TreeView] attach-give-up view=${ ViewId } (no workbench tree descriptor)` ,
1298- } ) . catch ( ( ) => { } ) ;
1299- return ;
1300+ if ( ! Services ?. TreeViewByViewId ) return ;
1301+ for ( const ViewId of [ ...PendingAttaches ] ) {
1302+ const TreeView = Services . TreeViewByViewId ( ViewId ) ;
1303+ if ( TreeView ) {
1304+ PendingAttaches . delete ( ViewId ) ;
1305+ AttachToDescriptor ( ViewId , TreeView ) ;
13001306 }
1301- setTimeout ( ( ) => AttachDataProvider ( ViewId , Retries - 1 ) , 150 ) ;
1302- return ;
13031307 }
1308+ } ;
1309+
1310+ const AttachToDescriptor = (
1311+ ViewId : string ,
1312+ TreeView : NonNullable < ReturnType < NonNullable < CelServices [ "TreeViewByViewId" ] > > > ,
1313+ ) : void => {
13041314 if ( TreeView . dataProvider ) {
13051315 // Already wired (e.g. by a prior register for the same id
13061316 // during a reload). Keep the existing provider to respect
@@ -1319,14 +1329,56 @@ export async function InstallSkyBridge(): Promise<void> {
13191329 tag : "tree-view" ,
13201330 message : `[TreeView] attach-ok view=${ ViewId } ` ,
13211331 } ) . catch ( ( ) => { } ) ;
1332+ // First successful attach can mean the workbench bridge has
1333+ // finally wired up its `TreeViewByViewId` map. Sweep any
1334+ // pending attachers - cheap (~one HashMap lookup each) and
1335+ // rescues the views whose retry budget hadn't quite expired.
1336+ RetryPendingAttaches ( ) ;
1337+ } ;
1338+
1339+ // Exponential-ish backoff with a generous total budget. Stock
1340+ // VS Code's view contributions register within ~3 s of extension
1341+ // activation; gitlens / clangd / heavy extensions stretch that
1342+ // to ~5 s. Total budget here is ~10 s across 12 retries; the
1343+ // last ~half are 1 s apart so we don't keep firing setTimeouts
1344+ // indefinitely. After budget exhaustion we register in
1345+ // `PendingAttaches` so any later successful attach can sweep
1346+ // the still-missing entries.
1347+ const AttachBackoffSchedule :number [ ] = [
1348+ 100 , 200 , 400 , 600 , 800 , 1000 , 1000 , 1000 , 1500 , 1500 , 1500 , 1500 ,
1349+ ] ;
1350+
1351+ const AttachDataProvider = ( ViewId : string , Step : number ) : void => {
1352+ const Services = GetServices ( ) ;
1353+ const GetTreeView = Services ?. TreeViewByViewId ;
1354+ const TreeView =
1355+ typeof GetTreeView === "function" ? GetTreeView ( ViewId ) : null ;
1356+ if ( ! TreeView ) {
1357+ if ( Step >= AttachBackoffSchedule . length ) {
1358+ PendingAttaches . add ( ViewId ) ;
1359+ invoke ( "RenderDevLog" , {
1360+ Tag : "tree-view" ,
1361+ Message : `[TreeView] attach-pending view=${ ViewId } (queued for late workbench wiring)` ,
1362+ tag : "tree-view" ,
1363+ message : `[TreeView] attach-pending view=${ ViewId } (queued for late workbench wiring)` ,
1364+ } ) . catch ( ( ) => { } ) ;
1365+ return ;
1366+ }
1367+ setTimeout (
1368+ ( ) => AttachDataProvider ( ViewId , Step + 1 ) ,
1369+ AttachBackoffSchedule [ Step ] ?? 1500 ,
1370+ ) ;
1371+ return ;
1372+ }
1373+ AttachToDescriptor ( ViewId , TreeView ) ;
13221374 } ;
13231375 document . addEventListener ( "cel:tree-view:create" , ( Event : Event ) => {
13241376 const Detail = ( Event as CustomEvent ) . detail as
13251377 | { viewId ?: string ; extensionId ?: string }
13261378 | undefined ;
13271379 const ViewId = Detail ?. viewId ?? "" ;
13281380 if ( ! ViewId ) return ;
1329- AttachDataProvider ( ViewId , 5 ) ;
1381+ AttachDataProvider ( ViewId , 0 ) ;
13301382 // Prime the DOM fan-out with the initial children too so
13311383 // side-panel shims that mirror tree state don't need to wait
13321384 // for a user-triggered expand.
0 commit comments