refactor(arch): close DI, import-boundary and lifecycle gaps#2813
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
apps/code/src/renderer/components/Providers.tsx:79
**"Retry" button does nothing in the `failed` state**
`invalidateConnection()` only refetches the `workspaceServer.getConnection` query. When status is `"failed"` the workspace server process has exhausted all its auto-retry attempts and `wsServer.getConnection()` still returns `null`, so the refetch just returns `null` again and the banner persists. To actually restart the server, something must call `wsServer.start()` (with `restartAttempts` reset, as `stop()` does), which requires a new tRPC mutation or procedure — `invalidateConnection` does not trigger that path.
### Issue 2 of 2
packages/ui/src/utils/clearStorage.ts:19-30
**Always reloads even when server-side clear fails**
Previously, a failure in `clearAllData` showed an alert and did **not** reload — the user kept the app in its original state. With `Promise.allSettled`, the page always reloads regardless of whether either operation succeeded. If `folders.clearAllData` fails, the server-side folder registrations survive while `localStorage` is wiped, potentially leaving the app in an inconsistent state on reload (e.g., no locally-known folders but the server still has them). Errors are only logged to the console, so the user gets no indication that the clear was partial.
Reviews (1): Last reviewed commit: "tighten architecture boundaries and DI s..." | Re-trigger Greptile |
a8670ec to
9943d59
Compare
There was a problem hiding this comment.
Gates denied this PR: it matches the auth deny-list, exceeds size limits (2830 lines, 33 files), and is classified as a never-auto-approve tier due to its multi-area refactor scope. Additionally, there are unresolved bot comments raising substantive concerns about broken retry behavior and a behavior regression in clearStorage error handling.
There was a problem hiding this comment.
Gates denied this PR: it touches auth code, exceeds size limits at 2830 lines across 33 files, and is classified as a never-auto-approve multi-area refactor. There are also unresolved substantive bot comments about a broken retry button and a behavior regression in clearStorage error handling that need to be addressed.
9e7ce55 to
800c58e
Compare
There was a problem hiding this comment.
Gates denied this PR: it touches auth code, exceeds size limits (3014 lines, 34 files), and is classified as a never-auto-approve multi-area refactor. Two substantive bot-raised concerns remain: the "Retry" button doing nothing in the failed state, and a behavior regression in clearStorage that always reloads even when server-side clearing fails.
800c58e to
65aae56
Compare
There was a problem hiding this comment.
Gates denied this PR: it touches auth code, exceeds size limits (3014 lines, 34 files), and is classified as a never-auto-approve multi-area refactor. Two substantive bot-raised concerns also remain unaddressed: the "Retry" button doing nothing in the failed state, and a behavior regression in clearStorage that always reloads even when server-side clearing fails.
9760f97 to
f7c7d9d
Compare
f7c7d9d to
c501f7d
Compare
Merge activity
|
) ## Problem Two reliability and security gaps from the package split surfaced, deliberately split out from #2813 because they touch runtime-sensitive Electron config and warrant their own review. 1. If `boot()`, a module-scope import or an Inversify resolution throws during renderer startup, the user gets a blank window with no error and no recovery short of force-quitting. 2. The main window runs with `nodeIntegration: true`, which applies in production, not just dev. ## Changes - Add a `BootErrorBoundary` (`apps/code/src/renderer/components/BootErrorBoundary.tsx`) around the root React tree, plus a `try/catch` around the synchronous bootstrap in `main.tsx` and a `.catch` on the async `boot(container)`. Any of those failing now renders a minimal, theme-independent error screen with a Reload button instead of a blank window, and logs the failure. - Set `webPreferences.nodeIntegration: false` on the main window. `contextIsolation: true` is already set (so the renderer main world never had Node anyway) and the renderer is Vite-bundled with zero `node:*`/`require` usage in the main world, so this is defense in depth with no functional change. Scope note: the dev-only `webSecurity: false` override is left in place. It does not affect production (production already runs with `webSecurity: true`) and flipping it risks breaking dev resource loading. The remaining dev/prod parity gap is deferred. ## How did you test this? All run locally in the branch worktree: - `pnpm typecheck`: 22/22 packages pass - `pnpm lint`: clean - `pnpm test`: `apps/code` 151 pass - `node scripts/check-host-boundaries.mjs`: no new violations Not yet done: a manual smoke test of the running app (dev and packaged). The `nodeIntegration` flip is low risk given `contextIsolation: true`, but it changes runtime Electron config, so a human should confirm the window still loads and agents/terminals work before merge. This PR is left as a draft for that reason. ## Automatic notifications - [ ] Publish to changelog? - [ ] Alert Sales and Marketing teams?

Problem
A review of the architecture post package split surfaced a set of gaps where
apps/codeand the portable packages had drifted from this repo's own stated rules: portablecore/ui, strongly-typed DI, a thin host, one-line tRPC forwards. None of these are structural redesigns. They are the codebase not yet matching its own architecture doc.Changes
DI token safety and import boundaries
Object.freezetoken bagsMAIN_TOKENS,RENDERER_TOKENS(apps/code) andTOKENS(workspace-server); use the standaloneSymbol.for(...)consts at every call site soTypedContainer<BindingMap>actually type-checksget/bind.biome.jsonc: drop the!@posthog/corenegations that allowed import cycles back intoapi-client,workspace-clientandplatform; blockcore,workspace-clientandhost-routerfromworkspace-server.workspace-server/src/trpc.ts: replace the package-levelcontainer.get()service-locator with acreateAppRouter(services)factory resolved inserve.ts(the composition seam). Thecontainerimport is removed entirely so any missed closure is a compile error.GIT_DIFF_SOURCEbinding uses the injectorctx.get()instead of the module-level container.Lifecycle and composition root
container.unbindAll()out ofAppLifecycleServiceinto theindex.tscomposition root via abeforeExithook ongracefulExit, so the service no longer reaches for the global container.State placement
@posthog/coreas azustand/vanillastore;@posthog/uikeeps a thin React wrapper. The immer proxy-lifetime guards in the dequeue paths are preserved verbatim.SessionService.previewConfigOptionsCacheso stale cloud model lists do not persist for the renderer lifetime.Reliability and correctness
WorkspaceServerService: supervised restart with exponential backoff and a max-attempts cap, astatusChangedevent stream, and a renderer failure banner with retry.packages/agent: thread LLM gateway config explicitly (GatewayEnv) into the Claude subprocess env instead of mutating the shared globalprocess.env, which clobbered config across concurrent sessions.clearApplicationStoragenow clears the encrypted SecureStore alongsidelocalStorage(previously encrypted settings survived a factory reset).AgentService.getOrCreateSessionoverloads remove the unreachablenullreturn on the new-session path.Also removes a redundant
immerdependency added tocore/package.json(immer already resolves via zustand's peer, and declaring it would break--frozen-lockfile).How did you test this?
All run locally in the branch worktree:
pnpm typecheck: 22/22 packages passpnpm lint: cleanpnpm test:apps/code151,@posthog/core1617,@posthog/ui787,@posthog/agent778 pass.@posthog/workspace-server511 pass / 5 fail, the 5 being a pre-existingbetter-sqlite3native-ABI mismatch (NODE_MODULE_VERSION 145vs147), unrelated to these changes.pnpm install --frozen-lockfile: consistentnode scripts/check-host-boundaries.mjs: no new violationsRewrote
app-lifecycle/service.test.tsfor the container-teardown move and added abeforeExithook test. The session store move is covered by the existingsessionStore(10),sessionServiceHost(119) and recovery integration (7) suites.Not yet covered by an automated test: the
WorkspaceServerServicerestart supervisor (the service had no prior test file). Worth a follow-up.Automatic notifications