Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fd6d7ec
fix(source-control): dedupe mobile diff render, force unified on phones
ClaudiaFang Sep 1, 2026
cf2239d
fix(docs): align agent guidance with source control architecture
ClaudiaFang Sep 1, 2026
8fcdfea
fix(e2e): exercise current remote delete application path
ClaudiaFang Sep 1, 2026
0f11e17
fix(docs): mark legacy source control migration docs historical
ClaudiaFang Sep 1, 2026
beba48d
fix(test): guard removed sync status presentation imports
ClaudiaFang Sep 1, 2026
6baa0da
fix(source-control): preserve per-change action overrides in sync sel…
ClaudiaFang Sep 1, 2026
601013f
fix(source-control): resolve queue grouping from actual sync action
ClaudiaFang Sep 1, 2026
735bdc0
fix(source-control): honor explicit actions in sync queue execution
ClaudiaFang Sep 1, 2026
8d5d427
fix(source-control): add compact per-row action control to the Sync Q…
ClaudiaFang Sep 1, 2026
b44389d
fix(source-control): shrink the inline Download button to icon-only o…
ClaudiaFang Sep 1, 2026
1a54acb
fix(source-control): add per-kind advanced action menu to Repository …
ClaudiaFang Sep 1, 2026
c4a198c
fix(sync-plan): mark each plan row with its section's direction icon
ClaudiaFang Sep 1, 2026
d91fa6e
docs: record explicit sync intent session in progress.md
ClaudiaFang Sep 1, 2026
f554031
refactor(source-control): isolate sync intent orchestration
ClaudiaFang Sep 1, 2026
1c69aed
fix(source-control): remove Conflict filter chip and dedupe sync time…
ClaudiaFang Sep 1, 2026
025a8c7
docs(architecture): define module boundaries and bug-fix rules
ClaudiaFang Sep 1, 2026
e94a6f7
refactor(sync): split SyncStatusRefreshService into discovery/resolve…
ClaudiaFang Sep 1, 2026
f21f169
refactor(runtime): extract createSyncRuntime as the sync/Source Contr…
ClaudiaFang Sep 1, 2026
68c3683
refactor(ui): extract SyncQueueSection and RepositoryChangesSection f…
ClaudiaFang Sep 1, 2026
9f7449c
refactor(architecture): add ESLint boundary guards and fix a reverse …
ClaudiaFang Sep 1, 2026
69e5540
docs: sync CLAUDE.md and architecture.md with the enforced module bou…
ClaudiaFang Sep 1, 2026
499ac9e
refactor(source-control): responsibility cleanup (PR2) (#154)
ClaudiaFang Sep 1, 2026
3a053a9
fix(mobile): remove duplicate safe-area padding from sync bar
ClaudiaFang Sep 1, 2026
6aab586
fix(ui): remove duplicate batch conflict divider
ClaudiaFang Sep 1, 2026
adee61d
fix(ui): use Obsidian CSS helper for conflict divider
ClaudiaFang Sep 1, 2026
15e1a6b
fix(ui): drop setCssProps last-row override, use CSS :last-child instead
ClaudiaFang Sep 1, 2026
17f160c
Merge remote-tracking branch 'origin/1.6.1' into test-merge
ClaudiaFang Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,27 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- Test: `npm run test` (runs vitest suite)
- Version bump: `npm run version` (updates manifest.json and versions.json via script)

## Code Architecture
- **Type**: Obsidian Plugin (TypeScript) that syncs vault files with a GitLab or GitHub repository.
- **Entry Point**: `src/main.ts` contains the main `GitLabFilesPush` class extending `Plugin`.
## Architecture Contract

Before production code changes:
- Read `docs/architecture.md`.
- For bug fixes also read `docs/bug-fix-guidelines.md`.
- Identify the owning module before editing.
- Preserve dependency direction.
- Do not bypass `SyncWorkspace`.
- Do not move domain/provider logic into UI.
- Do not duplicate status/conflict/rename/action policy.

`docs/architecture.md` is the canonical module map (layers, ownership table, MUST/MUST NOT rules, current hotspots) — this file does not duplicate it. A short summary:

- **Type**: Obsidian Plugin (TypeScript) that syncs vault files with a GitHub, GitLab, or Gitea repository.
- **Entry point**: `src/main.ts` owns only Obsidian lifecycle (settings load/save, command/view/ribbon/vault-event registration); the sync/Source Control constructor graph is wired by `src/runtime/createSyncRuntime.ts`.
- **Settings**: `src/settings.ts` defines `GitLabFilesPushSettings` interface, `DEFAULT_SETTINGS` object, and `GitLabSyncSettingTab` for the Obsidian UI.
- **Services**: `src/services/` abstracts the git provider behind `GitServiceInterface`, with `GitHubService` and `GitLabService` implementations sharing common logic via `BaseGitService`.
- **Sync logic**: `src/logic/sync-manager.ts` handles push/pull, conflict detection, and rename detection; `src/logic/gitignore-manager.ts` merges local and remote `.gitignore` rules.
- **UI**: `src/ui/SyncStatusView.ts` renders the sync status side panel; `src/ui/components/` holds its sub-views.

Two compatibility gotchas not covered by `docs/architecture.md`:
- Do not reintroduce `SyncStatusView` or `ui/sync-status/*` — that legacy presentation layer was replaced by the Source Control surface (`SourceControlItemView`/`SourceControlView`) and is blocked by an ESLint `no-restricted-imports` rule (`eslint.config.*`). The historical migration docs live in `docs/source-control-refactor/` and are marked as such; they are not current implementation guidance.
- `SOURCE_CONTROL_VIEW_TYPE` (`'sync-status-view'`) and the `open-sync-status` command id are intentionally kept as-is for pinned-leaf/workspace-layout compatibility — they resolve to the current `SourceControlItemView`, not a leftover of the old UI. Do not rename them as "cleanup."

- **Bundling**: Uses `esbuild.config.mjs` for compilation from TypeScript to a single `main.js` file.
- **Deployment**: Relies on `manifest.json` for plugin metadata and `versions.json` for version mapping/compatibility.

Expand Down
179 changes: 179 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Architecture

This document is the canonical architecture guide for the current `git-files-sync` implementation.

Historical design notes and refactor plans are useful context, but they are **not** current implementation guidance. When code and an old plan disagree, follow this document and the current code boundaries.

## 1. System layers

```text
Obsidian / UI
Source Control application layer
SyncWorkspace
Sync domain
GitServiceInterface
GitHub / GitLab / Gitea
```

The dependency direction should normally flow downward. Results and state flow back upward through return values, stores, subscriptions, or interaction ports rather than by introducing reverse imports.

## 2. High-level modules

| Layer | Module | Owns | Interacts with | Must not own |
| --- | --- | --- | --- | --- |
| Plugin runtime | `src/main.ts` | Obsidian lifecycle, command/view/event registration | settings, `createSyncRuntime`, UI | sync/Source Control constructor graph, sync planning, conflict algorithms, provider-specific workflow |
| Plugin runtime | `createSyncRuntime` (`src/runtime/createSyncRuntime.ts`) | wires `SyncManager`, `SyncStatusRefreshService`, `SyncDiffService`, `SyncWorkspace`, and the Source Control application layer together | the constructors it composes | Obsidian lifecycle events, commands, views, ribbons |
| UI | `src/ui/source-control/*` | rendering, user interaction, Source Control composition | `SourceControlViewModel`, `SourceControlActionService` | provider API calls, sync classification rules |
| Application | `ChangeRepository` | authoritative Source Control `SyncChange` snapshot | `FileStatusAdapter`, ViewModel, action services | remote Git or filesystem access |
| Application | `SyncSelectionStore` | queued selection and explicit per-change action override | ViewModel, current repository snapshot | sync execution |
| Application | `ChangeActionPolicy` | allowed/default action for each change kind | ViewModel, selection reconciliation, intent execution | UI rendering, network calls |
| Application | `SourceControlViewModel` | read-only projection of application state for UI | repository, selection, operation/refresh state | side effects, provider calls, filesystem writes |
| Application | `SourceControlActionService` | stable UI-facing facade for immediate Source Control commands | `SyncWorkspace`, `SyncIntentExecutor` | provider-specific logic, duplicated sync planning |
| Application | `SyncIntentExecutor` | one Sync Queue workflow: resolve intent, plan, confirm, execute, aggregate | repository, action policy, `SyncWorkspace`, notifier | UI DOM, provider API implementation |
| Boundary | `SyncWorkspace` | application-to-sync execution boundary | `SyncManager`, refresh service, diff service | Source Control rendering |
| Sync domain | `SyncManager` | compatibility/domain facade for sync operations | coordinators, executors, metadata/status services | Source Control UI state |
| Sync domain | `PushCoordinator` | batch push use case including planning/conflict/review/commit coordination | planner, conflict resolver, push executor | Source Control selection state |
| Sync domain | `PullCoordinator` | batch pull planning and application | pull executor, conflict flow | Source Control UI rendering |
| Sync domain | `SyncPlanner` | sync plan construction | coordinators | network/UI side effects |
| Sync domain | `ConflictResolver` | conflict validation and resolution coordination | interaction port, provider state | Source Control rendering |
| Sync domain | `PushExecutor` | provider-side batch mutations | `GitServiceInterface`, metadata | UI state |
| Sync domain | `PullExecutor` | local file application for pulls | Obsidian vault, metadata | Source Control UI state |
| Sync domain | `RemoteDeleteExecutor` | remote deletion execution | `GitServiceInterface` | UI |
| Sync domain | `SyncStatusRefreshService` | orchestrates discovery → resolve → reconcile → publish, plus incremental create/modify/delete/rename event handling | `SyncFileDiscovery`, `SyncStatusResolver`, `RenameReconciler`, status store | Source Control rendering, the three algorithms below (delegates to their owning class) |
| Sync domain | `SyncFileDiscovery` | vault/hidden-file/remote-tree/gitignore/symlink discovery, remote-only vs local-deleted classification | vault, provider, gitignore, status store | status resolution, rename reconciliation |
| Sync domain | `SyncStatusResolver` | local-vs-remote status resolution: SHA/content comparison, baseline diff direction, `FileStatus` classification | provider, sync manager, status store | discovery, rename reconciliation |
| Sync domain | `RenameReconciler` | out-of-band (external) rename detection by orphan/candidate blob-sha matching | sync manager, status store | discovery, status resolution |
| Sync domain | `SyncStatusService` | observable `FileStatus` store and status classification | refresh/sync domain, adapters | UI orchestration |
| Sync domain | `SyncMetadataStore` | last-synced SHA and rename metadata persistence | manager/executors/coordinators | presentation |
| Sync domain | `SyncDiffService` | diff content/stat loading and cache | status store, blob loader, workspace, `DiffStat` | sync orchestration |
| Sync domain | `DiffStat` (`src/logic/sync/DiffStat.ts`) | pure +/- diff-stat computation and the `DiffStatLoadResult` contract | diff utilities | UI rendering, provider calls |
| Interaction boundary | `SyncInteractionPort` | domain-facing confirmation/conflict interaction contract | domain, Obsidian adapter | provider implementation |
| UI adapter | `ObsidianSyncInteraction` | Obsidian modal/notice implementation of interaction port | `SyncInteractionPort`, modal UI | sync algorithms |
| Infrastructure | `GitServiceInterface` | provider abstraction used by the sync domain | concrete provider services | UI/application state |
| Infrastructure | `BaseGitService` | shared provider HTTP, encoding and error behavior | concrete provider services | sync workflow |
| Infrastructure | `GitHubService` / `GitLabService` / `GiteaService` | provider-specific Git API behavior | remote Git service | Source Control UI |
| Infrastructure | `GitignoreManager` | `.gitignore` evaluation for remote/local scope | refresh/scanning logic | UI behavior |
| Shared | settings model/helpers | persisted configuration and configuration rules | runtime, providers, sync | settings rendering concerns when avoidable |
| Shared | `utils/*` | low-level reusable helpers | low-level consumers | application workflow state |

## 3. Core runtime flows

### Status/read flow

```text
Vault + Remote Git
SyncStatusRefreshService
SyncStatusService
FileStatusAdapter
ChangeRepository
SourceControlViewModel
SourceControlView
```

Status classification belongs in the sync/status path. The UI must not repair or reinterpret an incorrect status by adding presentation-only exceptions.

### Immediate action flow

```text
SourceControlView
SourceControlActionService
SyncWorkspace
SyncManager / executors
GitServiceInterface or Obsidian Vault
```

### Sync Queue flow

```text
SourceControlView
SourceControlActionService.sync()
SyncIntentExecutor
resolve current ChangeId + revalidate explicit action
build one merged Sync Plan
confirm once
SyncWorkspace
├─ remote mutation bucket (max one provider batch)
└─ local pull bucket
```

## 4. Architecture rules

### MUST

- Source Control execution must cross the sync boundary through `SyncWorkspace`.
- `SourceControlViewModel` reads must be observational and side-effect free.
- Provider-specific behavior must stay behind `GitServiceInterface` and concrete provider services.
- A status/change classification rule must have one source of truth rather than being duplicated in UI and domain code.
- Explicit Sync Queue actions must be revalidated against the current change kind immediately before planning/execution.
- One Sync Queue action must produce one merged review/confirmation flow.
- Remote mutations from one Sync Queue execution must be grouped into at most one provider mutation batch when supported by the current workflow.
- Existing compatibility identifiers such as `sync-status-view` and `open-sync-status` must be preserved unless a migration explicitly removes them.

### MUST NOT

- UI code must not import concrete `GitHubService`, `GitLabService`, or `GiteaService` to fix a feature or bug.
- Source Control application code must not bypass `SyncWorkspace` to call `SyncManager`, coordinators, executors, or providers directly.
- Sync-domain modules must not import `src/ui/source-control/*`.
- `getState()`, render methods, or projection helpers must not mutate selection, metadata, filesystem, or remote state.
- Bug fixes must not duplicate status, rename, conflict, or action-selection rules in a second layer.
- Provider differences must not be handled by scattering `serviceType === ...` checks through Source Control or UI code when the provider abstraction can own the behavior.

## 5. Change placement guide

Use the owning module first. The shortest patch is not automatically the correct patch.

| Problem | Primary owner | Do not fix by |
| --- | --- | --- |
| Source Control displays the wrong label/icon/layout | UI presentation / `SourceControlViewModel` projection | changing `SyncManager` |
| A row chooses the wrong push/pull/delete action | `ChangeActionPolicy`, `SyncSelectionStore`, or `SyncIntentExecutor` | adding special-case `if` logic in `SourceControlView` |
| A file is classified incorrectly (`modified`, `remote-only`, `local-deleted`, conflict direction) | `SyncStatusRefreshService` / `SyncStatusService` classification path | correcting the status only in UI |
| Local/remote files are missing from refresh results | `SyncStatusRefreshService`, scope/gitignore logic | fetching the provider directly from ViewModel/UI |
| Rename/move is detected incorrectly | sync metadata + rename reconciliation in the sync refresh/domain path | duplicating rename detection in UI or provider service |
| Push plan/conflict behavior is wrong | `PushCoordinator`, `SyncPlanner`, `ConflictResolver` | reproducing the algorithm in `SourceControlActionService` |
| Pull application is wrong | `PullCoordinator` / `PullExecutor` | adding filesystem writes to ViewModel/UI |
| GitHub-only API behavior is wrong | `GitHubService` (or shared `BaseGitService` if common) | checking GitHub inside Source Control application code |
| GitLab/Gitea/provider-common HTTP behavior is wrong | provider implementation or `BaseGitService` | copy/pasting workarounds into all callers |
| Diff content/stat is wrong | `SyncDiffService` / diff presentation components depending on the defect | performing ad-hoc provider reads from the row component |

If the owning module cannot fix a bug without crossing a forbidden boundary, improve the boundary first instead of adding a shortcut.

## 6. Current hotspots

Some current modules have high responsibility density. That is not permission to bypass them, and file size alone is not a reason to split them.

- `main.ts`: reduced to Obsidian lifecycle (settings load/save, command/view/ribbon/vault-event registration). The sync/Source Control constructor graph now lives in `createSyncRuntime` (`src/runtime/createSyncRuntime.ts`).
- `SyncStatusRefreshService`: reduced to orchestration (discovery → resolve → reconcile → publish) plus the incremental create/modify/delete/rename handlers. Discovery, status resolution, and rename reconciliation each now have a single owner: `SyncFileDiscovery`, `SyncStatusResolver`, `RenameReconciler`.
- `SourceControlView`: reduced by extracting the "Sync Queue" and "Repository Changes" regions into `SyncQueueSection`/`RepositoryChangesSection` (`src/ui/source-control/`, pure state+callbacks render functions matching `FilterMenu`/`SourceControlHeader`). Still owns the diff pane, scroll-state management, and section composition.
- `PushCoordinator`: large, but still centered on one batch-push use case; split only when a stable responsibility boundary is identified.

Future refactors should reduce these hotspots while preserving the dependency direction in this document.

## 7. Documentation hierarchy

- `docs/architecture.md` — canonical repo-wide architecture and dependency rules.
- `docs/source-control.md` — current Source Control subsystem details and invariants.
- `docs/bug-fix-guidelines.md` — required workflow for bug fixes and small changes.
- `docs/source-control-refactor/*` — historical migration/refactor records only; not current implementation guidance.

When an architectural boundary changes, update this document in the same PR as the code change.
58 changes: 58 additions & 0 deletions docs/bug-fix-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Bug Fix Guidelines

Bug fixes must preserve the architecture, not merely make the failing behavior disappear.

Read `docs/architecture.md` before changing production code that crosses module boundaries.

## Required workflow

1. **Identify the owner.** Determine which module owns the incorrect behavior using the responsibility matrix and change-placement guide in `docs/architecture.md`.
2. **Reproduce at the owning layer.** Add or update the narrowest useful test that proves the defect. Prefer unit/domain tests for policy and classification bugs; use integration/provider E2E when the defect depends on a real boundary.
3. **Fix the owner or its dependency.** Put the correction where the rule already belongs.
4. **Preserve dependency direction.** Do not bypass `SyncWorkspace`, provider interfaces, or existing application boundaries for convenience.
5. **Keep one source of truth.** Do not copy an existing status/conflict/rename/action rule into a second module.
6. **Review the diff for architecture drift.** A small bug fix should not quietly create a new cross-layer dependency or a second implementation of the same policy.

## Hard rule

> A bug fix must not introduce a new cross-layer dependency merely because it is the shortest patch.

If the correct owner cannot solve the problem cleanly, improve the boundary first or keep the workaround explicitly local and documented until a boundary fix can be made. Do not normalize a shortcut into permanent architecture.

## Common examples

| Bug | Correct place to start | Avoid |
| --- | --- | --- |
| Wrong Source Control status | sync/status classification path | UI-only remapping |
| Wrong Sync Queue direction | `ChangeActionPolicy` / intent flow | row-component conditionals |
| Wrong conflict behavior | `PushCoordinator` / `ConflictResolver` | duplicating conflict logic in application facade |
| Missing remote file | refresh/discovery path | provider call from ViewModel |
| Provider-specific API failure | concrete provider / `BaseGitService` | `serviceType` branches in UI/application |
| Wrong local pull/write | pull executor/domain | filesystem operations in UI |

## PR architecture check

Before merging a bug fix, confirm:

- [ ] Owning module was identified.
- [ ] The regression is covered at the appropriate layer.
- [ ] No new forbidden cross-layer dependency was introduced.
- [ ] No existing business rule was duplicated.
- [ ] UI did not gain provider or filesystem responsibility.
- [ ] Source Control did not bypass `SyncWorkspace`.
- [ ] Provider-specific behavior remains behind the provider abstraction.
- [ ] Architecture documentation was updated if a responsibility boundary changed.

## AI-assisted changes

When using an AI coding agent, provide or reference `docs/architecture.md` and require the agent to name the owning module before implementation.

A valid fix plan should answer these questions before code changes:

1. What module owns the bug?
2. What invariant is currently violated?
3. What test demonstrates the regression?
4. Which existing boundary will the fix use?
5. Does the fix add any new dependency edge? If yes, why is that edge architecturally valid?

Do not accept a patch solely because tests pass if it moves domain logic into UI, introduces direct provider access above `SyncWorkspace`, or duplicates an existing policy.
3 changes: 3 additions & 0 deletions docs/source-control-refactor/phase-1-viewmodel-foundation.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Phase 1 — Source Control ViewModel Foundation

> **Historical migration roadmap. Do not use as current implementation
> guidance.** See `docs/source-control.md` for the current architecture.

## Goal

建立 Source Control UI 與 Sync domain 之間的 ViewModel layer。
Expand Down
3 changes: 3 additions & 0 deletions docs/source-control-refactor/phase-2-action-unification.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Phase 2 — Sync Action Unification

> **Historical migration roadmap. Do not use as current implementation
> guidance.** See `docs/source-control.md` for the current architecture.

## Goal

統一 Source Control、Context Menu、Single File 操作的 pipeline。
Expand Down
3 changes: 3 additions & 0 deletions docs/source-control-refactor/phase-3-source-control-ui.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Phase 3 — Source Control UI

> **Historical migration roadmap. Do not use as current implementation
> guidance.** See `docs/source-control.md` for the current architecture.

## Goal

建立 VS Code style Source Control workflow。
Expand Down
Loading
Loading