Skip to content

feat(source-control): integrate execution result and conflict status - #130

Closed
ClaudiaFang wants to merge 3 commits into
claude/source-control-foundationfrom
feat/source-control-integration
Closed

ClaudiaFang wants to merge 3 commits into
claude/source-control-foundationfrom
feat/source-control-integration

Conversation

@ClaudiaFang

Copy link
Copy Markdown
Member

PR130 — Source Control execution-result + conflict status

Stacks on #129 (claude/source-control-foundation). Adds the three-state (success / conflict / failed) operation-outcome model to the Source Control logic layer so the UI can later render a batch summary ("7 completed / 3 conflicts / 1 failed").

No UI changes. No sync-domain changes — reuses existing executor conflict semantics.

Changes

  • OperationState: OperationStatus gains 'conflict' + conflict() — a distinct lifecycle from 'failed' (needs-resolution, resolvable, not an error).
  • ExecutionResult (new): thin UI projection { completed, conflicts, failed }: ChangeId[] + emptyExecutionResult(), derived in SourceControlActionService from existing PushResults / SyncResult / RemoteDeleteResult — not a new sync model.
  • SourceControlActionService: push / pull / deleteRemote / deleteLocal / resolveConflict now return Promise<ExecutionResult>; push reads PushResults.conflictedPaths → 'conflict' (conflict precedence over error); new classify() helper maps per-path outcome to OperationStatus + result in one pass.
  • SourceControlViewModel: SourceControlViewState.lastOperationResult + setOperationResult / clearOperationResult for batch-summary exposure.
  • Tests (TDD): new ExecutionResult.test.ts; extended OperationState (conflict lifecycle), ActionService (conflict mapping, mixed 7/3/1 batch, conflict-precedence, throw → all-failed, return-value contracts), ViewModel (result exposure).

Design notes

  • Pull asymmetry (by design): PushResults exposes per-path conflictedPaths; SyncResult (pull) carries only a conflicts count. So push maps conflicts to ChangeIds; pull conflicts surface through change-model reclassification (kind: 'conflict') on the next repository refresh, not via this projection.
  • 'conflicts' filter stays change-model-driven (change.kind === 'conflict'); operation 'conflict' status is the per-change push outcome — complementary, not coupled.

Verification

npx eslint .   -> 0 errors
npm run build  -> PASS (tsc + Obsidian 1.11.0 compat + esbuild)
npx vitest run -> 66 files / 700 tests  (source-control logic: 7 files / 60 tests)

Manual Obsidian verification deferred (no runtime UI surface changed in this PR).

Roadmap: docs/source-control-refactor/roadmap.md. Next: PR131 (Phase B — batch conflict workflow + ConflictClassifier).

PR130 — surface the three-state (success/conflict/failed) operation outcome
in the Source Control logic layer so the UI can render a batch summary
("7 completed / 3 conflicts / 1 failed"). No UI changes, no sync-domain
changes; reuses existing executor conflict semantics.

- OperationState: add 'conflict' to OperationStatus with conflict() — a
  distinct lifecycle from 'failed' (needs-resolution, resolvable, not error).
- ExecutionResult (new): thin UI projection {completed, conflicts, failed}
  as ChangeId[], derived in SourceControlActionService from existing
  PushResults/SyncResult/RemoteDeleteResult (not a new sync model).
- SourceControlActionService: push/pull/deleteRemote/deleteLocal/
  resolveConflict return Promise<ExecutionResult>; push reads
  PushResults.conflictedPaths -> 'conflict' (conflict precedence over error);
  new classify() helper maps per-path outcome to OperationStatus + result.
- SourceControlViewModel: expose lastOperationResult via
  setOperationResult/clearOperationResult in SourceControlViewState.

Known asymmetry by design: PushResults exposes per-path conflictedPaths;
SyncResult (pull) carries only a conflict count, so pull conflicts surface
through change-model reclassification (kind:'conflict') on next refresh, not
this projection. The 'conflicts' filter stays change-model-driven; operation
'conflict' status is the per-change push outcome — complementary.

Verification: npx eslint . — 0 errors; npm run build — PASS incl. Obsidian
1.11.0 compatibility; npx vitest run — 66 files / 700 tests (source-control
logic 7 files / 60 tests).
@ClaudiaFang
ClaudiaFang force-pushed the feat/source-control-integration branch from 2677fad to b3379b7 Compare August 22, 2026 09:07
PR131 / Phase 2 — reorganize Source Control UI state from scattered View-local
fields + direct store access into one SourceControlState container, with the
ViewModel as the single facade the View reads from and mutates through. Pure
refactor; no behavior change (all 553 tests pass).

State model (new src/logic/source-control/state/):
- SourceControlState: thin container composing the change model + every UI
  state slice (selection, operation, filter, expanded nodes, selected
  change). Not a god-object — each slice keeps its own invariants/methods.
- SelectionState: moved/renamed from PushSelectionStore (same API).
- OperationState: moved from src/logic/source-control/ (incl. 'conflict'
  status from PR #130).
- FilterState / ExpandedNodesState / SelectedChangeState: new slices that
  previously lived as View-local fields (active filter, collapsed
  sections/folders, selected change id).

Facade:
- SourceControlViewModel is constructed from SourceControlState and is the
  sole mutation surface (setFilter, toggleSection/Folder, selectForPush/
  deselectFromPush, selectForDiff/clearSelection, getCollapsedFolders,
  setOperationResult/clear). The View reaches no store directly.
- SourceControlView holds no state of its own and mutates only through the
  ViewModel; getFilter/getSelectedChangeId moved to the ViewModel.
  Constructor is now (viewModel, callbacks).
- main.ts / SourceControlItemView construct SourceControlState; plugin exposes
  sourceControlState (replaces pushSelectionStore/operationState).
- SourceControlFilter.matchesFilter selection param type is SelectionState.

Tests: moved OperationState/PushSelectionStore tests into state/ (renamed to
SelectionState.test.ts); new FilterState/ExpandedNodesState/SelectedChangeState
tests; updated ViewModel/View/ItemView/ActionService tests for the new
construction.

Verification: npx eslint . — 0 errors; npm run build — PASS incl. Obsidian 1.11.0
compatibility; npx vitest run — 59 files / 553 tests.
…te-model

refactor(source-control): unify UI state into SourceControlState
@sonarqubecloud

Copy link
Copy Markdown

@ClaudiaFang
ClaudiaFang deleted the feat/source-control-integration branch September 1, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant