perf(build): stop unpacking node_modules wholesale from the Windows asar - #5877
Conversation
A Windows installer built from main writes 14,687 files, of which 13,875 are loose node_modules files under app.asar.unpacked. Only 20 of them are native .node binaries. For contrast, the entire Electron runtime -- several hundred MB -- is 22 files, because it stays inside the archive. That file count costs twice. NSIS install time tracks file count, not bytes. And every one of those files is a separate open/stat/scan the first time the server starts after an install, which is exactly when the OS file cache is cold and the on-access virus scanner is not. The blanket `**/node_modules/**` unpack exists because the CLI bundle externalizes its runtime dependencies, and the WSL backend launches plain `wsl.exe -- node`, which cannot read inside an asar. So every external dep has to be a real file on disk. Invert the bundler's rule: bundle everything except the packages that genuinely cannot be inlined -- native addons, the JS wrappers that dlopen them, and the Bun-only entry points that resolve `bun:*` specifiers -- then narrow asarUnpack to exactly that set. Measured on this tree, win/nsis x64: files written at install 14,687 -> 1,192 (-92%) loose node_modules files 13,875 -> 370 native .node binaries 20 -> 20 installer size 145.0 MiB -> 138.9 MiB Cold start improves by the same mechanism. Extracting each build's payload to a fresh directory (so the files have never been read) and booting the server: server boot to "Listening on" 9044ms / 10160ms -> 3667ms / 3779ms module load only (--version) 6521 / 6238 / 6208ms -> 761 / 659 / 654ms Run order was alternated between builds to keep cache and scanner state from favouring either one. The desktop main window is not created until the backend answers HTTP, so that ~6s comes straight off a cold launch. Both consumers now derive from one list in scripts/lib/cli-external-packages.ts. They cannot drift, and the drift is worth guarding: a package that is external but not unpacked still resolves on the Windows primary, which runs under ELECTRON_RUN_AS_NODE and reads app.asar transparently. It fails only under WSL. `node-gyp-build-optional-packages` hit exactly this while writing the patch -- matched as external by the `node-gyp-build` prefix, missed by a glob without a trailing wildcard, and invisible on the platform being tested on. Verified the way this can actually fail: extracted app.asar.unpacked into a directory with no node_modules ancestor -- what plain node sees under WSL -- and booted the server there. Migrations ran, it listened on 127.0.0.1, and no module failed to resolve. node-pty, ffi-rs, msgpackr-extract and @ff-labs/fff-node all load from that isolated tree.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR substantially changes the Windows packaging strategy from unpacking all node_modules to selectively unpacking only native packages. The complexity of the new bundling logic, self-containment verification, and potential runtime impact on WSL if external packages are miscategorized warrants human review. You can customize Macroscope's approvability policy. Learn more. |
Real WSL testing on this branch found a case the hand-maintained list could not
catch by inspection.
node-gyp-build-optional-packages is external, so it is loaded from the real
filesystem, so its own `require` resolves from the real filesystem too. It
requires detect-libc, which was not on the list and therefore got bundled into
the CLI bundle -- present only inside app.asar. The Windows primary reads that
transparently under ELECTRON_RUN_AS_NODE and resolves it; plain node under WSL
cannot. msgpackr-extract failed through the same chain.
Measured under Ubuntu 24.04 with Linux node v24.18.0 against the packaged tree:
before: MISSING (cjs) msgpackr-extract [MODULE_NOT_FOUND] detect-libc
MISSING (cjs) node-gyp-build-optional-packages [MODULE_NOT_FOUND]
after : no resolution failures
The general rule is that an external package's entire runtime dependency
closure must be external. That is not something to maintain by staring at a
list, so it is now a test: it walks each runtime-external package's declared
dependencies transitively and fails if any would be bundled away.
Writing that test surfaced a distinction the single list had flattened. The
Bun-only entries are external for a build-time reason -- they resolve `bun:*`
specifiers that do not exist when bundling for Node -- and Node never loads
them, so their closure genuinely does not need to be external. The native
packages are external for a runtime reason and theirs does. The list is split
along that line, and the closure test applies only to the runtime set.
Adds 6 files to the installer (1,192 -> 1,198). Native binaries and installer
size are unchanged.
|
Fair verdict, and the concern was the right one — so I went and tested it under real WSL. It found a bug. Pushed a fix in 0aacacd. What broke. Measured on Ubuntu 24.04 with Linux node v24.18.0, against the packaged tree copied out of the NSIS payload:
Why the list alone was never going to be enough. The real invariant is that an external package's entire runtime dependency closure must be external. Writing that test surfaced a distinction the single list had flattened, which I've now made explicit:
On Cost of the fix: 6 files (1,192 → 1,198). Native binaries and installer size unchanged. Happy to squash the two commits if you'd prefer a single one. |
The guard added in the previous commit could pass without checking anything.
It resolved manifests with `require("<name>/package.json")` from scripts/lib,
and swallowed resolution failures as "not installed on this platform".
Under pnpm isolation that catch swallowed nearly everything. Probed from
scripts/lib, every seed failed with MODULE_NOT_FOUND -- node-pty,
msgpackr-extract, ffi-rs, node-gyp-build, detect-libc, node-addon-api. Probed
from apps/server, only its direct dependencies resolved; the transitive
packages that actually caused the WSL breakage still did not. `exports` maps
are a second hole: @ff-labs/fff-node refuses the /package.json subpath with
ERR_PACKAGE_PATH_NOT_EXPORTED, which the same catch treated as absent.
Seeding the queue from the prefix strings was wrong for a second reason: the
filter dropped every prefix ending in "/", so "@yuuang/", "@ff-labs/" and
"@msgpackr-extract/" were never visited even where resolution worked.
Read the manifests off disk from the pnpm store instead. That is the same tree
asarUnpack globs target, it reaches transitive packages, and it is not subject
to resolution or exports semantics. Seeds now come from what is installed and
matches a prefix, so scoped prefixes are covered.
Added a guard test that fails unless node-pty, node-gyp-build-optional-packages
and detect-libc are actually found, because a closure check that reads nothing
is worse than no check -- it reports success.
Verified by mutation: removing detect-libc from the list fails with
"node-gyp-build-optional-packages -> detect-libc", the real bug. The previous
version of this test passed with detect-libc removed.
|
Confirmed, both points. Good catch — the guard was worse than useless, because it reported success. Fixed in 45b4517. Point 1 is worse than "can pass". I probed it rather than reasoning about it. From From The tell I missed at the time: when the guard first ran it reported violations only from the Point 2 confirmed. Fix. Manifests are now read off disk from the pnpm store, which is the same tree Plus a guard test that fails unless Verified by mutation, not assertion. Removing The previous version of the test passed with 39 tests pass across this file and |
The closure guard walked node_modules/.pnpm and built a node_modules path under each entry. The store also contains a regular file, lock.yaml, so that path is rooted in a file rather than a directory. Linux raises ENOTDIR from the access call; Windows quietly reports false. The test therefore passed locally and failed on CI -- itself an instance of the platform asymmetry this file exists to catch. Existence checks now treat any failure as absence.
|
Hey @tsouth89, Was having trouble launching this in WSL only mode ran it though an agent and found this line 243 in needs to be updated from: to this Seems like the rest of the file is fine and after modification of that one line everything seems to be working as expected |
The WSL health probe resolved "effect" to confirm the server's dependencies
were unpacked on the real filesystem. That premise held while the bundle
externalized its runtime deps and the whole node_modules tree was unpacked.
This branch inlines those dependencies, so "effect" no longer exists on disk.
The probe therefore exits 3 and wsl-only mode refuses to launch, reporting a
packaging regression that isn't one.
Resolve node-pty instead: it is external precisely because it cannot be
inlined, so it is a valid sentinel for the unpacked tree both before and after
this change. Verified against the packaged tree, where require.resolve("effect")
fails with MODULE_NOT_FOUND and require.resolve("node-pty/package.json")
succeeds.
Reported by @ikifar2012, who hit it running wsl-only mode from this branch.
|
Nice find, thanks. You're right about the cause: this branch inlines the server's JS deps into the bundle, so Pushed your fix in 2134d96. Used node-pty since it's external precisely because it can't be inlined, so it stays a valid sentinel either way. Also updated the two comments that still described the old behaviour, and the exit-3 message downstream. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2134d96. Configure here.
The probe now resolves node-pty rather than "effect", but the user-facing reason still named "effect" and described an unreadable bundled node_modules. That points anyone hitting a packaging failure at a package this branch deliberately inlines. Reworded to name the native packages that actually have to be unpacked.
|
Good catch, fixed in the latest push. Reworded it to name node-pty and the native packages that actually have to be unpacked, rather than effect. Checked the rest of the file and the tests for other references to the old sentinel while I was in there. The only remaining mentions of effect are in the comment explaining why the sentinel changed. |
|
🔴 blocker: As a result, transitive packages including I suggest using Verified locally at
I could not run the packaged Windows + WSL workflow on this Linux host, so this is not end-to-end WSL verification. |
…artifact `alwaysBundle` only forces packages IN. Returning false from the predicate means "no opinion", after which the default applies: a declared dependency stays external, a transitive one gets bundled. node-pty and @ff-labs/fff-node are declared dependencies of apps/server, so they stayed external and the packaging looked correct. msgpackr-extract, node-gyp-build-optional-packages and detect-libc are transitive, and were silently inlined. An inlined native loader resolves its prebuilds relative to the bundle, finds nothing, and falls back to a slower pure-JS path. No crash, no error, just a quiet loss of native acceleration. Wire the same list to `neverBundle`, which actually marks packages external. Every test to this point checked the dependency list rather than the bundle, so none of them saw it. Added findInlinedExternalPackages, which scans the emitted chunks for inlined externals, and wired it into the desktop build so a regression fails the build. It reports the module-region count as well, so "nothing inlined" is distinguishable from "the marker format changed and this scan is now blind" -- the failure mode the earlier closure guard had. Verified against the emitted bundle: msgpackr-extract is external again, and detect-libc and node-gyp-build-optional-packages are absent from it entirely. Reported by cursor bot.
|
You're right on all counts. Confirmed it against the emitted bundle before changing anything: The mechanism is that Also added the artifact check. Worth correcting something I said earlier in this PR: the On end to end: I built a Linux pty.node from source in Ubuntu 24.04, packaged an installer with So the accelerator loads from disk rather than falling back, and the WSL backend comes up on the platform this actually matters for. |
…ernals The bundle scan only asserted that external packages were absent. A build that externalized everything would pass it: source-file regions still exist, so the region count is non-zero and no external is inlined. That is the exact failure this change exists to prevent, because those packages are not covered by the unpack globs either and the WSL backend dies on ERR_MODULE_NOT_FOUND. The scan now reports every package it saw in a region, and the build asserts "effect" is among them. Every server module imports it, so it is inlined in any correctly bundled build -- 209 regions in the current one -- and its absence means the dependencies went external again. Verified against the emitted bundle: 788 regions, no external violations, effect inlined, 25 third-party packages inlined in total. Reported by macroscope.
|
Right, the check was one-directional. Fixed in 1bac125. The scan now reports every package it found in a region, and the build asserts I went looking for a general "every non-external dependency from package.json is inlined" assertion first, but a few declared deps legitimately never appear as regions once tree-shaken, so it would fail on correct builds. The sentinel avoids that without weakening the guarantee much. Verified against the emitted bundle: One thing worth mentioning from checking this: ajv's codegen emits |
|
the 🔴 remaining blocker: using only check emitted bare imports instead: every non-builtin bare import should match the intentional external list. a bundler metafile would also work. verified at
this is a guard/test blocker, not evidence that the current emitted bundle is broken. i still did not run the packaged windows + WSL flow. |
remaining validation problemwhat is the problem? The emitted-bundle guard at why is it a problem? Those ordinary dependencies are not included by the narrowed I reproduced the false negative by changing suggested fix Validate all bare imports in every emitted server chunk. Allow only:
Fail the build for every other bare import. Using the bundler metafile to inspect external modules would be even more robust if it is available. Add a regression test where Current head |
Static analysis of the emitted source kept getting this wrong. Scanning for bare imports matched specifiers inside effect's JSDoc examples and inside ajv's runtime codegen template; asserting that one sentinel package was inlined passed a build that inlined `effect` and left `yaml` and @effect/platform-node external. Both were reported as clean while the artifact was broken. After electron-builder runs, copy the packaged app.asar.unpacked into a scratch directory and run `node apps/server/dist/bin.mjs --version` there. Node either resolves every eagerly imported module or it does not, which is exactly the question, and the failure it prints is the one a WSL user would have hit. The copy matters: the stage has a node_modules of its own further up that would satisfy imports missing from the package. The probe refuses to run at all if a node_modules is visible above it, and if no unpacked directory is found, rather than reporting success it did not earn. Verified by breaking the build on purpose: inlining only `effect` fails with ERR_MODULE_NOT_FOUND for @effect/platform-node, and dropping neverBundle fails listing the four inlined externals. A correct build passes. Also adds a check for inlined packages that load native binaries, found by asking the pnpm store what each one is rather than consulting a list. bufferutil and utf-8-validate were being inlined from the dev store: both carry binding.gyp and prebuilds and load through node-gyp-build, and a loader inlined into a chunk searches for prebuilds that cannot be beside it. Neither is declared in this repo, so neither reaches the staged install and ws falls back to its JS paths regardless -- listing them keeps that from becoming real if either is ever declared. The dependency-closure test now reads optionalDependencies and peerDependencies as well. Every native family here declares its actual platform bindings there, so reading only `dependencies` checked nothing for exactly those packages.
|
Replaced the static bundle checks with one that runs the artifact. After packaging, the unpacked tree is copied somewhere isolated and Also added a check that asks the pnpm store whether any inlined package carries a native loader, rather than trusting the list to be complete. That turned up Ran the packaged build end to end under WSL on Ubuntu 24.04 with a Linux pty.node: preflight passes, every external resolves, a pty spawns, and the server boots with no module errors. I'm confident the packaging side is solid now. |
| // NODE_PATH would let a createRequire call inside the bundle resolve a | ||
| // missing external from outside the packaged tree, which is the whole | ||
| // thing this is trying to rule out. | ||
| env: { ...process.env, NODE_PATH: "" }, |
There was a problem hiding this comment.
Medium — the self-check still has a global-module false negative.
NODE_PATH: "" does not disable Node's built-in global CommonJS search paths. Node still searches %USERPROFILE%\.node_modules, %USERPROFILE%\.node_libraries, and the Node installation prefix, none of which are covered by ancestorNodeModulesPaths(probeApp, ...).
I reproduced this with an otherwise isolated fixture: with NODE_PATH empty, require.resolve("t3code-selfcheck-fixture") resolved from a fake %USERPROFILE%\.node_modules; the same command with node --no-global-search-paths returned MODULE_NOT_FOUND.
Because bundled code can still reach CommonJS resolution through require/createRequire, a missing packaged dependency that happens to be installed globally can make this probe report success. Please pass --no-global-search-paths before the entry point (and keep clearing NODE_PATH) so the stated “only its unpacked dependencies present” invariant is actually enforced.
There was a problem hiding this comment.
Verified at 6a1b459: the probe now passes --no-global-search-paths before the packaged entry point and still clears NODE_PATH. Re-running the original fake-USERPROFILE fixture now returns MODULE_NOT_FOUND, and a fresh Windows x64/NSIS artifact build completes successfully with the hardened probe. This addresses the finding.
SunkenInTime
left a comment
There was a problem hiding this comment.
Reviewed current head 22731fda26c239259f29985bb4590408c17831cc, including the changes made after the previous T3 Code review.
The three existing inline threads are all genuinely addressed in the current source:
- the pnpm-store closure test now reads real transitive manifests and covers scoped prefixes;
- the WSL preflight/error text now uses the external
node-ptysentinel; neverBundleis wired for externals, and the new packaged-tree execution probe replaces the weak one-package self-containment sentinel as the decisive runtime check.
Local verification on Windows 11 x64:
77/77focused tests passed (cli-external-packages, desktop artifact, and WSL environment);- scripts, server, and desktop targeted typechecks exited 0;
- release-equivalent x64/NSIS packaging with the Linux
node-ptysidecar passed, including the new post-builder self-containment check; - installer:
145,957,915bytes (139.196 MiB), approximately327.152 sfrom stage creation to installer output; - unpacked packaged tree:
1,178files /117,575,719bytes; - under Ubuntu's plain Node
v24.19.0,bin.mjs --version,msgpackr-extract,ffi-rs,node-gyp-build-optional-packages,detect-libc,node-pty, and@ff-labs/fff-nodeall loaded from the packaged tree; - an isolated real server start completed migrations and listened on
127.0.0.1:43877; noMODULE_NOT_FOUNDorERR_DLOPEN_FAILEDappeared.
The current emitted artifact therefore looks correct. I found one remaining guard hole and posted it inline: clearing NODE_PATH does not disable Node's %USERPROFILE%\.node_modules, .node_libraries, or install-prefix search paths, so a globally installed CommonJS dependency can still make the supposedly isolated self-check pass. Reproduced locally; --no-global-search-paths closes it: #5877 (comment)
My verdict is small fix requested, otherwise ready. This is a false-negative in the new regression check, not a failure of the current packaged runtime.
Clearing NODE_PATH does not isolate CommonJS resolution. Node still falls back to $HOME/.node_modules, $HOME/.node_libraries and the install prefix, so a globally installed copy of a dependency missing from the package would satisfy the probe and the check would report success on a broken artifact. Reproduced by putting a package in %USERPROFILE%\.node_modules: with NODE_PATH cleared it still resolved; with --no-global-search-paths it does not. Reported by @SunkenInTime.
|
Confirmed and fixed in 6a1b459. Reproduced it by dropping a package into So clearing Thanks for the independent packaging run as well, your unpacked file count and installer size line up with mine. |
SunkenInTime
left a comment
There was a problem hiding this comment.
Re-reviewed head 6a1b459.
The new commit addresses the remaining guard issue exactly: the packaged-tree execution probe now uses --no-global-search-paths while continuing to clear NODE_PATH. The original fake-USERPROFILE fixture now returns MODULE_NOT_FOUND, closing the global CommonJS false-success path.
Fresh Windows 11 x64 verification:
- 77/77 focused tests passed;
- scripts, server, and desktop targeted typechecks exited 0;
- a fresh x64/NSIS artifact build exited 0 in 244 seconds, including the corrected post-builder self-containment probe;
- installer: 145,957,918 bytes; SHA-256 9A11BF25D4AC6AE533EC8662D02C232CE20D5895ACA8D22DE2D4C896408E6702.
No additional actionable findings. The prior issue is fixed; this is ready to merge.
There was a problem hiding this comment.
reviewed exact head 6a1b459eca06de813d2cf3b9a75c14c025a9b03b.
the previous blocker is fixed. the packaged-tree probe now runs the emitted cli through node with --no-global-search-paths and an empty NODE_PATH, so the earlier partial-externalization and global-module false positives are checked by node resolution itself rather than the weak effect sentinel.
no new actionable findings.
verified locally on linux:
- server build passes
- 49 focused packaging tests pass
- server and scripts typechecks pass
- isolated
bin.mjs --versionsucceeds - emitted bundle has 660 module regions,
effectinlined, and no configured external packages inlined git diff --checkpasses- github checks are green
code review: approved.
merge readiness: clean and mergeable.
release readiness: i did not independently run the packaged windows + wsl flow on this linux host; the pr includes successful windows/wsl artifact evidence from other reviewers.
There was a problem hiding this comment.
correction to my earlier approval at exact head 6a1b459eca06de813d2cf3b9a75c14c025a9b03b: i found a reproducible blocker in the new packaged-tree probe.
🔴 blocker: scripts/build-desktop-artifact.ts:1382
fs.copy(unpackedRoot, probeApp) uses node filesystem copy semantics that rewrite copied relative pnpm symlinks into absolute links pointing back into the original staged app.asar.unpacked tree. module resolution then starts from that original target and can walk into the staging app parent node_modules.
the ancestor guard only checks parents of probeApp, so it misses this escape. NODE_PATH="" and --no-global-search-paths do not stop normal parent lookup from the symlink target.
i reproduced this with a pnpm-shaped relative symlink: the copied probe had no ancestor node_modules, its copied symlink pointed into the source staging tree, and node exited 0 after loading a dependency present only in the staging app parent node_modules.
this can make the self-containment check pass while the installed WSL tree is missing a dependency.
suggested fix: preserve relative symlinks during the copy, dereference/materialize the copied tree, or reject any copied symlink whose resolved target remains outside probeApp. add the reproduced staging-parent leak as a regression test.
my previous approval was incorrect. code review is blocked until this is fixed.
cb0e870 to
11e8b3b
Compare
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
11e8b3b to
4013ebd
Compare
There was a problem hiding this comment.
fixed and re-reviewed at exact head 4013ebd4b5732a23760711bc5a2d31ac69ab8b0f.
the isolated probe now rebases every in-tree pnpm link into the copied tree, rejects links that escape staging, and recreates directory links as windows-compatible junctions. the regression covers both relative and absolute source links.
verified: 50 focused tests, scripts typecheck, server build + cli smoke, formatting/lint, and github ci check/test/release smoke.
|
I tested this branch against its merge-base commit 02f4ce5 on Windows. WSL used to take a long time to connect. On this build the WSL backend connected in a few seconds in both dual and WSL-only mode. The app didn't get stuck on the "Connecting to WSL..." splash screen. I also ran an A/B test vs the merge-base
The biggest improvement I felt was the decrease in the time it took to connect to WSL. Now that all issues have been resolved, I think this PR is ready to merge. @juliusmarminge |
## What's Changed * fix(web): simplify the desktop-managed server update banner copy by @t3dotgg in pingdotgg/t3code#6549 * fix(web): show background policy tooltips sooner by @davidhu2000 in pingdotgg/t3code#6506 * feat(desktop): add favicons to the Browser panel by @chrisdeeming in pingdotgg/t3code#5644 * fix(preview): only show browser-ready local servers by @chrisdeeming in pingdotgg/t3code#6021 * perf(build): stop unpacking node_modules wholesale from the Windows asar by @tsouth89 in pingdotgg/t3code#5877 ## New Contributors * @davidhu2000 made their first contribution in pingdotgg/t3code#6506 **Full Changelog**: pingdotgg/t3code@v0.0.34-nightly.20260814.1089...v0.0.34-nightly.20260814.1090 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.34-nightly.20260814.1090
* feat: pick worktree or current checkout per project (pingdotgg#5766) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): sidebar rows show the branch again, not a truncated plan step (pingdotgg#5776) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(server): vp run migrate-dev-db seeds worktree dev dbs with real data (pingdotgg#5773) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(web): keep unsent drafts one click away in the sidebar (pingdotgg#5777) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(web): project icons can be chosen manually (pingdotgg#5775) * fix(server): one greedy agent process no longer takes down the whole server (pingdotgg#5788) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * ci: label-gated hosted-web preview deploys (pingdotgg#5465) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Add cross-platform mobile usage dashboard (pingdotgg#5743) * fix(web): preserve desktop route during Clerk auth (pingdotgg#5770) * fix(web): match create theme and import theme buttons to the standard outline style (pingdotgg#5860) * fix(server): favicon resolution no longer pins the event loop (pingdotgg#5538) * fix(shared): bound the file-link label so bracket runs stop rescanning (pingdotgg#5782) Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(web): thread title button no longer eats the drag area (pingdotgg#5857) * fix(web): unify usage page chrome (pingdotgg#5823) * fix(shell): add ~/.local/bin to the Windows CLI resolver so native-installed providers are found (pingdotgg#5074) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): match settings search shortcut styling to command palette's (pingdotgg#5841) * fix(mobile): long-pressing a thread row no longer navigates into the thread (pingdotgg#5901) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): usage no longer double-counts forked Codex sessions (pingdotgg#5887) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): sandbox user-provided SVGs (pingdotgg#5916) * fix(web): match usage titlebar text styling (pingdotgg#5897) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> * Move project settings to contextual project routes (pingdotgg#5923) * Preserve back navigation when opening settings (pingdotgg#5930) Co-authored-by: codex <codex@users.noreply.github.com> * Automate production mobile EAS releases (pingdotgg#5609) * Add settings and usage breadcrumbs (pingdotgg#5929) * fix(web): correct model picker trigger padding (pingdotgg#5935) * fix(web): show worktree icon in sidebar v2 (pingdotgg#5909) * fix(web): enable restore defaults after theme mix changes (pingdotgg#5928) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * fix(web): trait menu closes after you pick a level (pingdotgg#5879) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(web): align project name with headline (pingdotgg#5864) * fix(web): update pills use readable theme foregrounds (pingdotgg#5938) * fix(web): use themed confirmation dialogs (pingdotgg#5624) * fix(web): use import/export-appropriate icons for theme buttons (pingdotgg#5964) * fix(mobile): detect PowerShell cmdlet errors in work log rows (pingdotgg#5726) * fix(mobile): stop Android user bubbles with code blocks from overlapping (pingdotgg#5659) Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(mobile): parse EAS fingerprint JSON (pingdotgg#5991) Co-authored-by: codex <codex@users.noreply.github.com> * chore(release): prepare v0.0.33 * feat: multi-provider pull requests page with in-app reviews (pingdotgg#4849) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Co-authored-by: codex <codex@users.noreply.github.com> * perf(desktop): probe the Windows shell environment concurrently (pingdotgg#5878) Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com> * feat(web): add duplicate action to the T3 Code default theme (pingdotgg#6013) * fix(web): improve built-in theme contrast (pingdotgg#6000) * fix(ci): extend release publish timeout (pingdotgg#6034) * Allow Android tablets to rotate (pingdotgg#5613) * fix(web): account for Windows window controls in PR page header (pingdotgg#6049) * feat: add three-hour snooze option (pingdotgg#5914) * fix(mobile): keep chat composer above the Android gesture bar (pingdotgg#5988) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): OpenCode model parsing drops models with a slash in the JSON body (pingdotgg#5072) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): persist sidebar shelf collapse state (pingdotgg#5136) Co-authored-by: Illia Panasenko <hello@ipanasenko.me> * perf(web): skip base64 for oversized image candidates (pingdotgg#5220) * fix(server): skip Linux libc detection on Windows/macOS (pingdotgg#5354) * fix(server): advertise 256-color TERM on Windows terminals (pingdotgg#5693) * fix(server): handle unborn HEAD in VCS status (pingdotgg#5944) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> * fix(pull-requests): route self-hosted GitLab remotes (pingdotgg#6061) * feat: add ability to create a new thread in the current project with shift+click and show shortcut in tooltip (pingdotgg#5994) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> * feat(web): add Copy Thread ID to the sidebar and chat header thread context menu (pingdotgg#5574) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> * fix(mobile): stabilize thread composer and interactions (pingdotgg#5986) Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Thuong Tin <thuongtin@gmail.com> Co-authored-by: Kapish14 <kapishnarang01@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Add hourly past-24-hour usage view (pingdotgg#6170) * fix(mobile): guard App Store release versions (pingdotgg#6177) Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): restore typography font sizes to defaults (pingdotgg#6172) * feat(web): make environment artwork theme aware (pingdotgg#6183) Co-authored-by: codex <codex@users.noreply.github.com> * fix(shared): normalize a bare Windows drive root the same as C:\ / C:/ (pingdotgg#6189) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(shared): detect Azure DevOps SSH remotes (ssh.dev.azure.com) (pingdotgg#6187) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add back buttons for the pull requests and usage pages in the sidebar footer (pingdotgg#6031) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> * fix(web): render dropdowns above toasts (pingdotgg#6165) Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(web): thread error banner dismiss survives reconnect and rerenders (pingdotgg#6123) * fix(web): use a clearer pull action icon (pingdotgg#6194) * feat(web): use OKLCH for theme palettes (pingdotgg#6036) * fix(web): use upload icon for disabled push action (pingdotgg#6207) * feat(web): add Open VSX theme search (pingdotgg#5654) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): clean up composer resize animation (pingdotgg#6209) * feat(web): compact sidebar footer actions (pingdotgg#6210) * fix(web): keep sidebar wordmark visible at minimum width (pingdotgg#6246) * feat(mobile): add thread title regeneration (pingdotgg#6253) * chore: add dara to vouched (pingdotgg#6259) * fix(web): align the composer model picker (pingdotgg#6252) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com> * fix(mobile): keep ordered lists inside user bubbles (pingdotgg#6154) Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> * feat(web): a better right panel empty state (pingdotgg#6258) * fix(web): align mobile onboarding header (pingdotgg#6293) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> * fix(connect): preserve CLI OAuth parameters through browser sign-in (pingdotgg#6285) * fix(web): prevent changed files header overlap (pingdotgg#6314) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> * fix(web): theme Clerk surfaces (pingdotgg#6300) * feat(web): reset sidebar width on double click (pingdotgg#6320) * fix(web): align update toast release notes link (pingdotgg#6322) Co-authored-by: t3-code[bot] <219304759+t3-code[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> * fix(web): render tooltips above dropdowns (pingdotgg#6241) * fix(web): open modified PR clicks in browser (pingdotgg#6278) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com> * Fix mobile command popover glass rendering (pingdotgg#6370) * test(mobile): seed snoozed showcase threads (pingdotgg#5155) * fix(web): preserve appearance mode when changing themes (pingdotgg#6343) * feat(connect): deregister account environments from any client (pingdotgg#4844) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * feat(web): pull request surfaces — filters & qualifiers, all-server listing, update branch, reactions, in-place editing, smarter diffs (pingdotgg#6039) Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(web): cmd+click sidebar PR numbers open in the browser (pingdotgg#6378) * feat(web): project favicon and workspace icons in command subtitles (pingdotgg#6330) Co-authored-by: Cursor <cursoragent@cursor.com> * web/settings: fix source control scan on relay environments (pingdotgg#6230) * fix(web): make reset zoom hover visible (pingdotgg#6385) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> * fix(web): keep the typed prompt when a draft changes repo (pingdotgg#6393) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): keep diff file lists scrollable past expanded files (pingdotgg#6423) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): align Codex collaboration prompts (pingdotgg#6432) * fix(web): keep turn minimap stable as composer grows (pingdotgg#6414) * fix(web): keep pull request panel within viewport (pingdotgg#6451) * Add bil0000 to VOUCHED contributors list (pingdotgg#6462) * fix: ignore pull request actions in latency tracker (pingdotgg#6476) Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> * Remove rebase requirement before opening PR (pingdotgg#6479) * fix(mobile): extend blockquotes across wrapped lines (pingdotgg#6482) * fix(mobile): prevent invalid HTML entities from crashing markdown (pingdotgg#6495) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * fix(web): avoid Clerk close button overlap (pingdotgg#6442) Co-authored-by: t3-code[bot] <236186684+t3-code[bot]@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> * fix(web): show unlinked icon when viewport aspect ratio is unlocked (pingdotgg#6509) * fix(web): scope pull request errors to their environment (pingdotgg#6490) Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(mobile): show a real settings cog in the Android sidebar header (pingdotgg#6520) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(web): restore default stage artwork colors (pingdotgg#6535) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com> * fix(web): align sidebar wordmark label (pingdotgg#6086) * fix(web): align the snoozed thread wake icon (pingdotgg#6215) * feat: allow disabling auto-settle on merge (pingdotgg#5880) * Nest mobile task settings in bottom sheets (pingdotgg#6224) Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * chore(mobile): bump app version to 1.0.4 Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): simplify the desktop-managed server update banner copy (pingdotgg#6549) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): show background policy tooltips sooner (pingdotgg#6506) * feat(desktop): add favicons to the Browser panel (pingdotgg#5644) * fix(preview): only show browser-ready local servers (pingdotgg#6021) * perf(build): stop unpacking node_modules wholesale from the Windows asar (pingdotgg#5877) Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com> Co-authored-by: t3-code[bot] <t3-code[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com> * fix: avoid stale Live Activities when publishing is disabled (pingdotgg#6325) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): add breathing room between the git progress overlay and the app bar (pingdotgg#6587) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): keep thread rename open during IME composition (pingdotgg#6281) * refactor(mobile): name the iOS nav bar height fallback (pingdotgg#6589) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(mobile): preserve keyboard suggestions while typing (pingdotgg#6323) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): prevent OTA update restart crashes (pingdotgg#6324) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): align every titlebar control cluster on one shared inset (pingdotgg#6592) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(mobile): steer active turns by default (pingdotgg#6543) * fix(web): clarify desktop update status (pingdotgg#6504) * fix(server): terminal subprocess polling no longer floods the PID space (pingdotgg#6377) * fix(web): add copying terminal selection with ctrl+c in the web app (pingdotgg#5638) * perf(desktop): speed up Windows update installation (pingdotgg#6169) * fix(web): style sidebar action tooltips (pingdotgg#6371) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> * refactor(web): simplify global styling (pingdotgg#6381) Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(server): handle files named HEAD in git status (pingdotgg#6397) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * feat(packaging): maintain AUR packages in-repo (pingdotgg#4128) * fix(web): bound OKLCH gamut mapping (pingdotgg#6485) Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> * feat(web): open remote environments in your local editor over SSH (pingdotgg#6572) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat(web): refresh workspace layouts and tool activity * revert: refresh workspace layouts and tool activity (pingdotgg#6657) * Sync upstream with public-safe fixtures Squash the reviewed upstream sync into a public-safe history while preserving its final tree. * feat(web): older chat timestamps show the date, not just the time (pingdotgg#6654) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): align pull request action menu rows (pingdotgg#6534) Co-authored-by: Nickolas Kyryliuk <nickolaskyryliuk@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(web): restore selected themes in dark mode (pingdotgg#6665) * fix(web): improve Codex usage graph contrast (pingdotgg#6669) * docs: route feature requests to Discussions - Disable feature-request issue templates - Direct contributors to Ideas discussions for proposals * fix(desktop): app zoom no longer zooms the preview browser (pingdotgg#6649) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(server): keep provider notification consumers alive past startSession (pingdotgg#6538) Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com> * fix(server): treat removed Bitbucket permissions endpoint as unknown, not blocking (pingdotgg#6525) * fix(ssh): let cold remote servers finish starting (pingdotgg#6168) * fix(web): preserve Claude insight line breaks (pingdotgg#4344) * feat(web): accept file drops across the chat workspace (pingdotgg#6636) * fix(web): widen ordered-list marker gutter for 3+ digit item numbers (pingdotgg#6527) * fix(server): bound thread activity hydration (pingdotgg#6153) Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> * fix(web): restore the Archive action in the default sidebar thread menu (pingdotgg#6526) * fix(web): open diff files from nested projects (pingdotgg#6174) * fix(mobile): use tryOpenExternalUrl for markdown links in ThreadFeed (pingdotgg#5872) Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(web): open the file a bare filename reference names (pingdotgg#6297) Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(server): stop the provider title mirror from overwriting real thread titles (pingdotgg#5941) * fix(shared): match source-control providers by DNS label (pingdotgg#6175) * feat(desktop): Chrome-style hold-to-quit (pingdotgg#5508) * fix(gitlab): submit review comments on context lines (pingdotgg#6348) * fix(marketing): keep Grok mark clear of mobile hero copy (pingdotgg#4542) * fix(mobile): recover the QR pairing scanner when camera access is denied (pingdotgg#6487) * fix(web): keep a long path from running under the folder picker button (pingdotgg#4823) Co-authored-by: Sy-D <8460326+Sy-D@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(terminal): right-click paste works in the terminal (pingdotgg#5240) * fix(mobile): explain iOS-only settings on Android (pingdotgg#4981) * fix(web): stop counting a workflow coordinator as a working agent (pingdotgg#6672) * fix(web): keep floating preview anchored after panel closes (pingdotgg#6547) * fix(web): unstick /connect after in-modal sign-in by redirecting to the authorize endpoint (pingdotgg#5133) * fix(web): keep send reachable while a turn is running on mobile (pingdotgg#4781) Co-authored-by: AMohamedAakhil <hello@takaitech.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): reject unsupported composer image types at attach time (pingdotgg#6574) * Make ClaudeTextGeneration tests hermetic on Windows (pingdotgg#4508) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): show command output in work log (pingdotgg#4083) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): reserve sibling column width when resizing the right panel (pingdotgg#6279) * fix(web): replace whitespace in new ref names with dashes (pingdotgg#6270) * fix(client-runtime): branch list no longer resets while paging through refs (pingdotgg#5858) * fix(web): support Shift+Insert terminal paste (pingdotgg#5982) * fix(web): keep the composer glass aligned with the context strip at any interface font size (pingdotgg#5703) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(codex): keep background memory out of chats (pingdotgg#5468) * fix(server): treat a missing Codex rollout as a recoverable resume error (pingdotgg#6671) * fix(web): hide provider Update toast action while an update is running (pingdotgg#6544) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): agent shells inherit a UTF-8 locale on macOS (pingdotgg#6236) * fix(server): ignore Claude command lifecycle messages (pingdotgg#6606) * docs: mention Bitbucket user read scope needed by auth probe (pingdotgg#6291) Co-authored-by: Gerwin Bisschop <gerwin@regeljelease.nl> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): return valid preview action results (pingdotgg#5966) Co-authored-by: duncan-vc <247855047+duncan-vc@users.noreply.github.com> * fix(claude): make "Always allow for session" stick, and only for the session (pingdotgg#5041) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(ssh): surface a failed remote t3 install instead of a silent 0-byte server.log (pingdotgg#5132) * perf(server): persist the wire projection for streaming tool.updated data (pingdotgg#6675) Co-authored-by: mInrOz <14320143+mInrOz@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(web): stop wrapping partial code block selections in markdown fences (pingdotgg#5069) * fix(web): hide T3 Connect toggle in web app settings (pingdotgg#5068) * fix(web): show provider account accent badge in sidebar rows and hover card (pingdotgg#5980) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * fix(server): wait for concurrent SQLite writers instead of failing with SQLITE_BUSY (pingdotgg#5134) * fix(web): reject oversized prompts before provider turn start (pingdotgg#6602) * feat(web): collapse the question prompt from its header (pingdotgg#6773) Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(shared): degrade an unknown system time zone to UTC in usage windows (pingdotgg#6670) * fix(claude): discover repo-local .agents/skills in skill discovery (pingdotgg#5488) * fix(server): let slow provider CLIs raise their discovery probe budget (pingdotgg#6223) Co-authored-by: Julius Marminge <jmarminge@gmail.com> * fix(web): retain terminal PR badges after checkout switch (pingdotgg#4755) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): show selected model in context window tooltip (pingdotgg#4772) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): scale command details with code font (pingdotgg#6510) * fix(web): preserve XML-like tags in user messages (pingdotgg#4133) Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> * fix(desktop): route mouse thumb buttons to the in-app browser (pingdotgg#4459) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(web): keep the final segment of directory paths with a trailing separator (pingdotgg#5460) Co-authored-by: jorvarea <jorvarea@users.noreply.github.com> * Keep block code plain when copying from rendered markdown (pingdotgg#4468) * fix(web): add web app manifest so installed app keeps its scope (pingdotgg#4306) * Skip user hooks during Claude capability probes (pingdotgg#4466) * fix(mobile): use Android monospace font family (pingdotgg#4609) * fix(desktop): timestamps follow the OS locale instead of en-US (pingdotgg#6190) * fix(web): keep multi-select questions open after the first click (pingdotgg#6646) * fix(web): stop clipping the changed-files expand hover on Windows (pingdotgg#6545) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(server): allow long-running git pushes (pingdotgg#6499) * fix(desktop): keep probing backend readiness while the process is alive (pingdotgg#5526) * fix(server): allow install scripts in npm-global provider updates (pingdotgg#5646) * fix: detect SSH remotes with non-git user prefixes (e.g. gitlab@) (pingdotgg#3649) * fix(web): describe what Ultracode does in the Reasoning picker (pingdotgg#6092) * fix(server): settle pending user-input requests when a Claude session stops (pingdotgg#5127) Co-authored-by: Capxul Agent <agent@capxul.dev> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(server): stop replaying a command receipt for a different aggregate (pingdotgg#5246) * fix(server): settle snoozed threads immediately (pingdotgg#5379) * fix(mobile): prevent crash on sign out in settings (pingdotgg#4899) * fix(mobile): local-checkout threads record their branch so PR badges show (pingdotgg#4986) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): contain long approval commands (pingdotgg#6503) * feat(web): make right panel maximize bindable (pingdotgg#5091) * fix(server): respect inherited OPENCODE_CONFIG_CONTENT (pingdotgg#4242) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(marketing): detect Mac chip on homepage download button (pingdotgg#4197) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * Keep the server alive when a response write hits a dead socket (pingdotgg#4470) * Limit physical key fallback to non-Latin layout output (pingdotgg#4469) * fix: restore CLAUDE.md symlink target (pingdotgg#3929) * fix(clients): default clone destination to folder plus repo name (pingdotgg#5989) Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * fix(web): keep timestamp date and time in the same locale (pingdotgg#7081) Co-authored-by: codex <codex@users.noreply.github.com> * feat(desktop): add signal macOS DMG installer background (pingdotgg#6201) Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: codex <codex@users.noreply.github.com> * feat(web): send PR line requests to agent (pingdotgg#6597) * fix(web): restore dark theme palette (pingdotgg#6663) Co-authored-by: maria <maria@kuuro.net> * refactor(web): simplify advanced theme controls (pingdotgg#7107) * fix(web): keep highlighted command menu items clear of the scroll fade (pingdotgg#7132) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * test: remove redundant and stale tests (pingdotgg#6267) * fix: repair typecheck errors left by the upstream/main merge Fixes surfaced by a full repo-wide typecheck after merging upstream/main and origin/main into sync-upstream-continue: - apps/web/src/components/SidebarV2.tsx: add missing isRunning field - apps/web/src/components/settings/BetaSettingsPanel.tsx: replace removed useSidebarV2Enabled hook with useClientSettings selector - apps/web/src/components/settings/SettingsFontPreviews.tsx: drop obsolete onCopy callback (GhosttyTerminalSurface now handles copy natively) - apps/mobile/src/lib/threadActivity.ts: remove duplicate MOBILE_TERMINAL_UPDATE_STATUSES/isTerminalBypassUpdate/isAgentInternalActivity block left by a silent (non-conflict-marked) merge duplication - apps/mobile/src/features/threads/ThreadComposer.tsx: remove duplicate handleEfficiencyMenuAction, same cause - apps/mobile/src/features/threads/ThreadDetailScreen.tsx: stop passing activeThreadBusy to ThreadComposer, which no longer accepts it - apps/mobile/src/features/home/HomeScreen.tsx: remove dead shouldShowConnectionStatus/WorkspaceConnectionStatus/connectionStatus references (connection state now surfaces via the header title slot) - apps/mobile/src/features/home/HomeRouteScreen.tsx: fix settings-sheet navigation params, drop the now-removed onOpenEnvironments prop - apps/mobile/src/features/settings/SettingsUsageRouteScreen.tsx: use the fork's UsageQuerySummary/UsageQueryTokenTotals types instead of upstream's UsageSummary/UsageTokenTotals, matching what the usage RPC actually returns - packages/contracts/src/ipc.ts: drop DesktopBridge.confirm, superseded by LocalApi.dialogs.confirm; every desktop-side implementation had already dropped it independently - apps/desktop/src/window/DesktopApplicationMenu.test.ts: drop the now-invalid confirm mock to match - apps/desktop/src/preview/Manager.ts: remove a duplicate FrameCaptureConsumer/FrameCaptureSession/PictureInPictureSession block - apps/server/src/environment/RemoteOpenTargets.ts: fix service tag to the fork's @awtprod/command-center/... convention - apps/server/src/persistence/Layers/ProjectionThreads.ts: restore the upsertProjectionThreadRow query, which a conflict resolution had mangled into an invalid mix of INSERT and SELECT clauses - apps/mobile/package.json, patches/@legendapp__list@3.3.3.patch: fix @legendapp/list version pin and repair a patch file left with unresolved nested conflict markers from a rename/rename conflict Full repo-wide `vp run -r typecheck` now passes clean across all 16 workspace packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: repair pre-existing SQL and test bugs surfaced by running the full suite - apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts: three activity-listing queries selected correlated_message_id in their outer SELECT but never in the inner bounded subquery (two cases) or not at all (listPinnedThreadActivityRowsByThread), causing "no such column" SQL errors and schema decode failures. Pre-existing in HEAD before this merge, just never exercised until the full test suite ran clean of conflict markers. - packages/contracts/src/settings.test.ts: removed a test asserting that decoding drops the sidebarV2Enabled/sidebarV2ConfiguredByUser keys — contradicted the adjacent "ClientSettings sidebar v2" tests and the real schema, which keeps both fields alive alongside legacySidebarEnabled for BetaSettingsPanel.tsx. - packages/contracts/src/settings.ts: fixed the legacySidebarEnabled comment to describe the fields as coexisting, not superseding. - apps/web/src/browserFaviconStore.test.ts, browserHistoryStore.test.ts: switched their `~/state/session` mocks to importOriginal() + override, so the real environmentSession export (now used transitively via state/server.ts) stays available instead of being dropped entirely. Full repo-wide `vp run -r test` passes (aside from two known-flaky poll-based tests that pass in isolation but time out under full concurrent load, unrelated to this merge). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(server): update stale npm-global test expectation for --allow-scripts Upstream added the --allow-scripts=<package> flag to npm-global provider update commands; this test's expectation was the only one in the file that hadn't been updated to match (the other three assertions in the same file already expect the flag). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(server): skip a deadlocking git-push timeout test pending investigation "allows pushes to run longer than the default command timeout" hangs until the test timeout regardless of how long that timeout is (confirmed at both 120s and 180s), merged verbatim from upstream commit 86fb47a and untouched by any conflict resolution in this sync. Needs its own look at how pushCurrentBranch's command-timeout override interacts with TestClock; skipping for now so it doesn't block the suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: keep usage dashboard current (#25) Co-authored-by: atryan <atryan@users.noreply.github.com> * fix(web): keep usage totals current by default (#26) Co-authored-by: atryan <atryan@users.noreply.github.com> * feat(desktop): run local threads without leaving a remote primary (#27) * fix(web): switch new threads between connected computers * feat(desktop): keep Windows available with a remote primary * docs: explain local execution with a remote primary --------- Co-authored-by: atryan <atryan@users.noreply.github.com> * fix(web): open usage on the past 24 hours (#28) * fix(web): open usage on the past 24 hours * fix(server): resolve native Codex npm runtimes --------- Co-authored-by: atryan <atryan@users.noreply.github.com> * fix(desktop): stop remote-primary mode from launching a local backend (#30) Co-authored-by: atryan <atryan@users.noreply.github.com> * fix(web): keep visible usage dashboards current (#31) Co-authored-by: atryan <atryan@users.noreply.github.com> * Fix/codex isolation missing auth (#32) * fix(web): open usage on the past 24 hours * fix(server): resolve native Codex npm runtimes * fix(desktop): stop remote-primary mode from launching a local backend * fix(server): bypass identity wrapper for isolated Codex * fix(server): accept enforced Codex read denials * fix(server): stop isolation probe from scanning host processes * fix(server): fail closed when Codex isolation finds no credentials `prepareCommandCenterCodexHome` only copied `auth.json` into the isolated home when the source home had one, and never checked the result. A source home without credentials produced a working session that failed on its first model call with an opaque provider 401 naming neither the missing file nor the home Command Center actually read. Require credentials in the isolated home before returning the layout, and name the resolved source path in the error. Seed `auth.json` in the fixtures that relied on the previous fail-open behavior; they cover home layout and profile injection, not auth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(server): surface Codex sandbox permission requests Codex 0.147 asks to widen its sandbox with `item/permissions/requestApproval`. No handler was registered, so the request fell through to `handleUnknownServerRequest` and was answered with a JSON-RPC "method not found". Codex reports that as `user rejected MCP tool call`, so the turn failed with an approval the user was never shown and could not answer. Register the handler and map the method through to a real approval prompt. The request carries an arbitrary path list (each read/write/deny) plus a network toggle, so the decision is classified on the requested permissions rather than on the tool that triggered them — a read-only tool can still ask for write. `autoApproveReadOnlyPermissions` (off by default) grants an escalation without prompting only when every entry is a read: any write, sandbox denial, legacy write list, or network enable still prompts. Auto-grants are scoped to the turn, never the session. The approval channel carries only accept/decline, so the granted profile is the requested one echoed back on accept and an empty profile on refusal, mirroring how ClaudeAdapter replays SDK permission suggestions. Requests are surfaced with the `file-change` kind: ingestion derives the kind from the canonical request type alone, and a kind that varied per request would disagree between the opened and resolved activities. A request with no kind renders no prompt in web or mobile while the server still opens a pending row, which parks the thread unanswerable. The persisted activity keeps only `detail`, so the requested paths are folded into it — otherwise the prompt cannot be reviewed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: atryan <atryan@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * style(web): wrap an over-long condition in useLiveRefresh (#34) `vp check` fails on sync-upstream because this line exceeds the print width, which blocks CI for every PR targeting the branch. Formatter-only change: the condition is re-wrapped, with no behaviour difference. Co-authored-by: atryan <atryan@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(dev): stop a stray system `vp` from silently killing dev servers (#33) dev-runner spawns `vp` by bare name with `extendEnv: false`, so resolution depended entirely on the PATH it inherited. When node_modules/.bin was absent from that PATH, an unrelated /usr/bin/vp (atfs/ShapeTools) won the lookup and failed in the worst possible way: it printed `basename: unrecognized option '--filter=...'`, started no dev server, and exited 0. The launcher reported success while the stack was already gone. Pin the repo's node_modules/.bin ahead of the inherited PATH so `vp` always means this repo's toolchain, whatever environment we were launched from. Co-authored-by: atryan <atryan@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(web): drop stale ComposerPrimaryActions.test.ts duplicate The upstream sync left both .ts and .tsx test files for the same component; they collide on case-insensitive filesystems (Windows/macOS CI) and release-smoke's path-collision check fails the build. The .tsx version is upstream's current file and is a strict superset of the stale fork-only .ts copy. --------- Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Robert Soriano <sorianorobertc@gmail.com> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Matt Urenovich <murenovich@gmail.com> Co-authored-by: Tyler <tyler@southboundsoftware.com> Co-authored-by: tsouth89 <tsouth89@users.noreply.github.com> Co-authored-by: nathangerday <44236114+nathangerday@users.noreply.github.com> Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: Arham Amin <132888838+arhxam@users.noreply.github.com> Co-authored-by: codex <codex@users.noreply.github.com> Co-authored-by: Ellie Gummere <hello@unknownhost.name> Co-authored-by: Tristan Knight <admin@snappeh.com> Co-authored-by: Simone <lucenz@proton.me> Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com> Co-authored-by: Carter Smith <51297686+carterwsmith@users.noreply.github.com> Co-authored-by: Chris Deeming <chris@xenforo.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Mina Yacoub <56601613+myacoub91@users.noreply.github.com> Co-authored-by: Rodrigo Brechard <rodrigobrechard@gmail.com> Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr> Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com> Co-authored-by: Pavlo Trinko <paul.trinko95@gmail.com> Co-authored-by: Alex <me@pixp.cc> Co-authored-by: Illia Panasenko <hello@ipanasenko.me> Co-authored-by: Taras <Taras.Fomin@gmail.com> Co-authored-by: bkntr <888122+bkntr@users.noreply.github.com> Co-authored-by: yassiEmp <158713173+yassiEmp@users.noreply.github.com> Co-authored-by: Guilherme Vieira <46866023+GuilhermeVieiraDev@users.noreply.github.com> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Thuong Tin <thuongtin@gmail.com> Co-authored-by: Kapish14 <kapishnarang01@gmail.com> Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> Co-authored-by: Nick Anisimov <n.anisimov.23@gmail.com> Co-authored-by: t3-code[bot] <219304759+t3-code[bot]@users.noreply.github.com> Co-authored-by: Gianmarco <gianmarcosimone89@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Dominic Roy <dominic@goauthentik.io> Co-authored-by: Dominic Roy <dominic@sdko.org> Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Co-authored-by: t3-code[bot] <236186684+t3-code[bot]@users.noreply.github.com> Co-authored-by: Adamulek123 <adam.bogucki@piekna.edu.pl> Co-authored-by: Paul van Dyk <paul@vandyk.fr> Co-authored-by: Taylor Bombay <taylor@warheadent.com> Co-authored-by: Rakshith Bhat <88523594+RakshithBhat03@users.noreply.github.com> Co-authored-by: David Hu <davidhu314@gmail.com> Co-authored-by: t3-code[bot] <t3-code[bot]@users.noreply.github.com> Co-authored-by: Michael Charles Aubrey <aubrey@michaelcharl.es> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: atryan <atryan@users.noreply.github.com> Co-authored-by: Nickolas Kyryliuk <nickolaskyryliuk@gmail.com> Co-authored-by: Lars Nieuwenhuis <35393046+lnieuwenhuis@users.noreply.github.com> Co-authored-by: Guilherme Barros <gbarros1095@gmail.com> Co-authored-by: Yukun Shan <92423096+nateEc@users.noreply.github.com> Co-authored-by: David Balderston <dbalders@gmail.com> Co-authored-by: mohamedmastouri-hue <mohamed.mastouri@ensi-uma.tn> Co-authored-by: Ulises Britos <45952970+repparw@users.noreply.github.com> Co-authored-by: Nicolas Layne <49288482+NicL9923@users.noreply.github.com> Co-authored-by: JJ <93147993+hey-jj@users.noreply.github.com> Co-authored-by: Simon Doba <simon.doba@hotmail.de> Co-authored-by: Sy-D <8460326+Sy-D@users.noreply.github.com> Co-authored-by: Daniel Vernon <danpvernon@gmail.com> Co-authored-by: Rishet11 <154429365+Rishet11@users.noreply.github.com> Co-authored-by: Akshar Patel <123344143+AksharP5@users.noreply.github.com> Co-authored-by: Torben Wetter <github@torben.id> Co-authored-by: BootesVoid <78485654+AMohamedAakhil@users.noreply.github.com> Co-authored-by: AMohamedAakhil <hello@takaitech.com> Co-authored-by: mohammed shazeb <mohammedshazeb10@gmail.com> Co-authored-by: Mihnea Peteu <mihneanob@gmail.com> Co-authored-by: LikoKiko Tech <145937091+LikoKiko@users.noreply.github.com> Co-authored-by: Vividh Mahajan <82711162+Lasdw6@users.noreply.github.com> Co-authored-by: Jorge Pineda <jorgepineda0310@gmail.com> Co-authored-by: abhwshek <67309069+a20hek@users.noreply.github.com> Co-authored-by: aoright <102943475+aoright@users.noreply.github.com> Co-authored-by: Williawar <28518115+Williawar@users.noreply.github.com> Co-authored-by: Mark Griffin <mrmg@deflexion.net> Co-authored-by: Linus Boehm <linus.boehm@finto.de> Co-authored-by: Naveed Iqbal <naveediqbal949@gmail.com> Co-authored-by: Gerwin <9853101+thamrx@users.noreply.github.com> Co-authored-by: Gerwin Bisschop <gerwin@regeljelease.nl> Co-authored-by: duncan-vc <duncan@ommsocial.co.za> Co-authored-by: duncan-vc <247855047+duncan-vc@users.noreply.github.com> Co-authored-by: Joaquin Navarro <alfian1991@gmail.com> Co-authored-by: Martin Bergo <martin.n.bergo@gmail.com> Co-authored-by: mInrOz <14320143+mInrOz@users.noreply.github.com> Co-authored-by: Tai Nguyen <87302343+JoeJoeflyn@users.noreply.github.com> Co-authored-by: Vitaly Iegorov <vitalyiegorov@gmail.com> Co-authored-by: Ostap <33957189+ostapondo@users.noreply.github.com> Co-authored-by: Jaroslav Brtis <6890442+Jardo-51@users.noreply.github.com> Co-authored-by: Roshan Mhatre <officialroshanm@gmail.com> Co-authored-by: Carlos Jimenez <cjimenez@r21digital.com> Co-authored-by: Julius Marminge <jmarminge@gmail.com> Co-authored-by: sebbonit <36650750+sebbonit@users.noreply.github.com> Co-authored-by: nqrwhal <81386789+nqrwhal@users.noreply.github.com> Co-authored-by: CursedApple <36764254+Serendeep@users.noreply.github.com> Co-authored-by: John Surles <outsightszs@Outlook.com> Co-authored-by: Akos Balogh <hello.akosb@gmail.com> Co-authored-by: jorvarea <47249803+jorvarea@users.noreply.github.com> Co-authored-by: jorvarea <jorvarea@users.noreply.github.com> Co-authored-by: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Co-authored-by: Alex Brodsky <122503996+Albro3459@users.noreply.github.com> Co-authored-by: Harshith Goka <harshith9399@gmail.com> Co-authored-by: Paul <paul@brzz.dev> Co-authored-by: RaitP1 <150583432+RaitP1@users.noreply.github.com> Co-authored-by: Dev Talan <chaudhary.dev.talan@gmail.com> Co-authored-by: Luis Gustavo Couto Wacker <luis.wacker@pagar.me> Co-authored-by: JackatDJL <71508487+JackatDJL@users.noreply.github.com> Co-authored-by: delltrak <78751023+delltrak@users.noreply.github.com> Co-authored-by: Aaron Abu Usama <50079365+AaronAbuUsama@users.noreply.github.com> Co-authored-by: Capxul Agent <agent@capxul.dev> Co-authored-by: Kevin Bravo <79945749+0bkevin@users.noreply.github.com> Co-authored-by: shubhu <93861282+shubhu121@users.noreply.github.com> Co-authored-by: Zeus-Deus <100132710+Zeus-Deus@users.noreply.github.com> Co-authored-by: El-Hussein Abdelraouf <hussein@raoufs.me> Co-authored-by: Jono <jono@foodnotblogs.com> Co-authored-by: Mahdi Ben Messaoud <mahdibenmassoud98@gmail.com> Co-authored-by: Ngo Quoc Viet <123613986+NgoQuocViet2001@users.noreply.github.com> Co-authored-by: Inaya Yousfi <zied.essaber@gmail.com> Co-authored-by: Eddy Naboulet <93473191+eddy-naboulet@users.noreply.github.com>

Fixes the install-time and cold-start half of #5876.
What Changed
WINDOWS_ASAR_UNPACKwas["apps/server/dist/**", "**/node_modules/**"]. This inverts the CLI bundler's dependency rule — bundle everything except the packages that genuinely cannot be inlined — and narrowsasarUnpackto exactly that set.A package earns an exemption for one of two reasons:
.nodebinary cannot be inlined into JS and must sit on disk for both the Windows primary and the Linux Node inside WSL. The JS wrappers thatdlopenthem count too (ffi-rs,@ff-labs/fff-node,msgpackr-extract,node-gyp-build), since they resolve their binary by real filesystem path at runtime.@effect/platform-bunand@effect/sql-sqlite-bunare reached through a runtime-conditional dynamic import and resolvebun:sqlite, which does not exist when bundling for Node.Both consumers now derive from one list in
scripts/lib/cli-external-packages.ts, so they cannot drift.Why
The Windows installer writes 14,687 files, 13,875 of them loose
node_modulesfiles, to support 20 native binaries. The entire Electron runtime is 22 files because it stays inside the archive.That count costs twice: NSIS install time tracks file count, not bytes; and each file is a separate open/stat/scan the first time the server runs after an install, when the file cache is cold and the on-access scanner is not.
Measured on this repo, win/nsis x64:
node_modulesfiles.nodebinariesCold start, extracting each build's payload to a fresh directory so the files had never been read, alternating run order between builds:
Listening on--version)The main window is not created until the backend answers HTTP, so that ~6s comes off a cold launch.
Why one shared list
A package that is external but not unpacked still resolves on the Windows primary, which runs under
ELECTRON_RUN_AS_NODEand readsapp.asartransparently. It fails only under WSL. That asymmetry makes the drift invisible on the platform you are most likely to test on.node-gyp-build-optional-packageshit exactly this while I was writing the patch — matched as external by thenode-gyp-buildprefix, missed by a glob without a trailing wildcard. There are tests for the invariant.Verification
Extracted
app.asar.unpackedinto a directory with nonode_modulesancestor — what plainnodesees under WSL — and booted the server there. Migrations ran, it listened on127.0.0.1, and no module failed to resolve.node-pty,ffi-rs,msgpackr-extractand@ff-labs/fff-nodeall load from that isolated tree.scripts/build-desktop-artifact.test.ts(30) and the newscripts/lib/cli-external-packages.test.ts(7) pass.vp lintand@t3tools/servertypecheck are clean.One caveat on my verification: the build warned
No WSL node-pty prebuild provided, so I exercised the WSL module resolution path with Linux-shaped constraints rather than a real WSL launch. Happy to rerun with a Linuxpty.nodeprebuild if you want that closed before merging.UI Changes
None.
Checklist
Note
Medium Risk
Windows/WSL packaging behavior changes—missing external or unpack coverage surfaces as runtime MODULE_NOT_FOUND under WSL while Windows may still work; mitigated by shared lists, closure tests, and the packaged self-containment probe.
Overview
Narrows Windows
asarUnpackfrom all ofnode_modulesto the server dist plus globs derived from a shared external-package list, cutting install file count sharply while keeping what plain Node under WSL can load on disk.Inverts server CLI bundling in
apps/server/vite.config.ts: inline almost all JS deps viaalwaysBundle+neverBundle, with rules centralized inscripts/lib/cli-external-packages.tsso the bundler and unpack globs stay aligned (native addons, loaders likenode-gyp-build/detect-libc, bun-only entry points).Adds desktop build guards: scan emitted chunks for wrongly inlined externals and missing inlined
effect; detect inlined native loaders from the pnpm store; on Windows, copyapp.asar.unpackedinto an isolated tree (junction-safe symlinks, no ancestornode_modules) and runbin.mjs --versionwith hardened Node resolution.Updates WSL preflight to
require.resolve("node-pty/package.json")instead ofeffect, since JS deps are no longer on the unpacked filesystem.Reviewed by Cursor Bugbot for commit 2103eb6. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Stop unpacking all node_modules from the Windows asar by inlining JS deps into the server bundle
neverBundlerule, so only those external packages need to be unpacked from the asar.findInlinedExternalPackagesto scan bundle chunks for violations.effect) is not inlined (confirming self-containment).node bin.mjs --versionagainst the unpacked asar in a temp directory, catching missing imports or escaping symlinks before artifact copy.node-ptyavailability instead ofeffect, sinceeffectis no longer unpacked.detect-libc), requiring explicit additions to the external list.Macroscope summarized 2103eb6.