chore: re-sync with upstream, fork behaviour taking priority - #77
Merged
Merged
Conversation
…tgg#9909) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…gdotgg#9910) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tgg#9911) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…dotgg#9912) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ingdotgg#9913) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…t installs - OpenCode and Antigravity text generation implement generateContinuationSummary, which the fork's TextGeneration service requires - merge a duplicated prompt import - projection carries faviconPath, and ThreadEnvMode imports from the package root
Upstream's ProjectionSnapshotQuery keeps its six newer methods (thread search, replay stats, agent-session import, runtime context, turn-start message and user-input activity) while now selecting and mapping the fork's gitHubAccount, worktreeBranchPrefix, defaultWorktreeBranch, reviewModelSelection, previewPort and worktreeCopyFiles.
listAccountRoutes and getDefaultModelSelectionForCwd (with their row readers, schemas and the isPathWithin helper) let per-project provider accounts resolve, which the fork's multi-account setup depends on.
- portDiscovery.subscribe takes its scan inputs explicitly - add the newer Codex plan labels (edu_plus, edu_pro, ent) - re-export PATCH_RENDER_PREFIX_ARGS from the git driver core
upstream's ws.ts carries 45 newer handlers the fork's did not have, so it is the base now; git.mergePullRequest and orchestration.generateContinuationSummary are ported onto it with their transcript helper.
git.mergePullRequest is wired to the git workflow service. The continuation summary RPC answers explicitly for now: generating it needs TextGeneration in the handler's context, which the upstream server graph does not put there, and pulling it in makes the CLI require the whole server context.
ProjectWorktreeFileCopier.layer itself requires ProjectionSnapshotQuery, so adding it to CliRuntimeLayer made every CLI invocation — including --help — die with 'Service not found' before any command ran.
GitManager copies configured files into new worktrees, so the server graph has to supply ProjectWorktreeFileCopier. The copier sits in an import cycle, so the reference is deferred with Layer.suspend — referencing it eagerly leaves the binding in its temporal dead zone and the bundle throws at module init.
The sync dropped the AppSidebarLayout switch, so SidebarV2 sat in the tree with no importer and every user fell back to the upstream sidebar regardless of their sidebarV2Enabled setting. Restore the fork's condition: v2 for the chats list, the settings nav on settings routes.
…surfaces The upstream sync landed upstream tests on fork implementations (and the reverse) in a number of places, plus a handful of half-applied merges. This repairs them: - ProjectionProjects: restore the autoPull boolean/integer bridge and the projectIcon JSON decode. SQLite cannot bind a boolean, so every projection write failed and took ~230 tests with it. - GitVcsDriverCore: add the service-shaped remoteBranchExists and the defaultBranch field statusDetailsRemote's contract requires. - vcs contracts: restore the detail strings for the fork's repository-not-found, permission-denied, merge-blocked and provider-unavailable failure kinds. - gitHubAuthStatus: take upstream's JSON parser to match the `--json hosts` probe the merge had already adopted; the fork's text parser could never read it, so GitHub auth always reported unknown. - providerStatus: take upstream's ordering so an error is not hidden behind a stale authenticated state, keeping the fork's timed-out branch and dot color. - ComposerCommandMenu: skills reached as `/skill:name` had no group in the slash trigger and were dropped from the menu entirely. - primary/auth: await the session after exchanging a credential, so manual token submission no longer resolves before authentication lands. - messageAttachmentReuse: documents carry previewUrl too, so re-staging a sent message's documents works again. - DiffWorkerPoolProvider: restore upstream's shared pool lifecycle and main-thread fallback, keeping the fork's custom diff themes. - index.css: restore .sidebar-brand, without which the header wordmark lost its display and wrapped "Code" onto its own line. - keybindings: drop upstream's filePicker.toggle, which shadowed the fork's wired file.open on the same chord. - composerDraftStore / session-logic / useHandleNewThread: port upstream's catalog guards, command-output details and route-abandon guard. Also clears the 21 lint errors the upstream rules introduced on fork code.
GitVcsDriverCore kept the fork's older shape for several operations that upstream had since fixed, so eleven of its tests failed: - removeWorktree: give it the five-minute budget cleanup needs, and treat a worktree that is already gone as a no-op, pruning the stale registration rather than failing a later `worktree add` at the same path. - createWorktree: populate submodules, which `git worktree add` leaves empty. - pushCurrentBranch: publish a branch that tracks its base under its own name instead of pushing feature commits onto the shared base, recording the base as gh-merge-base first since `-u` retargets the upstream. - executeGit: honor `timeoutMs: null` again — `??` collapsed it back to the default budget, so network pushes were cut off mid-flight. - review diff previews: pass PATCH_RENDER_PREFIX_ARGS at all three call sites. The constant survived the merge, its uses did not, so a repository with diff.noprefix produced patches the clients parse as unnamed files. - statusDetails: separate paths from revisions with `--` so a file named HEAD is not read as the revision, and match the "bad revision" wording that separator produces when HEAD is unborn. - statusDetailsRemote: read the branch symbolically on an unborn HEAD instead of failing the whole remote-status read. - statusDetails: restore the origin-exists and default-branch caches, keyed on the repository so worktrees share one entry, invalidated by ref mutations. Also adopts upstream's rule that project creation records no model default — only a metadata update does — and scopes the Claude probe's CLAUDE_CONFIG_DIR assertion to the CLI's own spawns, since the fork also shells out to the keychain for per-account credentials.
…hey belong Two placements the merge dropped: - SidebarUpdatePill renders a SidebarMenuItem, but the footer rendered it as a bare sibling outside any menu. A stray <li> keeps its list marker, so the footer grew an empty row showing a lone bullet next to the refresh icon. It now sits in the footer menu's row alongside Settings, the way upstream places it, and the architecture warning it exports is rendered again. - The panel layout controls lost their container's positioning, keeping only a `workspace-titlebar-controls` class that no rule matches (the styling hook is the data attribute). Unanchored, they fell into normal flow and floated in the middle of the chat pane instead of sitting in the titlebar row.
…ummary The titlebar layout controls are a fixed overlay centered in the topbar band, which reaches into the tab row below it, so the Review button collided with the panel toggles. The tab row now reserves the same right inset the header does. The diff header also lost its change summary. It is back, summed from the same per-file stats the navigator lists so the count always matches the rendered diff: file count plus total insertions and deletions.
…bar controls A session record keeps `status: "error"` after a provider process dies, and the v2 row labelled that Failed unconditionally — so a thread that recovered and went on to finish its work flew a red flag forever. The label now requires the thread's latest turn to be unsettled too, which is the same question sidebar v1 asks; v1 never labelled these rows at all. The titlebar also drops the maximize and terminal-drawer toggles, leaving the right-panel toggle. The terminal is still reachable from the run bar and its own shortcut.
The merge shimmed upstream's structured TerminalSessionState.output back to a flat string, but the session keeps output as chunks and evicts the oldest once the retained byte cap is hit — so the flattened string loses its head as a session runs. The drawer diffed that string by prefix, so once eviction kicked in it wrote the wrong slice into xterm and printed garbage. Reading the delta through readTerminalOutputUpdate fixes that, and lets an append skip clearSelection: the unconditional clear on every version bump was wiping any selection the moment new output arrived, which is why copying from a live terminal never worked.
main carried 172 commits of fork work cut after this branch, so 83 files conflicted. Resolution kept the fork's newer features and upstream's additions together rather than picking a side: - Migrations: kept this branch's 1..54 ledger, which is what the live database actually recorded. main stops at 40 and numbers the fork's six project migrations 35-40; this branch renumbers them 48-53 behind upstream's 35-47. The migrator tracks by numeric id and never validates names, so the ledger has to match the database it will run against. - ws.ts: combined both sides throughout — upstream's client-attribution helpers and this branch's coalescer alongside main's continuation-summary transcript, worktree file copier and idempotent draft bootstrap. The create now routes through upstream's attributed dispatchFromClient while tolerating main's "thread already exists" invariant, and drains the deletion fence only when it actually created the thread. - serverConfigProjection: ported main's retainModelsAcrossTimeouts into the module upstream moved the projection to. A timed-out probe ships an empty model list, which was wiping the catalogue the picker needs. - server.ts: kept upstream's scoped runtime services and layered main's ambient GitHubAccountResolver above them. - OpenCodeTextGeneration: implemented generateContinuationSummary, which main added to the interface while OpenCode's driver predated it. - Restored GitHubSourceControlProvider.mergeChangeRequest, dropped by the merge. - Removed main's duplicate settings.projects.$environmentId.$projectId route: it nests under upstream's flat /settings/projects page, which renders no Outlet. Also cleaned up auto-merge duplicates git produced without conflicting: repeated service bindings, stub properties and RPC handlers.
…edia Providers publish quota windows as `usageLimits`; the sidebar pill still read the older `usage` field, which nothing populates any more — so the widget found no provider with usage and hid itself entirely. It now adapts `usageLimits` at the edge and lists every connected instance, so several Claude and Codex accounts each get their own meters instead of only whichever one happened to report the legacy shape. Inline markdown media also drops from a 30rem to a 16rem height cap. A square avatar or OpenGraph thumbnail was filling most of the message column; a real screenshot is still one click from full size.
A fresh terminal printed its prompt three times, showed stray query-response characters, and would not hold a selection. Two causes: - Several mount paths each fit and then resize — the 30ms fit timer and the layout effect both fire on open. A themed shell repaints its prompt on every SIGWINCH, so resending a size the PTY already has is not a harmless no-op. Sizes are now deduplicated, and the record clears when the terminal is recreated so a genuinely new PTY still learns its size. - The write effect returned on an unchanged version before reading the output delta, but the ref starts at the current version — so the first pass never advanced the cursor and left the next update to resynchronize from a full reset, clearing the selection with it. The delta is read first now; the version guard only gates the error and exit handling that needs it.
The dock always defaulted to the Run tab, so a project with no run command opened on an empty "configure me" pane and the plain shell tabs looked absent even though they were a click away. Without a run script the default is now the base shell; an explicit pick still wins and is still remembered, but the derived default is no longer written back to storage, which would have pinned the dock to whatever it first happened to open on.
The tab strip is a horizontally scrolling flex row, so once the tabs overflow it reserves scrollbar gutter space at its bottom. That lifts the tabs inside it while the run button beside it stays centred on the full row, which reads as the button sitting too low. The strip now fills the row height and hides its scrollbar, so both sides centre against the same box.
Opening the dock landed on Run, whose only affordance is running a command, so the terminal read as having been replaced by a task runner. It opens on the base shell now; Setup, Run and custom scripts stay one click away and an explicit pick is still remembered. The storage key moves to v2 because the previous one was written on every render, including the derived default — so every existing install has a tab pinned that the user never chose, and would otherwise keep landing on Run.
A shell tab has no script, so an unstarted one fell through to the script empty state and asked the user to "Set Terminal command" — a plain shell was demanding a command it will never have. Shell tabs now open a terminal directly; only Setup, Run and custom scripts keep the run-first empty states.
The retained output buffer evicts its head at an arbitrary byte offset, which lands in the middle of whatever escape sequence was being written at the time. A reset replays from there, so the terminal printed that sequence's tail as literal text — the run of stray characters sitting above the first real line, different on every session because it is whatever sequence got cut. Replays now resynchronize on the first line break when the head was evicted. Scrollback loses its partial leading line, which costs nothing. Nerd Font families also lead the font stack: prompts like powerlevel10k draw branch and status icons from the private-use area, which the plain system monospace fonts render as empty boxes.
Upstream renders terminals through ghostty and does not import xterm's CSS, so the merge dropped the import this fork's xterm-based drawer needs. Without it xterm's hidden helper textarea — the element it reads keystrokes from — has no positioning, and renders as a visible, resizable box over the terminal with its own caret beside the real one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Re-syncs the fork with current
upstream/mainwith fork behaviour taking priority, replacing the earlierchore/sync-upstream-2026-09attempt which silently dropped fork work.Base is
upstream/main(internally coherent) with fork work replayed on top, rather than merging upstream into the fork hunk-by-hunk.Why the previous sync had to be redone
chore/sync-upstream-2026-09merged main but resolved conflicts in upstream's favour, so fork commits were in the history while their effect was gone. Content audit of the 274 fork-changed files:035–040deleted and re-added as048–053, which desynced the migration ledger and crash-looped the desktop app on boot (no such column: default_thread_env_mode)Lost features included
SidebarV2.tsx(3,409 lines),providerUsage.ts(341 lines) and itsClaudeProviderwiring,HeaderOverflowMenu,ProjectScriptsField, the per-project provider settings route, and the Archive sidebar entry.Fork features verified present
SidebarV2·providerUsage+ClaudeProviderwiring ·HeaderOverflowMenu·ProjectScriptsField·FileViewModeToggle·useDiffThemeName·workspaceThreadRef·BetaSettingsPanel· per-project provider settings route · Archive nav ·messageAttachmentReuse·PullRequestThreadDialog.logicResolution policy
Examples:
threadSettled.tskeeps fork's settled logic and upstream's snooze presets;ChatAttachmentunions upstream's file/unknown types and fork's document type; upstream's deliberateenableAssistantStreaming→enableLegacyTokenStreamingrename is adopted rather than reverted.Status — builds, typechecks, tests, and ships a working desktop build
Every workspace package typechecks (
contracts,shared,client-runtime,server,web,desktop), lint is at 0 errors, and the test suite is at 12,711 passing.The 13 remaining suite failures all reproduce identically on
upstream/mainin this same checkout, so none are caused by this branch:/var→/private/var, which breaks path-equality assertionsnodeonPATHinside a stubbed script.github/scripts/*.test.cjs, which usenode:testand are not vitest suites at allapps/mobilereports errors here too (68 onupstream/main, 59 on this branch) — all downstream of expo's gitignored generated types, which were never generated in this checkout.A DMG was built, installed, and verified booting: backend healthy, no restart loop, migration ledger intact at 54 migrations.
Merge regressions found and fixed
The first pass at this integration compiled but had quietly broken behaviour. Each of these was traced to a specific dropped hunk:
ProjectionProjects— lostautoPull's boolean↔integer bridge. SQLite cannot bind a boolean, so every projection write failed and took ~230 tests with it. Also restored theprojectIconJSON decode.GitVcsDriver— seven service methods were missing from the service object, sogit.remoteExists is not a functionbroke worktree creation at runtime.gitHubAuthStatus— the merge adopted upstream's--json hostsinvocation but kept the fork's text parser, so GitHub auth always reportedunknown.primary/auth— dropped theawaiton session establishment, so manual token submission resolved before authentication landed.ComposerCommandMenu— skills reached as/skill:namematched no group in the slash trigger and were dropped from the menu entirely.messageAttachmentReuse— a too-narrow type guard meant documents could never be re-staged from a sent message..sidebar-brand— the CSS rule was dropped, so the sidebar wordmark lost itsdisplay: flex, clipped toΓ3, and wrapped "Code" onto its own line.keybindings— upstream'sfilePicker.toggleshadowed the fork's wiredfile.openon the same ⌘P chord.GitVcsDriverCore— restored upstream's fixes for worktree removal, submodule checkout, publishing a branch that tracks its base, unbounded push timeouts, patch prefixes, a file literally namedHEAD, unbornHEAD, and the origin/default-branch caches.UI repairs
SidebarMenuItemoutside any<ul>, so it drew a stray list marker and claimed an empty row. It is back in the footer menu row.+/−), summed from the same per-file stats the navigator lists.session.status === "error"no longer labels a thread Failed forever; the label now also requires the latest turn to be unsettled, which is the question sidebar v1 already asked.Approaches that were tried and rejected
git merge -X ours— 148 errors. Resolves per hunk, so it kept the fork's definition-less side while upstream's usages landed, producing code referencing symbols that do not exist, plus 9 syntactically broken files (mismatched JSX tags).useSettings,threadSettledboth failed this way).Model: Claude Opus 5 · Harness: Claude Code
Update:
mainmerged in, and the fixes that followedmainmoved 172 commits after this branch was cut, so it has been merged inhere. 83 files conflicted; resolution kept the fork's newer features and
upstream's additions together rather than picking a side. The notable calls:
database actually recorded.
mainstops at 40 and numbers the fork's sixproject migrations 35-40, while this branch renumbers them 48-53 behind
upstream's 35-47. The migrator tracks by numeric id and never validates names,
so the ledger has to match the database it will run against.
ws.tscombines both sides: upstream's client-attribution helpers andthis branch's coalescer alongside main's continuation-summary transcript,
worktree file copier and idempotent draft bootstrap.
serverConfigProjectiongained main'sretainModelsAcrossTimeouts, portedinto the module upstream moved the projection to.
server.tskeeps upstream's scoped runtime services with main's ambientGitHubAccountResolverlayered above them.Fixes since the merge, all found by using the built app:
reset replaying from a mid-escape-sequence byte (the runs of stray
characters), and the missing
@xterm/xterm/css/xterm.cssimport, withoutwhich xterm's hidden helper textarea renders as a visible box with its own
caret. Upstream renders terminals through ghostty and does not import that
stylesheet, so nothing flagged its absence.
usageLimits, but the sidebar pill still read thelegacy
usagefield that nothing populates, so it hid itself entirely. It nowadapts at the edge and lists every connected instance.
instead of asking the user to configure a command it will never have.
Every package typechecks, lint is clean, and the desktop build was installed and
verified booting against the real database.
Not included
These open PRs are not fully contained here and stay open:
this PR's own implementation does not.
workspaceContentTabsStoreandWorktreeThreadTabsare present,RightWorkspacePanel.tsxis not.