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).
|
||||
Reference in New Issue
Block a user