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>
204 lines
15 KiB
Markdown
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 <base>")
|
|
([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).
|