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>
This commit is contained in:
361
docs/features/git-diff-panel/artifacts/decision-log.md
Normal file
361
docs/features/git-diff-panel/artifacts/decision-log.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# Decision log — Git diff panel
|
||||
|
||||
Decisions behind [`feature-specification.md`](../feature-specification.md). Full decisions carry rationale,
|
||||
evidence, and rejected alternatives; trivial decisions are one-liners. Shared D# counter.
|
||||
|
||||
## Full decisions
|
||||
|
||||
### D1 — Placement: a tab inside the file browser
|
||||
**Question:** Where does the diff view live in the workspace?
|
||||
**Decision:** The diff view lives in the right-side file panel as a Files / Git tab, occupying the same
|
||||
slot as the file tree, rather than as a new standalone workspace pane.
|
||||
**Rationale:** The request was "instead of the file browser," and the file browser is a right-side sidebar,
|
||||
not a workspace-grid pane. The reference design (Paseo) puts Changes / Files tabs in one sidebar slot. A
|
||||
new workspace pane would require new pane-kind plumbing for an affordance the user described as a
|
||||
replacement, not an addition.
|
||||
**Evidence:** User answer (2026-06-02). Codebase: the file browser is the right-rail sidebar; the legacy
|
||||
"file_browser" pane kind is unused. Paseo `explorer-sidebar.tsx` (Changes/Files tabs in one slot).
|
||||
**Rejected alternatives:**
|
||||
- A standalone git-diff workspace pane — rejected: it is an addition, not a replacement, and adds pane
|
||||
plumbing the user did not ask for.
|
||||
**Driven by findings:** —
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D9, D10.
|
||||
**Referenced in spec:** Actors and triggers, Primary flow, User interactions.
|
||||
|
||||
### D2 — Scope: the project repository, with two comparison modes
|
||||
**Question:** What repository and comparison modes does the panel cover?
|
||||
**Decision:** The panel shows the **project repository's** changes, with a selector between **Uncommitted**
|
||||
(working tree vs. last commit) and **Committed** (current branch vs. its upstream tracking branch when
|
||||
set, otherwise the repository's default branch). It does not show the session agent's separate
|
||||
working-copy diff — that remains the pending-changes panel's job. In Committed mode the view labels the
|
||||
base it resolved ("Git — branch vs <base>"); when no base resolves the panel falls back to
|
||||
Uncommitted and labels the mode as a fallback.
|
||||
**Rationale:** The file browser is scoped to the project repository, so the diff "instead of" it should
|
||||
share that scope. The user chose both comparison modes (Paseo-style) over a single mode. The agent
|
||||
working-copy diff is already surfaced by the pending-changes panel; duplicating it here would create two
|
||||
overlapping surfaces. Labeling the base prevents silent ambiguity (F11).
|
||||
**Evidence:** User answer (2026-06-02, "Both, with a selector"). Codebase: the file browser and project
|
||||
git-metadata are scoped to the project path; agent worktree diffs already flow to the pending-changes
|
||||
panel. Paseo uncommitted/committed mode selector. F11 (base unlabeled finding).
|
||||
**Rejected alternatives:**
|
||||
- Project working tree only — rejected: user wanted both modes.
|
||||
- Session agent worktree — rejected: overlaps the pending-changes panel and is not the file browser's scope.
|
||||
- No base labeling — rejected (F11): the base is ambiguous between upstream tracking branch and default
|
||||
branch; unlabeled output invites confusion.
|
||||
**Driven by findings:** F11.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D3, D4, D5, D11, D13.
|
||||
**Referenced in spec:** Outcome, Primary flow, Edge cases and failure modes, Coordinations.
|
||||
|
||||
### D3 — Mode auto-selection and session pinning
|
||||
**Question:** How is the initial mode chosen and how does it behave across refreshes?
|
||||
**Decision:** Auto mode-selection applies on first open only: Uncommitted when the working tree is dirty,
|
||||
Committed when it is clean. Once the user selects a mode explicitly it is pinned for the session;
|
||||
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.
|
||||
**Rationale:** Paseo's convention is auto-select by state. However, a refresh-triggered silent mode swap
|
||||
would dislocate the user's view without warning — they could be mid-review and suddenly see a different
|
||||
file list (F12). Pinning after explicit selection preserves the user's intent.
|
||||
**Evidence:** Paseo convention (auto-select by dirty state). F12 (silent-dislocation finding;
|
||||
design-judgment resolution).
|
||||
**Rejected alternatives:**
|
||||
- Always auto-select on every refresh — rejected (F12): silently dislocates the view when the tree state
|
||||
changes mid-session.
|
||||
- No auto-selection (always start in a fixed mode) — rejected: ignores the most useful starting point.
|
||||
**Driven by findings:** F12.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D14.
|
||||
**Referenced in spec:** Primary flow, Alternate flows and states.
|
||||
|
||||
### D5 — Binary and large-file handling
|
||||
**Question:** What does the panel show for files it cannot diff or whose diff is too large?
|
||||
**Decision:** Binary files show a "Binary file" placeholder instead of a diff body. Files over a display-
|
||||
size threshold show "Diff too large to display" in place of the diff body. A git read that does not
|
||||
complete within a deadline exits the loading state, shows an error, and offers Refresh. The total
|
||||
response payload is bounded so a huge change set cannot stall the panel.
|
||||
**Rationale:** Paseo-style caps prevent the panel from hanging or overflowing on large repos. The read-
|
||||
deadline (F7) is a distinct concern from the large-result cap: a slow git process can stall the panel
|
||||
even when individual files are small.
|
||||
**Evidence:** Paseo codebase (display-size caps). F7 (hanging git-read finding; evidence-backed addition).
|
||||
**Rejected alternatives:**
|
||||
- No cap — rejected: a huge change set can stall or overflow the panel.
|
||||
- Combine deadline and size cap into one mechanism — rejected (F7): they address different failure modes
|
||||
(slow process vs. large output); both are needed.
|
||||
**Driven by findings:** F7.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Edge cases and failure modes.
|
||||
|
||||
### D6 — v1 actions: stage, unstage, commit, discard (no push)
|
||||
**Question:** Which write actions are included in v1, and in which modes are they available?
|
||||
**Decision:** v1 includes staging/unstaging files, committing staged files with a message, and discarding a
|
||||
file's changes — all available only in Uncommitted mode. Committed mode is read-only review with no write
|
||||
actions. Pushing, pulling, PRs, and merges are excluded. Commit author/committer identity is derived
|
||||
server-side; the request cannot set or influence it.
|
||||
**Rationale:** The user chose to include stage/commit over a read-only view. Remote operations were not
|
||||
requested and the assistant-level rule already treats remote writes as out of band, so v1 stops at local
|
||||
history. Allowing write actions in Committed mode would mean reverting committed history (per-file resets
|
||||
of committed commits), which was not requested and creates a different risk profile. Committer identity
|
||||
must be server-derived to prevent the request body from spoofing authorship (F3).
|
||||
**Evidence:** User answer (2026-06-02, "Include stage/commit"). Convention: the assistant cannot push to
|
||||
remotes (project docs) — signals remotes are deliberately out of band. F3 (committer-identity
|
||||
finding). F14 (mode-scoping finding; design-judgment resolution).
|
||||
**Rejected alternatives:**
|
||||
- Read-only review — rejected by the user.
|
||||
- Full git actions incl. push/pull/PR (Paseo's set) — deferred (YAGNI): not requested.
|
||||
- Write actions in Committed mode too — rejected (F14): reverting committed history is a distinct,
|
||||
unrequested capability with a different risk profile.
|
||||
- Request-supplied commit identity — rejected (F3): allows spoofing; server-derived identity is the only
|
||||
safe source.
|
||||
**Driven by findings:** F3, F14.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D7, D8, D12.
|
||||
**Referenced in spec:** Primary flow, User interactions.
|
||||
|
||||
### D7 — Discard requires a plain confirmation
|
||||
**Question:** What confirmation does discard require, and what wording?
|
||||
**Decision:** Discarding a file's changes prompts a plain Cancel / Discard confirmation with wording that
|
||||
distinguishes the two cases: "Discard changes to X?" for a tracked file (reverts to committed content)
|
||||
and "Delete X? It has never been committed and cannot be recovered" for an untracked file (permanently
|
||||
removed). Stage, unstage, and commit do not prompt.
|
||||
**Rationale:** Discard is the only irreversible action in the set; a confirmation guards an accidental tap,
|
||||
especially on mobile. The project's stated preference is plain confirm dialogs, never type-the-name
|
||||
patterns. A tracked revert and an untracked permanent delete are different losses — the user deserves to
|
||||
know which one they are confirming (F4).
|
||||
**Evidence:** Convention: destructive actions use plain Cancel/Confirm dialogs (no type-to-confirm).
|
||||
Stage/unstage/commit are reversible (commits can be amended/reset), so they need no prompt. F4
|
||||
(tracked-vs-untracked and affordance-separation finding; design-judgment resolution).
|
||||
**Rejected alternatives:**
|
||||
- No confirmation — rejected: irreversible data loss on a stray tap.
|
||||
- Type-the-filename-to-confirm — rejected: against the project's confirmation convention.
|
||||
- Single generic confirmation for tracked and untracked — rejected (F4): hides the difference in
|
||||
consequence (revert vs. permanent delete) from the user.
|
||||
**Driven by findings:** F4.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D15.
|
||||
**Referenced in spec:** Alternate flows and states.
|
||||
|
||||
### D8 — Git write is a user action, not an assistant tool
|
||||
**Question:** Are the panel's write actions gated by session type (e.g. read-only-assistant sessions)?
|
||||
**Decision:** The diff panel's write actions (stage/commit/discard) are available wherever the file panel
|
||||
appears, including read-only-assistant sessions, because they are the human user's own UI actions, not
|
||||
the AI's. The git-write endpoints are never registered as assistant tools, and the artifact sandbox
|
||||
prevents a rendered artifact from invoking them.
|
||||
**Rationale:** The "read-only" rule constrains what the AI assistant's tools may do. A human committing
|
||||
their own repository through a panel is a different actor; gating the panel by session type would be a
|
||||
category error and produce inconsistent behavior across sessions. The artifact-sandbox commitment (F1)
|
||||
closes the indirect path an artifact might otherwise exploit.
|
||||
**Evidence:** Convention: the read-only invariant is defined over the assistant's tool surface; the file
|
||||
browser (also user-driven) already appears in all sessions. F1 (artifact-sandbox finding; evidenced by
|
||||
`connect-src 'none'` in the artifact iframe sandbox per BOOCHAT.md output-format section).
|
||||
**Rejected alternatives:**
|
||||
- Restrict the write actions to write-capable (coder) sessions only — rejected: conflates the assistant's
|
||||
tool permissions with the user's UI affordances; produces inconsistent behavior.
|
||||
**Driven by findings:** F1.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D12.
|
||||
**Referenced in spec:** Actors and triggers, Coordinations.
|
||||
|
||||
### D10 — Refresh on open, on mutation, on turn completion, on demand, with coalescence
|
||||
**Question:** When does the panel re-read the repository, and how are concurrent triggers handled?
|
||||
**Decision:** 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. Continuous file-watching is
|
||||
excluded.
|
||||
**Rationale:** These triggers cover every event that can change the project repository's state within the
|
||||
app's single-user workflow. Coalescence (F8) prevents a burst of triggers (e.g. multiple rapid mutations)
|
||||
from causing redundant concurrent reads or a stale intermediate result overwriting a fresher one. A
|
||||
continuous file watcher adds cost without a multi-user need.
|
||||
**Evidence:** Convention: event-driven refresh follows the session-event / broker model already used for
|
||||
other panels. F8 (concurrent-refresh finding; evidence-backed addition).
|
||||
**Rejected alternatives:**
|
||||
- Continuous file-watch stream — rejected (YAGNI): event- and demand-driven covers the single-user case;
|
||||
deferred under YAGNI.
|
||||
- No coalescence (each trigger spawns its own read) — rejected (F8): can produce concurrent reads and
|
||||
stale-snapshot overwrites.
|
||||
**Driven by findings:** F8.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Alternate flows and states.
|
||||
|
||||
### D11 — All git operations scoped to the project repository path
|
||||
**Question:** How is the git operation target scoped and validated?
|
||||
**Decision:** Every read and write the panel performs is confined to the project's own repository. The
|
||||
repository root is derived server-side from the session's project record — never from the request. Per-
|
||||
file arguments are validated as repo-relative paths and rejected if they escape the repository root.
|
||||
User-supplied text (commit message, file arguments) is passed as discrete arguments and never
|
||||
interpolated into a shell string.
|
||||
**Rationale:** The panel acts on the project repo only. Deriving the root server-side and validating
|
||||
per-file arguments closes the path-escape and command-injection vectors (F2). Passing text as discrete
|
||||
arguments (not a shell string) ensures user-supplied content cannot be interpreted as git flags or shell
|
||||
syntax.
|
||||
**Evidence:** Convention: project file operations resolve and scope to the project path via the existing
|
||||
path-scoping guard; git-metadata reads already do this. F2 (derivation + argument-safety finding;
|
||||
evidenced by the existing path-scoping guard).
|
||||
**Rejected alternatives:**
|
||||
- Accept a caller-supplied repository path — rejected: needless write surface, no use case.
|
||||
- Validate path only at the root level (not per-file arguments) — rejected (F2): per-file arguments can
|
||||
escape the repo root via `../` traversal if not independently validated.
|
||||
- Build git invocations as a shell string — rejected (F2): user-supplied content (commit message, file
|
||||
names with special characters) can be interpreted as flags or shell syntax.
|
||||
**Driven by findings:** F2.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** D12.
|
||||
**Referenced in spec:** Coordinations.
|
||||
|
||||
### D12 — Git-write security posture
|
||||
**Question:** What are the combined security commitments for the git-write surface?
|
||||
**Decision:** The git-write surface (stage / commit / discard) has three security commitments: (1) these
|
||||
actions are user-initiated UI actions only and are never registered as assistant tools; the artifact
|
||||
sandbox prevents a rendered artifact from invoking them; (2) all git operations target only the project's
|
||||
own repository, with the root derived server-side and per-file paths validated inside it; (3) commit
|
||||
author/committer identity is derived from a server-side source (host git configuration) and cannot be set
|
||||
by the request.
|
||||
**Rationale:** F1, F2, and F3 each attacked a distinct vector — artifact-driven invocation, path/argument
|
||||
injection, and identity spoofing — that D8 and D11 individually did not close. D12 records all three
|
||||
commitments together as the complete security posture of the write surface.
|
||||
**Evidence:** F1 (artifact-sandbox; `connect-src 'none'` per BOOCHAT.md output-format section).
|
||||
F2 (path-scoping guard in the codebase; derivation and validation commitments). F3 (server-derived
|
||||
identity commitment; design-judgment that no request field should influence authorship).
|
||||
**Rejected alternatives:**
|
||||
- Trust the client-supplied repository path — rejected (F2): see D11.
|
||||
- Allow request-supplied commit identity — rejected (F3): allows spoofing; no legitimate use case in a
|
||||
single-user app.
|
||||
- Rely on session-type gating instead of endpoint-level exclusion from tool registry — rejected (F1):
|
||||
session type is the wrong layer; artifact-sandbox closes the actual indirect path.
|
||||
**Driven by findings:** F1, F2, F3.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Actors and triggers, Coordinations.
|
||||
|
||||
### D13 — Committed-mode base resolution and labeling
|
||||
**Question:** What is the base for Committed mode, and how is it surfaced when resolution fails?
|
||||
**Decision:** Committed mode compares the current branch against its upstream tracking branch when one is
|
||||
set, falling back to the repository's default branch (main/master). The panel labels the base it used in
|
||||
the mode header ("Git — branch vs <base>"). When no base resolves (no tracking branch and no
|
||||
discoverable default branch), the panel falls back to showing uncommitted changes and labels the mode as
|
||||
a fallback, rather than erroring or silently swapping.
|
||||
**Rationale:** "Base" was undefined in D2, leaving the committed comparison ambiguous (F11). The tracking-
|
||||
branch-first resolution matches git's own upstream model and is the most useful default for contributors
|
||||
tracking a remote. Labeling the resolved base makes the comparison unambiguous to the user. A labeled
|
||||
fallback is more informative than an error and does not leave the panel empty.
|
||||
**Evidence:** F11 (base-unlabeled finding; UX-002, JD-002; design-judgment resolution). Git upstream
|
||||
model (tracking branch as natural "base" for a contributor's branch).
|
||||
**Rejected alternatives:**
|
||||
- Always compare against the default branch, ignoring tracking — rejected (F11): wrong for contributors
|
||||
whose tracking branch is a personal fork or a PR target branch, not the default.
|
||||
- Error when no base resolves — rejected: leaves the panel useless; an unlabeled fallback is more
|
||||
helpful.
|
||||
- Silently swap to uncommitted without a label — rejected (F11): the original spec's behavior; confusing
|
||||
because the mode selector still shows "Committed".
|
||||
**Driven by findings:** F11.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Primary flow, Edge cases and failure modes, User interactions.
|
||||
|
||||
### D14 — Mode pinning and first-open auto-selection
|
||||
**Question:** Does auto mode-selection persist across refreshes after the user has acted?
|
||||
**Decision:** Auto mode-selection applies on first open only. Once the user selects a mode explicitly (via
|
||||
the selector), that choice is pinned for the session. Refreshes do not override a pinned mode. If a
|
||||
refresh would change the auto-selected mode (e.g. the tree transitioned from dirty to clean while
|
||||
Uncommitted was pinned), the panel briefly notes the change rather than swapping silently.
|
||||
**Rationale:** Auto-select on every refresh would dislocate the user mid-review without warning —
|
||||
illustrated by the scenario where the tree goes clean while the user is reading the uncommitted diff (F12).
|
||||
A brief note on a state change preserves awareness without overriding intent.
|
||||
**Evidence:** F12 (silent-dislocation finding; design-judgment resolution). D3 (auto-selection origin).
|
||||
**Rejected alternatives:**
|
||||
- Re-run auto-selection on every refresh — rejected (F12): dislocates the user's active view.
|
||||
- No notification on a would-be mode change — rejected: leaves the user unaware that the repository
|
||||
state changed in a way that would normally affect the view.
|
||||
**Driven by findings:** F12.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Primary flow.
|
||||
|
||||
### D15 — Discard is irrecoverable; tracked vs. untracked confirmation; separated affordance
|
||||
**Question:** What are the full discard semantics and the UI placement of the discard control?
|
||||
**Decision:** Discard is hard-delete and irrecoverable. The confirmation dialog uses two distinct wordings:
|
||||
"Discard changes to X?" for a tracked file (which reverts to its committed content; the work is lost but
|
||||
the file remains in history) and "Delete X? It has never been committed and cannot be recovered" for an
|
||||
untracked file (permanent deletion with no recovery path). The Discard affordance is placed in an
|
||||
overflow or secondary position rather than as an equal-weight sibling of Stage/Unstage.
|
||||
**Rationale:** The spec previously called discard "irrecoverable" but left the git mechanic ambiguous.
|
||||
Owning the word and spelling out the two cases (F4) ensures the confirmation is honest. Separating the
|
||||
affordance from Stage/Unstage reduces the risk of an accidental tap on mobile (F4, UX concern).
|
||||
**Evidence:** F4 (discard-semantics and affordance-separation finding; on-call-engineer OCE-002,
|
||||
UX-005, adversarial-security-analyst; design-judgment resolution). Convention: plain
|
||||
Cancel/Confirm dialogs.
|
||||
**Rejected alternatives:**
|
||||
- Single generic confirmation for tracked and untracked cases — rejected (F4): obscures the difference
|
||||
in consequence.
|
||||
- Discard at equal weight alongside Stage/Unstage — rejected (F4): accidental-tap risk on mobile.
|
||||
**Driven by findings:** F4.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Alternate flows and states, User interactions.
|
||||
|
||||
### D16 — Tab named "Git"
|
||||
**Question:** What is the new tab called?
|
||||
**Decision:** The new tab is named **Git**, giving a Files / Git tab pair. The existing "Pending Changes"
|
||||
panel is not renamed; that rename is out of scope.
|
||||
**Rationale:** "Changes" (the working name in the initial spec) collides with "Pending Changes" — the name
|
||||
of an existing distinct panel — creating discoverability confusion (F10, UX-001, UX-008, JD-001). "Git"
|
||||
is shorter, unambiguous, and describes the surface (the project's git state) without implying overlap
|
||||
with the pending-changes panel.
|
||||
**Evidence:** F10 (naming-collision finding; design-judgment resolution). Existing surface name: "Pending
|
||||
Changes" panel in the codebase.
|
||||
**Rejected alternatives:**
|
||||
- "Changes" — rejected (F10): collides with "Pending Changes"; confusion in context-switching.
|
||||
- Rename "Pending Changes" to disambiguate — rejected (F10): out of scope; would require changes to an
|
||||
existing surface the user did not ask to rename.
|
||||
**Driven by findings:** F10.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Actors and triggers, User interactions, Out of scope.
|
||||
|
||||
### D17 — Ambient dirty indicator and empty-state hint
|
||||
**Question:** How does the user discover the Git tab when the panel defaults to Files?
|
||||
**Decision:** An ambient indicator on the file-panel toggle/header signals the repository is dirty (derived
|
||||
from the refresh data already gathered), making the Git tab findable without opening it. 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.
|
||||
**Rationale:** Without a visual signal the Git tab is invisible until the user already knows to look for it
|
||||
(F10, UX-001). The indicator reuses state already gathered by the refresh cycle — no additional read
|
||||
needed. The empty-state hint prevents the user from concluding the panel is broken when what they are
|
||||
looking for is actually in the adjacent pending-changes panel.
|
||||
**Evidence:** F10 (discoverability finding; design-judgment resolution). Refresh cycle already produces
|
||||
dirty/clean state (D10).
|
||||
**Rejected alternatives:**
|
||||
- No ambient indicator (rely on the user knowing the tab exists) — rejected (F10): undiscoverable by
|
||||
new users.
|
||||
- Always show dirty indicator (not just when dirty) — rejected: misleading on clean repos.
|
||||
**Driven by findings:** F10.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** Actors and triggers, Alternate flows and states.
|
||||
|
||||
### D18 — Mobile tap-target and header-fit
|
||||
**Question:** What are the layout and accessibility constraints for the new tab and controls?
|
||||
**Decision:** All interactive controls in the diff panel follow the app's existing mobile tap-target
|
||||
minimum. The Files / Git tab strip and header fit on one line without horizontal scroll or wrapping;
|
||||
existing header elements are condensed if needed to maintain fit.
|
||||
**Rationale:** The app has an existing toolbar-fit rule (no scroll/wrap on crowded control bars) and a
|
||||
mobile-first posture. The new Git tab and its in-panel controls must not break either. Condensing
|
||||
existing elements rather than scrolling is the project's established pattern (F15, UX-009, JD-008).
|
||||
**Evidence:** F15 (mobile-fit finding; convention). Project convention: toolbars must fit one line (no
|
||||
scroll or wrapping); MEMORY.md toolbar-fit rule.
|
||||
**Rejected alternatives:**
|
||||
- Allow horizontal scroll if the header gets crowded — rejected: against the project's toolbar-fit rule.
|
||||
- Wrap the header to a second line — rejected: against the project's toolbar-fit rule.
|
||||
**Driven by findings:** F15.
|
||||
**Linked technical notes:** —
|
||||
**Dependent decisions:** —
|
||||
**Referenced in spec:** User interactions.
|
||||
|
||||
## Trivial decisions
|
||||
|
||||
- D4: Untracked files included in Uncommitted view — untracked files appear in the Uncommitted file list as additions (considered tracked-only; rejected because the user's new files are part of "what changed"). — Referenced in spec: Primary flow.
|
||||
- D9: Unified layout, syntax-highlighted — diffs render in a single-column unified layout reusing the existing code highlighter (considered side-by-side; deferred under YAGNI as a desktop-only enhancement). — Referenced in spec: User interactions.
|
||||
168
docs/features/git-diff-panel/artifacts/team-findings.md
Normal file
168
docs/features/git-diff-panel/artifacts/team-findings.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Team findings — Git diff panel
|
||||
|
||||
Review-team findings on [`feature-specification.md`](../feature-specification.md) and resolutions. Team
|
||||
(medium): junior-developer, user-experience-designer, adversarial-security-analyst, on-call-engineer.
|
||||
Dispatched 2026-06-02. All findings resolved by evidence/convention/design-judgment; none required new
|
||||
user input beyond the three foundational answers already given. Shared F# counter. All resolutions
|
||||
applied to spec and decision log 2026-06-02.
|
||||
|
||||
## Major findings
|
||||
|
||||
### F1 — Assistant could drive the new git-write endpoints via a rendered artifact (security)
|
||||
- **Raised by:** adversarial-security-analyst (D8). Highest priority.
|
||||
- **Concern:** D8 says the read-only-assistant rule covers the AI's tools, not the user's UI. But the AI can
|
||||
emit HTML artifacts; if an artifact could POST to the new stage/commit/discard endpoints using the user's
|
||||
Authelia cookie, the assistant would gain a write path it is forbidden.
|
||||
- **Resolved by:** evidence. The HTML artifact iframe is sandboxed with `connect-src 'none'` (per
|
||||
`BOOCHAT.md` output-format section — fetch/WebSocket do not work in artifacts), so an artifact cannot reach
|
||||
the endpoints. Spec gains an explicit commitment: the git-write actions are user-initiated UI actions only,
|
||||
are never registered as an assistant tool, and the artifact sandbox prevents an artifact from invoking them.
|
||||
- **Affected decisions:** D8 (expanded), D12 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Actors and triggers, Coordinations.
|
||||
|
||||
### F2 — D11 scoping needs explicit derivation + argument-safety commitments (security)
|
||||
- **Raised by:** adversarial-security-analyst (D11).
|
||||
- **Concern:** "confined to the project path" is a destination constraint that doesn't say the path is
|
||||
derived server-side, that per-file arguments are validated to resolve inside the repo, or that the commit
|
||||
message and file arguments are passed as discrete arguments (not shell-interpolated).
|
||||
- **Resolved by:** evidence (the existing project path-scoping guard derives roots from the project record,
|
||||
never from the request). Spec/D11 gain three commitments: repository root derived server-side from the
|
||||
session's project; per-file arguments validated as repo-relative and rejected if they escape; commit
|
||||
message and file arguments passed as discrete arguments, never built into a shell string.
|
||||
- **Affected decisions:** D11 (expanded), D12 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Coordinations.
|
||||
|
||||
### F3 — Commit author identity must be server-derived, not request-supplied (security + clarity)
|
||||
- **Raised by:** adversarial-security-analyst, junior-developer (JD-004; Open items).
|
||||
- **Concern:** identity was deferred entirely to implementation; an unauthenticated local request could set an
|
||||
arbitrary author, and the codebase already hardcodes differing identities elsewhere.
|
||||
- **Resolved by:** evidence + commitment. Spec commits: a panel commit's author/committer is derived from a
|
||||
server-side source (host git config or a configured value); the request body cannot set or influence it.
|
||||
The exact source is left to plan-implementation.
|
||||
- **Affected decisions:** D6 (expanded), D12 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Primary flow, Open items (closed).
|
||||
|
||||
### F4 — Discard semantics: own "irrecoverable", distinguish tracked vs untracked, separate the affordance
|
||||
- **Raised by:** adversarial-security-analyst (D7), on-call-engineer (OCE-002), user-experience-designer (UX-005).
|
||||
- **Concern:** the spec calls discard "irrecoverable" but defers the git mechanic, creating a tension; a
|
||||
tracked-file revert and an untracked-file permanent delete are different losses; and Discard sitting next to
|
||||
Stage at equal weight invites accidental taps on mobile.
|
||||
- **Resolved by:** design-judgment. Discard hard-deletes (own the word "irrecoverable"). The confirmation uses
|
||||
two variants — "Discard changes to X?" (tracked, reverts to committed content) vs "Delete X? It has never
|
||||
been committed and cannot be recovered" (untracked). The Discard affordance is separated from Stage/Unstage
|
||||
(an overflow/secondary placement), not an equal-weight sibling button.
|
||||
- **Affected decisions:** D7 (expanded), D15 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Alternate flows and states, User interactions.
|
||||
|
||||
### F5 — Index-lock contention with concurrent agent turns is unnamed (resilience)
|
||||
- **Raised by:** on-call-engineer (OCE-001).
|
||||
- **Resolved by:** evidence. Spec names the case: when a write fails because the repository is busy (its 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 git error.
|
||||
- **Affected decisions:** —
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Edge cases and failure modes.
|
||||
|
||||
### F6 — "Leave state unchanged" is unenforceable for partial failures (resilience wording)
|
||||
- **Raised by:** on-call-engineer (OCE-002).
|
||||
- **Resolved by:** reword. "Leaves the repository state unchanged" → "leaves the repository as close to its
|
||||
pre-action state as the git layer allows, and the list refreshes to reflect the repository's true state";
|
||||
an untracked-directory discard that fails partway may leave a partially-removed tree, surfaced on refresh.
|
||||
- **Affected decisions:** —
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Edge cases and failure modes.
|
||||
|
||||
### F7 — No deadline on a hanging git read (resilience)
|
||||
- **Raised by:** on-call-engineer (OCE-003).
|
||||
- **Resolved by:** add commitment. If a git read does not complete within a deadline, the panel leaves the
|
||||
loading state, shows an error, and offers Refresh (distinct from the large-result cap in D5).
|
||||
- **Affected decisions:** D5 (expanded).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Edge cases and failure modes.
|
||||
|
||||
### F8 — Concurrent refresh triggers have no coalescence commitment (resilience)
|
||||
- **Raised by:** on-call-engineer (OCE-004).
|
||||
- **Resolved by:** add commitment. Concurrent refresh triggers are coalesced — a refresh already in flight
|
||||
absorbs later triggers instead of spawning a second concurrent read; the panel settles to a single final
|
||||
snapshot.
|
||||
- **Affected decisions:** D10 (expanded).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Alternate flows and states.
|
||||
|
||||
### F9 — In-progress git states (merge/rebase/cherry-pick/bisect) make writes fail opaquely (resilience)
|
||||
- **Raised by:** on-call-engineer (OCE-005).
|
||||
- **Resolved by:** add commitment. When the repository is mid-operation (merge, rebase, cherry-pick, or bisect),
|
||||
the panel disables its write affordances and shows the repository's state rather than letting stage/commit/
|
||||
discard fail with raw git errors.
|
||||
- **Affected decisions:** —
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Edge cases and failure modes.
|
||||
|
||||
### F10 — The Changes tab is undiscoverable and collides with "Pending Changes" naming (UX)
|
||||
- **Raised by:** user-experience-designer (UX-001, UX-008), junior-developer (JD-001).
|
||||
- **Resolved by:** design-judgment. (a) The new tab is named **Git** (Files / Git), distinct from the existing
|
||||
"Pending Changes" panel; the existing panel is NOT renamed (out of scope). (b) An ambient indicator on the
|
||||
file-panel toggle/header signals the repository is dirty (derived from the refresh data already gathered),
|
||||
so the tab is findable. (c) 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.
|
||||
- **Affected decisions:** D16 (new tab name), D17 (new dirty indicator + empty-state hint).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Actors and triggers, Alternate flows and states, User interactions, Coordinations, Out of scope.
|
||||
|
||||
### F11 — Committed-mode base is undefined and unlabeled (UX + correctness)
|
||||
- **Raised by:** user-experience-designer (UX-002), junior-developer (JD-002).
|
||||
- **Resolved by:** decision. Committed mode compares the current branch against its **base** — the upstream
|
||||
tracking branch when set, otherwise the repository's default branch (main/master). The view labels the base
|
||||
it used ("Git — branch vs <base>"). When no base resolves, the panel shows uncommitted changes and labels
|
||||
the mode as falling back, rather than silently swapping.
|
||||
- **Affected decisions:** D2 (expanded), D13 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Primary flow, Edge cases and failure modes, User interactions.
|
||||
|
||||
### F12 — Auto-mode selection silently dislocates the view on refresh (UX)
|
||||
- **Raised by:** user-experience-designer (UX-003).
|
||||
- **Resolved by:** design-judgment. Auto mode-selection applies on first open only; once the user picks a mode
|
||||
it is pinned for the session and refreshes do not override it. A refresh that would change the mode (e.g. the
|
||||
tree went clean) briefly notes the change rather than swapping silently.
|
||||
- **Affected decisions:** D3 (expanded), D14 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Primary flow, Alternate flows and states.
|
||||
|
||||
### F13 — Staged vs unstaged distinction must not be color-only (UX/accessibility)
|
||||
- **Raised by:** user-experience-designer (UX-004).
|
||||
- **Resolved by:** add commitment. Staged and unstaged files are distinguished by more than color (a label/icon,
|
||||
and grouping into staged/unstaged sections); each stage/unstage control carries an accessible name that
|
||||
includes the file path.
|
||||
- **Affected decisions:** —
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** Primary flow, User interactions.
|
||||
|
||||
### F14 — Discard's availability across modes is unspecified (clarity)
|
||||
- **Raised by:** junior-developer (JD-003).
|
||||
- **Resolved by:** decision. Stage, unstage, commit, and discard are available only in Uncommitted mode;
|
||||
Committed mode is read-only review (no per-file revert of committed history in v1).
|
||||
- **Affected decisions:** D6 (scoped), D15 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** User interactions, Alternate flows and states.
|
||||
|
||||
### F15 — Mobile fit + tap-target convention for the new tab and controls (UX)
|
||||
- **Raised by:** user-experience-designer (UX-009), junior-developer (JD-008).
|
||||
- **Resolved by:** convention. All interactive controls in the panel follow the app's existing mobile tap-target
|
||||
minimum; the Files / Git tab strip and header fit one line without horizontal scroll or wrapping (the project's
|
||||
toolbar-fit rule), condensing existing header elements if needed.
|
||||
- **Affected decisions:** D18 (new).
|
||||
- **Affected tech-notes:** —
|
||||
- **Changed in spec:** User interactions.
|
||||
|
||||
## Minor edits
|
||||
|
||||
- F16: Successful commit shows a brief, non-blocking success confirmation (not just files disappearing) — user-experience-designer (UX-006) — **Affected decisions:** — **Affected tech-notes:** — **Changed in spec:** Primary flow.
|
||||
- F17: Error placement — commit-area errors appear by the commit control, per-file action errors appear in the affected file row — user-experience-designer (UX-007) — **Affected decisions:** — **Affected tech-notes:** — **Changed in spec:** Edge cases and failure modes.
|
||||
- F18: Which service runs the git read vs write operations (read-only server vs write-capable host service) is an architecture/module-boundary decision routed to plan-implementation; the spec stays behavioral ("the system performs…") — junior-developer (JD-005) — **Affected decisions:** — **Affected tech-notes:** — **Changed in spec:** — (plan-implementation input).
|
||||
- F19: The git-write surface is the larger half of v1; implementation should sequence diff-display before the write actions even within v1 — junior-developer (JD-007) — **Affected decisions:** — **Affected tech-notes:** — **Changed in spec:** — (plan-implementation note).
|
||||
- F20: The refresh-on-pending-apply and refresh-on-turn-complete triggers require an event/frame wiring that must follow the project's WS-frame / sessionEvents parity steps — junior-developer (JD-009) — **Affected decisions:** — **Affected tech-notes:** — **Changed in spec:** — (plan-implementation note).
|
||||
- F21: D8's commit-button-in-a-read-only-session affordance needs no extra label; the "Git" tab name and dirty indicator make it clearly the user's own git surface, not assistant output — junior-developer (JD-006) — **Affected decisions:** D8 confirmed, no extra label needed — **Affected tech-notes:** — **Changed in spec:** — (D8 confirmed).
|
||||
203
docs/features/git-diff-panel/feature-specification.md
Normal file
203
docs/features/git-diff-panel/feature-specification.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user