Skip to content

Branch staging: create → write → review → merge in the app chrome (P6)#19

Merged
aaltshuler merged 2 commits into
mainfrom
branch-staging
Jul 7, 2026
Merged

Branch staging: create → write → review → merge in the app chrome (P6)#19
aaltshuler merged 2 commits into
mainfrom
branch-staging

Conversation

@aaltshuler

@aaltshuler aaltshuler commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Every notebook write previously landed straight on the graph's main branch — live edits against prod with no review step and no undo story. omnigraph-server 0.7+ has first-class branches (copy-on-write forks, three-way all-or-nothing merge with structured conflicts); this slice plumbs them into the web app as session branch staging.

Client facade

createBranch / mergeBranch / deleteBranch / snapshot on Client. mergeBranch returns a discriminated result ({ok, outcome} | {ok: false, conflicts}) — a conflict is an expected outcome of reviewing staged work, and the target is untouched, so callers render it and continue. Includes a raw-body fallback for the SDK bug where ConflictError.mergeConflicts is always undefined (camelCase read of a snake_case error body; present in 0.7.0 and 0.8.0).

Switch = keyed remount with carried state

The session branch is React state above the runtime; switching remounts RuntimeApp with a source targeting the branch (runtime defaultTarget and ServerSource opts stay in lockstep by construction) and the previous snapshot's state as initialState — selections survive the hop. ?branch= follows via replaceState, and the session branch is deliberately distinct from the declared base (CLI --branch / injected config, never the URL param): reloading a shared ?branch=work-x link lands you back ON the working branch while "merge into" still targets the declared base. Zero core-runtime changes.

BranchBar (header chrome)

  • Dropdown (refreshes on open — branches created by an agent or another tab appear without a reload), inline New branch prefilled work-<local date> (a 409 name collision means "exists" → switch to it).
  • On a working branch, Review & merge: table-level change summary from two /snapshot reads, two-step-confirmed merge (outcome toast on success; the structured conflict list renders on 409 with the base untouched), and delete-to-abandon. Merge never auto-deletes the source branch.
  • Divergence detection: table versions are per-lineage counters, so two diverged branches can share a version number with different contents. Deltas therefore compare version + row-count + writer lineage — numeric ties with a different last-writer render as diverged instead of a false "No changes". (Found live: the naive compare disabled a merge that would conflict.)

Verification

  • Tests: client 26 → 31, web +7 (delta computation incl. the diverged case, BranchBar flows incl. conflict rendering, config split).
  • Live on the RustFS demo (headed): create → chrome + URL follow → writes stay off main (5 vs 3 in-review) → delta review (Task ±0, v10 → v12) → fast-forward merge picked up by main → delete. Conflict path: divergent approve vs reject on the same row → second merge blocked with divergent_update rendered structured, main intact. Prod smoke read-only.
  • Row-level diff review is a noted follow-up (needs a server /changes route over the existing Rust diff_between engine).

🤖 Generated with Claude Code

https://claude.ai/code/session_011TrhtF1SiJghJASMWECzBh

Greptile Summary

This PR adds branch staging to the notebook web app. The main changes are:

  • Client methods for creating, merging, deleting, and snapshotting branches.
  • Web chrome for switching branches, reviewing staged table changes, merging, and abandoning branches.
  • Runtime remounting so reads and writes follow the active session branch.
  • Config handling that separates the declared base branch from the URL session branch.
  • Tests for branch deltas, conflict rendering, merge flow, and config behavior.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • Base-only table removals now appear in the review summary.
  • Removed-only staged changes keep the merge action available.

Important Files Changed

Filename Overview
packages/web/src/branch.ts Computes table-level branch deltas, including base-only tables as removed entries.
packages/web/src/components/BranchBar.tsx Adds branch switching, staged-change review, merge conflict display, and branch deletion controls.
packages/client/src/http.ts Adds branch facade methods and snapshot shaping for the web branch workflow.
packages/web/src/config.ts Separates the declared base branch from the active session branch and exposes source rebuilding.
packages/web/src/App.tsx Moves branch state above the runtime and remounts the runtime when the active branch changes.

Reviews (2): Last reviewed commit: "Delta summary covers base-only tables (s..." | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)

Every notebook write previously landed straight on the graph's main
branch — no review step, no undo. This adds session branch staging to
the web app over the server's first-class branches.

Client facade
- createBranch/mergeBranch/deleteBranch/snapshot on Client. mergeBranch
  returns a discriminated result ({ok, outcome} | {ok:false, conflicts})
  — conflicts are an EXPECTED review outcome, not an exception. SDK
  0.7/0.8 never populate ConflictError.mergeConflicts (they read
  camelCase from a snake_case error body), so the facade falls back to
  the raw body.

Switch = keyed remount with carried state
- BranchedApp holds the session branch ABOVE the runtime; switching
  remounts RuntimeApp with a source targeting the branch
  (defaultTarget + ServerSource opts stay in lockstep) and the previous
  snapshot state as initialState, so selections survive. ?branch=
  follows via replaceState. The session branch is distinct from the
  DECLARED base (CLI --branch / injected config, never the URL param):
  a ?branch= reload lands back ON the working branch while merges still
  target the declared base.

BranchBar (header chrome)
- Branch dropdown (refreshes on open, so branches created elsewhere
  appear), inline create prefilled work-<local date> (409 = "exists" →
  switch to it), and on a working branch a Review & merge popover:
  table-level deltas from two /snapshot reads, two-step-confirmed merge
  (outcome toast; structured conflict list on 409 with the base
  untouched), delete-to-abandon. Merge never auto-deletes the branch.
- Table deltas compare version + row_count + WRITER lineage: versions
  are per-lineage counters, so two diverged branches can collide
  numerically while contents differ — same numbers with a different
  last-writer render as "diverged" instead of a false "no changes".

Tests 219 → (client 31, web 71 here). Verified live on the RustFS demo:
branch isolation (writes stayed off main), delta review, fast-forward
merge + delete, and a real divergent_update conflict rendering
structured while main stayed intact; prod smoke read-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TrhtF1SiJghJASMWECzBh
Comment thread packages/web/src/branch.ts
Greptile: computeTableDeltas iterated only the branch's tables, so a
table present on the base but gone on the branch vanished from the
review summary — the merge could be disabled ("no changes") or a
destructive removal omitted. The union is now walked: base-only tables
appear as removed deltas (badge "removed", -N rows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TrhtF1SiJghJASMWECzBh
@aaltshuler aaltshuler merged commit 0a590d1 into main Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant