feat: show sync conflict details

This commit is contained in:
mirivlad 2026-06-29 03:38:03 +08:00
parent a4d4e5ed0e
commit efaeed7bcb
2 changed files with 40 additions and 0 deletions

View File

@ -6,6 +6,7 @@
let errorMsg = '' let errorMsg = ''
let resultMsg = '' let resultMsg = ''
let resultKind = '' let resultKind = ''
let conflictDetails = []
let connectionResult = '' let connectionResult = ''
let connectionOk = null let connectionOk = null
@ -119,14 +120,36 @@
return parts.join(' · ') return parts.join(' · ')
} }
function conflictField(conflict, keys) {
for (const key of keys) {
const value = conflict && conflict[key]
if (value != null && String(value).trim()) return String(value)
}
return ''
}
function formatSyncConflict(conflict) {
const entityType = conflictField(conflict, ['entity_type', 'entityType']) || 'item'
const entityId = conflictField(conflict, ['entity_id', 'entityId', 'path']) || 'unknown'
const opId = conflictField(conflict, ['op_id', 'opId'])
const reason = conflictField(conflict, ['reason', 'message'])
const parts = [entityType + ': ' + entityId]
if (opId) parts.push('op ' + opId)
if (reason) parts.push(reason)
return parts.join(' · ')
}
async function runSyncNow() { async function runSyncNow() {
loading = true loading = true
errorMsg = '' errorMsg = ''
resultMsg = '' resultMsg = ''
conflictDetails = []
try { try {
const r = await syncAPI().now() const r = await syncAPI().now()
const summary = 'Pushed ' + (r?.pushed || 0) + ', pulled ' + (r?.pulled || 0) const summary = 'Pushed ' + (r?.pushed || 0) + ', pulled ' + (r?.pulled || 0)
const warning = syncResultWarning(r) const warning = syncResultWarning(r)
const conflicts = Array.isArray(r?.conflicts) ? r.conflicts : []
conflictDetails = conflicts.slice(0, 5).map(formatSyncConflict)
resultMsg = warning ? summary + ' · ' + warning : summary resultMsg = warning ? summary + ' · ' + warning : summary
resultKind = warning ? 'warning' : '' resultKind = warning ? 'warning' : ''
await load() await load()
@ -193,6 +216,14 @@
{#if resultMsg && !errorMsg} {#if resultMsg && !errorMsg}
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;border-radius:6px;font-size:0.85rem;{resultKind === 'warning' ? 'background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);color:#f59e0b;' : 'background:rgba(52,211,153,0.1);border:1px solid rgba(52,211,153,0.3);color:#34d399;'}">{resultMsg}</div> <div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;border-radius:6px;font-size:0.85rem;{resultKind === 'warning' ? 'background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);color:#f59e0b;' : 'background:rgba(52,211,153,0.1);border:1px solid rgba(52,211,153,0.3);color:#34d399;'}">{resultMsg}</div>
{/if} {/if}
{#if conflictDetails.length > 0 && !errorMsg}
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);border-radius:6px;color:#f59e0b;font-size:0.85rem;">
<div style="font-weight:600;margin-bottom:0.35rem;">Sync conflicts</div>
{#each conflictDetails as detail}
<div>{detail}</div>
{/each}
</div>
{/if}
{#if settings && settings.lastError && !errorMsg} {#if settings && settings.lastError && !errorMsg}
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:6px;color:#ff6b6b;font-size:0.85rem;"> <div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:6px;color:#ff6b6b;font-size:0.85rem;">
Last sync error: {sanitizeError(settings.lastError)} Last sync error: {sanitizeError(settings.lastError)}

View File

@ -15,5 +15,14 @@ if (!source.includes('sanitizeError(settings.lastError)')) {
if (!source.includes('Last sync error')) { if (!source.includes('Last sync error')) {
throw new Error('SyncSettings must label the persisted sync error'); throw new Error('SyncSettings must label the persisted sync error');
} }
if (!source.includes('function formatSyncConflict')) {
throw new Error('SyncSettings must format individual sync conflicts');
}
if (!source.includes('conflictDetails')) {
throw new Error('SyncSettings must store sync conflict details after Sync Now');
}
if (!source.includes('Sync conflicts')) {
throw new Error('SyncSettings must label the conflict details section');
}
console.log('sync plugin smoke passed'); console.log('sync plugin smoke passed');