All notable changes to Land (Monorepo) are documented here in our voice - what we built, why we built it, what shipped. Format adapted from Keep a Changelog.
Land is our top-level monorepo. Under Element/ we keep every component
we ship (Mountain, Cocoon, Wind, Sky, Air, Echo, Rest, Output, Worker,
Grove, Common, Vine, Mist, Maintain, SideCar) as a git submodule.
Dependency/ holds our vendored Tauri ecosystem fork plus the VS Code
source we lift from. Each Element keeps its own CHANGELOG; this file
weaves them together.
We brought the bundled-electron profile to correctness parity with the unbundled path and started landing the architectural patches that will take us past it. The headline number: we drove the Cocoon→Mountain circuit-breaker cascade from 17,134 rejections per session to zero in the same workspace.
Switching to a Vite-bundled VS Code workbench exposed three classes of bug we hadn't seen on the unbundled path: marker-string drift after esbuild rewrote single quotes to double quotes, Rollup tree-shaking dropped side-effect-only contribution imports, and the Mountain → Sky event race amplified because the 25 MB bundle takes ≈1500 ms to boot while Cocoon starts activating extensions inside 600 ms. We worked through them all.
- We widened the benign-404 classifier on both sides of the gRPC
bridge. Mountain's
LooksLike404predicate now matchesno such file or directory,entity not found,os error 2,path is outside of the registered workspace,permission denied for operation, andworkspace is not trusted, so Rust'sstd::io::Errorformatting stops tripping the breaker on extension probes. Mountain stamps JSON-RPC code-32004for benign 404s; Cocoon classifies on code first and falls back to the regex. - We made
GetURLFromURIComponentsDTOaccept three URI shapes - plain string,{external}object, and{scheme, path}components - so diagnostic-publish storms from rust-analyzer stop dropping on the floor. - We canonicalized both sides of
IsPathAllowedForAccess'sstarts_withcomparison so workspace folders mounted under/Volumes/...on macOS stop rejecting their own descendants, and surfaced a reject-detail log line under thevfstag for future diagnostics. - We short-circuited the Promise-protocol probes (
then,catch,finally,constructor,valueOf,toString,toJSON,@@iterator,@@asyncIterator) in our channel proxy so each workbench wake stops invoking<channel>:thenand loggingUnknown IPC command.
- We taught
FileWatcherProviderto treat watch-on-absent-path as a deferred no-op instead of an error, so the optional config dirs every workspace probes (~/.roo/skills-*,.vscode/settings.jsonin fresh workspaces) stop counting against the breaker. Same provider now acceptsValue::Number(N)for the handle so Cocoon's numericNextProviderHandle()doesn't collapse 136 watcher handles into a single empty-string entry. - We replaced our
BuildOpenTextDocumentshim with one that decodes Mountain'sVec<u8>JSON-array body to a UTF-8 string and exposes the full TextDocument API (positionAt,offsetAt,lineAt,getWordRangeAtPosition,validateRange,validatePosition). The npm extension'sreadScriptscallspositionAtonce per script entry; it was throwingis not a function, whichgetScriptswrapped as a user-visible "failed to parse the file" error. - We fixed the search panel showing zero matches despite the Rust
searcher returning 466 files / 2560 line-matches. The workbench's
ITextSearchMatchinterface evolved to{previewText, rangeLocations}and our mapper still emitted the older{preview: {text, matches}, ranges}shape, so the result accumulator dropped every match.
- We fixed our
ExposeWorkbenchAccessorOutput transform on two axes. Its import markers used single quotes ('../../nls.js') but esbuild emits double quotes post-bundle, soSource.includes(...)always returned false and the entire__CEL_SERVICES__patch was silently skipped - that's why search retries exhausted, the SCM bridge was null, the sidebar came up empty. We also wrapped eachinstantiationService.invokeFunction(...)in a per-keytry-IIFEso a single failed lookup degrades that key tonullinstead of taking down the whole__CEL_SERVICES__literal. - We extended our extension-scanner shim so the user-extensions path
retries on empty result with the same exponential backoff
(
100/200/400/800/1500 ms) the system path already had. The attach-pending tree views from gitlens, clangd, and the dependency inspector were stuck because the workbench's view registry never saw user extensions'contributes.views: the scanner returned 0 user extensions on the first call (Mountain's scan hadn't finished yet) and the workbench cached that.
- We added a replay handler that drains tree-view registrations, SCM
providers, extension-registered commands, and per-terminal output
buffers when the renderer bridge installs. The renderer invokes it
at the end of bridge install, so events Mountain emitted during
Cocoon's ~600 ms activation cascade still reach listeners that
install ~1500 ms later. We also added an
Identifierfield toSourceControlManagementProviderDTOso SCM replay re-emits with the original provider id (git,github,hg, …) instead of a hardcoded fallback.
- We landed our first channel-event bridge in the workbench's IPC
proxy.
localPty.onProcessData,onProcessReady, andonProcessExitnow route tosky://terminal/{data,create,exit}Tauri events with payload-shape remappers. Without this, the workbench's terminal subscribed to channel events that our proxy was returning no-op disposables for, so xterm never received PTY output even though the reader task was emitting hundreds of times per session. We also wrote a per-terminal 64 KB ring buffer in the terminal provider so the shell's first prompt and zsh MOTD survive the boot race and replay into late listeners. - We dropped the
tree:getChildrentimeout from 5000 ms to 1500 ms so a slow extension's hung tree-data-provider stops eating five seconds of perceived UI responsiveness, and added a parent-PID watchdog in Cocoon: it pollsprocess.kill(ppid, 0)every 2 s and self-exits with code 130 onESRCH, so a force-quit on Mountain doesn't orphan Cocoon and require the next boot's port sweep.
We brought the bundled VS Code workbench up in our Tauri webview, end-to-end. Mountain compiled clean, the Cocoon extension host attached over gRPC, all 113 of our test extensions scanned, the workbench rendered, and we kept chasing the gaps until search, SCM, debug, and custom editors all worked.
- Mountain. We split
WindServiceHandlersinto 24 domain modules andCocoonServiceinto 15 submodules, landed 18 CocoonService handlers and 14 language-feature RPC methods, and rolled thedev_log!macro across 170 files so every IPC arm tags into a tag-filterable stream. - Cocoon. We split
GRPCServerServiceinto 7 dedicated handlers and thevscodeAPI shim into 10 namespaces (Window,Workspace,Languages,Commands,Env,Scm,Debug,Tasks,Authentication,Extensions), wired workspace events, and added document-content mirroring so extensions readingvscode.workspace.openTextDocumentsee the same bytes the editor does. - Wind. We finalised 24 IPC channel routes and 5 streaming
listen()subscriptions, all verified end-to-end against Mountain. - Sky. We disabled workspace-trust prompts across our five build profiles (the prompt flow assumes Electron's window-managed dialogs) and wired the telemetry bridges to OpenTelemetry locally and PostHog for opt-in product analytics.
- Common. We grew the
ProviderTypeenum to 33 variants covering every contribution point we forward, and finished the PascalCase rename so every Rust identifier in the workspace matches our project convention.
We took these from "renders empty" to "fully working" inside this sprint window:
- Search panel populates from a real walk: 8775 files / 139895
line-matches in 3.2 s on the Land workspace. We exposed the bundled
URIconstructor on the workbench accessor so result rows carry real URI instances (the panel's dedup map callsuri.with(...), which a POJO can't satisfy), enabledline_number(true)on the ripgrep-flavour searcher so every match carries a line number, and reshaped the find-files cache into an LRU keyed on(folders, include, exclude, cap, flags). - SCM ships a single stable provider handle per repo. We aligned
Cocoon's sequential handle allocator with Mountain's marker maps so
register_scm_resource_groupandupdate_scm_groupnotifications resolve into the same provider entry instead of warning "unknown provider handle: N" for every group update. The git extension reads configuration withoutundefined, status, refs and diffs all arrive, and 36 SCM providers register cleanly across the monorepo's submodules. - Debug + custom editors. We finished the wire shapes for
$resolveCustomEditor, thesky://command/*family, and the debug configuration provider, then audited every Cocoon↔Mountain DTO boundary forcamelCaseconsistency end-to-end. - DualTrack. We added the
LAND_DEFER_RUST*environment knobs so any method, any domain, or all traffic can be A/B-routed between Mountain (Rust) and Cocoon (Node) at runtime - useful for isolating whether a regression sits in our Rust path or upstream's Node one. - Effect-TS Proxy fallback. We wrapped all 10
vscode.*namespaces in a heuristic Proxy so any future API the workbench reaches for that we haven't shimmed yet surfaces as an audit log line under[VSCODE-API-GAP]instead of aTypeErrorthat crashes the host. The classifier infers the right shape from the property name:requestResourceTrustreturnstrue,onDid…returns a noop disposable,register…returns a disposable,is…/has…/should…returnsfalse,create…/get…/make…returnsundefined.
- Extension type filtering and post-install activation. Three
bugs converged on the Extensions sidebar's "install succeeded but
nothing happened" symptom. Our
getInstalledhandler was dropping theExtensionTypefilter (0=System, 1=User), so every call returned the full list markedtype: 0, isBuiltin: trueand VSIX-installed extensions never appeared under Installed with an Uninstall action. The post-install path wasn't firingonStartupFinishedafter$deltaExtensions, so extensions with that activation event registered but stayed dormant. And the IPC switch wasn't routingextensions:scanSystemExtensions/extensions:scanUserExtensionstogetInstalledwith the right type filter. We fixed all three together: VSIX extensions show up under Installed, activate immediately, and the sidebar refresh works on first install. - Manifest fields always emitted, VSIX preview without extraction.
The workbench's trusted-publishers migration calls
manifest.publisher.toLowerCase()unconditionally at boot. We were suppressing empty fields viaskip_serializing_if, so a publisher-less manifest crashed the renderer withTypeError: undefined is not an object. We dropped the guard onName,Version, andPublisher, then injected explicit"unknown"fallbacks at the IPC envelope so the renderer always sees a string. We also added anextensions:getManifesthandler that readsextension/package.jsonstraight from a.vsixarchive, enabling the Install-from-VSIX preview dialog without disk extraction. - Eager log initialization. We moved log-file creation to binary
startup so a session log exists before any panic, preserving
post-mortem evidence. We also fixed the binary-signature splitter
so PascalCase segments like
ElectronProfileproduceelectron.profileand logs land in the right app-data directory. - Atomic shutdown guard. Mountain's graceful shutdown was running
twice:
app_handle.exit(0)at the end of the first pass made Tauri re-deliverExitRequested { code: Some(0) }, the shutdown task re-entered, tried to SIGKILL an already-dead Cocoon, and logged spurious tcp-connect errors. An atomic guard in the Tauri entry blocks the second pass. - Static asset path resolution. We extended path resolution to
accept both
/Static/Application/(absolute) andStatic/Application/(relative) forms. The WKWebView WASM loader (used byvscode-oniguruma→onig.wasm) strips the leading slash before reaching ourfile:readhandler, which used to fail withENOENTand broke TextMate syntax highlighting for every grammar. - Build identifiers. We landed a two-step rename - first to a
verbose profile-tagged binary name, then back to the clean
Mountainidentifier - to align our Cargo and Tauri config with the new build pipeline. Same window: we updated theposthog-rs0.5 API call (api_endpoint()→host()) and migratedsha20.11's removedLowerHeximpl tohex::encode()in our session-id hash, and addedvscode-file://vscode-appto the CSPimg-srclist so VS Code's internal file protocol resolves images.
We took the editor from "compiles" to "boots and renders," landing the first end-to-end run of the workbench in our Tauri webview against a live Cocoon extension host.
- Mountain. We grew to 351 Rust files, exposed 70+ gRPC RPCs, and organised the IPC surface into 24 domain modules. We landed the terminal PTY substrate, secret storage with AES-256-GCM, TLS, and the first OpenTelemetry + PostHog telemetry plumbing.
- Cocoon. We composed 7 Effect-TS layers (~2,325 lines) - Tracer,
Telemetry, Mountain client, vscode-API factory, extension-host,
bootstrap, transport - and wrote the gRPC client (~1,206 lines)
fronting all 70+ RPCs the Rust side serves. The
ModuleInterceptor(~493 lines) became ourrequire('vscode')entry point so every extension module loads through one shim, and we settled on 13 service interfaces as the stable contract. - Wind. We delivered our first
TauriMainProcessService(theIMainProcessServicereplacement that swaps Electron's IPC for our Tauri-backed channel proxy), the renderer-sidePreload.ts, and aBootstrap/Types/directory mirroring 30+ VS Code interfaces we rely on at the type boundary. AddedDevLog.tsso the renderer emits the same tag-filtered stream as the Rust side. - Sky. We migrated to Astro 6, TypeScript 6, and Vite 8.
- Common. We renamed the crate to
CommonLibraryand shipped the Transport Registry abstraction that lets us swap between gRPC, WebSocket, and in-process transports without touching call sites. - Output. We compiled the VS Code source tree to 4,287 JS files (~169 MB), wrote 6 polyfill modules (~5,200 lines) for browser APIs the workbench expects but WKWebView omits, and adopted an ESBuild dual-compiler pattern so the bundled and unbundled paths share the same transform pipeline.
- Rest. We migrated from SWC to OXC across 7 transform modules and grew the regression suite to ~3,800 lines so each rewrite the bundler does is shape-checked before it lands.
- Air. We got 73 Rust modules into shape, including the DNS resolver and 35 closed TODOs from the prior cycle.
- Maintain. We split
Build.rsacross 5,008 lines / 55 files and replaced the inline build-script glue with Rhai-driven configuration so build profiles can be tuned without rebuilding the build tool itself.
We held the platform steady while we planned the next push. We rolled Effect-TS to 3.19.x, Astro to 5.15-5.16, and TypeScript to 5.9.x; kept the Cloudflare Workers types and Wrangler 4.x in lock-step; and otherwise stayed in stabilisation mode across every component.
We shipped the heaviest single quarter of the project so far. Mountain gained 556 commits (464 in a single month) and put every core module in place - gRPC handlers, WebSocket, terminal, storage, diagnostics, configuration. Cocoon landed 647 commits and roughly 229K lines on top of Effect-TS 3.17.x. Wind landed 537 commits and ~226K lines wiring its Effect-TS services to Mountain. Common added 7,418 lines of Rust DTOs and trait surfaces. Echo's work-stealing scheduler reached the API refinement we'd been targeting, with priority lanes still on the roadmap for a later cycle.
We brought the extension-host into existence. Cocoon was created from scratch (291 commits, ~32,982 lines) as the Effect-TS-driven sidecar that replaces Electron's utility-process model. Wind pivoted to Effect-TS at the end of May; we ended the quarter with 370 TypeScript files. Sky stood up the Tauri-hosted workbench bootstrap and shipped keyboard layouts for 37 locales. The Worker element gained service- worker caching plus on-the-fly CSS transpilation. Grove and Mist moved from concept into architecture planning, ready to be implemented the moment the rest of the stack stabilised. We also publicly announced our funding from the NLnet NGI0 Commons Fund this quarter.
We migrated Astro 4 → 5, TypeScript 5.7 → 5.8, and pulled Vite to 6.x
across every package. Mountain's ApplicationState reorganisation
landed (renaming app_state → ApplicationState, normalising 12
DTOs, and exporting a Knowledge.dot dependency graph we still
generate from CI). The Turborepo + pnpm workspace stabilised - clean
incremental builds, predictable cache keys, no more "rebuilds the
world" surprises.
We brought Output into the project: ~32K+ JS files lifted from the
VS Code source tree, ready for the transform pipeline we'd build out
in the next cycle. Mountain gained the Android Gradle scaffolding
and an 11,162-line mobile schema, plus the Cargo.toml feature-flag
family (AirIntegration, ExtensionHostCocoon, …) that controls
what we link in per profile. We finished importing the VS Code
module tree as proper git submodules under Dependency/.
We laid down the bones. Mountain: 168 commits, ~28K lines of Rust,
Tauri 2.x scaffold, the first gRPC scaffolding. Sky: 600 commits
covering Astro + React + Firebase. Wind: 166 commits, Monaco editor
mounted, SCSS-based styling. Echo: 334 commits delivering a
sequence-based task scheduler on top of crossbeam-deque. Rest was
created on September 14 (33 files, the first SWC-driven compiler
shape). We also did our biggest schema reduction in Mountain in the
same window: 8,467 → ~1,000 lines (a 78 % cut).
We set up the monorepo. Element/ and Dependency/ as git
submodules with .gitmodules: ignore = all so a stray git add .
doesn't convert submodule gitlinks to trees. The Cargo.toml
workspace covered Common, Echo, Maintain, Mountain, Air, Grove,
Mist, Rest, SideCar, plus tauri-plugin-localhost. The
pnpm-workspace.yaml covered Dependency/{Biome,Microsoft,OXC, Rolldown,SWC,Tauri,Vercel}/NPM/**, Element/**, and !**/Target/**.
We adopted the turbo.json task definitions with global env passthrough
(ANDROID_HOME, JAEGER_VERSION, our shared API keys, Apple signing
identifiers, …), wired GitHub Actions and Dependabot, and adopted
PascalCase as the project-wide naming convention. We chose Biome as
our TypeScript formatter and rustfmt-nightly as our Rust formatter.
Mountain's Tauri scaffold landed in June, Sky's Astro 4 baseline in
April, and Common's first Workers library shape went up in April too.