Skip to content

GitStudio 1.0.1 — repair release - #3

Open
antonarnaudov wants to merge 12 commits into
mainfrom
fix/1.0.1-quality
Open

GitStudio 1.0.1 — repair release#3
antonarnaudov wants to merge 12 commits into
mainfrom
fix/1.0.1-quality

Conversation

@antonarnaudov

Copy link
Copy Markdown
Contributor

1.0.0 shipped a walkthrough advertising features that silently did nothing. This is the repair release.

Root causes fixed

Pull demanded a rebase, and took the merge editor down with it. SyncOps.pull ran a bare git pull, which since git 2.34 is a hard error on divergent branches with no pull.rebase/pull.ff config ("Need to specify how to reconcile divergent branches") — surfaced verbatim to the user. Because the pull aborted, no merge was attempted, no conflict was created, and the 3-pane merge editor could never appear. Strategy is now always explicit. Regression test reproduces the fatal against real git and proves the fix.

The merge editor was unreachable from GitStudio's own Changes view — a conflicted file opened a plain 2-way diff. Its only entry points were VS Code's built-in SCM view and the editor title bar.

Inline blame stacked on top of other providers. The duplicate check tested inspect().defaultValue instead of the effective value; git.blame.editorDecoration.enabled defaults to false, so the built-in decoration — the one that actually overlaps — was never detected, while the only branch that could fire silently rewrote the user's global settings to disable the status-bar item (which overlaps nothing). Now: effective values, GitLens detection, ask-don't-assume, and we never write another extension's settings unprompted.

File history had no working UI at all — routed exclusively through registerTimelineProvider, a proposed API absent from stable typings, so the call threw at activation into a catch {}. Added GitStudio: Show File History. Line history re-opened its QuickPick on top of the diff it had just opened.

History/blame/merge commands silently no-op'd with no active editor — and the walkthrough IS the active tab, so its buttons did nothing while the step still ticked green.

Windows: one path helper was duplicated 5× with a /-only boundary that never matches a \ fsPath — history, blame and merge were dead there outright.

Staging. The Staged group was display:none when empty, so users never saw the model; committing with nothing staged then failed with literally "unknown error" (git reports "no changes added to commit" on stdout, we only read stderr). Now: Staged always visible with a hint, a Stage All & Commit offer, no row snap-back on partially-staged files, Discard styled destructive, conflicts say Mark as Resolved.

Walkthrough rewritten: every button runs a command that works from the walkthrough, real screenshots as media (now shipped in the vsix), and the AI step points at the real Connect AI panel. Its old merge step's primary button started a destructive interactive rebase.

Icons: macOS dock icon was full-bleed (hence oversized) and theme-swapped to a white tile that washed the mark out. Now: Apple's icon margin, a properly-tuned light tile, and optical sizing — thin lanes for display surfaces, thick lanes for the dock.

Verification

  • Regression test for the pull fatal (real git, divergent branches).
  • 0 type errors, all 7 test suites pass, engine/host-bridge purity OK.
  • Staging + walkthrough states verified in the real rendered webview.

🤖 Generated with Claude Code

antonarnaudov and others added 12 commits July 14, 2026 19:33
The walkthrough advertised features that silently did nothing. Root causes,
all verified against the code:

Pull (the big one)
- SyncOps.pull ran a bare `git pull`. Since git 2.34 that is a HARD ERROR on
  divergent branches when the user has no pull.rebase/pull.ff config
  ('fatal: Need to specify how to reconcile divergent branches'), and we piped
  that stderr straight to the user — so Pull looked like GitStudio was
  demanding you go configure a rebase. Worse: the pull aborted, so no merge was
  attempted, no conflict was created, and the 3-pane merge editor could never
  appear. Strategy is now always explicit (respects the user's config, else
  merges). Regression test reproduces the fatal and proves the fix.

Merge editor
- Clicking a conflicted file in GitStudio's OWN Changes view opened a plain
  2-way diff; the merge editor's only entry points were VS Code's built-in SCM
  view and the editor title bar. Conflicts now open the 3-pane editor.
- Auto-open swallowed every error, so a failed open was indistinguishable from
  'the feature doesn't work'. It now reports, and offers the plain diff.

Blame (the 'overlapping 3 other extensions' bug)
- The duplicate-blame check tested inspect().defaultValue instead of the
  EFFECTIVE value. git.blame.editorDecoration.enabled defaults to false, so the
  built-in inline blame — the one that actually overlaps us — was NEVER
  detected, while the only branch that could fire silently rewrote the user's
  GLOBAL settings to disable the status-bar item, which overlaps nothing.
  Now: effective values, GitLens detection, and we never touch another
  extension's settings without an explicit click.
- The annotation was never cleared when focus left an editor, so a split view
  kept a stale line annotation — which itself looked like duplicate blame.

History ('explore files and linear history does not work at all')
- Per-file history had NO working surface: it was routed through
  registerTimelineProvider, a PROPOSED API absent from stable typings, so the
  call threw at activation and was swallowed by a catch{}. Added a real
  'GitStudio: Show File History' command.
- Line history re-opened its QuickPick ON TOP of the diff it had just opened,
  so the diff was hidden behind the picker and it read as 'nothing happened'.
- Every history/blame/merge command silently returned when there was no ACTIVE
  text editor — and the walkthrough IS the active tab, so its buttons did
  nothing while the step still ticked green. They now resolve the visible file
  (and map a gitstudio-rev: revision back to its working-tree file), and say
  something when they truly can't.
- isInside() was duplicated 5x with a forward-slash-only boundary that never
  matched a Windows '\' fsPath — killing history, blame and merge on Windows
  outright. One shared, separator- and case-tolerant implementation now.

Walkthrough
- Rewritten: every button is a command that actually works from the walkthrough
  context, real product screenshots replace the wordmark on every step, and the
  AI step now points at the real Connect AI panel (zero-key Copilot/Cursor,
  OpenAI-compatible, or a local agent CLI) instead of the old Anthropic-only
  key prompt. Its old primary button literally started a destructive interactive
  rebase.
- media/shots/** now SHIPS in the vsix: walkthrough media loads from inside the
  extension (unlike README images), so excluding it would have rendered a
  walkthrough of broken image boxes.
- 'cli' was missing from the gitstudio.ai.provider enum, so anyone connecting a
  local agent got a red squiggle in settings.json.

Icons
- The macOS dock icon was full-bleed with zero transparent margin, so macOS
  scaled it edge-to-edge into the dock slot and it rendered visibly larger than
  every neighbouring app. It now carries Apple's ~10% margin (824px art in a
  1024 canvas); Windows/Linux/Marketplace stay full-bleed.
- The dock icon theme-swapped to a white tile in light mode, which washed the
  mark's violet lanes and commit nodes out. A dock icon is fixed identity — no
  real app does that. One dark squircle, which reads on any dock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'Staging is confusing af' — it was. Root causes:

- The STAGED group was display:none when empty, so with nothing staged the
  word 'Staged' did not exist in the UI. Users saw one flat list of files and
  never learned the model. It now always renders, with an empty-state hint.
- Then Commit failed with literally 'commit failed — unknown error': git exits
  1 and prints 'no changes added to commit' on STDOUT, but we only ever read
  stderr, which is empty. CommitResult now carries stdout, the message says the
  real reason, and a nothing-staged commit offers 'Stage All & Commit' instead
  of blaming the user.
- Optimistic staging snapped rows back: a partially-staged file (git status
  'MM') is in BOTH lists before git even runs, so the pending op was 'satisfied'
  by the first state push and the row visibly jumped back to Unstaged. A stage
  is now only satisfied once the path LEAVES the unstaged/merge lists.
- Discard sat in the same slot where Unstage sits in the staged group, and
  hovered brand-purple exactly like the safe +. Muscle memory from one group
  destroyed work in the other. It now reads as destructive (red).
- On a conflicted file, + was labelled 'Stage file'. Staging a conflict means
  'I resolved this' — it now says 'Mark as Resolved' with a check.
- Double-click on a row fired click twice AND opened the actions menu, so the
  diff opened twice with a popover over it. Right-click keeps the menu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tile

Removing the light variant was the wrong correction. macOS can't carry
appearance variants inside an .icns, so app.dock.setIcon() at runtime is the
only way an Electron app adapts — the original intent was right; the light tile
was just badly drawn.

It reused the dark mark's colours verbatim: light-violet lanes (#C4ADFF) and
near-white node cores (#F3EEFF) on a near-white tile. The three corner nodes
straddle the cube's edge, so half of each sat on the pale background and simply
vanished — 'the white background hides the logo git lines and circles'.

Re-tuned for light: deep brand-violet lanes and rings (#6B5BE6/#8B75F5) that
carry on BOTH the pale tile and the slate cube, with white cores bounded by
those rings. The theme swap is restored, and both tiles now carry Apple's icon
margin so neither renders oversized in the dock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…read

Three things, one root cause.

The mark punches its commit nodes out with an SVG <mask> so the dots are true
HOLES showing the tile through them — that is what gives them a clean dark
centre. I regenerated the icons with cairosvg, which SILENTLY IGNORES the mask:
the white node cores (#F3EEFF) painted straight over the holes, so every dot
grew a white centre. It also quietly regressed the Marketplace icon, which had
been correct since 1.0.0.

- Rasterize with headless Chrome instead. It honors <mask>. A generator assert
  now fails loudly if a renderer ever ignores it again (dark node cores must
  come out dark).
- Make the mark read: the cube was only ~56% of the tile, so it scales up, and
  the merge-Y lanes — the brand's whole signature — go 21 -> 27 wide and
  brighter (#DCCFFF -> #AB8FFF on dark). Verified at real dock sizes (48-128px),
  not just at 1024.
- Both tiles are generated from ONE parametric source, so dark and light can no
  longer drift apart.

The activity-bar glyph is untouched — it was already right.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Top-bar mark (the app's logo): rebuilt on the activity-bar glyph's recipe —
three translucent isometric FACES for a real 3D body (it was a flat wireframe
silhouette before), a soft closing silhouette, and the merge-Y as the bold
signature with every node punched through by a mask. Painted in the wordmark's
violet->magenta gradient, so the glyph and the logotype read as one brand.

Dock tile: the lanes and nodes disappeared at real dock size (48-64px), and the
light tile was effectively paper (#FBFAFE) so the mark had nothing to sit on.
- lanes 27 -> 33, nodes 28 -> 33 (centre 31 -> 37): the merge-Y is the
  signature and it now survives at 48px.
- the light tile is a darker shade of white (#E7E3F3 -> #D2CCE6, lavender-grey)
  with a stronger cube silhouette, so the cube and the deep-violet lanes have a
  ground instead of dissolving into a light dock.
Judged on the backdrops each tile actually sits on (a pale dock strip and a dark
one) at 48/64/96/128 — not at 1024, which is what hid the problem.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…agree)

Two things were wrong.

1. The coloured glyph only landed in the app's top bar. The DOCK tile was still
   the old design — an opaque slate cube with lanes painted on top.
2. Because that cube was opaque and fixed, the light tile had to flip its lane
   colours to stay readable, so the two tiles read as two different logos.

The dock tile is now built on the same activity-bar glyph recipe as the in-app
mark: the cube is translucent tints of ONE brand gradient (never a second hue),
closed by a gradient silhouette, with the merge-Y bold on top and every commit
node punched through as a real hole. Because the body is tints rather than a
fixed slate, the SAME mark works on either ground — only the tile changes:

- dark tile: the bright end of the wordmark gradient (#C9B6FF -> #D98BFF)
- light tile: the deep end (#7C5FF0 -> #B44FE0) on lavender-grey, because the
  bright gradient washes out on a pale ground

Same structure, same hues, same logo — one dark, one light. Judged at 48-128px
on the dock strips each tile actually sits on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the dock

One lane weight cannot serve a 180px welcome hero and a 48px dock slot. Thin
lanes read as elegant when large and DISAPPEAR when small; thick lanes stay
legible when small and look clumsy when large. Chasing one weight for both is
why the icon kept ping-ponging between 'too thick' and 'not visible'.

So the same mark now ships in two optical weights, and each surface gets the one
it needs:

  DISPLAY (lane 23) — the welcome hero, the Marketplace listing, the README.
  DOCK    (lane 38) — the macOS dock and Windows taskbar, seen at 32-64px.

The dock tile also gets a lighter cube body (#8E8CBC/#454373/#63608F) and
brighter lanes (#E6DCFF -> #B79EFF): the old slate sank into the dark tile at
48px and the whole mark went muddy.

Judged at the size each surface is ACTUALLY seen at — 180/128px for display,
32/48/64px on real dock strips for the dock — which is the step I kept skipping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reported breakage — 'no branch shown, can't click branches' — is the
Changes view collapsing to its no-repo state. Root cause is structural: the
view read the branch/file lists from vscode.git's live `repo.state`, and any
throw there propagated to doPushState's bare `catch { hasRepo = false }`,
blanking the whole view (no branch pill, empty menu).

Every layer tested clean in isolation (git-service listRefs/status.read against
the real repo; the webview renders the real branch list with zero errors), so
the failure lives in vscode.git's live state on the user's machine — the one
path not reproducible headlessly. Hardened it so the view can't die there:

- resolveState uses vscode.git ONLY once it has a real HEAD branch; during the
  attach window (branch not yet computed) or on ANY throw, it falls through to
  our own `git status`, which needs no vscode.git and is already proven.
- collectBranches failures no longer sink the state push — keep the last-known
  branch list instead of emptying the menu.
- doPushState keeps hasRepo TRUE when a repo is open but a status read hiccups,
  so the view stays interactive instead of flipping to onboarding.
- All three failure points now console.error the real reason, so if anything
  DOES still fail it names itself in the exthost log / Output → GitStudio.

Also: earlier review-fix batch (Stage-All-&-Commit, clean-tree staged group,
blame→showFileHistory, branch.<name>.rebase, doCommit re-entrancy) and the
dialog hover/tooltip work (visible hover + wrapping tooltips for clipped
names / long commands).

NOT published — marketplace stays on 1.0.0 until this is confirmed live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'empty branch pill / dead clicks' report reproduces on no machine I can
test — the webview renders the real branch list correctly even under the
production CSP, git-service listRefs/status.read work against both of the user's
repos, and the host logs 'every section wired with no errors'. Because webview
console errors never reach the extension-host log, a silent script failure in
the real vscode-webview context would be invisible.

So the webview now posts back — via the existing bridge — a 'script alive' ping
on start, plus any window error and any securitypolicyviolation; the host
console.errors them (readable in the exthost log / Output → GitStudio), and logs
when 'ready' arrives. One reload will show definitively whether the webview
script runs, and if not, why. Tripwire — reverted once the cause is found.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
THE BUG, finally pinned from a screenshot: the pill showed '—' (empty branch)
with the composer visible (hasRepo=true). That state is only reachable when
resolveState returns branch=undefined WITHOUT throwing — which happens when
vscode.git has attached the repo but not yet computed state.HEAD (a race other
Git extensions on the same repo, e.g. git-ai, can trigger). branch: head?.name
is then undefined, no error fires, and the pill has nothing to show and no
branch to click.

Fix: doPushState now GUARANTEES the branch. If resolveState yields no branch
while a repo is open, it recovers the branch from our own git-service getHead()
— which is authoritative and doesn't depend on vscode.git's timing. The pill
can no longer be empty on a real repo, whatever vscode.git is doing.

Also adds a readable diagnostic trail to Output → GitStudio (log.ts shared
sink): webview alive/error/CSP pings, 'ready' receipt, and the exact
hasRepo/branch/counts each pushState sends — so any remaining edge names itself
instead of failing silently (webview console never reaches a log).

NOT published — marketplace stays 1.0.0 until confirmed live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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