diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 3107e1c..873eb38 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -380,6 +380,32 @@ function RepoGroupView(props: { }); } + const [syncingAll, setSyncingAll] = createSignal(false); + const [syncErrors, setSyncErrors] = createSignal<{repoName: string, path: string, errorMsg: string}[]>([]); + + const handleSyncAll = async () => { + setSyncingAll(true); + setSyncErrors([]); + try { + const repos = snapshot()?.repos ?? []; + const tracked = repos.filter(r => isTrackedRepo(r.repo.path)).map(r => r.repo.path); + + const promises = tracked.map(async path => { + try { + await syncChanges(path); + } catch (e) { + const name = path.split('/').pop() || path; + setSyncErrors(errs => [...errs, { repoName: name, path, errorMsg: String(e) }]); + } + }); + await Promise.all(promises); + + await refetch(); + } finally { + setSyncingAll(false); + } + }; + let timer: ReturnType | undefined; onMount(() => { timer = setInterval(() => { @@ -392,9 +418,21 @@ function RepoGroupView(props: { return (
-
-

{props.group.display_name}

- {props.group.path} +
+
+

{props.group.display_name}

+ {props.group.path} +
+
{(group) => ( <> + 0}> + +
{ + const isConflict = props.error.errorMsg.toLowerCase().includes("conflict") || props.error.errorMsg.toLowerCase().includes("merge"); + const prompt = isConflict + ? `I tried to sync the repository at '${props.error.path}' but ran into a git merge conflict. Please find the conflicting files, carefully resolve the conflicts preserving logic from both branches, and then run 'git commit' to finish the merge.` + : `I tried to sync the repository at '${props.error.path}' but encountered this git error:\n\n${props.error.errorMsg}\n\nPlease investigate and fix this issue so I can sync successfully.`; + + void navigator.clipboard.writeText(prompt); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ {props.error.repoName}: {props.error.errorMsg} +
+ +
+ ); +} + function StatusCard(props: { label: string; value: string; diff --git a/apps/desktop/src/styles.css b/apps/desktop/src/styles.css index 8522222..ff40217 100644 --- a/apps/desktop/src/styles.css +++ b/apps/desktop/src/styles.css @@ -1105,3 +1105,47 @@ body, padding: var(--space-xxs) 0; margin: 0; } + +.btn--sync-all { + background: var(--color-component-selected-bg); + color: var(--color-component-selected-fg); + border-radius: var(--radius-m); + padding: 6px 12px; + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + border: 1px solid var(--color-component-selected-bg); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + transition: all 0.3s; + cursor: pointer; +} + +.btn--sync-all:hover:not(:disabled) { + background: var(--color-component-selected-bg); + filter: brightness(1.2); + transform: scale(1.01); + box-shadow: 0 4px 12px var(--color-component-selected-bg); +} + +.btn--sync-all:active:not(:disabled) { + transform: scale(0.99); +} + +.btn--sync-all:disabled { + background: var(--color-component-selected-bg); + color: var(--color-component-selected-fg); + opacity: 0.5; + cursor: not-allowed; + border-color: var(--color-component-selected-bg); + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.icon-spin { + animation: spin 1s linear infinite; +}