@@ -103,6 +103,83 @@ export default defineConfig({
103103 Error ,
104104 ) ;
105105 }
106+ // Step 1b: Copy root-level files VS Code loads directly
107+ // (nls.messages.js, nls.messages.json, bootstrap-*.js, etc.)
108+ // into `Static/Application/`. These sit alongside `vs/` at
109+ // the Output root, and the workbench HTML references them at
110+ // URLs like `/Static/Application/nls.messages.js` — without
111+ // this step the webview 404s to the SPA fallback and the
112+ // browser reports `Unexpected token '<'` when trying to
113+ // parse the HTML as a JS module.
114+ const StaticApplicationDir = join (
115+ TargetDir ,
116+ "Static" ,
117+ "Application" ,
118+ ) ;
119+
120+ const OutputRoot = resolve (
121+ process . cwd ( ) ,
122+ "../Output/Target/Microsoft/VSCode" ,
123+ ) ;
124+
125+ const DependencyOutBuild = resolve (
126+ process . cwd ( ) ,
127+ "../../Dependency/Microsoft/Dependency/Editor/out-build" ,
128+ ) ;
129+
130+ const DependencyOut = resolve (
131+ process . cwd ( ) ,
132+ "../../Dependency/Microsoft/Dependency/Editor/out" ,
133+ ) ;
134+
135+ // Whitelist only the root files the webview actually loads.
136+ // tsconfig/*, *.map, typings/, date/ etc. stay out of the
137+ // shipped bundle even if the upstream ships them.
138+ const RootFilesToCopy = [
139+ "nls.keys.json" ,
140+ "nls.messages.js" ,
141+ "nls.messages.json" ,
142+ "nls.metadata.json" ,
143+ "bootstrap-esm.js" ,
144+ "bootstrap-import.js" ,
145+ "bootstrap-meta.js" ,
146+ ] ;
147+
148+ console . log (
149+ `[CopyVSCode] Step 1b: Copying ${ RootFilesToCopy . length } root-level VSCode files → Static/Application/` ,
150+ ) ;
151+
152+ for ( const File of RootFilesToCopy ) {
153+ // Primary: Output. Fallbacks: Dependency/out-build,
154+ // then Dependency/out. The .env.Land tier set
155+ // controls which trees have been populated at any
156+ // given time; try each until one resolves.
157+ const Candidates = [
158+ join ( OutputRoot , File ) ,
159+ join ( DependencyOutBuild , File ) ,
160+ join ( DependencyOut , File ) ,
161+ ] ;
162+
163+ let Copied = false ;
164+ for ( const Source of Candidates ) {
165+ try {
166+ await copyFile (
167+ Source ,
168+ join ( StaticApplicationDir , File ) ,
169+ ) ;
170+ Copied = true ;
171+ break ;
172+ } catch {
173+ // Try next candidate
174+ }
175+ }
176+
177+ if ( ! Copied ) {
178+ console . warn (
179+ `[CopyVSCode] Step 1b: ${ File } not found in Output or Dependency — skipping` ,
180+ ) ;
181+ }
182+ }
106183 // Step 2: Supplement with tsc-compiled files from Dependency
107184 // (fills gaps like workbench.web.main.js missing from Output)
108185 const DependencySource = resolve (
@@ -325,6 +402,68 @@ export default defineConfig({
325402 console . log (
326403 "[CopyVSCode] Step 7: Replaced ElectronIPCMainProcessService with TauriMainProcessService (ESM wrapper)" ,
327404 ) ;
405+
406+ // Step 7b: Replace sharedProcessService.js too.
407+ // The Extensions sidebar queries
408+ // `sharedProcessService.getChannel('extensions')`
409+ // (see `extensionManagementServerService.ts:35`
410+ // in VS Code's electron-browser source — it's the
411+ // channel ExtensionManagementChannelClient wraps).
412+ // Land has no Electron shared process, so the
413+ // shipped SharedProcessService tries
414+ // `acquirePort(...)` → hangs forever → the
415+ // @builtin sidebar stays empty despite 94
416+ // extensions being scanned. Substituting
417+ // `TauriMainProcessService` here routes every
418+ // `getChannel(name)` through the same
419+ // `ChannelRouteMap` that already backs the main
420+ // process — `extensions` maps to Mountain's
421+ // `extensions:*` handler family (getAll /
422+ // getInstalled / scanSystemExtensions / …).
423+ const SharedProcessDir = join (
424+ Destination ,
425+ "workbench" ,
426+ "services" ,
427+ "sharedProcess" ,
428+ "electron-browser" ,
429+ ) ;
430+ try {
431+ // The original service exports
432+ // `{ SharedProcessService }` with a ctor
433+ // `(windowId, logService)` and a
434+ // `notifyRestored()` method. Our replacement
435+ // extends TauriMainProcessService (which
436+ // provides `getChannel`) and stubs the
437+ // restore-barrier API as a no-op — Land
438+ // doesn't gate channel access on window
439+ // restoration.
440+ await writeFile (
441+ join ( SharedProcessDir , "sharedProcessService.js" ) ,
442+ [
443+ `import { TauriMainProcessService } from '../../../../platform/ipc/electron-browser/TauriMainProcessService.js';` ,
444+ `` ,
445+ `class SharedProcessService extends TauriMainProcessService {` ,
446+ ` constructor(windowId, _logService) { super(windowId); }` ,
447+ ` notifyRestored() { /* Land has no shared process; channels go direct to Mountain */ }` ,
448+ ` async getConnection() { return this; /* self-satisfy the IPC Client shape */ }` ,
449+ `}` ,
450+ `` ,
451+ `export { SharedProcessService };` ,
452+ `export default SharedProcessService;` ,
453+ `` ,
454+ ] . join ( "\n" ) ,
455+ "utf-8" ,
456+ ) ;
457+
458+ console . log (
459+ "[CopyVSCode] Step 7b: Replaced SharedProcessService with TauriMainProcessService-backed shim (unblocks @builtin Extensions sidebar)" ,
460+ ) ;
461+ } catch ( Error ) {
462+ console . warn (
463+ "[CopyVSCode] Step 7b: SharedProcessService replacement failed:" ,
464+ Error ,
465+ ) ;
466+ }
328467 } catch ( Error ) {
329468 console . warn (
330469 "[CopyVSCode] Step 7: TauriMainProcessService replacement failed:" ,
@@ -363,7 +502,27 @@ export default defineConfig({
363502 "const configuration2 = await resolveWindowConfiguration()" ,
364503 `performance.mark("land:wb:resolveConfig");const configuration2 = await resolveWindowConfiguration()` ,
365504 ) ;
366- // 8b-fix: Ensure profile URIs exist for reviveProfile().
505+ // 8b-fix: Ensure profile URIs exist for reviveProfile(),
506+ // and backfill every init-data field VS Code assumes is
507+ // non-null. Each missing field produced a concrete
508+ // PostHog error in the 2026-04-21 report (counts below
509+ // from a single boot session):
510+ //
511+ // colorScheme → nativeHostColorSchemeService
512+ // destructures {highContrast,dark}
513+ // from the scheme literal (14×)
514+ // detectedProfiles → terminalPlatformConfiguration
515+ // .map over the profile list (25×)
516+ // externalTerminal → .windows / .osx / .linux pane (5×)
517+ // backupPath → base/common/path rejects undefined (33×)
518+ // perfMarks → timerService .marks.filter (14×)
519+ // watcher / utilityProcess → destructure .reason (14×)
520+ // colorScheme initial → same service, initial read (14×)
521+ //
522+ // Providing neutral defaults here means the workbench
523+ // boots cleanly even when Mountain hasn't yet populated
524+ // a field; later, when Mountain DOES send a real value
525+ // via Tauri IPC, the assignment overwrites our default.
367526 Content = Content . replace (
368527 "setupNLS(configuration2)" ,
369528 [
@@ -378,6 +537,26 @@ export default defineConfig({
378537 `_fix(configuration2.profiles.profile);` ,
379538 `if(Array.isArray(configuration2.profiles.all))configuration2.profiles.all.forEach(_fix);` ,
380539 `}` ,
540+ // Backfill colorScheme so nativeHostColorSchemeService's
541+ // `initial.highContrast` read (nativeHostColorSchemeService.ts:46)
542+ // and the subsequent promise-resolved `update({highContrast,dark})`
543+ // never destructure from undefined.
544+ `if(!configuration2.colorScheme)configuration2.colorScheme={dark:false,highContrast:false};` ,
545+ // Backfill detectedProfiles so terminalPlatformConfiguration's
546+ // .map call doesn't explode on cold boots where the terminal
547+ // profile detection hasn't completed yet.
548+ `if(!Array.isArray(configuration2.detectedProfiles))configuration2.detectedProfiles=[];` ,
549+ // Backfill external-terminal per-OS config so
550+ // externalTerminal.electron-browser doesn't destructure
551+ // .windows / .osx / .linux from undefined.
552+ `if(!configuration2.externalTerminal)configuration2.externalTerminal={windows:{},osx:{},linux:{}};` ,
553+ // Backfill perfMarks so timerService's marks.filter()
554+ // doesn't fire on a missing array.
555+ `if(!Array.isArray(configuration2.perfMarks))configuration2.perfMarks=[];` ,
556+ // Backfill an empty backupPath so base/common/path
557+ // validators that assume a string get one. Empty
558+ // string is also what the browser workbench carries.
559+ `if(typeof configuration2.backupPath!=="string")configuration2.backupPath="";` ,
381560 `performance.mark("land:wb:setupNLS");setupNLS(configuration2)` ,
382561 ] . join ( "" ) ,
383562 ) ;
@@ -389,10 +568,30 @@ export default defineConfig({
389568 "return { result: result2, configuration: configuration2 }" ,
390569 `performance.mark("land:wb:importDone");return { result: result2, configuration: configuration2 }` ,
391570 ) ;
392- // 8c: Wrap result.main(configuration) with try/catch + await
571+ // 8c: Wrap result.main(configuration) with try/catch + await,
572+ // bookended by Atom H1c diagnostic:log invokes that route to
573+ // Mountain.dev.log. Confirms whether each post-nav page reload
574+ // enters result.main, what workspace the new page reads, and
575+ // how many extensions survive to the workbench registry after
576+ // boot completes.
393577 Content = Content . replace (
394578 "result.main(configuration)" ,
395- `performance.mark("land:wb:main");try{await result.main(configuration);performance.mark("land:wb:mainDone")}catch(_e){performance.mark("land:wb:mainError")}` ,
579+ [
580+ `performance.mark("land:wb:main");` ,
581+ `try{` ,
582+ `const _L=(tag,msg,...extras)=>{try{const I=globalThis.__TAURI__?.core?.invoke??globalThis.__TAURI__?.invoke;if(typeof I==="function")I("MountainIPCInvoke",{method:"diagnostic:log",params:[tag,msg,...extras]});}catch{}};` ,
583+ `_L("wb:boot","pre-main href="+location.href+" search="+location.search,{folderUri:configuration?.folderUri??null,workspace:configuration?.workspace??null,windowId:configuration?.windowId??null});` ,
584+ `await result.main(configuration);` ,
585+ `performance.mark("land:wb:mainDone");` ,
586+ `_L("wb:boot","post-main completed","reloadCount="+(performance.getEntriesByType?performance.getEntriesByType("navigation")?.length??-1:-1));` ,
587+ // Probe the workbench's DI container for the ExtensionService
588+ // one event loop later (give microtasks time to settle) and
589+ // report how many extensions registered + active.
590+ `setTimeout(()=>{try{const W=globalThis;const MP=W.__monacoPostBoot??null;const reg=(W.monaco&&W.monaco.extensions)||null;_L("wb:boot","ext-registry-probe",{monacoExtensionsApi:!!reg,postBootHook:!!MP,pluginApiKeys:reg?Object.keys(reg):null});}catch(e){_L("wb:boot","ext-registry-probe-error",String(e));}},1500);` ,
591+ `}catch(_e){performance.mark("land:wb:mainError");` ,
592+ `try{const I=globalThis.__TAURI__?.core?.invoke??globalThis.__TAURI__?.invoke;if(typeof I==="function")I("MountainIPCInvoke",{method:"diagnostic:log",params:["wb:boot","main-threw",String(_e?.message||_e),String(_e?.stack||"").slice(0,400)]});}catch{}` ,
593+ `}` ,
594+ ] . join ( "" ) ,
396595 ) ;
397596
398597 await writeFile ( WorkbenchJS , Content , "utf-8" ) ;
@@ -556,10 +755,7 @@ export default defineConfig({
556755 "node_modules" ,
557756 Pkg ,
558757 ) ;
559- const FallbackSource = resolve (
560- VSCodeNodeModules ,
561- Pkg ,
562- ) ;
758+ const FallbackSource = resolve ( VSCodeNodeModules , Pkg ) ;
563759 const Destination = join (
564760 TargetDir ,
565761 "Static/Application/node_modules" ,
0 commit comments