Skip to content

Commit e2015cc

Browse files
ClaudiaFangclaude
andcommitted
fix(source-control): remove Conflict filter chip and dedupe sync time in header
The header showed both "Last sync" and "Last checked" times, which read as duplicated; keep only "Last checked" and drop the now-unused Conflict filter chip (conflicts remain reachable via the default Needs Sync / All views). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 2ec81c8 commit e2015cc

9 files changed

Lines changed: 22 additions & 48 deletions

File tree

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/i18n/locales/en.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ const en = {
178178
'sourceControl.filter.changes': 'Changes',
179179
'sourceControl.filter.local': 'Local',
180180
'sourceControl.filter.remote': 'Incoming',
181-
'sourceControl.filter.conflict': 'Conflict',
182181
'sourceControl.filter.readyToPush': 'Ready to Push',
183182
'sourceControl.filter.remoteChanges': 'Incoming',
184183
'sourceControl.filter.conflicts': 'Conflicts',
@@ -216,10 +215,8 @@ const en = {
216215
'sourceControl.detail.back': 'Back',
217216
'sourceControl.mobile.filesSelected': '{count} files selected',
218217
'sourceControl.mobile.sync': 'Sync',
219-
'sourceControl.info.lastSync': 'Last sync: {time}',
220218
'sourceControl.info.lastChecked': 'Last checked: {time}',
221219
'sourceControl.info.justChecked': 'Last checked: just now',
222-
'sourceControl.info.neverSynced': 'Never synced',
223220
'sourceControl.search.placeholder': 'Filter by path…',
224221
'sourceControl.search.clear': 'Clear filter',
225222
'sourceControl.folder.selectAll': 'Select all in folder',

‎src/i18n/locales/zh-cn.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ const zhCn: Partial<Record<TranslationKey, string>> = {
180180
'sourceControl.filter.changes': '更改',
181181
'sourceControl.filter.local': '本地',
182182
'sourceControl.filter.remote': '传入',
183-
'sourceControl.filter.conflict': '冲突',
184183
'sourceControl.filter.readyToPush': '待推送',
185184
'sourceControl.filter.remoteChanges': '传入',
186185
'sourceControl.filter.conflicts': '冲突',
@@ -218,10 +217,8 @@ const zhCn: Partial<Record<TranslationKey, string>> = {
218217
'sourceControl.detail.back': '返回',
219218
'sourceControl.mobile.filesSelected': '已选 {count} 个文件',
220219
'sourceControl.mobile.sync': '同步',
221-
'sourceControl.info.lastSync': '上次同步:{time}',
222220
'sourceControl.info.lastChecked': '上次检查:{time}',
223221
'sourceControl.info.justChecked': '上次检查:刚刚',
224-
'sourceControl.info.neverSynced': '尚未同步',
225222
'sourceControl.search.placeholder': '按路径过滤…',
226223
'sourceControl.search.clear': '清除过滤',
227224
'sourceControl.folder.selectAll': '选取文件夹内全部项目',

‎src/i18n/locales/zh-tw.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ const zhTw: Partial<Record<TranslationKey, string>> = {
180180
'sourceControl.filter.changes': '變更',
181181
'sourceControl.filter.local': '本地',
182182
'sourceControl.filter.remote': '傳入',
183-
'sourceControl.filter.conflict': '衝突',
184183
'sourceControl.filter.readyToPush': '待推送',
185184
'sourceControl.filter.remoteChanges': '傳入',
186185
'sourceControl.filter.conflicts': '衝突',
@@ -218,10 +217,8 @@ const zhTw: Partial<Record<TranslationKey, string>> = {
218217
'sourceControl.detail.back': '返回',
219218
'sourceControl.mobile.filesSelected': '已選 {count} 個檔案',
220219
'sourceControl.mobile.sync': '同步',
221-
'sourceControl.info.lastSync': '上次同步:{time}',
222220
'sourceControl.info.lastChecked': '上次檢查:{time}',
223221
'sourceControl.info.justChecked': '上次檢查:剛剛',
224-
'sourceControl.info.neverSynced': '尚未同步',
225222
'sourceControl.search.placeholder': '以路徑過濾…',
226223
'sourceControl.search.clear': '清除過濾',
227224
'sourceControl.folder.selectAll': '選取資料夾內全部項目',

‎src/ui/source-control/FilterMenu.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type { SourceControlCounts } from '../../logic/source-control/SourceContr
1515
* bucket in the view (see `SourceControlView`) rather than via a domain
1616
* change. Surfaced as an opt-in overview; the default stays on Needs Sync
1717
* so a quiet workspace stays quiet.
18-
* - **Incoming / Conflict / Synced** — the matching domain filters (chip id stays `remote`; the label reads "Incoming" — a file only on the remote, or changed only on the remote, is something coming *in*).
18+
* - **Incoming / Synced** — the matching domain filters (chip id stays `remote`; the label reads "Incoming" — a file only on the remote, or changed only on the remote, is something coming *in*).
1919
*
2020
* "Local" (domain `changes`) is intentionally not a chip: Needs Sync already
2121
* covers local-side changes, and a standalone local-only view added a
@@ -30,12 +30,11 @@ export interface FilterChip {
3030
count: (counts: SourceControlCounts) => number;
3131
}
3232

33-
/** The five filter chips, in display order. */
33+
/** The four filter chips, in display order. */
3434
export const FILTER_CHIPS: readonly FilterChip[] = [
3535
{ id: 'all', filter: 'all', showSynced: true, labelKey: 'sourceControl.filter.all', count: c => c.all + c.synced },
3636
{ id: 'needsSync', filter: 'all', showSynced: false, labelKey: 'sourceControl.filter.needsSync', count: c => c.all },
3737
{ id: 'remote', filter: 'remote-changes', showSynced: false, labelKey: 'sourceControl.filter.remote', count: c => c['remote-changes'] },
38-
{ id: 'conflict', filter: 'conflicts', showSynced: false, labelKey: 'sourceControl.filter.conflict', count: c => c.conflicts },
3938
{ id: 'synced', filter: 'synced', showSynced: true, labelKey: 'sourceControl.filter.synced', count: c => c.synced },
4039
];
4140

@@ -55,8 +54,8 @@ export interface FilterMenuOptions {
5554
}
5655

5756
/**
58-
* Renders the Source Control filter row: five chips — All / Needs Sync /
59-
* Incoming / Conflict / Synced. On mobile a single `<select>` dropdown
57+
* Renders the Source Control filter row: four chips — All / Needs Sync /
58+
* Incoming / Synced. On mobile a single `<select>` dropdown
6059
* replaces the chips (same chip ids, counts inline as "Label (N)").
6160
*
6261
* Per-filter counts come straight from the ViewModel's single-source counts

‎src/ui/source-control/SourceControlHeader.ts‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ export interface SourceControlWorkspaceInfo {
88
serviceName: string;
99
branch: string;
1010
vaultFolder: string;
11-
/** Epoch ms of the most recent successful push/pull, or 0 if nothing has synced yet. */
12-
lastSyncTime: number;
1311
/**
1412
* Epoch ms the Source Control view last completed a status refresh
1513
* (any reason — manual, startup, local-change), or 0 if it hasn't
@@ -104,14 +102,6 @@ function renderInfoStrip(container: HTMLElement, info: SourceControlWorkspaceInf
104102
folder.createSpan({ text: ` ${info.vaultFolder}` });
105103
}
106104

107-
strip.createSpan({ cls: 'scv-info-sep', text: '·' });
108-
strip.createSpan({
109-
cls: 'scv-info-time',
110-
text: info.lastSyncTime > 0
111-
? t('sourceControl.info.lastSync', { time: new Date(info.lastSyncTime).toLocaleTimeString() })
112-
: t('sourceControl.info.neverSynced'),
113-
});
114-
115105
if (info.lastCheckedAt > 0) {
116106
strip.createSpan({ cls: 'scv-info-sep', text: '·' });
117107
const elapsed = Date.now() - info.lastCheckedAt;

‎src/ui/source-control/SourceControlItemView.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,8 @@ export class SourceControlItemView extends ItemView {
188188

189189
private getWorkspaceInfo(): SourceControlWorkspaceInfo {
190190
const info = this.plugin.syncWorkspace.getInfo();
191-
const lastSyncTime = Object.values(this.plugin.settings.syncMetadata)
192-
.reduce((latest, metadata) => Math.max(latest, metadata.lastSyncedAt), 0);
193191
const lastCheckedAt = this.plugin.refreshState.getLastCheckedAt();
194-
return { ...info, lastSyncTime, lastCheckedAt };
192+
return { ...info, lastCheckedAt };
195193
}
196194

197195
getViewType(): string { return SOURCE_CONTROL_VIEW_TYPE; }

‎tests/ui/source-control/FilterMenu.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ describe('renderFilterMenu', () => {
1919
callbacks = { onFilterChange: vi.fn() };
2020
});
2121

22-
it('renders the five chips (All/Needs Sync/Incoming/Conflict/Synced)', () => {
22+
it('renders the four chips (All/Needs Sync/Incoming/Synced)', () => {
2323
renderFilterMenu(container, { filter: 'all', showSynced: false }, zeroCounts, callbacks);
2424

2525
const chips = Array.from(container.querySelectorAll('.scv-filter-option')).map(el => el.getAttribute('data-filter'));
26-
expect(chips).toEqual(['all', 'needsSync', 'remote', 'conflict', 'synced']);
26+
expect(chips).toEqual(['all', 'needsSync', 'remote', 'synced']);
2727
});
2828

2929
it('labels the chips with their display names', () => {
3030
renderFilterMenu(container, { filter: 'all', showSynced: false }, zeroCounts, callbacks);
3131

3232
const labels = Array.from(container.querySelectorAll('.scv-filter-option .scv-filter-label')).map(el => el.textContent);
33-
expect(labels).toEqual(['All', 'Needs Sync', 'Incoming', 'Conflict', 'Synced']);
33+
expect(labels).toEqual(['All', 'Needs Sync', 'Incoming', 'Synced']);
3434
});
3535

3636
it('marks the current (filter, showSynced) chip as active', () => {
@@ -66,6 +66,6 @@ describe('renderFilterMenu', () => {
6666
expect(container.querySelector('.scv-filter-dropdown')).not.toBeNull();
6767
expect(container.querySelector('.scv-filter-option')).toBeNull();
6868
const options = Array.from(container.querySelectorAll('.scv-filter-dropdown option')).map(o => (o as HTMLOptionElement).value);
69-
expect(options).toEqual(['all', 'needsSync', 'remote', 'conflict', 'synced']);
69+
expect(options).toEqual(['all', 'needsSync', 'remote', 'synced']);
7070
});
7171
});

‎tests/ui/source-control/SourceControlView.test.ts‎

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ function buildView(changes: SyncChange[], callbacks: Partial<SourceControlViewCa
2525
serviceName: 'GitHub',
2626
branch: 'main',
2727
vaultFolder: '',
28-
lastSyncTime: 0,
2928
lastCheckedAt: 0,
3029
}));
3130
return { view, selection, operations, refreshState, refreshSource, onSync, onRefresh };
@@ -46,7 +45,6 @@ function buildViewWithRepository(changes: SyncChange[], callbacks: Partial<Sourc
4645
serviceName: 'GitHub',
4746
branch: 'main',
4847
vaultFolder: '',
49-
lastSyncTime: 0,
5048
lastCheckedAt: 0,
5149
}));
5250
return { view, repository, selection, operations, refreshState, refreshSource, onSync, onRefresh };
@@ -110,23 +108,23 @@ describe('SourceControlView', () => {
110108

111109
it('shows a flat tree (no sections) once a specific filter is selected', () => {
112110
const { view } = buildView([
113-
{ id: toChangeId('c-1'), path: 'a.md', kind: 'conflict' },
111+
{ id: toChangeId('c-1'), path: 'a.md', kind: 'remote-only' },
114112
{ id: toChangeId('c-2'), path: 'b.md', kind: 'local-only' },
115113
]);
116114
view.render(container);
117115

118-
(container.querySelector('.scv-filter-option[data-filter="conflict"]') as HTMLButtonElement).click();
116+
(container.querySelector('.scv-filter-option[data-filter="remote"]') as HTMLButtonElement).click();
119117

120118
expect(container.querySelectorAll('.scv-section')).toHaveLength(0);
121119
expect(container.querySelectorAll('.scv-change-item')).toHaveLength(1);
122-
expect(view.getFilter()).toBe('conflicts');
120+
expect(view.getFilter()).toBe('remote-changes');
123121
});
124122

125123
it('shows the empty state when the active filter has no items', () => {
126124
const { view } = buildView([{ id: toChangeId('c-1'), path: 'a.md', kind: 'local-only' }]);
127125
view.render(container);
128126

129-
(container.querySelector('.scv-filter-option[data-filter="conflict"]') as HTMLButtonElement).click();
127+
(container.querySelector('.scv-filter-option[data-filter="remote"]') as HTMLButtonElement).click();
130128

131129
expect(container.querySelector('.scv-empty')).not.toBeNull();
132130
});
@@ -1284,7 +1282,7 @@ describe('SourceControlView', () => {
12841282
const view = new SourceControlView(
12851283
viewModel,
12861284
{ onSync: vi.fn(), onRefresh: vi.fn() },
1287-
() => ({ serviceName: 'GitHub', branch: 'main', vaultFolder: '', lastSyncTime: 0, lastCheckedAt }),
1285+
() => ({ serviceName: 'GitHub', branch: 'main', vaultFolder: '', lastCheckedAt }),
12881286
);
12891287
return { view, refreshState };
12901288
}
@@ -1294,28 +1292,26 @@ describe('SourceControlView', () => {
12941292
view.render(container);
12951293

12961294
const infoTimes = container.querySelectorAll('.scv-info-time');
1297-
// Only the "Never synced" line is present; no "Last checked" line.
1298-
expect(infoTimes).toHaveLength(1);
1299-
expect(infoTimes[0]?.textContent).toBe('Never synced');
1295+
expect(infoTimes).toHaveLength(0);
13001296
});
13011297

13021298
it('shows "Last checked: just now" when the last refresh was within a minute', () => {
13031299
const { view } = buildViewWithInfo(Date.now());
13041300
view.render(container);
13051301

13061302
const infoTimes = container.querySelectorAll('.scv-info-time');
1307-
expect(infoTimes).toHaveLength(2);
1308-
expect(infoTimes[1]?.textContent).toBe('Last checked: just now');
1303+
expect(infoTimes).toHaveLength(1);
1304+
expect(infoTimes[0]?.textContent).toBe('Last checked: just now');
13091305
});
13101306

13111307
it('shows "Last checked: <time>" when the last refresh was over a minute ago', () => {
13121308
const { view } = buildViewWithInfo(Date.now() - 120_000);
13131309
view.render(container);
13141310

13151311
const infoTimes = container.querySelectorAll('.scv-info-time');
1316-
expect(infoTimes).toHaveLength(2);
1317-
expect(infoTimes[1]?.textContent).toContain('Last checked:');
1318-
expect(infoTimes[1]?.textContent).not.toBe('Last checked: just now');
1312+
expect(infoTimes).toHaveLength(1);
1313+
expect(infoTimes[0]?.textContent).toContain('Last checked:');
1314+
expect(infoTimes[0]?.textContent).not.toBe('Last checked: just now');
13191315
});
13201316
});
13211317
});

0 commit comments

Comments
 (0)