diff --git a/Dockerfile b/Dockerfile
index 34e5b17f..0fd5dbf4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,8 +17,9 @@ RUN pnpm build
FROM base AS installer
+# git: required by the git-sync VaultGit (shells out to git)
RUN apt-get update \
- && apt-get install -y --no-install-recommends curl bash \
+ && apt-get install -y --no-install-recommends curl bash git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json
index ba02a451..6c6e7333 100644
--- a/apps/client/public/locales/en-US/translation.json
+++ b/apps/client/public/locales/en-US/translation.json
@@ -1220,6 +1220,8 @@
"Ran tool {{name}}": "Ran tool {{name}}",
"AI-agent": "AI-agent",
"Edited by AI agent on behalf of {{name}}": "Edited by AI agent on behalf of {{name}}",
+ "Git sync": "Git sync",
+ "Synced from Git on behalf of {{name}}": "Synced from Git on behalf of {{name}}",
"Endpoints": "Endpoints",
"where we fetch models": "where we fetch models",
"All endpoints are OpenAI-compatible. Point the Base URL at OpenAI, OpenRouter, a local Ollama, or any self-hosted server.": "All endpoints are OpenAI-compatible. Point the Base URL at OpenAI, OpenRouter, a local Ollama, or any self-hosted server.",
diff --git a/apps/client/src/components/ui/git-sync-badge.tsx b/apps/client/src/components/ui/git-sync-badge.tsx
new file mode 100644
index 00000000..805d0d0e
--- /dev/null
+++ b/apps/client/src/components/ui/git-sync-badge.tsx
@@ -0,0 +1,36 @@
+import { Badge, Tooltip } from "@mantine/core";
+import { IconGitMerge } from "@tabler/icons-react";
+import { useTranslation } from "react-i18next";
+
+interface GitSyncBadgeProps {
+ authorName?: string;
+}
+
+/**
+ * Badge marking a version written by the git-sync data plane — a VaultGit pull
+ * applied through the native datasource (provenance §8.1). Like {@link AiAgentBadge}
+ * it is ADDITIVE — shown next to the human author, never replacing them. A
+ * git-sync edit is NOT an agent edit and has no chat to deep-link into, so it is
+ * a small, neutral, non-clickable label.
+ */
+export function GitSyncBadge({ authorName }: GitSyncBadgeProps) {
+ const { t } = useTranslation();
+
+ const tooltip = t("Synced from Git on behalf of {{name}}", {
+ name: authorName ?? "",
+ });
+
+ return (
+
+ }
+ >
+ {t("Git sync")}
+
+
+ );
+}
diff --git a/apps/client/src/features/page-history/components/history-item.tsx b/apps/client/src/features/page-history/components/history-item.tsx
index ccb15c0a..9a6d25a5 100644
--- a/apps/client/src/features/page-history/components/history-item.tsx
+++ b/apps/client/src/features/page-history/components/history-item.tsx
@@ -1,6 +1,7 @@
import { Text, Group, UnstyledButton, Avatar, Tooltip } from "@mantine/core";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import { AiAgentBadge } from "@/components/ui/ai-agent-badge.tsx";
+import { GitSyncBadge } from "@/components/ui/git-sync-badge.tsx";
import { formattedDate } from "@/lib/time";
import classes from "./css/history.module.css";
import clsx from "clsx";
@@ -41,6 +42,7 @@ const HistoryItem = memo(function HistoryItem({
const contributors = historyItem.contributors;
const hasContributors = contributors && contributors.length > 0;
const isAgentEdit = historyItem.lastUpdatedSource === "agent";
+ const isGitSyncEdit = historyItem.lastUpdatedSource === "git-sync";
return (
setHistoryModalOpen(false)}
/>
)}
+
+ {isGitSyncEdit && (
+
+ )}
);