Resolve the Changes-view conflict without reverting branch or commit comparisons. Pair canonical action patches with displayed blob identities, refresh all path views after mutation, exclude historical snapshots, and reject stale or multi-file patches at the server boundary.
Preserve CRLF bytes and make long hunk menus keyboard-reachable. Workspace type-check, lint and build passed; focused parser, menu, view and real Git regressions passed.
Branch comparisons could retain an old base or omit local edits, while Changes and walkthrough selected their sources independently.
Share branch and commit selectors across both panels, honor exact refs, include local branch edits, and support first-parent commit diffs with the latest 50 commits. Compact message metadata and move touch actions into a shared sheet.
Validated with workspace type-check, lint and build, focused Git and UI tests, and maintainer testing in the app.
simple-git 3.35/3.36 moved its unsafe-config blocklist into
@simple-git/argv-parser and expanded it to include credential.helper.
setLocalIdentity already opted in for the SSH branch (core.sshCommand)
via createGit({ allowUnsafeSshCommand: true }), but the token branch
(addConfig("credential.helper", "store")) was left without the matching
opt-in, so switching to a token-auth identity throws:
Configuring credential.helper is not permitted without enabling
allowUnsafeCredentialHelper
Forward a new allowUnsafeCredentialHelper option through createGit and
enable it in setLocalIdentity alongside the existing SSH opt-in. Cover
the token branch (and the token -> SSH cleanup) with tests mirroring
the existing SSH case.
* fix(sessions): keep a shared chat directory until its last session is deleted
Deleting a root chat session removed its managed scratch directory even
when forks, side threads, or subagents still lived in it; OpenCode then
failed every prompt in those sessions with FileSystem.realPath NotFound.
The directory is now removed only once no other known session resolves
to it. The deleted subtree does not count, because the server cascade-
deletes it, and an unloaded global cache keeps the directory instead of
guessing.
Closes#3312.
* fix(sessions): relocate a session whose worktree directory disappeared
A worktree removed outside OpenChamber, by the agent or by hand, left its
sessions pointed at a path that no longer exists: every terminal create
and restart failed with "Invalid working directory" and the tab stayed
stuck, while Git, Files, and prompts kept targeting the dead path.
The terminal server now names that one rejection (TERMINAL_CWD_MISSING)
instead of substituting a directory of its own. The shared UI reuses the
archived-restore fallback for live sessions: a server-confirmed missing
directory moves the session and its stranded subtree to the project's
primary directory through the control-plane move, clears the worktree
hint, re-selects the session, and tells the user where it went. It runs
from a terminal failure and on activation of any session whose directory
is neither a project root nor a managed chat directory; available,
unknown, and failed probes leave everything untouched.
Closes#3338.
* fix(scripts): make oc-dev load again after the changelog cleanup
The changelog cleanup referenced fs.existsSync in a module that imports
existsSync by name and never binds fs, so every oc-dev invocation failed
with "fs is not defined" before reaching its action.
* fix(sessions): probe directory availability on disk, not through OpenCode path resolution
OpenCode's /path never checks that a directory exists: it echoes the
requested path and resolves its project through Git discovery that
swallows errors, so a deleted worktree came back as a valid location and
every missing-directory fallback (draft recovery, archived restore,
session relocation) stayed inert on a real server. The probe now asks
OpenChamber's own /api/fs/list, which stats the path and reports
not-found and not-directory explicitly; anything else stays unknown.
* fix(sidebar): keep a worktree whose directory is gone visible as missing
git keeps a worktree registered after its directory is deleted outside
git and marks it prunable; the list parser ignored that line, so a
deleted worktree looked alive, and nothing in the app asked for a new
listing anyway. The server now reports prunable, the UI keeps such a
worktree in the topology with worktreeStatus missing and a warning icon
on its sidebar group, and relocating a session out of a confirmed-
missing directory raises an in-app topology signal the sidebar
rediscovers on. Dropping the worktree instead would hide every session
that lived there, and a hidden session can never be opened or relocated.
No idle polling is added.
* fix(sessions): never relocate a session to the filesystem root
OpenCode files a directory outside any Git repository under its global
project, whose worktree is the filesystem root. A managed chat whose
directory vanished would otherwise be moved to /. The relocation now
refuses a root destination, and the activation probe recognizes chat
directories through the home-based check as well, so it does not depend
on the chats root having been resolved yet.
* test(sessions): mirror the relocation action in the issue-2039 session-actions mock
session-ui-store now imports relocateSessionFromMissingDirectory, and the
mocked module in this test listed every other action but not that one, so
the file failed on import.
* refactor(worktrees): fetch source once during creation
* fix(worktrees): remove worktrees in background
* fix(worktrees): show background removal progress
* fix(worktrees): name the worktree in removal toasts
* feat(worktrees): fetch remote source branch before worktree creation
New worktrees based on a local branch that is behind its upstream now
fetch first and branch from the remote-tracking ref, so they are not
born stale. A global setting (on by default) in Settings > Behavior
controls this, and fetch failures toast a warning and fall back to
local state instead of blocking creation.
* fix(worktrees): wire fetch-source toggle to store and honor failed runtime fetches
The Behavior toggle only persisted the setting; the consumer reads the
config store at creation time, so a just-toggled-off setting kept
fetching until the next hydration. Update the store optimistically on
toggle and on page load, and roll it back when the save fails.
The VS Code runtime bridge resolves git fetches with { success: false }
instead of throwing, which the consumer read as success and silently
based the worktree on the stale remote ref. Treat any non-success
result as a failed fetch: warn and fall back to local state, matching
the web/desktop/mobile path.
* fix(worktrees): stop new remote-based worktrees from tracking the base branch
Creating a worktree with a remote start ref made git auto-track the
base branch (branch.autoSetupMerge), so with the new remote fetch every
behind-root worktree was born with upstream origin/<base> and plain
git push refused under push.default=simple.
The new branch's own upstream does not exist until its first push, and
the bootstrap deliberately refuses to write tracking config for refs
that were never fetched, so --set-upstream-to cannot re-point it.
Suppress the auto-track with --no-track on new-mode creation from a
remote ref: the branch ships with no upstream, matching the behavior
before the remote fetch until the first push sets it. Explicit
upstream keys now also win over the remote start ref inference,
aligning the create path with the validate path and the VS Code
runtime.
* fix(worktrees): keep the pre-create remote ref refresh soft
The client fetch and the server's pre-create fetchRemoteBranchRef both
refresh the same branch, and the second fetch throws on failure — so a
connection dropped between the two turned the promised soft fallback
into a rejected creation even though the remote-tracking ref was
already available locally.
The refresh is now best-effort when the ref exists locally (creation
proceeds from it) and still mandatory when the ref was never fetched,
preserving the materialization behavior for remote-only branches.
Applied to both the web server and the VS Code runtime.
* chore: ignore the .openchamber app runtime state directory
* feat(ui): block branch switches on dirty trees
* feat(ui): show unpushed commits in git branch selector
* feat(ui): show recent branches in git selector
* fix(ui): persist recent branch status
* feat(ui): add mobile branch picker
* fix(ui): guard mobile branch checkout
* fix(i18n): restore Turkish git empty state labels
* feat(ui): flag dirty draft directories on the branch selector
Replaces the draft dirty-directory banner with an indicator on the branch
selector: a warning icon plus a hover tooltip that opens by itself for five
seconds when the dirty state first appears, then stays hover-only. The copy
states the situation and the options (commit or worktree) without prescribing
either.
* feat(ui): optional push in the dirty branch switch dialog
Commit-and-switch gains an opt-in "Push after commit" checkbox. When the
push fails the commit stands but the switch is cancelled with an explicit
toast, so the user is never moved off a branch without knowing its push did
not happen. Without the checkbox the toast states the commit is local only.
* fix(i18n): align dirty-directory copy across locales
* fix(a11y): name the unpushed-commit badge in the branch picker
The badge showed a bare arrow and number with no accessible name or tooltip.
Both the desktop recents list and the mobile picker now carry a localized
"N commits not pushed" title and aria-label.
* fix(mobile): push before switching dirty branches
Honor the dirty-switch dialog's push option on the mobile Changes surface.
A failed push leaves the new commit on its source branch, refreshes state, and
cancels checkout. Mobile branch selection now also shows the existing dirty
switch notice.
Post-merge follow-ups for #2740#2735#2734#2690#2676#2738#2684#2689#2733#2739#2462#2687#2736#2618#2697, plus three regressions
found while reviewing them:
- ctrl/cmd+digit while typing no longer switches session tabs (#2503 was
still open in practice: the guard only covered the mod+alt surface binding)
- Shiki template-call sanitizer now covers every bundled grammar, including
the js/ts aliases and embedding grammars; timed-out highlight requests are
memoized and no longer cancel unrelated in-flight requests
- settings flush on suspend uses keepalive and also fires on Capacitor
appStateChange; keeps the selected model persisted across mode switches
- remote-only branches fetch before checkout; range helpers fail clearly
- git status invalidation now fires for runtime adapters too
- settings number inputs and select triggers size in ch so they scale with
the interface font
- recent-activity timestamps tick from one list-level ticker
- Markdown preview find goes through the shared find_in_file keybind with
containment, no longer counts its own bar, and debounces observer runs
- #2676 reverted; #2524 fixed by fading the sticky header's own background
instead of overlaying the content below it
- sticky group headers in the model picker and sidebar render again
(oc-sticky-fade-scroller class restored after 9b9d7069c)
- project switcher names are left-aligned again (wrapper lost in 26dbc2f30)
- tool card quick-open icon is always visible and opens the same line as the
expanded card's button
- tautological tests replaced or removed; new oxlint findings fixed
Follow-ups promised on merge, plus review findings on the batch itself:
- chat: task-tool output now respects the 512KiB render cap; quick-open
icon is visible at rest on coarse pointers and reachable by keyboard
(row keydown no longer swallows inner-button Enter/Space); composer
inline-code decoration drops the metric-shifting padding; a btw fork
send carries only the boundary instruction, never the promotion notice
- sync: cascade revert/unrevert aborts busy descendants, busy state is
read from every child store at the moment of use; rule 9 documents
redo clearing all descendant revert markers
- electron: renderer recovery keeps memory-eviction (a valid
render-process-gone reason) and both windows share one
attachRendererRecovery helper
- vscode: process registry is a thin re-export of the web module
(provider-env-aliases precedent) with ordered register/unregister
writes and an awaited close
- server/cli: managed-process registry takes injectable deps (fixes the
unreaped-orphans ReferenceError), corrupt settings errors name the
file, getWorktrees test restores console.warn
- tests: module-mock harnesses removed (AgentsSidebar, SettingsView
mobile focus — behaviors stay live but uncovered, accepted trade),
QuestionMarkdown asserts rendered DOM
- i18n: German gains the debug-panel request keys, Japanese/German drop
removed worktree keys, Ukrainian unit spacing fixed
- changelog: Copilot AI Credits entries (main + VS Code)
git switch -c / git checkout -b from the current branch record
'branch: Created from HEAD' in the reflog, but parseBranchCreationSource
only rejected 'HEAD@{...}' (detached start) and raw commit hashes. The
bare HEAD passed through, so getBranchBase returned { base: 'HEAD' }
and the branch scope computed diffs against HEAD itself, which is empty
when the service is checked out on that branch and wrong otherwise.
Treat bare HEAD like HEAD@{...}: no named source is recorded, so return
null and let the UI ask the user to pick a base.
Show every change on the current branch relative to its base in the
Changed/Staged/Last turn dropdown. The base comes from the branch's
reflog record or an explicit per-branch user choice (persisted), never
a main/master guess; when git has no record the user picks a base once
from a searchable branch list.
- server: GET /api/git/branch-base (reflog-derived base),
GET /api/git/range-files (name-status -z with rename/copy
destination paths and -C copy detection)
- shared UI: optional getBranchBase/getGitRangeFiles runtime APIs
with boundary parsing; persisted per-branch overrides keyed by
runtime+directory+branch
- DiffView: branch scope with confirmed-unavailability coercion of
persisted tabs (detached HEAD, default-branch checkout, metadata
settled without a default), range-invalidated diff cache guarded
against stale completions, bounded branch-metadata retry, read-only
diff actions in branch scope; hidden in VS Code
- helper module branchDiffScope.ts with tests for coercion,
availability, race conditions, and retry exhaustion
* fix(git): create worktrees from forked PRs via refs/pull/<n>/head fallback
A worktree created from a linked GitHub PR whose head branch lives in a fork
failed when the fork's head repository was missing (deleted fork) or
unfetchable (auth, network): the dialog threw 'PR head repository URL is
unavailable' before any git command ran, and the server had no fallback to
refs/pull/<n>/head, which GitHub serves on the base repository.
- NewWorktreeDialog: when pr.headRepo is absent, send a prRef config
(refs/pull/<n>/head from origin) instead of throwing; the fork config now
also carries prRef so the server can fall back when the fork fetch fails.
- git service: fetchPullRequestHeadRef fetches refs/pull/<n>/head into
refs/remotes/<remote>/pr-<n>-head (same refspec shape as
fetchRemoteBranchRef) and both validateWorktreeCreate and
attachGitWorktreeToCandidate fall back to it when the fork path fails;
fallback worktrees get --no-track and no upstream config because a PR ref
is not pushable. When both paths fail the original fork error surfaces.
- Focused tests cover the prRef-only path and the fork-unreachable fallback.
Fixes#2422
* fix(git): harden PR worktree fallback against stale fork refs (#12)
After a fork fetch fails, resolve immediately from refs/pull/<n>/head
instead of accepting a cached remotes/<fork>/<branch> tracking ref.
Match the PR base repository by URL (not a hardcoded origin remote),
store fetched PR heads under refs/openchamber/pull/<n>/head, and share
one existing-mode resolver between validate and create.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(git): make PR head SHA authoritative and namespace private refs
Reuse local/remote branches for linked PRs only when their tip matches
pr.headSha; otherwise fall through to fork fetch / refs/pull. Store PR
heads under refs/openchamber/github/<owner>/<repo>/pull/<n>/head, prefer
HTTPS for direct base-repo fallback, and surface composite fork+fallback
errors when both paths fail.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* test(ui): assert validate/create forward deleted-fork PR payload fields
Guards the dialog wiring regression where validate omitted prRef while
create included it, by asserting worktreeManager forwards prRef,
prBaseRepoUrl, and related fields for deleted-fork configs.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* refactor(git): always checkout linked PRs from refs/pull/<n>/head
Move PR worktree resolution to the server. The UI now sends only
pullRequest identity (number + baseRepoUrl + optional head fields);
the server always fetches the authoritative PR head and best-effort
configures fork upstream afterward.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* refactor(ui): drop PrWorktreeConfig; send PR identity only
Delete the prWorktreeConfig module. NewWorktreeDialog maps linked PRs
straight to pullRequest identity, skips upstream defaults for that path,
and leaves checkout + optional fork tracking to the server.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* refactor(git): linked PRs are {number, baseRepoUrl} only
Drop fork upstream / tracking and head/base owner-repo fields from the
linked-PR worktree path. Fetch refs/pull/<n>/head, create --no-track, done.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(git): meet #2422 Must/Should without refs/pull fallback
Linked PRs send fork identity only; the server provisions pr-<owner>,
fetches the head branch, and fails clearly when the fork is missing or
unreachable. Local reuse requires a matching headSha. Prefer HTTPS for
headRepoUrl. Do not write upstream tracking when the upstream ref was
never fetched.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(git): drop invalid upstream fallback and PR branch collisions
Remove setBranchTrackingFallback: if upstream fetch fails, leave tracking
unset. When a linked PR's head branch already exists locally with a
different tip, create pr-<number> instead of git worktree add -b on the
colliding name.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(git): strip PR worktree create back to fork-remote provision (#15)
Keep the original ensureRemoteName/Url path for linked fork PRs, prefer
HTTPS clone URLs, fail clearly when the fork is unreachable, and leave
upstream tracking unset when the upstream ref was never fetched.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
`getWorktrees` logs a warn-level line every time the managed OpenCode
process or any other caller passes a directory that is not inside a
git repository. The OpenChamber desktop main.log fills with hundreds
of these "Failed to list worktrees, returning empty list: fatal: not
a git repository ..." entries over a normal session.
The empty-list fallback is already correct (worktrees are an optional
feature), but the warning is noise that hides real git failures. Use
the existing `isNotGitRepositoryError` helper to suppress the warn
specifically for the "not a git repository" case and keep the
warning for genuine failures (lock contention, permission errors,
corrupt repos, etc.).
Worktrees are created with `git worktree add --no-checkout` and populated
with `git reset --hard`, neither of which runs git's post-checkout hook.
Invoke the hook explicitly after population with git's standard arguments
(null ref previous HEAD, checked-out HEAD, flag 1) and the worktree as cwd,
mirroring `git worktree add` without --no-checkout.
A missing or non-executable hook is skipped (matching git) and a failing
hook is logged as a warning, never failing worktree creation or session
bootstrap. Applied to both the web server git service and the VS Code
runtime's git service.
Follow-up to #2629, which stopped the walkthrough from comparing against a
branch that does not exist. The same guessing, and the same near-misses in how
the answer was applied, were left elsewhere:
- The default branch travelled as `rootBranchHint`, whose documented meaning is
"the branch the project root worktree is on". It gets its own option, because
a parameter that means two things is one the next caller gets wrong.
- A candidate equal to the branch being compared is skipped. In a plain checkout
the root hint *is* the current branch, so it won every time and produced a
comparison with itself; the repository default now wins there.
- The Changes and pull-request surfaces read the default branch too. A pull
request opened against a branch that does not exist is a worse failure than a
walkthrough that will not generate.
- `hasResolvableBaseBranch` matched `origin/feature/main` for a base of `main`,
passing the check and then failing the comparison it exists to prevent.
- `getRangeDiff` promoted only `origin/<base>`. A base carried by any other
remote stayed a bare name, which git resolves against refs/heads and nowhere
else, so it failed exactly as before.
- `getBranches` dropped every branch of a remote that did not answer, turning
"we could not ask" into "these branches are gone" — offline, that silently
removed comparisons that work fine against local remote-tracking refs.
- A remote with no `remote/HEAD` is asked once with `ls-remote --symref` rather
than falling back to the guess this data exists to replace.
The `defaultBranches` contract was documented under the status response; it
belongs to the branches response, which now has a section of its own.
Untracked symlinks now show as link entries in diff output.
File diffs display symlink targets instead of following them.
Added tests for patch and split diff behavior.
simple-git without baseDir inherits process.cwd(), so launching
OpenChamber from a neutral directory (e.g. $HOME) and opening a git
project elsewhere produced repeated "not a git repository" status
errors and could abort project/session enumeration. Always require an
explicit baseDir, soft-handle non-repo GitErrors on status/check
routes, and cover non-git, foreign-cwd, and nested-repo cases.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Add a root-session action that creates a generated worktree from the session directory's current branch, transfers uncommitted changes, and moves the parent session plus its descendants through OpenCode's control-plane API.
Reuse existing project/worktree topology and quick-create behavior, keep the UI non-blocking, reconcile live and global session state across directories, and roll back partial moves and failed worktree creation safely.
Split worktree bootstrap readiness into directory-created, git-ready, and setup-ready phases across web and VS Code. Session moves wait for Git readiness while existing setup-aware flows continue waiting for full setup completion, and worktree removal is serialized with active bootstrap tasks.
Expose the move only for idle root sessions, show localized progress and explanatory tooltips in the sidebar, and keep pending/ready worktree metadata synchronized with authoritative session attachments to avoid stale setup indicators.
Add coverage for control-plane payloads, session-state migration, bootstrap phase ordering and compatibility, removal races, progress metadata, and fast-ready attachment races.
Retries transient index.lock conflicts during worktree population
Removes unchanged stale locks automatically and continues bootstrap
Adds coverage for stale lock recovery
Add per-hunk staging, unstaging, and discarding to the Changes diff
view, so a single change region inside a file can be acted on in
isolation instead of forcing whole-file stage/revert. The change is
wired end-to-end across the web server, the shared UI runtime API
contract, and the VS Code extension, with Electron inheriting the web
path unchanged (it boots the server in-process).
Server
------
- New `applyHunk(directory, filePath, { patch, action })` in
packages/web/server/lib/git/service.js. It resolves the repository
context and validates the file path with the same helpers used by
stageFiles/unstageFiles (resolveGitFileContext +
validateRepositoryFilePaths), then writes the single-hunk patch to a
temporary file in the OS temp dir (never inside the repo, so it
cannot show up as an untracked file) and runs `git apply` with flags
chosen per action:
stage -> git apply --cached (working tree -> index)
unstage -> git apply --cached --reverse (index -> working tree)
discard -> git apply --reverse (revert in working tree)
A `git apply --check` runs first with the same flags, so a stale
hunk that no longer applies fails with a clear "Hunk no longer
applies - refresh and try again" message instead of leaving a
partial mutation. The patch's target path is parsed and must match
the requested file (with /dev/null tolerated for new/deleted files),
preventing a patch from silently targeting a different path. The
whole operation runs inside withGitIndexMutationQueue to avoid
racing with concurrent stage/unstage. The temp file is removed in a
finally block.
- New `POST /api/git/apply-hunk` route in routes.js, registered
alongside stage/unstage. Validates directory, path, non-empty patch,
and action before delegating.
- DOCUMENTATION.md updated with the new service entry.
Patch extraction
----------------
- packages/ui/src/lib/diff/patchFileDiff.ts gains
splitPatchIntoHunks(patch) and extractHunkPatch(patch, hunkIndex).
They keep the original file header (diff --git / index / --- / +++)
and emit exactly one @@ hunk per standalone patch, which is what
`git apply` expects. Each emitted patch is guaranteed to end with a
trailing newline (without it git apply reports "corrupt patch").
Runtime API contract
--------------------
- GitAPI (packages/ui/src/lib/api/types.ts) gains optional
stageGitHunk / unstageGitHunk / revertGitHunk, matching the
stageGitFiles? / unstageGitFiles? precedent so runtimes that do not
support it degrade gracefully.
- gitApi.ts delegates to the registered runtime git API, falling back
to gitApiHttp, exactly like the existing whole-file helpers.
- gitApiHttp.ts posts to /api/git/apply-hunk.
- Web runtime composes the three methods in packages/web/src/api/git.ts.
VS Code parity
--------------
- packages/vscode/src/gitService.ts adds applyGitHunk(), implemented
natively with the existing execGit helper + a temp patch file +
`git apply` (--cached / --cached --reverse / --reverse), mirroring
the server's --check-first safety and temp-file cleanup.
- bridge-git-runtime.ts handles the new api:git/apply-hunk bridge
message; webview/api/git.ts sends it. VS Code users get identical
stage/unstage/discard-hunk behavior.
UI
--
- New DiffHunkActions component renders a compact per-hunk strip
above each expanded file diff in the Changes view. Each hunk chip
shows its +additions / -deletions counts and offers:
working scope -> Stage + Discard
staged scope -> Unstage
Clicking extracts that hunk's standalone patch via
extractHunkPatch(patch, hunkIndex) and calls the runtime git API.
Because the chip index comes directly from fileDiff.hunks[] and the
patch is sliced in the same order, the hunk the user sees is always
the hunk that gets applied. While any action is in flight all buttons
disable to prevent conflicting concurrent mutations; the per-hunk
spinner reflects in-flight state.
- DiffView wires DiffHunkActions into InlineDiffViewer (text diffs
only; binary/image and full-file-content modes are excluded since
they have no patch). MultiFileDiffEntry passes directory/staged
through and handles onHunkApplied by bumping the diff reload nonce
(so the file's diff re-fetches and the affected hunk disappears)
and refreshing git status (so file counts and the staged/changed
scope update). Hunk actions are therefore available wherever the
default patch-context diff is shown.
i18n
----
- 10 new keys (diffView.hunk.*) added to all 9 locales (en, es, fr,
ko, pl, pt-BR, uk, zh-CN, zh-TW), including stage/unstage/discard
labels, tooltips with the hunk index, a stale-hunk error message,
and an unsupported-runtime fallback.
Tests
-----
- packages/ui/src/lib/diff/patchFileDiff.test.ts covers
splitHunks/extractHunkPatch: multi-hunk split, header preservation,
single-hunk and empty patches, out-of-range indices.
- service.test.js adds an applyHunk suite that builds real temp repos
with two separate hunks and verifies: staging one hunk leaves the
other unstaged, discarding reverts only the targeted hunk in the
working tree, unstaging removes only one hunk from the index, and a
retargeted patch (different file path) is rejected. Also covers
invalid-action / missing-hunk-header validation.
- packages/web/src/api/git.test.ts mock completed with the new methods
(and previously-missing exports that prevented the test from
loading) and asserts the three hunk methods are exposed.
- routes.test.js continues to pass under bun.
CHANGELOG updated under [Unreleased].
* feat(types): add parents to GitLogEntry and new commit action types
* feat(git): add parent hashes and --all flag to getLog
* fix(git): move record separator to start of log format string
* feat(git): add checkoutCommit server function and route
* feat(git): add cherryPick server function and route
* feat(git): add revertCommit server function and route
* feat(git): add resetToCommit server function and route
* fix(tests): make git service tests branch-name portable, add error path tests
* feat(client): add checkoutCommit, cherryPick, revertCommit, resetToCommit API wrappers
* feat(git-graph): add lane assignment algorithm with tests
* feat(git-graph): add GitGraphSegment per-row SVG renderer
* feat(i18n): add locale strings for git graph action buttons
* fix(git-graph): handle lane convergence, fix SVG path coords, add connector tests
* feat(git-graph): add ref badges and action buttons to HistoryCommitRow
* fix(git-graph): add loading guards to reset actions, use theme tokens for ref badges
* fix(git-graph): conditional hooks, stale graph log, conflict handling, i18n
* fix(types): replace toBeDefined with toBeTruthy, fix toast API usage
* fix(lint): remove unused variables
* fix(git-graph): fix SVG height causing 150px row spacing
* fix(git-graph): smooth bezier curves, fill row height, round line caps
* fix(git-graph): non-scaling-stroke fixes bezier white spaces, sort curves on top
* fix(git-graph): remove viewBox scaling, match SVG height to actual row height
* fix(git-graph): ResizeObserver tracks actual row height, eliminates SVG height mismatch
* feat(git-graph): replace SVG with Canvas for graph rendering
* fix(git-graph): isolate canvas from flex layout to prevent replaced-element height leak
* feat(git-graph): align action buttons, add confirmation popups for all actions
* fix(git-graph): address code review findings CR-001 through CR-005
- CR-001: VS Code getGitLog now forwards 'all' option and parses %P parents
- CR-002: VS Code bridge/gitService implement checkoutCommit, cherryPick,
revertCommit, resetToCommit with conflict detection and hard-reset guard
- CR-003: server-side commit hash validated with /^[0-9a-fA-F]{7,40}$/
in both routes.js and service.js; 12 new rejection tests added
- CR-004: cherry-pick/revert conflict path now refreshes fetchStatus/
fetchBranches/fetchLog; conflict toast uses i18n keys in all 7 locales
- CR-005: corrected O(n) comment to O(n x lanes)
* fix(i18n): add zh-TW locale and common.language.traditionalChinese key to all locales
upstream/main added zh-TW.ts after branch diverged; CI type-check fails
when PR is merged because zh-TW.ts was missing all gitView.history.actions.*
keys and loadMore/loadingMore. Also adds common.language.traditionalChinese
to en.ts and all 6 non-English files to match upstream en.ts.
* fix: harden git history actions
* feat: split git history graph view
* chore: remove git graph planning docs
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: make upstream sync actions target the selected remote
Ensure fetch and pull actually honor upstream selection so fork maintenance works from the Git sidebar, and surface upstream branch status alongside the primary origin-tracking indicators.
* feat: add Windows Electron desktop foundation
* fix(electron): stabilize Windows desktop packaging
* fix(electron): stabilize Windows desktop chrome
Use native Windows titlebar behavior with an Alt-accessible hidden menu, and harden Windows dev command launching so the desktop app follows platform conventions.
* fix(electron): stabilize Windows dev startup
* fix(electron): clarify desktop artifact names
* fix(electron): harden Windows desktop release and launch
* fix(electron): address Windows release review
* fix(electron): point updater and release links to org repo
* Fix Windows settings persistence fallback
* Fix Windows Electron dev startup
* Add Windows Electron window controls
* Fix Windows Electron install and opencode launch
* fix: resolve git status for repositories without upstream
Fixes repository detection stuck on Checking repository
Handles git status when no upstream is configured
Adds regression coverage for git status loading
* Add Windows app menu button
* fix: preserve file editor line endings
* ci: add desktop release smoke workflow
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* chore: add .worktrees/ to gitignore for worktree workflow
* fix: resolve remote-tracking base ref in getLog for PR description generation
getLog was calling git log <base>..<head> with a bare branch name that
often doesn't exist locally (e.g. main when only origin/main is present),
causing a fatal 'unknown revision' error and HTTP 500.
Apply the same origin/<base> resolution already used in getRangeDiff and
getRangeFiles: check refs/remotes/origin/<base> first and prefer that ref
if it exists.
Also fix getGitLog in gitApiHttp.ts to read the JSON error body on
failure instead of falling back to response.statusText, so the actual
git error message surfaces in the toast instead of 'Internal Server Error'.
* fix(git): use local-first ref resolution in getLog and port to VS Code
- Replace unconditional origin/<from> preference in getLog() with a
local-first fallback: prefer the local ref, only use origin/<from>
when the local ref cannot be resolved, and pass through unchanged
when neither resolves so git surfaces a meaningful error.
- Extract the logic into an exported resolveBaseRefForLog(from, checkRef)
helper so it is unit-testable without a real git repo.
- Add service.test.js with 6 cases covering local-wins, origin-fallback,
neither-exists passthrough, and falsy/empty inputs.
- Port the same local-first resolution to packages/vscode/src/gitService.ts
getGitLog() to close the cross-runtime parity gap; also handles
from-only ranges as from..HEAD, matching the web service contract.
* fix(vscode): add missing to-only range branch in getGitLog
When only 'to' is supplied (no 'from'), the web service appends it as a
positional git-log argument. The VS Code port was missing this branch and
silently returned unbounded history instead. Adds the else-if to restore
full cross-runtime parity.
* fix(vscode): surface git log errors
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>