Conversation
The preload script hardcoded its own copy of the version; it now asks the main process via a sync 'app:version' channel (app.getVersion() reads package.json), so releases bump the version in one place. A plain require of package.json would pass in Jest but throw in the real app because the preload is sandboxed. Also removes the unused 'dir:current' channel / getCurrentDirectory API and the inert 'new-window' handler (the event was removed in Electron 22; setWindowOpenHandler already governs window opening). https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
The lifecycle never emits 'running' (only starting / ready / running-unresponsive / stopping / stopped / failed), but the renderer's ACTIVE_STATUSES, status labels, and CSS carried it, letting the two sides drift. The renderer set now mirrors lib/projectLifecycle.js. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
Commands run through a shell on Windows, where cmd.exe expands %VAR% and treats ^ as its escape character; none of these were in the metacharacter blocklist, contradicting the documented guarantee on exactly the platform that uses a shell. Quotes are rejected too so no quoting tricks survive. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
The win32-specific spawn (shell: true) and taskkill process-tree paths previously first ran in CI at release-tag time. Lint, format, and audit stay ubuntu-only since they are platform-independent. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
Safety-net tests that document two known gaps before they are fixed, so the fixes flip explicit assertions instead of changing behavior silently: - ProjectStore treats a corrupt, malformed, or unreadable projects.json as empty, and the next save then silently discards every previously saved project (the DATA LOSS test demonstrates the full wipe). - ProjectLifecycle.stop reports 'stopped' after the 5s grace period even when the process ignored SIGTERM and never exited, and the default killProcessTree sends a single SIGTERM with no SIGKILL escalation. Each pinned assertion is marked KNOWN GAP with the fix that flips it. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
A corrupt or unreadable projects.json could previously cascade into silently wiping every saved project: reads swallowed all errors and returned an empty list, and the next save overwrote the file with it. - Reads now fail closed with a typed STORE_CORRUPT error, so mutations refuse to run against an unreadable store instead of destroying it. - Writes are atomic (temp file + rename), so a crash mid-write leaves either the old file or the new one, never a truncated mix. - Every successful save mirrors the file to projects.json.bak. - On launch, an unreadable store prompts Restore Backup / Start Fresh / Quit; Start Fresh moves the unreadable file aside with a timestamped name rather than deleting it. - Tray refresh is guarded so a mid-session read failure cannot crash the lifecycle event handler. Flips the M0 safety-net assertions that pinned the old behavior and adds durability coverage (atomicity, backup mirroring, restore, start-fresh preservation). https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
Stop previously sent a single SIGTERM, waited 5s, and reported 'stopped' unconditionally - a process that ignored the signal kept running and holding its port while the UI said otherwise. - killProcessTree now escalates SIGTERM -> grace -> SIGKILL on POSIX and resolves with whether an exit was actually observed (taskkill /F was already forceful on Windows; it now also waits for the exit). - stop() only reports 'stopped' on an observed exit; a survivor is shown as running-unresponsive with a Doctor warning, stays active so edits remain locked, and Stop can be retried. - Readiness polling is cancelled through the AbortSignal support that readiness.js already had, replacing the readinessToken flag - stopping no longer leaves the URL being probed for up to 30 more seconds. - Each start gets a run id; output and exit events from a previous run are ignored after a restart instead of bleeding into the new run. Flips the M0 safety-net assertions and verified end-to-end against a real SIGTERM-trapping process (killed after the 5s grace, ~5.0s stop). https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
main.js registered all ~28 privileged IPC channels as inline closures over module globals, leaving the entire security boundary untestable - and untested. lib/ipcHandlers.js now builds the channel maps via createIpcHandlers(), with Electron-bound seams (app, shell, dialog, clipboard, window access, the preview controller) injected and the Electron-free lib functions used directly, matching the codebase's existing DI style. Preview bounds clamping is a pure exported function. main.js keeps only window/tray/preview mechanics, update checks, store recovery, and app lifecycle, and registers the maps verbatim (846 -> 616 lines). New suite covers the previously untested glue at ~90% lines: the running-project mutation guard, doctor-action application, preview readiness gating, delete/stop preview cleanup, clipboard copies, dialog cancel paths, and bounds clamping. The preload contract test continues to pin the channel names. Also adds a LOCALWRAP_USER_DATA escape hatch so end-to-end tests can isolate their config directory. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
The renderer hand-mirrored lib definitions (active statuses, Doctor check list, action ids, mutating-action set, the auto-URL regex), and they had already drifted once (the phantom 'running' status removed earlier). public/shared-constants.js is now the single source: the renderer loads it as a plain script tag before app.js (it has no module system and a script-src 'self' CSP), lib modules require the same file, and app.js fails fast with a clear error if the script tag is missing. Renderer doctor-action ids reference the shared DOCTOR_ACTIONS map instead of string literals. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
One end-to-end journey covering every layer that unit tests cannot: launch the real app with an isolated LOCALWRAP_USER_DATA config dir, create the bundled sample project from the first-run empty state, start it, watch readiness reach Ready against the real sample server, stop it, and exit cleanly. Verified locally under xvfb (~13s). Playwright drives the locally installed Electron binary, so no browser downloads are needed. Jest ignores e2e/; the new CI job runs it under xvfb on Linux and is informational (continue-on-error) until it has proven itself flake-free. https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the quick wins plus Milestones 0–2 from the technical audit (see
AUDIT.mdon theclaude/repo-technical-audit-8wi6lmbranch). 11 commits, reviewable one at a time.Critical fixes
ProjectStorereads now fail closed with a typedSTORE_CORRUPTerror instead of treating a corrupt/unreadableprojects.jsonas empty; writes are atomic (temp file + rename); every successful save mirrors toprojects.json.bak. An unreadable store at launch prompts Restore Backup / Start Fresh / Quit — the unreadable file is preserved aside, never deleted.stoppedis only reported when an exit was actually observed, otherwise the project shows Running, no response and Stop can be retried. Verified against a real SIGTERM-trapping process. Readiness polling is cancelled via AbortSignal at stop (previously probed for up to 30 more seconds), and per-start run ids stop a previous run's late output/exit from bleeding into a restart.High-leverage improvements
lib/ipcHandlers.js(audit A1/T1): all ~28 privileged channels are now built by a dependency-injected factory and unit-tested at ~90% line coverage (mutation guard, doctor actions, preview gating, dialog/clipboard paths).main.jskeeps only window/tray/preview mechanics (846 → 616 lines). The preload contract test pins the channel names.public/shared-constants.js(audit A3): active statuses, Doctor checks/action ids, and the auto-URL pattern are defined once and consumed by both the renderer (script tag) and lib (require) — the phantomrunningstatus this drift had already caused is gone.npm run test:e2e, Playwright + Electron): first-run → sample project → start → Ready → stop → clean exit, with an isolated config dir. Passing locally under xvfb in ~13s; runs in CI as an informational job until proven flake-free.Quick wins
requireapp files, hence the syncapp:versionchannel).taskkill/shell: truecode paths run in CI rather than at release-tag time.%,^, and quotes (cmd.exe expansion/escaping on the Windows shell path).new-windowhandler (event removed in Electron 22) and the unuseddir:currentIPC channel.Safety nets
Failure-mode tests were committed before each critical fix, pinning the old broken behavior, then flipped by the fix — the diffs show exactly which behavior changed and why.
Test status
package.jsonstays at 2.7.0 — bumping is a one-line release decision now that the version is single-sourcedThis PR's CI run will be the first execution of the Windows/macOS matrix legs and the e2e job — if the Windows leg surfaces a real platform issue, that's the matrix doing its job.
https://claude.ai/code/session_01C6ffm2y19xXgQ2uGyoeLZF
Generated by Claude Code