Skip to content

chore: re-sync with upstream, fork behaviour taking priority - #77

Merged
enisze merged 1458 commits into
mainfrom
sync/combined
Sep 7, 2026
Merged

enisze merged 1458 commits into
mainfrom
sync/combined

Conversation

@enisze

@enisze enisze commented Sep 7, 2026 •

Copy link
Copy Markdown
Owner

What this is

Re-syncs the fork with current upstream/main with fork behaviour taking priority, replacing the earlier chore/sync-upstream-2026-09 attempt 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-09 merged 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:

  • 31 files byte-identical to upstream (fork edit discarded)
  • 17 files missing entirely
  • migrations 035–040 deleted and re-added as 048–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 its ClaudeProvider wiring, HeaderOverflowMenu, ProjectScriptsField, the per-project provider settings route, and the Archive sidebar entry.

Fork features verified present

SidebarV2 · providerUsage + ClaudeProvider wiring · HeaderOverflowMenu · ProjectScriptsField · FileViewModeToggle · useDiffThemeName · workspaceThreadRef · BetaSettingsPanel · per-project provider settings route · Archive nav · messageAttachmentReuse · PullRequestThreadDialog.logic

Resolution policy

Situation Policy
Both sides added to the same file Combine — both sets of fields/exports survive
Heavily interleaved fork-owned UI Fork version wholesale
Upstream is a strict superset Upstream
Upstream deleted a file the fork still develops Keep fork's
Fork deleted a file upstream still modifies Keep the deletion

Examples: threadSettled.ts keeps fork's settled logic and upstream's snooze presets; ChatAttachment unions upstream's file/unknown types and fork's document type; upstream's deliberate enableAssistantStreaming → enableLegacyTokenStreaming rename 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/main in this same checkout, so none are caused by this branch:

  • 10 are macOS resolving /var → /private/var, which breaks path-equality assertions
  • 1 needs node on PATH inside a stubbed script
  • 2 are .github/scripts/*.test.cjs, which use node:test and are not vitest suites at all

apps/mobile reports errors here too (68 on upstream/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 — lost autoPull's boolean↔integer bridge. SQLite cannot bind a boolean, so every projection write failed and took ~230 tests with it. Also restored the projectIcon JSON decode.
  • GitVcsDriver — seven service methods were missing from the service object, so git.remoteExists is not a function broke worktree creation at runtime.
  • gitHubAuthStatus — the merge adopted upstream's --json hosts invocation but kept the fork's text parser, so GitHub auth always reported unknown.
  • primary/auth — dropped the await on session establishment, so manual token submission resolved before authentication landed.
  • ComposerCommandMenu — skills reached as /skill:name matched 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 its display: flex, clipped to Γ3, and wrapped "Code" onto its own line.
  • keybindings — upstream's filePicker.toggle shadowed the fork's wired file.open on 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 named HEAD, unborn HEAD, and the origin/default-branch caches.

UI repairs

  • The sidebar update pill rendered a SidebarMenuItem outside any <ul>, so it drew a stray list marker and claimed an empty row. It is back in the footer menu row.
  • The titlebar layout controls lost their container's positioning and fell into normal flow mid-pane. Re-anchored, with the tab row reserving space so the Review button no longer collides.
  • The diff header shows its change summary again (file count, total +/−), summed from the same per-file stats the navigator lists.
  • A stale 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).
  • Wholesale-fork on shared helpers — silently drops upstream's exports (useSettings, threadSettled both failed this way).

Model: Claude Opus 5 · Harness: Claude Code


Update: main merged in, and the fixes that followed

main moved 172 commits after this branch was cut, so it has been merged in
here. 83 files conflicted; resolution kept the fork's newer features and
upstream's additions together rather than picking a side. The notable calls:

  • Migrations keep 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, 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.ts combines both sides: upstream's client-attribution helpers and
    this branch's coalescer alongside main's continuation-summary transcript,
    worktree file copier and idempotent draft bootstrap.
  • serverConfigProjection gained main's retainModelsAcrossTimeouts, ported
    into the module upstream moved the projection to.
  • server.ts keeps upstream's scoped runtime services with main's ambient
    GitHubAccountResolver layered above them.

Fixes since the merge, all found by using the built app:

  • Terminal: three separate faults — duplicate resizes repainting the prompt, a
    reset replaying from a mid-escape-sequence byte (the runs of stray
    characters), and the missing @xterm/xterm/css/xterm.css import, without
    which 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.
  • Usage: providers publish usageLimits, but the sidebar pill still read the
    legacy usage field that nothing populates, so it hid itself entirely. It now
    adapts at the edge and lists every connected instance.
  • The tasks dock opens on a plain shell, and a terminal tab renders a terminal
    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:

juliusmarminge and others added 30 commits September 5, 2026 00:12
…tgg#9909)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tgg#9911)

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.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 7, 2026
@enisze
enisze merged commit 483639b into main Sep 7, 2026
10 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.