Repository navigation
Conversation
Bundled pets sat frozen at a persisted point. They now walk a lane resting on the status bar, mirror on turn, and hang-sway while grabbed. - pet-walk-lane.ts: pure pacing math — bounds, lane y, turn-at-edge. Collapses to a fixed point when the viewport is narrower than the pet, so coincident edges can't flip direction every frame. - usePetWalkLane.ts: rAF driver, idle while dragging/hidden/reduced-motion. - Facing lives on its own layer; bob and sway own `transform`, so a flip declared beside them would lose to the animation. - Persist on drag end only — the walk moved position every frame, which would have hammered localStorage 60x/s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rendering the lane top while state kept a stale y hid the staleness until you picked the pet up: the grab measures its offset against `position`, so the pet jumped to the old y — 0 whenever the mount happened before the window reported a height. Sync y into state instead, and drop the render-time override. Found by driving a real pointer drag against a dev build over CDP. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…landing Releasing above the lane snapped the pet home instantly. It now falls. Modelled on Shimeji's actions.xml rather than plain gravity: - `Falling` pairs gravity with air resistance, so there is a terminal velocity. Pure acceleration reads as a stone, not a pet. - `Thrown` seeds the drop with the cursor's velocity, so a flick arcs the pet sideways; it stops dead at the lane's walls instead of sliding out of reach. - `Bouncing` is a two-pose blink, hence the 220ms landing squash pivoting on the floor rather than a long wobble. Catching the pet mid-air cancels the drop; reduced motion keeps the snap. Also adds the held-pose plumbing: `BundledPet.heldUrl` renders in place of the idle art while the pet is in hand. No bundled pet ships one yet — the three WebPs have no arms-up frame (verified by decoding all 96/73/73), and the Codex bundle contract has no held row, so imported pets keep the sway alone. DetectedSpriteFrame moved to its own file to stay under max-lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Derived from each pet's own artwork rather than generated: the arm pixels are isolated by palette (the vest/shirt/jeans fail the skin test), rotated about the shoulder or elbow, and composited back. Identity is exact because the pixels are the originals. - Gremlin and OpenCode read clearly as hands-up at render size. - Claudino's change is subtle and will stay that way: its canvas is 320x180 with a small character, so the arms are ~4px on screen. Shipped for consistency — a pet that ignores being picked up while the others react is worse than a quiet reaction. 142KB for all three, against 2.2MB for the animated idles. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pet slid along the lane as a static image. Each bundled pet now has a four-frame walk cycle: its own legs, cut at the hem and swung about the hip, with the body lifting on the passing poses. Rides the existing `.codex-pet` sprite renderer rather than adding a second animation path — a strip is just a one-row sheet, so `walkSpriteFrom` adapts the descriptor and SpriteFrame steps it. - The legs stop while the pet is held or falling; it isn't walking then. - The CSS bob is suppressed for walk-strip pets: the cycle already lifts the body, and two vertical oscillations at different periods wobble. - Swing is 20-24 degrees, far past anatomical. At the size the overlay renders (~141x180) a realistic 9 degrees was invisible. 227KB for three strips. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two faults in the walk strips, both from treating a front-on drawing as if it had a side view hiding in it. The pets stand with each foot splayed outward, which is right for an idle front pose. Walking, that left the trailing foot pointing against the direction of travel. One leg is now mirrored about its own content centre — not the cut box, which would slide it sideways — so both feet face the same way and the overlay's existing flip serves the other direction. The sinusoidal swing was also wrong: a stride has nothing to project onto in a front view, and on half the frames the legs scissored through each other. Replaced with alternating single-leg steps — one foot lifts and tilts while the other stays planted, so they can never cross. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous cycle only lifted a foot in place, so the legs never moved horizontally and stayed at the idle stance width the whole way round — wider than anyone actually walks. They now converge past each other on the contact frames and separate on the passing ones, and which leg is drawn on top alternates so the crossing reads as one leg in front of the other rather than a merge. A constant inward pull closes the idle splay that made them look planted apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dropped from a height the pet now tips over as it falls, lands flat on its back, lies there a beat, and pushes itself upright over 1.2s before resuming its walk. All transform, no new art: the body pivots on its feet so it falls over backwards rather than spinning about its middle. A 90-degree turn about that point would bury half the body under the lane, so the pose lifts by half the rendered artwork width to lay it on the floor. The fall hook grew a phase machine — falling, downed, rising — decided at release rather than impact, since by the time it lands the height it fell is no longer measurable. Drops under 80px keep the old quick squash; a pet toppling over after a 5px stumble reads as broken, not hurt. The walk holds through all three phases. Also fixes the test harness: cancelAnimationFrame was a no-op, so cancelled frames kept firing and hid the walk running while the pet was supposed to be lying down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two toggles in the status-bar pet menu, both on by default so nothing changes for anyone who does not go looking. - Walk around: off, the pet stands still with its walk strip frozen on a standing frame. - Drop to the floor when released: off, the pet stays wherever you put it — no fall, no topple, and the lane stops owning y. It still paces, just along the height you left it at, so the two settings stay independent rather than one silently disabling the other. Persisted alongside petVisible/petSize. The RPC schema gains two optional booleans, which docs/reference/remote-wire-compatibility.md classifies as safe for older clients; hydration defaults both to true so profiles written before this land upright. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bundled pets had one animation — a walk strip that played whatever the pet was doing. They now carry a 4x4 sheet (idle breathing, the crossing walk, an impatient toe tap, a hop) and pick the row from live agent state, the way imported .codex-pet bundles already could. Every pose is derived from each pet's own artwork by the same canvas pipeline, so identity is exact and no image model was involved. Two things fall out of having more than one row: - Arbitration. Pacing and the agent-state row disagree: walking with a breathing row reads as gliding, and a waiting pose should park the pet. `arbitratePetPose` decides both at once, so the row on screen and whether the lane advances can never contradict. Hover now genuinely stops the pet instead of sliding it along mid-hop. - Per-row timing. Breathing at running speed reads as panic, so each row carries its own frame hold rather than sharing the sheet fps. Every PetAnimationName maps to a row, so SpriteFrame never silently falls back — an unmapped state used to look like a stuck animation. The drag rows share locomotion; `review` borrows the waiting pose. Removes the walk strips and their one-row descriptor, now dead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From an independent review of the branch. - A hard upward flick peaked ~1200px above the release point: the fall clamped x to the lane walls and y only at the floor. For the ~2s round trip the pet was off-screen and ungrabbable, with `busy` suppressing both the walk and the lane sync. It now stops at the ceiling and drops its upward velocity so gravity brings it straight back. Verified live: a hard flick now peaks at y=1 instead of leaving the window. - Both rAF loops read the lane and pet size through refs, so a resize can no longer restart them. A restart drops the timestamp baseline and the tick after one only records, so a stream of them would stall the motion. The review flagged this as a resize stall; I could not reproduce the symptom, but a loop that restarts on value changes is wrong regardless. - The supine lift fell back to half the overlay box for pets without a pose sheet, leaving a narrow custom sprite floating above the lane. It now reads the real frame from whichever descriptor is active. - The settled position is persisted instead of the mid-air release point, which reappeared hanging on relaunch with the floor return off. Also pins the behaviour the pose-sheet refactor already fixed: turning "Walk around" off used to freeze the pet solid, because the paused strip and the suppressed bob left no motion at all. It now breathes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The topple was a CSS rotation of the standing pose: the pet stayed on its feet in every frame, just turned sideways, and the rise was a linear interpolation back to zero. Both are now drawn rows on the pose sheet. The sheet grows from four rows to seven — falling tips through four angles with a slight air stretch, downed lies flat and breathes shallow, and rising peels off the floor and over-rotates a few degrees onto its feet before settling, which is the beat an interpolation cannot express. Three things this needed: - Tipping about the feet swings the body out past its cell, where the neighbouring frame clips it. Each tipped frame is measured against the artwork's own box and slid back to centre. - `buildSpriteAnimationCss` grew a `loop` flag. Getting up must settle on its last frame; looping would drop the pet back on the floor. The rise row is also paced to span exactly PET_RISING_MS so playback and phase end together. - The fall phase now enters pose arbitration ahead of everything else. Mid-air is mid-air: no agent state should stand the pet back up. Pets without the rows — every imported `.codex-pet` bundle — keep the CSS topple, so the fallback path stays intact and is still covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Groundwork for building pets from uploaded images. The leg boxes, pivots and animation amplitudes for the three bundled pets lived in environment variables of a throwaway script. They are now data in the repo, shared by the sheet generator and the coming image pipeline so the two cannot drift, plus a head slot per pet for the head-swap mode. `assessCutout` refuses an upload whose background could not be separated, rather than saving a pet that is visibly broken in every frame. Five measured criteria, thresholds in one named place because they will need tuning against real uploads: coverage too high or too low, a subject that is mostly scattered specks, a full-bleed photo, and a silhouette too uniform to be a character. An image that arrived with its own alpha is never second-guessed — someone already cut it out with more to work with than a corner fill. The uniformity test replaced a head-detection heuristic I wrote first. Its own test caught it: a rectangular slab has a full-width top band and so passed as "has a head". Without face detection any head rule is a guess that big-headed characters fail, while uniformity catches the real failure — a rectangular chunk of background where no character is distinguishable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 2: the pipeline, no UI yet. `buildPetFromImage` takes an upload and one of the three aesthetics and returns a seven-row sheet plus a manifest shaped exactly like the one the bundle importer already validates — so a generated pet and an imported one are the same thing downstream. Four pure modules, none touching a DOM, so the whole path is unit-testable and deterministic: the same image and style produce the same bytes, which is what lets the preview a user approves be what actually gets written. - `pet-image-cutout` separates subject from background. An image that arrived with alpha is used as-is; otherwise each corner floods against its own colour and gives up honestly on a busy photo rather than eating into the subject. - `pet-image-resample` fits the subject to the chosen pet's frame, standing on its floor line. Nearest-neighbour and aspect-preserving: smoothing makes an upload read as a photo pasted onto a drawing, and squashing a face to fill the frame is worse than leaving air. - `pet-raster-transform` does affine draws by inverse mapping, because the forward direction leaves holes wherever a transform stretches. - `pet-sheet-composer` builds the seven rows. Whole-body mode has no limbs it can trust, so the walk row leans and bounces rather than claiming an anatomy the image may not have. Two of my own test fixtures were wrong and the tests caught them. Four uniform quadrants are perfectly floodable, so "corners disagree" removed everything as it should — the real case is high-frequency noise. Both are now checked end to end against the quality gate, which is where a bad upload gets refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 3. `pet:import` copies a file the user picked, so there was no way to save bytes the app itself produced. `pet:createGenerated` takes the sheet and manifest from the renderer and writes them with the same atomic tmp-then-rename the bundle importer uses. Two things the renderer must not control are pinned in main: where the sheet lands, and what the manifest calls it. The caller's spritesheetPath is replaced *before* validation rather than after — sanitising a path we were never going to use would only hand the caller a way to fail the parse with one we then discard. Everything else goes through the same zod schema and the same geometry checks an external `.codex-pet` faces, and that validation runs before the rename, so a sheet that does not match its manifest never becomes a saved pet. A test asserts nothing is left on disk when it does not. Two bugs found while writing the tests, both mine: - `Buffer.concat` returns a view into a shared pool, so `.buffer.slice(0)` handed the whole pool across the boundary instead of the sheet, and the WebP header parse failed on bytes that were fine. - The manifest schema rejects `..` in a path, so overwriting the caller's path after parsing meant a traversal attempt failed validation instead of being ignored. Overwriting first is both simpler and what the comment already claimed was happening. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 4 — the feature is now reachable. "Create pet from image…" in the pet menu opens a dialog: pick a picture, pick one of the three aesthetics, watch the result move, then save. Nothing is written until the user confirms. The pipeline is deterministic, so the preview they approve is byte-for-byte what lands on disk, and switching aesthetic re-runs it from the decoded source rather than asking for the file again. A refused cutout never reaches a save button. The dialog shows what went wrong AND what to do — "the background is too busy to separate from the character, try a PNG with a transparent background" rather than "could not import". A test asserts every failure reason has such a message and that none of them leaks its internal code. The preview tours the rows instead of showing one: a bad cutout reads as a plausible blob standing still, and only gives itself away once it moves. `pet-image-decode` is the canvas edge and is deliberately logic-free, so everything that decides anything stays in the pure modules and testable without a rasteriser. It caps source pixels — a small file can declare enormous dimensions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Selecting "Create pet from image…" called preventDefault, which is what the toggles above it need — they are meant to be used several in a row — but wrong here. The menu stayed open behind the dialog, and once the dialog closed the next click on the trigger toggled the still-open menu shut, so the menu looked like it had stopped working. Found while validating the pipeline against real images, and diagnosed by counting menu items rather than guessing: the dropdown had six items open through the whole dialog lifetime. Also adds the component test this file never had. It pins both halves — the dialog item closes the menu, the toggles keep it open — so the two cannot be "fixed" into each other later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 5, completing the three modes. Whole body remains the default and the only one that works for any picture; the dialog now offers the other two beside it. Walking legs finds a rig in the upload's own silhouette, so a generated pet can walk rather than glide. `detectLegs` is written to say no more readily than yes — exactly two column runs, each wide and tall enough relative to the subject — because a wrong rig tears a seam through the middle of something that has no legs, while falling back merely looks calm. When it declines, the build degrades to whole body and the dialog says so; silently handing back a glide would read as the feature being broken. Head only composes the upload into the chosen pet's head slot, so the aesthetic is exact — it IS the pet, wearing the user's face. It needs the pet's artwork, and refuses with a message rather than quietly building something else when that cannot be loaded. `BUILD_PET_FAILURES` is now the source of `BuildPetFailure` rather than a copy of it. Adding the head-swap failure exposed the gap: the type grew, a hand-written test list did not, and only the compiler caught the missing message. Now both the switch and the test walk the same list. Verified in the app against real images: all three modes produce a preview, the rig is genuinely detected on a legged silhouette, and a legless blob degrades with the notice shown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three mode buttons shipped as bare English literals, which the localization coverage check refuses — and rightly: every other string in this dialog goes through translate(). They use getters rather than plain calls, matching pet-models: a module-level translate() would run before i18n is ready and would not follow a language change afterwards. The hover hints are translated too, though the audit did not flag them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The magenta key promised antialiased edges would fade proportionally, but the threshold and the multiplier together could never yield a score below 0.5, so every scored pixel was hard-cleared. Normalising across the band above the threshold makes the partial-alpha path reachable. Extracted to its own module so it can be tested without the blob cache. The importer trusted the file extension alone, so an executable renamed .png was copied into the pets directory unchallenged. Now the first bytes have to agree with the name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fading alpha alone left the pixel magenta, so making the partial band reachable would have turned a hard-cut edge into a visible pink fringe. Red and blue now fall to green in step with the score, so the edge loses the cast as it loses opacity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things the pipeline cannot work out on its own. Which of the things in a picture is the character is a question only the person who chose it can answer, so a drag on the source image now says it. And no single flood tolerance suits both a flat studio backdrop and a gradient, so the width of the fill is a slider rather than a constant. Fixes a real defect found on the way: the corner fills shared one visited set, and marked a pixel seen BEFORE testing it. The first corner's refusals therefore stood for all four, stranding background the others were well inside tolerance of — and walling off everything behind it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removing a pet already deletes its files. This is for the times that never ran: a crash between writing a bundle and persisting the list, or a profile restored without it. Conservative by construction — an entry has to be shaped like a pet id, unclaimed, and a full day old before it is touched, because a pet reaches disk before it reaches the list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…onse React Doctor found three real defects in this branch. Refs were written during render in four places; React may replay or discard a render, and a ref written in one that never commits would feed the animation loop a value the user never saw. They move to effects, which run after commit. fetch resolves on 404 as readily as on 200, so head-swap handed the error page to the decoder and failed talking about pixels. It now says what actually went wrong. The orphan sweep awaited one stat at a time; the stats are independent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Node refuses to spawn a .cmd without a shell, so both gates threw spawnSync EINVAL before oxlint or react-doctor ever started. On Windows neither had been running at all. ensure-native-runtime already had the fix; these two were missed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Windows suite starts from 303 failures across 99 files. At that size a phase cannot show it fixed its own cluster without disturbing another by reading the output, so this records the per-file counts and compares against them. It fails only on files that got worse or newly started failing. Demanding zero would make it useless until the very last phase, and offsetting regressions against progress would let a phase that broke something elsewhere read as a win. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Vite strips a shebang line-wise. A CRLF checkout leaves the stray CR behind, and any .mjs a test imports then fails to parse with a SyntaxError naming no file and no line. Node runs CRLF shebangs fine, which is why three test files carried this for as long as they did. The eight individual pins are subsumed by the blanket rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pinning .mjs to LF let three test files parse for the first time, and one of them then failed six ways — the SyntaxError had been hiding real bugs in the script it covers. Git quotes any pathname holding a character it must escape, so on Windows every `git diff --no-index` header comes back as "a/C:\tmp\..." with the separators doubled. The forward-slash prefix stripping never matched, and the generator wrote absolute scratch paths into the patch. The lockfile pattern matched a literal \n, so a checkout with autocrlf made the script report a patchedDependencies entry as missing while it sat right there. The test's own `git apply` needed the same neutralising. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`os.devNull` is `\.\nul` on Windows and Git rejects it outright — every `git init` in worktree-shared-directories died on the config path rather than on anything the test meant to exercise. A path that does not exist reads as an empty config on every platform. `stdio: 'ignore'` had been swallowing git's reason, so the failure said only "Command failed". The oxlint plugin test spawned `.bin/oxlint.cmd`, which Node refuses to launch without a shell. The shim only runs `node oxlint/bin/oxlint`, so running that directly needs no shell and no argument has to survive quoting. lint-react-doctor-changed was the third quality gate carrying the same .cmd defect, after the two fixed in a25372e. Fourteen developer-only scripts still carry it; none is reachable from the suite and none can be exercised here, so they are recorded rather than changed blind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7239ade8-3475-4282-a008-7b63c97d3dea
📒 Files selected for processing (15)
src/main/ipc/pet-generated-write.tssrc/main/ipc/pet.test.tssrc/main/ipc/pet.tssrc/main/updater.tssrc/renderer/src/components/pet/pet-raster-transform.test.tssrc/renderer/src/components/pet/pet-raster-transform.tssrc/renderer/src/components/pet/pet-sheet-composer.test.tssrc/renderer/src/components/pet/pet-sheet-composer.tssrc/shared/file-mode-capability.test.tssrc/shared/file-mode-capability.tssrc/shared/posix-shell.test.tssrc/shared/posix-shell.tssrc/shared/probe-scratch-directory.tssrc/shared/symlink-capability.test.tssrc/shared/symlink-capability.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/updater.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| const scale = Math.min((hx1 - hx0) / sourceWidth, (hy1 - hy0) / sourceHeight) | ||
| const width = Math.round(sourceWidth * scale) | ||
| const height = Math.round(sourceHeight * scale) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep a non-empty fitted head.
When an opaque upload has an extreme aspect ratio, the non-limiting dimension can round to 0. clearCell and drawTransformed then use an empty rectangle, so the composed sheet contains no uploaded head. Clamp both fitted dimensions to at least one pixel.
Proposed fix
- const width = Math.round(sourceWidth * scale)
- const height = Math.round(sourceHeight * scale)
+ const width = Math.max(1, Math.round(sourceWidth * scale))
+ const height = Math.max(1, Math.round(sourceHeight * scale))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const scale = Math.min((hx1 - hx0) / sourceWidth, (hy1 - hy0) / sourceHeight) | |
| const width = Math.round(sourceWidth * scale) | |
| const height = Math.round(sourceHeight * scale) | |
| const scale = Math.min((hx1 - hx0) / sourceWidth, (hy1 - hy0) / sourceHeight) | |
| const width = Math.max(1, Math.round(sourceWidth * scale)) | |
| const height = Math.max(1, Math.round(sourceHeight * scale)) |
| /** Why not the 30s process default: up to five candidates are tried, and the | ||
| * probe runs at `describe.skipIf` scope — during collection, which vitest's | ||
| * `testTimeout` does not govern. A shell that has not answered in 2s is a "no". */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Keep rationale comments concise. Reduce these comments to one-line explanations of the non-obvious constraint; avoid restating implementation details or general commenting guidance.
📍 Affects 2 files
src/shared/posix-shell.ts#L15-L17(this comment)src/renderer/src/components/pet/pet-sheet-composer.ts#L192-L197
Source: Coding guidelines
… blob Git classified the file as binary, so GitHub rendered "Binary file not shown" for it in the PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The click and result lines were scaffolding from bringing custom-pet upload up; they fire on every use and print the imported model. The warn/error paths stay — those still describe failures worth diagnosing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The overlay plays `rising` once and pins its last frame (`loop={animationName
!== 'rising'}`); the preview left `buildSpriteAnimationCss`'s `loop` at its
default, so the tour's last stop showed the pet standing up over and over —
a motion the pet never makes in the app the user is previewing it for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…assing empty Seven cases returned before their first assertion when the host lacked the thing under test, so Windows counted them as passes. Three would have stayed green with findPosixShell() regressed to null. runIf/skipIf reports them as skips; on this host that exposes one more vacuous case than expected -- findPosixShell() resolves the bare name 'sh', so the PATH-precedence case was never asserting either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
… zero The comment still said the suite does not pass on Windows and promised the flag would come off at zero. It is at zero. State the real reason the flag stays: the runner's own results have never been read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…asked for The generated manifest named only the seven physical rows, so `running-left`, `running-right` and `review` fell through to the default row — a generated pet played idle while being dragged and while a result waited for review. Alias them onto the same rows `bundled-pet-pose-sprite.ts` uses, typed against `PetAnimationName` so a new state cannot ship without one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The two overlay toggles drew a Check span inside a plain menuitem, so the state was visible but unannounced; they are DropdownMenuCheckboxItem now and carry aria-checked. Choosing a pet is a single choice over two lists, so both lists move into one DropdownMenuRadioGroup. The per-row delete was a <button> nested inside a role="menuitem" — invalid, and unreachable by the arrow keys that drive a menu, so keyboard users had no way to remove a custom pet at all. It becomes its own "Remove a pet" submenu, shown only when there is something custom to remove. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…d pet A generated pet is written with `kind: 'bundle'`, and every bundle is chroma-keyed by `keyMagenta` at load. The preview never ran that pass, so a pink subject looked intact on the confirm screen and came back with 700k pixels drained once saved — the dialog's byte-for-byte promise was false. Run the pass at the end of the build instead of dropping the promise: the user now sees what the overlay will render and can pick another image. The pass is idempotent (a keyed pixel scores 0 on the next run), so the loader repeating it changes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The comment called it a decompression-bomb guard, but the check runs after `createImageBitmap` has already decoded the file. No renderer API reports an image's dimensions without decoding it, so the check cannot move earlier; what it really prevents is the RGBA heap copy and the O(pixels) passes after it. The file comes from the user's own picker, so there is no adversary to defend from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The image dialog on this same branch uses <Slider> for the tolerance control; the size control a few clicks away was a bare <input type="range">, so two sliders in one feature read as two different products. The wrapper's stopPropagation still keeps the menu open while the thumb is dragged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…andled `loadCustomBlobUrl` was voided with a bare `.then`, so an IPC read that rejected became an unhandled rejection. A freshly generated pet is never in the blob cache, so it takes this path every time. The overlay already falls back to the bundled default while the load is in flight; log and stay there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
Style and build mode are one-of-N choices that were encoded only in a Button `variant`, so which one is active was a colour and nothing else — no role, no checked state. Both become ToggleGroup, whose items render as radios with aria-checked. The three modes differ in ways only `title` explained, and STYLEGUIDE lists `title` under "Don't use": it never reaches keyboard or touch. The selected mode's sentence now renders under the group and is wired to it with aria-describedby. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…eport it `usePetFallToLane` already caps its integration step at 50ms with a comment about teleporting through the lane; the walk passed the raw delta, so a backgrounded window resuming after five seconds slid the pet 180px in one frame. Same cap, same reason. `PetOverlay.walk.test.tsx` drives a single 1000ms frame as shorthand for a second of walking and now fails; the two step patterns that replace it are pinned here so the overlay test can adopt them directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…tried The owner keeps this component mounted and only flips `open`, so every piece of dialog state outlived the dialog: reopening after a save showed the last picture, its framing, and its verdict. An effect on `open` clears them. The file input kept the chosen path, so after a failure re-picking the same file fired no change event and the user's only recourse was another file. The decoded source moves from a ref to state: it sizes the crop surface during render, and a ref read there is a value React never promised had been committed — the anti-pattern 5c0c258 fixed on the writing side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…list `customPets` is optional on the persisted UI state, and a corrupt state file falls back to defaults where it is absent. `?? []` read that as "no pets are known", handing the orphan sweep an empty keep-set: every pet folder past the 24h grace period deleted, with no undo. An empty array is a real answer and still sweeps — that is the crashed-import case the sweep exists for. An absent one is not an answer; skip, and let the next launch that hydrates a list do the housekeeping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…_CROP The shipped pose sheets are 4x7 (claude-poses is 1280x1260 at a 320x180 cell; the other two are 1008x2240 at 252x320), not the four rows the comment claimed — the fall rows were missing from the list. FULL_CROP had no caller but its own test; the identity fast path it exercised is worth keeping, so the test states the rectangle inline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The resample wrote 255 for every drawn pixel, so an upload that arrived with a hand-made soft cutout came out with a hard edge — the one thing that makes a subject read as pasted onto the pet art rather than drawn with it. Pixels below the mask threshold are still skipped, so this only touches the feathered band. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
Dragging the tolerance slider ran the whole pipeline on every pointer sample — a test drag queues three — and each run is a flood fill, a connected-components pass, a resample and 28 full-sheet transforms on the renderer's own thread. The thumb still tracks the drag; the build now waits for onValueCommit, so a gesture costs one build. Arrow keys commit per press, so the keyboard rebuilds immediately as before. Overlapping runs also had no ordering: an older one finishing last replaced the newer sheet, and the first `finally` cleared the spinner while another build was still going. A run counter gates setDraft, setError and setBusy, and the reset-on-open bumps it so nothing in flight can repopulate a dialog that was just cleared. Not moved off the main thread: with one build per gesture the stall is a single hitch inside a dialog that already shows a spinner, and a worker would mean a new build entry, structured-cloning sheets both ways, and a second path to the bundled artwork. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The surface was a bare div with pointer handlers: no role, no tabIndex, no name, no keyboard path at all, so framing — the one thing the pipeline cannot derive — was mouse-only. It is now a focusable role="application" region (the arrows are the whole interaction; a browse cursor would eat them) with the arrow keys moving the frame, Shift with them resizing it, and a centred box appearing on the first press when nothing is framed yet. Committing on keyup rather than keydown is the same bargain the drag makes by committing on pointerup: an autorepeating arrow is one framing and one rebuild, not one per repeat. A gesture the OS takes away sends no pointerup, so pointercancel and lostpointercapture now clear the drag instead of leaving the rectangle stuck mid-drag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
The reset added in a6829c9 was an effect keyed on `open`, which React Doctor flags for the reason it is worth flagging: it clears the last picture a frame after the dialog is already on screen. Radix unmounts DialogContent on close, so moving the picking state into a form component rendered underneath it makes every reopen a fresh mount and the effect unnecessary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
Three cases drove a single 1000ms frame as shorthand for a second of walking. The walk now clamps a frame to 50ms, so that shorthand advances the pet 1.8px instead of 36px. `stepWalkFrames` carries the reason at its definition, so the loop does not read as an arbitrary elaboration of two calls. The edge-turn case stops at 600ms on purpose: twelve clamped frames cover the 20px to the right edge, and a thirteenth is the pet already walking back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
…nimations # Conflicts: # src/main/runtime/agent-prompt-submission-runtime.test.ts
Four clusters, all introduced by the origin/main merge: - config/scripts/vitest-host-ports-setup.ts left 28 suites failing on rmSync EPERM. POSIX lets an open file be unlinked; Windows does not, and a suite that keeps an OrchestrationDb open owns a handle in that scratch dir. Scratch cleanup under tmpdir must not fail a suite that passed. - core.longpaths=true (stablyai#15866) is prefixed to worktree add on Windows, so argv literals in three suites could only be right on one platform; they now derive the prefix from windowsLongPathGitArgs. - status-submodule-path-cache matched argv[0] for the subcommand, which is '-c' once that prefix is there. - The new Hermes skills-root cases used drive-less path literals; the file already has a VOLUME constant for exactly that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3oGCiyWVfKj1DXK2hTFTH
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/renderer/src/components/status-bar/PetStatusSegment.tsx (1)
90-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
choosePetin the pet-registration paths.handleGeneratedPet,handleImport, andhandleImportPetBundlerepeat the same show-then-select sequence thatchoosePetalready implements.♻️ Proposed refactor
const handleGeneratedPet = (model: Parameters<typeof addCustomPet>[0]): void => { addCustomPet(model) - if (!petVisible) { - setPetVisible(true) - } - setPetId(model.id) + choosePet(model.id) }src/relay/skill-upload-multi-relay.integration.test.ts (1)
94-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten the
retirecomment.Keep the cross-platform reason, but reduce this six-line JSDoc to one sentence.
Proposed change
-/** Ends a relay the way both platforms support. - * - * Why not `kill('SIGTERM')`: Windows has no signal delivery, so `kill` is a - * TerminateProcess and the relay's SIGTERM handler -- the thing that disposes - * its staging directory -- never runs. Closing stdin reaches the same - * `shutdown()` on every host; the short idle grace is what makes it prompt. */ +/** Close stdin so relay cleanup runs on Windows and POSIX. */As per coding guidelines: “Comments must be concise, non-obvious, and brief—prefer one line.”
Source: Coding guidelines
src/main/git/status-branch-line-total-exec-contract.test.ts (1)
146-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReduce the changed explanatory comments.
Replace each multi-line walkthrough with one concise comment that states only the non-obvious constraint.
src/main/git/status-branch-line-total-exec-contract.test.ts#L146-L148: reduce the cleanup rationale to one line.src/main/git/status-shared-symlinks.test.ts#L16-L19: reduce the capability-gating rationale to one line.src/main/git/worktree-shared-directories.test.ts#L26-L28: reduce the Git config-path rationale to one line.src/main/git/worktree-shared-directories.test.ts#L34-L35: reduce the stderr rationale to one line.src/main/ipc/filesystem-list-files-git-fallback-real.test.ts#L68-L72: reduce the cleanup rationale to one line.src/main/ipc/filesystem-list-files-git-fallback-real.test.ts#L165-L167: reduce the assertion rationale to one line.src/main/ssh/ssh-remote-commands.test.ts#L299-L303: reduce the ordering rationale to one line.src/main/ssh/ssh-remote-node-resolution.test.ts#L189-L192: reduce the capability rationale to one line.src/main/text-generation/commit-message-text-generation-model-discovery.test.ts#L37-L42: reduce the helper documentation to one concise comment.src/main/text-generation/commit-message-text-generation-model-discovery.test.ts#L45-L46: reduce the environment-restoration rationale to one line.As per coding guidelines: “Comments must be concise, non-obvious, and brief—prefer one line; do not explain obvious behavior or walk through code.”
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 611db604-3b17-465a-82ab-38ad9496dd11
📒 Files selected for processing (230)
.gitattributes.github/workflows/pr.yml.gitignoreconfig/scripts/check-changed-code-quality.mjsconfig/scripts/check-react-doctor-changed.mjsconfig/scripts/ensure-native-runtime.test.mjsconfig/scripts/lint-react-doctor-changed.mjsconfig/scripts/mobile-pairing-qrcode-import-plugin.test.mjsconfig/scripts/pr-workflow-parallelism.test.mjsconfig/scripts/rebuild-native-deps.test.mjsconfig/scripts/regenerate-xterm-patches.mjsconfig/scripts/regenerate-xterm-patches.test.mjsconfig/scripts/run-ai-vault-typing-bench.mjsconfig/scripts/verify-skills-cli-runtime.test.mjsconfig/scripts/vitest-host-ports-setup.tsconfig/scripts/windows-suite-baseline.mjsconfig/scripts/windows-suite-baseline.test.mjsconfig/windows-suite-baseline.jsondocs/windows-test-suite-remediation-plan.mdresources/claude-held.webpresources/claude-poses.webpresources/gremlin-held.webpresources/gremlin-poses.webpresources/opencode-held.webpresources/opencode-poses.webpsrc/main/agent-hooks/installer-utils.test.tssrc/main/agent-hooks/managed-hook-stdin-lifecycle.test.tssrc/main/ai-vault/codex-session-root-dedup.tssrc/main/ai-vault/session-delete-target.test.tssrc/main/ai-vault/session-delete.test.tssrc/main/ai-vault/session-scanner-codex-dual-root.test.tssrc/main/ai-vault/session-scanner-opencode-sources-wsl-stall.test.tssrc/main/ai-vault/session-scanner-values.test.tssrc/main/ai-vault/session-scanner-values.tssrc/main/ai-vault/session-scanner.test.tssrc/main/artifacts/artifact-create-intent-store.test.tssrc/main/bitbucket/credential-store.test.tssrc/main/claude-accounts/runtime-auth-service-materialization.test.tssrc/main/cli/linux-bare-orca-dispatcher.test.tssrc/main/cli/linux-terminal-orca-cli-shim.test.tssrc/main/codex-accounts/runtime-home-mirrored-status-home.test.tssrc/main/codex-accounts/runtime-home-service-per-account-migration.test.tssrc/main/codex/codex-session-resume-home.test.tssrc/main/codex/codex-session-resume-preparation.test.tssrc/main/daemon/client.tssrc/main/daemon/daemon-client-connect-attempt.test.tssrc/main/daemon/daemon-client-connect-attempt.tssrc/main/daemon/daemon-preflight-client-replacement.test.tssrc/main/daemon/daemon-pty-adapter.tssrc/main/daemon/pty-subprocess-cwd-cancel-identity.test.tssrc/main/daemon/pty-subprocess-env-inheritance.test.tssrc/main/daemon/pty-subprocess-foreground-identity.test.tssrc/main/daemon/pty-subprocess-foreground-scan-cadence.test.tssrc/main/daemon/pty-subprocess-handle-lifecycle.test.tssrc/main/daemon/pty-subprocess-wsl-launch.test.tssrc/main/daemon/pty-subprocess.test.tssrc/main/devin/hook-service.test.tssrc/main/durable-file-write-syscall-proof.test.tssrc/main/ephemeral-vm-runtime-service.test.tssrc/main/git/porcelain-v1-records.test.tssrc/main/git/repo-detection.test.tssrc/main/git/status-branch-line-total-exec-contract.test.tssrc/main/git/status-shared-symlinks.test.tssrc/main/git/status-submodule-path-cache.test.tssrc/main/git/worktree-add-local-base-refresh.test.tssrc/main/git/worktree-add-local-base-suggestion.test.tssrc/main/git/worktree-deferred-removal-real-git.test.tssrc/main/git/worktree-include-file.test.tssrc/main/git/worktree-shared-directories.test.tssrc/main/ipc/ephemeral-vm.test.tssrc/main/ipc/filesystem-list-files-git-fallback-real.test.tssrc/main/ipc/filesystem-watcher-local-unsubscribe.test.tssrc/main/ipc/folder-repo-git-upgrade.test.tssrc/main/ipc/local-network-connection-test.test.tssrc/main/ipc/native-chat.test.tssrc/main/ipc/pet-generated-write.tssrc/main/ipc/pet-image-signature.test.tssrc/main/ipc/pet-image-signature.tssrc/main/ipc/pet-orphan-sweep-disk.test.tssrc/main/ipc/pet-orphan-sweep.test.tssrc/main/ipc/pet-orphan-sweep.tssrc/main/ipc/pet.test.tssrc/main/ipc/pet.tssrc/main/ipc/pty-codex-account-attribution.test.tssrc/main/ipc/pty-daemon-spawn-wsl-runtime.test.tssrc/main/ipc/pty-login-shell-startup-commands.test.tssrc/main/ipc/pty-spawn-env-codex-resume-provenance.test.tssrc/main/ipc/pty-wsl-cwd-validation.test.tssrc/main/ipc/register-core-handlers.test.tssrc/main/ipc/register-core-handlers.tssrc/main/ipc/worktree-base-directory-event-filter.test.tssrc/main/ipc/worktree-base-directory-watcher.test.tssrc/main/ipc/worktree-symlinks.test.tssrc/main/ipc/worktree-symlinks.tssrc/main/kimi/hook-service.test.tssrc/main/local-builds/local-build-candidate.test.tssrc/main/local-downloaded-folder-promotion.test.tssrc/main/native-chat/transcript-read-cache.test.tssrc/main/native-chat/transcript-watch-error.test.tssrc/main/providers/local-pty-shell-ready-wrapper-generation.test.tssrc/main/pty/codex-shell-launch-preflight.test.tssrc/main/rate-limits/codex-fetcher.test.tssrc/main/rate-limits/service-account-target-selection.test.tssrc/main/rate-limits/service-inactive-account-previews.test.tssrc/main/rate-limits/service-refresh-orchestration.test.tssrc/main/runtime/agent-session-claim-identity.test.tssrc/main/runtime/exit-provenance-audit.test.tssrc/main/runtime/orca-runtime-files-terminal-artifact-grants.test.tssrc/main/runtime/orca-runtime-files-terminal-artifact-io.test.tssrc/main/runtime/orca-runtime-files-terminal-link-host-translation.test.tssrc/main/runtime/orca-runtime.test.tssrc/main/runtime/orca-runtime.tssrc/main/runtime/rpc/methods/ai-vault.test.tssrc/main/runtime/rpc/methods/client-ui-schemas.tssrc/main/skills/skill-bundle-creation.test.tssrc/main/skills/skill-bundle-creation.tssrc/main/skills/skill-provider-runtime-roots.test.tssrc/main/source-control/hosted-review-creation-shared-symlinks.test.tssrc/main/ssh/ssh-g-config-resolution.test.tssrc/main/ssh/ssh-relay-upload-stage-commands.test.tssrc/main/ssh/ssh-remote-commands.test.tssrc/main/ssh/ssh-remote-node-resolution.test.tssrc/main/ssh/ssh-system-fallback.test.tssrc/main/ssh/system-ssh-forward-process.test.tssrc/main/text-generation/commit-message-text-generation-model-discovery.test.tssrc/main/updater-test-harness.tssrc/main/updater.build-channel-selection.test.tssrc/main/updater.check-preflight.test.tssrc/main/updater.check-settlement.test.tssrc/main/updater.linux-root-package-install.test.tssrc/main/updater.nudge-campaign.test.tssrc/main/updater.prerelease-fallback.test.tssrc/main/updater.publishing-window-feed.test.tssrc/main/updater.quit-and-install.test.tssrc/main/updater.silent-settle.test.tssrc/main/updater.startup-scheduling.test.tssrc/main/updater.timer-disposal.test.tssrc/main/updater.tssrc/main/window/clipboard-file-copy.test.tssrc/preload/api/pet-api.tssrc/preload/index.tssrc/relay/ai-vault-handler.test.tssrc/relay/ai-vault-service-client.test.tssrc/relay/fs-handler-mock-dispatcher.tssrc/relay/fs-handler.test.tssrc/relay/git-handler-worktree-provisioning.test.tssrc/relay/rotating-log-writer.test.tssrc/relay/skill-upload-multi-relay.integration.test.tssrc/relay/subprocess.test.tssrc/relay/terminal-history-wsl.test.tssrc/renderer/src/app-startup-routing.test.tssrc/renderer/src/components/editor/monaco-content-sync.undo-history.test.tssrc/renderer/src/components/pet/DetectedSpriteFrame.tsxsrc/renderer/src/components/pet/PetFromImageDialog.test.tsxsrc/renderer/src/components/pet/PetFromImageDialog.tsxsrc/renderer/src/components/pet/PetOverlay.held-pose.test.tsxsrc/renderer/src/components/pet/PetOverlay.tsxsrc/renderer/src/components/pet/PetOverlay.walk.test.tsxsrc/renderer/src/components/pet/PetSheetPreview.test.tsxsrc/renderer/src/components/pet/PetSheetPreview.tsxsrc/renderer/src/components/pet/PetSourceCrop.test.tsxsrc/renderer/src/components/pet/PetSourceCrop.tsxsrc/renderer/src/components/pet/bundled-pet-pose-sprite.test.tssrc/renderer/src/components/pet/bundled-pet-pose-sprite.tssrc/renderer/src/components/pet/pet-agent-state.tssrc/renderer/src/components/pet/pet-blob-cache.tssrc/renderer/src/components/pet/pet-body-motion-css.test.tssrc/renderer/src/components/pet/pet-body-motion-css.tssrc/renderer/src/components/pet/pet-crop-drag.test.tssrc/renderer/src/components/pet/pet-crop-drag.tssrc/renderer/src/components/pet/pet-cutout-quality.test.tssrc/renderer/src/components/pet/pet-cutout-quality.tssrc/renderer/src/components/pet/pet-fall-physics.test.tssrc/renderer/src/components/pet/pet-fall-physics.tssrc/renderer/src/components/pet/pet-from-image-message.test.tssrc/renderer/src/components/pet/pet-from-image-message.tssrc/renderer/src/components/pet/pet-from-image.test.tssrc/renderer/src/components/pet/pet-from-image.tssrc/renderer/src/components/pet/pet-image-crop.test.tssrc/renderer/src/components/pet/pet-image-crop.tssrc/renderer/src/components/pet/pet-image-cutout.test.tssrc/renderer/src/components/pet/pet-image-cutout.tssrc/renderer/src/components/pet/pet-image-decode.tssrc/renderer/src/components/pet/pet-image-resample.test.tssrc/renderer/src/components/pet/pet-image-resample.tssrc/renderer/src/components/pet/pet-magenta-key.test.tssrc/renderer/src/components/pet/pet-magenta-key.tssrc/renderer/src/components/pet/pet-models.test.tssrc/renderer/src/components/pet/pet-models.tssrc/renderer/src/components/pet/pet-pose-arbitration.test.tssrc/renderer/src/components/pet/pet-pose-arbitration.tssrc/renderer/src/components/pet/pet-raster-transform.test.tssrc/renderer/src/components/pet/pet-raster-transform.tssrc/renderer/src/components/pet/pet-rig-detection.test.tssrc/renderer/src/components/pet/pet-rig-detection.tssrc/renderer/src/components/pet/pet-rigs.test.tssrc/renderer/src/components/pet/pet-rigs.tssrc/renderer/src/components/pet/pet-sheet-composer.test.tssrc/renderer/src/components/pet/pet-sheet-composer.tssrc/renderer/src/components/pet/pet-walk-lane.test.tssrc/renderer/src/components/pet/pet-walk-lane.tssrc/renderer/src/components/pet/sprite-animation-css.test.tssrc/renderer/src/components/pet/sprite-animation-css.tssrc/renderer/src/components/pet/usePetFallToLane.tssrc/renderer/src/components/pet/usePetPointerInteraction.test.tssrc/renderer/src/components/pet/usePetPointerInteraction.tssrc/renderer/src/components/pet/usePetUrl.load-failure.test.tssrc/renderer/src/components/pet/usePetUrl.tssrc/renderer/src/components/pet/usePetWalkLane.test.tssrc/renderer/src/components/pet/usePetWalkLane.tssrc/renderer/src/components/pull-request-page-host-boundary.test.tssrc/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.tssrc/renderer/src/components/status-bar/PetStatusSegment.test.tsxsrc/renderer/src/components/status-bar/PetStatusSegment.tsxsrc/renderer/src/components/terminal/initial-terminal-wiring.test.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/store/slices/ui-hydration-workspace-preferences.test.tssrc/renderer/src/store/slices/ui.tssrc/shared/file-mode-capability.test.tssrc/shared/file-mode-capability.tssrc/shared/node-markdown-document-discovery.test.tssrc/shared/persisted-ui-state-types.tssrc/shared/posix-shell.test.tssrc/shared/posix-shell.tssrc/shared/probe-scratch-directory.tssrc/shared/symlink-capability.test.tssrc/shared/symlink-capability.tssrc/shared/wsl-login-shell-command.test.tstests/e2e/cross-version-wire/release-checkout.tstests/e2e/helpers/nested-runtime-proxy-jump-fixture.unit.test.ts
💤 Files with no reviewable changes (1)
- src/main/ipc/worktree-symlinks.ts
🚧 Files skipped from review as they are similar to previous changes (181)
- src/preload/index.ts
- src/main/updater.check-settlement.test.ts
- src/main/updater.nudge-campaign.test.ts
- src/relay/subprocess.test.ts
- src/main/rate-limits/codex-fetcher.test.ts
- src/main/local-builds/local-build-candidate.test.ts
- src/main/ipc/pty-daemon-spawn-wsl-runtime.test.ts
- src/main/daemon/daemon-pty-adapter.ts
- src/main/ai-vault/session-scanner-codex-dual-root.test.ts
- src/renderer/src/components/right-sidebar/SourceControl.host-context-boundary.test.ts
- src/main/runtime/agent-session-claim-identity.test.ts
- src/renderer/src/app-startup-routing.test.ts
- src/renderer/src/components/pet/pet-models.test.ts
- src/main/ipc/pet-orphan-sweep.test.ts
- src/main/updater.prerelease-fallback.test.ts
- src/main/ipc/folder-repo-git-upgrade.test.ts
- src/main/rate-limits/service-refresh-orchestration.test.ts
- .gitattributes
- tests/e2e/helpers/nested-runtime-proxy-jump-fixture.unit.test.ts
- src/renderer/src/components/pet/pet-magenta-key.test.ts
- config/windows-suite-baseline.json
- src/renderer/src/components/pet/pet-raster-transform.ts
- src/main/daemon/pty-subprocess-foreground-identity.test.ts
- config/scripts/pr-workflow-parallelism.test.mjs
- src/shared/probe-scratch-directory.ts
- config/scripts/windows-suite-baseline.test.mjs
- src/main/ipc/pet-image-signature.ts
- src/relay/rotating-log-writer.test.ts
- src/renderer/src/components/pet/pet-image-resample.ts
- src/renderer/src/components/pull-request-page-host-boundary.test.ts
- src/main/ai-vault/codex-session-root-dedup.ts
- src/main/agent-hooks/installer-utils.test.ts
- src/main/updater.check-preflight.test.ts
- src/main/claude-accounts/runtime-auth-service-materialization.test.ts
- src/main/codex-accounts/runtime-home-service-per-account-migration.test.ts
- src/main/daemon/daemon-preflight-client-replacement.test.ts
- src/main/cli/linux-terminal-orca-cli-shim.test.ts
- src/main/ephemeral-vm-runtime-service.test.ts
- src/main/updater.timer-disposal.test.ts
- src/main/agent-hooks/managed-hook-stdin-lifecycle.test.ts
- src/main/runtime/orca-runtime-files-terminal-link-host-translation.test.ts
- config/scripts/check-react-doctor-changed.mjs
- config/scripts/run-ai-vault-typing-bench.mjs
- src/main/kimi/hook-service.test.ts
- src/main/ipc/pty-spawn-env-codex-resume-provenance.test.ts
- src/renderer/src/components/pet/pet-rig-detection.test.ts
- src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts
- src/main/ai-vault/session-scanner-values.ts
- src/renderer/src/components/pet/pet-blob-cache.ts
- src/renderer/src/components/pet/pet-pose-arbitration.test.ts
- src/main/updater.silent-settle.test.ts
- src/main/git/worktree-include-file.test.ts
- src/renderer/src/components/pet/DetectedSpriteFrame.tsx
- src/renderer/src/components/pet/pet-agent-state.ts
- src/shared/file-mode-capability.ts
- src/main/ai-vault/session-scanner-opencode-sources-wsl-stall.test.ts
- src/renderer/src/components/pet/pet-walk-lane.test.ts
- src/main/updater.linux-root-package-install.test.ts
- src/shared/node-markdown-document-discovery.test.ts
- src/main/updater.publishing-window-feed.test.ts
- src/main/runtime/orca-runtime-files-terminal-artifact-io.test.ts
- src/relay/ai-vault-service-client.test.ts
- src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts
- src/relay/ai-vault-handler.test.ts
- src/renderer/src/components/pet/pet-from-image-message.test.ts
- src/main/cli/linux-bare-orca-dispatcher.test.ts
- config/scripts/check-changed-code-quality.mjs
- src/main/ai-vault/session-scanner.test.ts
- src/main/runtime/exit-provenance-audit.test.ts
- src/main/ipc/register-core-handlers.ts
- src/renderer/src/components/terminal/initial-terminal-wiring.test.ts
- src/main/codex-accounts/runtime-home-mirrored-status-home.test.ts
- src/main/devin/hook-service.test.ts
- src/main/skills/skill-bundle-creation.test.ts
- src/main/ipc/ephemeral-vm.test.ts
- src/renderer/src/components/pet/pet-fall-physics.test.ts
- src/main/runtime/orca-runtime.test.ts
- src/renderer/src/components/pet/pet-crop-drag.test.ts
- src/relay/terminal-history-wsl.test.ts
- config/scripts/verify-skills-cli-runtime.test.mjs
- src/renderer/src/store/slices/ui-hydration-workspace-preferences.test.ts
- src/main/daemon/pty-subprocess.test.ts
- src/preload/api/pet-api.ts
- src/main/pty/codex-shell-launch-preflight.test.ts
- src/main/ai-vault/session-scanner-values.test.ts
- src/main/durable-file-write-syscall-proof.test.ts
- src/main/source-control/hosted-review-creation-shared-symlinks.test.ts
- src/renderer/src/components/pet/bundled-pet-pose-sprite.test.ts
- src/renderer/src/components/pet/pet-sheet-composer.test.ts
- src/main/updater.quit-and-install.test.ts
- src/main/daemon/pty-subprocess-foreground-scan-cadence.test.ts
- src/main/rate-limits/service-inactive-account-previews.test.ts
- src/main/ipc/pet-orphan-sweep-disk.test.ts
- src/main/window/clipboard-file-copy.test.ts
- src/main/daemon/pty-subprocess-env-inheritance.test.ts
- config/scripts/lint-react-doctor-changed.mjs
- src/renderer/src/components/pet/pet-rigs.test.ts
- src/main/daemon/pty-subprocess-cwd-cancel-identity.test.ts
- src/main/updater.startup-scheduling.test.ts
- config/scripts/regenerate-xterm-patches.mjs
- config/scripts/ensure-native-runtime.test.mjs
- src/main/runtime/rpc/methods/client-ui-schemas.ts
- src/renderer/src/components/pet/pet-body-motion-css.test.ts
- src/main/ssh/ssh-system-fallback.test.ts
- src/main/daemon/daemon-client-connect-attempt.ts
- src/renderer/src/components/pet/pet-models.ts
- src/renderer/src/components/pet/pet-from-image-message.ts
- src/renderer/src/components/pet/pet-crop-drag.ts
- src/renderer/src/components/pet/sprite-animation-css.test.ts
- config/scripts/mobile-pairing-qrcode-import-plugin.test.mjs
- src/main/daemon/pty-subprocess-wsl-launch.test.ts
- src/main/native-chat/transcript-read-cache.test.ts
- src/renderer/src/components/pet/pet-image-crop.test.ts
- src/renderer/src/components/pet/pet-pose-arbitration.ts
- src/renderer/src/components/pet/pet-raster-transform.test.ts
- src/main/ai-vault/session-delete.test.ts
- src/main/git/worktree-deferred-removal-real-git.test.ts
- config/scripts/regenerate-xterm-patches.test.mjs
- src/main/ipc/native-chat.test.ts
- .gitignore
- src/shared/symlink-capability.ts
- src/main/ipc/pty-codex-account-attribution.test.ts
- src/main/codex/codex-session-resume-preparation.test.ts
- src/renderer/src/i18n/locales/en.json
- config/scripts/rebuild-native-deps.test.mjs
- src/main/bitbucket/credential-store.test.ts
- src/main/ipc/pet-generated-write.ts
- src/main/runtime/orca-runtime-files-terminal-artifact-grants.test.ts
- src/main/runtime/orca-runtime.ts
- src/main/ipc/worktree-base-directory-event-filter.test.ts
- src/renderer/src/components/pet/usePetPointerInteraction.ts
- src/main/rate-limits/service-account-target-selection.test.ts
- src/renderer/src/components/pet/PetOverlay.walk.test.tsx
- src/renderer/src/components/pet/sprite-animation-css.ts
- src/renderer/src/components/pet/pet-fall-physics.ts
- src/renderer/src/components/pet/pet-image-cutout.test.ts
- src/main/skills/skill-bundle-creation.ts
- src/renderer/src/components/pet/usePetPointerInteraction.test.ts
- src/main/ai-vault/session-delete-target.test.ts
- src/renderer/src/components/pet/pet-walk-lane.ts
- src/main/updater-test-harness.ts
- src/main/ipc/worktree-base-directory-watcher.test.ts
- src/main/updater.ts
- src/renderer/src/components/pet/pet-image-resample.test.ts
- src/main/daemon/client.ts
- src/renderer/src/components/pet/usePetFallToLane.ts
- src/shared/persisted-ui-state-types.ts
- config/scripts/windows-suite-baseline.mjs
- src/main/updater.build-channel-selection.test.ts
- src/renderer/src/components/pet/pet-cutout-quality.ts
- src/main/local-downloaded-folder-promotion.test.ts
- src/shared/file-mode-capability.test.ts
- src/renderer/src/components/pet/bundled-pet-pose-sprite.ts
- src/renderer/src/components/pet/pet-image-decode.ts
- src/renderer/src/components/pet/pet-magenta-key.ts
- src/shared/posix-shell.ts
- src/main/native-chat/transcript-watch-error.test.ts
- src/main/ssh/ssh-g-config-resolution.test.ts
- src/main/daemon/pty-subprocess-handle-lifecycle.test.ts
- src/main/ipc/pty-login-shell-startup-commands.test.ts
- src/renderer/src/components/pet/pet-rig-detection.ts
- src/main/ipc/pet-orphan-sweep.ts
- src/renderer/src/components/pet/pet-rigs.ts
- src/renderer/src/components/pet/pet-image-cutout.ts
- src/main/ipc/local-network-connection-test.test.ts
- src/renderer/src/components/pet/PetOverlay.held-pose.test.tsx
- src/renderer/src/components/pet/pet-body-motion-css.ts
- src/main/codex/codex-session-resume-home.test.ts
- src/main/ipc/pet.test.ts
- src/renderer/src/store/slices/ui.ts
- src/renderer/src/components/pet/PetSheetPreview.tsx
- src/renderer/src/components/pet/pet-sheet-composer.ts
- src/main/runtime/rpc/methods/ai-vault.test.ts
- src/main/ipc/worktree-symlinks.test.ts
- src/main/ipc/pty-wsl-cwd-validation.test.ts
- .github/workflows/pr.yml
- src/renderer/src/components/pet/usePetUrl.ts
- src/renderer/src/components/pet/PetOverlay.tsx
- src/main/ssh/system-ssh-forward-process.test.ts
- src/main/ipc/filesystem-watcher-local-unsubscribe.test.ts
- src/renderer/src/components/pet/pet-from-image.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| 1. Write `config/scripts/windows-suite-baseline.mjs` that runs the suite, | ||
| strips ANSI, and emits a sorted `file → failing-test-count` JSON. | ||
| 2. Commit the current output as `docs/reference/windows-suite-baseline.json`. | ||
| 3. Add a `compare` mode that diffs a fresh run against the committed baseline | ||
| and exits non-zero **only on files that got worse or are newly failing**. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the baseline artifact path.
This plan directs contributors to docs/reference/windows-suite-baseline.json, but this PR stores the baseline at config/windows-suite-baseline.json. Updating the documented path would leave the actual comparison input stale. Update this reference and the final checklist reference at Line 686.
…nimations # Conflicts: # src/main/runtime/orca-runtime.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/runtime/orca-runtime.ts (1)
13422-13466: 🩺 Stability & Availability | 🟠 MajorForward
ProviderSnapshotReadOptionsthroughreadVisibleTerminalStateThe adopted-PTY path calls
readVisibleTerminalState(ptyId)without the probe options. Thus,onAcquisitiondoes not populatewaiter.probedAcquisition, and timeout retirement cannot run for this path. Pass the options through, or narrow the probe documentation to the fallback paths that support retirement. [medium_effort_and_high_reward]
🧹 Nitpick comments (1)
src/main/runtime/orca-runtime.test.ts (1)
20864-20967: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueDocument the non-mode-switch tail exclusion.
Add one short comment to the stale-snapshot tests. Provider frames are discarded when live output advances
snapshot.seq; live mode transitions instead use the headless emulator.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: df0fffb7-2b09-4d98-b605-cc6adae52e59
📒 Files selected for processing (2)
src/main/runtime/orca-runtime.test.tssrc/main/runtime/orca-runtime.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.
78 commits, two things: the Windows test suite goes from 289 failures to zero, and the image-to-pet creation feature this branch originally existed for.
The Windows work is the bulk of it. Full write-up, per-failure verdicts and reproduction steps live in
docs/windows-test-suite-remediation-plan.md— this body is orientation, not a transcript.The numbers
Measured with a full
vitest runon Windows 11 Pro (Node 24.16.0, Git 2.54.0.windows.1, pnpm 10.24.0). The 99/289 baseline was verified identical onmainand on this branch — none of it came from the feature work; it was accumulated platform rot that had never been visible, becausepr.ymlran the full suite only onubuntu-latestand the onewindows-2022job ran seven hand-picked files.What
config/windows-suite-baseline.json = {}means for youThe baseline file records which Windows tests are expected to fail. It is now
{}. Any Windows failure from here is a regression introduced by the change that caused it, not inherited rot you have to triage before you can tell whether your PR is at fault. That is the whole point of driving it to zero rather than to "fewer": a non-empty baseline is a list you have to read; an empty one is a gate.Product bugs found and fixed
These are real defects in Orca, not test problems. The tests were right; production was wrong.
1. An NTFS inode is bigger than a safe integer —
7d2ae74,src/main/ai-vault/codex-session-root-dedup.tscodexRolloutHardlinkIdentityrejected anystatwhoseinofailedNumber.isSafeInteger. A Windowsst_inois the 64-bit NTFS file index and routinely exceeds 2^53 — measured on this host at14073748837253212. The guard therefore returnednullfor every rollout, and pre-parse dual-root dedup was dead code on Windows entirely. Consequences beyond the failing test: bridged and backfilled aliases were never collapsed, so they ate the parse budget and pushed real sessions out of the listing, and the per-account resume repin never ran.devandnlinkkeep the strict check; the composite alias key still pins the execution namespace and the session UUID, so the relaxation cannot cause a false merge.2. A daemon respawn race —
0d64d92,src/main/daemon/client.tsA
DaemonClientconnect against an already-dead endpoint could still be in flight when respawn published its replacement, andensureConnectedjoined it unconditionally — so the retried spawn inherited the dead endpoint'sconnect ENOENT, which reads asisDaemonGoneError, which forked a second respawn. Generation checks alone don't catch it: the stale attempt starts afterdoRespawn's pre-respawndisconnect(), so it looks current.doRespawnnow retires the in-flight attempt oncerespawnFnreturns. The race is not Windows-specific — Windows only exposed it, because connecting to a missing named pipe fails far more slowly than to a missing unix socket, which widens the window. Fixed entirely in production; the test file is unchanged.3. A tui-idle probe leaked its provider buffer when the waiter died —
557613c,src/main/runtime/orca-runtime.tsThe probe's retire timer (
waiterMs - 1) raced the waiter's rejection timer (waiterMs). When the rejection won,readTerminalfound the acquisition un-retired, took the unboundedreturn acquisition.promisepath, and blocked on a capture whose only consumer had already given up — a 30 s hang. The 1 ms margin never guaranteed anything: Node stamps a timer's deadline from a libuv clock that advances mid-turn, so 0.1–1.7 ms of synchronous work between the twosetTimeoutcalls consumes the entire margin whenever it crosses a millisecond boundary. The retire is now anchored to the waiter's timeout death — a causal event, not a timer. Fullorca-runtime.test.tsunder sweep-shaped load: 5/10 hangs before, 0/36 after.Test-portability fixes (the other ~90%)
Separate category, kept deliberately separate. These are tests that hardcoded POSIX assumptions while Orca itself was fine: path separators and volume roots, CRLF folding where a test reads source or a model's value, fixtures rooted somewhere Windows doesn't recognise, tests reading the developer's own shell, one asserting an NTFS directory enumeration order the command never promised.
Two rules were held throughout, and they are why this isn't a
skipIfsweep:it.skipIf(process.platform === 'win32')is not a fix unless the behaviour is POSIX-only by design. 61 symlink tests across 14 files were stranded by Windows refusingCreateSymbolicLinkwithout elevation — they are gated on the detected capability, not the platform, precisely so they still run on an elevated CI runner. Most of that turned out to be a genuine fix rather than a gate: a junction needs no privilege, so every directory link now asks for one, recovering 26 tests outright.What is not claimed here
test_windowsstill carriescontinue-on-error: true. This PR does not flip it, and you should not read the zero as "the gate is on".The zero is measured on a developer machine. A GitHub runner differs in ways that move the numbers — most importantly it is elevated, so the ~61 symlink tests that skip locally will execute on the runner for the first time ever. This PR's
test_windowsrun is the first real look at them. A follow-up flipscontinue-on-erroroff once the runner's own results have been read and anything it surfaces is closed. Turning it into a blocking gate on the strength of a local number would be asserting something nobody has measured.For the same reason the job does no baseline comparison: the baseline is for local iteration, and the job reports what the runner itself sees.
Reproducing the zero locally — the node-pty precondition
If you run the suite on Windows and get four failures in the two
.win32job-object suites, this is why. A plainpnpm installwill not produce the zero. node-pty prefers its upstream prebuild, which does not contain Orca's Windows job-object patch, andrequiresPatchedNodePtySourceBuild()returnsfalseon win32. You need:This is documented in the "Reproduce" section of
docs/windows-test-suite-remediation-plan.md, along with the corepackpnpmshim the sweep scripts need and the full-sweep command itself (~41 min). CI's Windows lane already builds from source, so this affects local reproduction only.(Related, and left as upstream's to fix: because that predicate returns
falseon win32, a normal Windows install of Orca also runs an unpatched node-pty, so PTY teardown silently falls back to the pid-walk. Out of scope here — noted so it isn't lost.)Also in this branch: image-to-pet creation
The feature the branch was opened for. A pet can now be built from an uploaded image: chroma-key cutout with halo fade and magenta-cast drain, character framing with a configurable background-removal amount, head-swap and leg-rigged build modes, a preview before saving, persistence over its own IPC channel, and a quality gate on uploaded cutouts. Alongside it, the pet overlay animation work — gravity drop and flick-throw with squash on landing, a walking leg cycle, topple-and-get-up on a real drop, a four-row pose sheet driven by agent state, and a user setting to turn pacing and drop-to-floor off.
Reviewing 78 commits
fix(...)is a production change,test(windows): ...is portability,docs(windows): ...is the plan doc's running record,feat(pet): ...is the feature.