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:
2026-06-02 21:20:33 +00:00
parent e5ce01ae72
commit 2a05d2f9fe
27 changed files with 2210 additions and 17 deletions

View 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 &lt;base&gt;"); 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 &lt;base&gt;"). 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.

View 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 &lt;base&gt;"). 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).