Files
boocode/docs/features/git-diff-panel/feature-specification.md
indifferentketchup 2a05d2f9fe docs: archive shipped openspec batches; add feature/plan/research notes
Move 13 shipped openspec change docs under openspec/changes/archived/.
Add docs/features/git-diff-panel, docs/plans/post-review-backlog, and
docs/research/cross-app-contract-ssot.md (the research behind the
@boocode/contracts SSOT work). Update BOOCHAT.md, BOOCODER.md, and
boocode_roadmap.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:20:33 +00:00

204 lines
15 KiB
Markdown

# Feature specification — Git diff panel
## Source
No formal upstream PRD. Authored from a direct request (2026-06-02): "add a git diff panel that can be
shown instead of the file browser, similar to Paseo," plus a read-only review of the Paseo reference at
`/opt/forks/paseo`. Ground-truth conventions drawn from the project's own docs and existing surfaces.
## Outcome
A person working in a session can review the uncommitted (and on-branch) changes of the project's
repository in a diff view that lives in the same right-side panel slot as the file browser, switching
between the two with a tab. From that view they can read each changed file's diff, stage and unstage
files, commit staged work with a message, and discard a file's changes — without leaving the session or
opening a terminal. The view tells them, at a glance, which files changed and by how much, and stays
current as agents and the user make edits.
## Actors and triggers
- **The session user** (single user) opens the right-side file panel and switches its tab from Files to
Git. The Git tab is the trigger; switching back to Files restores the file tree in the same slot
([D1](artifacts/decision-log.md#d1--placement-a-tab-inside-the-file-browser)).
- The panel is available in any session that has the file panel, because the stage / commit / discard
actions are the human user's own UI actions, not assistant tools — the AI has no access to these
endpoints, and the artifact sandbox prevents a rendered artifact from reaching them
([D8](artifacts/decision-log.md#d8--git-write-is-a-user-action-not-an-assistant-tool),
[D12](artifacts/decision-log.md#d12--git-write-security-posture)).
- An ambient indicator on the file-panel header signals the repository is dirty, making the Git tab
findable without opening it ([D17](artifacts/decision-log.md#d17--ambient-dirty-indicator-and-empty-state-hint)).
## Primary flow
1. The user opens the right-side file panel and selects the **Git** tab.
2. The panel shows the changes of the **project's repository**, defaulting its comparison mode by the
repository state on first open: **Uncommitted** (working tree vs. the last commit) when the working
tree is dirty, **Committed** (the current branch vs. its base) when it is clean. Once the user
selects a mode explicitly, that choice is pinned for the session and subsequent refreshes do not
override it; if a refresh would change the auto-selected mode (e.g. the tree went clean while
Uncommitted was pinned), the panel briefly notes the change rather than swapping silently
([D2](artifacts/decision-log.md#d2--scope-the-project-repository-with-two-comparison-modes),
[D3](artifacts/decision-log.md#d3--mode-auto-selection-and-session-pinning),
[D14](artifacts/decision-log.md#d14--mode-pinning-and-first-open-auto-selection)).
3. The panel presents a list of changed files, each with its path, change type (added / modified /
deleted / renamed / untracked), and an added/removed line count. In Committed mode the header labels
the base used ("Git — branch vs &lt;base&gt;")
([D13](artifacts/decision-log.md#d13--committed-mode-base-resolution-and-labeling)).
4. The user expands a file to read its diff inline; collapsing hides it again. A control expands or
collapses all files at once.
5. The user **stages** or **unstages** individual files. Staged and unstaged files are grouped into
separate sections and distinguished by both a label/icon and grouping — not color alone — and each
stage/unstage control carries an accessible name that includes the file path.
6. The user writes a commit message and **commits** the staged files. The commit's author and committer
identity is derived from a server-side source; the request cannot set or influence it
([D12](artifacts/decision-log.md#d12--git-write-security-posture)). On success the committed files
leave the list, the panel refreshes to the new repository state, and a brief non-blocking confirmation
is shown ([D6](artifacts/decision-log.md#d6--v1-actions-stage-unstage-commit-discard-no-push)).
7. The user may switch the comparison mode explicitly (Uncommitted ↔ Committed) at any time; the file
list and counts update to the selected mode, and the choice is pinned for the remainder of the session.
## Alternate flows and states
- **Loading:** while the first difference is being computed the panel shows a brief loading indicator in
place of the file list.
- **Empty (no changes):** when the selected mode has no changes the panel shows a mode-specific empty
message ("No uncommitted changes" / "No changes vs. the base branch") instead of a file list. When
the Git view is empty but the session has unapplied pending changes, the empty state hints that those
live in the pending-changes panel
([D17](artifacts/decision-log.md#d17--ambient-dirty-indicator-and-empty-state-hint)).
- **Discard a file:** the user discards a single file's changes from an overflow or secondary affordance,
separated from the Stage/Unstage controls (not an equal-weight sibling). Because discard is
irrecoverable, the panel asks for a plain confirmation before acting, with wording that distinguishes
the two cases: "Discard changes to X?" for a tracked file (which returns to its committed content) and
"Delete X? It has never been committed and cannot be recovered" for an untracked file (which is
permanently removed). On confirmation the file's changes are applied and the list refreshes
([D7](artifacts/decision-log.md#d7--discard-requires-a-plain-confirmation),
[D15](artifacts/decision-log.md#d15--discard-is-irrecoverable-tracked-vs-untracked-confirmation-separated-affordance)).
- **Commit with an empty message or nothing staged:** the commit control is unavailable until at least one
file is staged and a non-empty message is present.
- **Refresh:** the panel re-reads the repository state when the Git tab is opened, after any stage /
unstage / commit / discard it performs, after an agent turn completes, after the user applies or
discards a queued change in the pending-changes panel, and on an explicit Refresh control. Concurrent
refresh triggers are coalesced — a refresh already in flight absorbs later triggers rather than
spawning a second concurrent read, so the panel settles to a single final snapshot
([D10](artifacts/decision-log.md#d10--refresh-on-open-on-mutation-on-turn-completion-on-demand-with-coalescence)).
## Edge cases and failure modes
- **Not a git repository:** when the project's path is not a git repository the Git tab is not offered;
the file panel stays on Files only.
- **Binary files:** a changed binary file appears in the list with its path and change type but its body
shows a "Binary file" placeholder instead of a diff.
- **Very large diffs:** a file whose diff exceeds a display threshold appears in the list with its path and
counts but its body shows a "Diff too large to display" placeholder; a git read that does not complete
within a deadline exits the loading state, shows an error, and offers Refresh; the overall response is
bounded so a huge change set cannot stall the panel
([D5](artifacts/decision-log.md#d5--binary-and-large-file-handling)).
- **A git action fails:** the panel surfaces the failure as an inline error — commit-area errors appear
near the commit control; per-file action errors appear in the affected file row. The panel leaves the
repository as close to its pre-action state as the git layer allows; the list refreshes to reflect the
repository's true state.
- **Repository busy (index locked):** when a write fails because the repository's index is locked by
another process (e.g. a concurrent agent turn), the panel communicates "the repository is busy, try
again" rather than a raw error.
- **In-progress git operations:** when the repository is mid-operation (merge, rebase, cherry-pick, or
bisect), the panel disables its write affordances and shows the repository's current state, rather than
allowing stage / commit / discard to fail with raw errors.
- **Concurrent edits during a read:** the displayed diff is a snapshot at read time; a later edit is picked
up on the next refresh rather than mutating the view mid-read.
- **The base branch cannot be resolved** (Committed mode, no discoverable base): the panel falls back to
showing uncommitted changes and labels the mode as a fallback, rather than silently swapping or erroring
([D13](artifacts/decision-log.md#d13--committed-mode-base-resolution-and-labeling)).
## User interactions
- A **Files / Git** tab switch in the file panel header. The diff view occupies the same slot as the
file tree; only one is shown at a time. The tab strip and header fit on one line without horizontal
scroll or wrapping; all interactive controls meet the app's existing mobile tap-target minimum. The
tab affordance and the panel work the same on mobile (where the panel is a slide-in drawer) as on
desktop ([D16](artifacts/decision-log.md#d16--tab-named-git),
[D18](artifacts/decision-log.md#d18--mobile-tap-target-and-header-fit)).
- An **ambient dirty indicator** on the file-panel toggle/header when the repository is dirty
([D17](artifacts/decision-log.md#d17--ambient-dirty-indicator-and-empty-state-hint)).
- A **comparison-mode** selector (Uncommitted / Committed) at the top of the Git view.
- Per-file rows showing path, change type, and an added/removed count, each expandable to reveal a
syntax-highlighted unified diff, with an expand-all / collapse-all control.
- Files grouped into **staged** and **unstaged** sections; each section is labeled and the grouping
itself is the primary distinction (supplemented by a per-file label/icon), not color alone; each
stage/unstage control carries an accessible name including the file path.
- Per-file **Stage / Unstage** affordances and a **Discard** affordance in an overflow or secondary
position (not an equal-weight sibling of Stage/Unstage), and a **commit message** field with a
**Commit** action. Stage / Unstage / Commit / Discard are available only in Uncommitted mode;
Committed mode is read-only review
([D6](artifacts/decision-log.md#d6--v1-actions-stage-unstage-commit-discard-no-push),
[D15](artifacts/decision-log.md#d15--discard-is-irrecoverable-tracked-vs-untracked-confirmation-separated-affordance)).
- A **Refresh** control.
- Diffs render in a single (unified) layout; additions and removals are visually distinguished with line
numbers ([D9](artifacts/decision-log.md)).
## Coordinations
- **The file panel** hosts the view and owns the Files ↔ Git tab state.
- **The project repository** is the single source of truth for the diff and the target of stage / commit /
discard. The repository root is derived server-side from the session's project record; per-file
arguments are validated to resolve inside the repository and rejected if they escape it; user-supplied
text (commit message, file targets) is passed as discrete arguments and never interpreted as commands.
The git-write actions are never registered as assistant tools; the artifact sandbox prevents a rendered
artifact from invoking them
([D11](artifacts/decision-log.md#d11--all-git-operations-scoped-to-the-project-repository-path),
[D12](artifacts/decision-log.md#d12--git-write-security-posture)).
- **The pending-changes panel** remains the place where unapplied agent edits (held in a separate working
copy) are reviewed and applied; the diff panel reflects the project repository's real state, so applying
or discarding a pending change is one of the events that refreshes the diff panel. The two panels are
complementary, not duplicates ([D2](artifacts/decision-log.md#d2--scope-the-project-repository-with-two-comparison-modes)).
- **Agent turns and the user's own edits** change the repository; turn completion is a refresh trigger.
## Out of scope
- Pushing, pulling, creating pull requests, merging, or any operation that talks to a remote.
- Per-line or per-hunk review comments and "send selected lines to an agent" — that is a separate feature
(the diff-line re-prompt item), and this panel deliberately does not build a line-selection/commenting
surface.
- Side-by-side (split) diff layout.
- Staging or discarding individual hunks/lines (stage and discard operate at whole-file granularity).
- A live file-system watcher that streams diffs as files change on disk; refresh is event- and
demand-driven, not continuous.
- Showing the session agent's separate working-copy diff in this panel; that remains the pending-changes
panel's job.
- Renaming the existing pending-changes panel; naming and scope changes to that panel are out of scope
for this feature.
## Deferred (YAGNI)
- **Push / pull / pull-request / merge actions.** Deferred — not requested (the request was "stage/commit"),
and the assistant-level no-remote-write rule signals remotes are out of band for now. Reopening trigger: a
stated need to publish commits from the panel. Evidence gate failed: no user-described need.
- **Side-by-side diff layout.** Deferred — the primary surface is mobile-first and unified reads well there;
a split layout is a desktop-only enhancement. Reopening trigger: a request to compare wide files
side-by-side on desktop. Evidence gate: simpler unified version satisfies the stated need.
- **Per-hunk staging / discarding.** Deferred — whole-file granularity covers the stated stage/commit need.
Reopening trigger: a request to commit part of a file.
- **Continuous file-watch streaming of the diff.** Deferred — event- and demand-driven refresh covers a
single-user workflow without a watcher's cost. Reopening trigger: the diff is observed to feel stale
between refresh events in practice.
## Open items
- None. Commit author/committer identity is settled: derived server-side from the host git configuration;
the request cannot set or influence it (F3, [D12](artifacts/decision-log.md#d12--git-write-security-posture)).
## Summary
A Files / Git tab in the right-side file panel that shows the project repository's diff in two modes
(uncommitted vs. HEAD, and the branch vs. its upstream/default base, auto-selected by repo state on
first open and then pinned to the user's choice), lets the user stage, unstage, commit (with
server-derived identity), and discard whole files (with irrecoverable-discard confirmation distinguishing
tracked and untracked cases), and stays current via coalesced event- and demand-driven refresh. Single
actor (the session user); the panel is complementary to the existing pending-changes panel. The tab is
named "Git" (Files / Git), distinct from the pending-changes panel. An ambient dirty indicator makes it
findable. Write affordances are disabled during in-progress git operations. 18 decisions recorded. Four
items deferred under YAGNI (remote actions, split layout, per-hunk granularity, file-watch streaming).
Review team: junior-developer, user-experience-designer, adversarial-security-analyst, on-call-engineer.
No load-bearing mechanics required a technical-notes file (the git mechanics are discoverable from the
codebase's existing git-metadata path).