feat(admin): one-click sources sync — admin-only button triggers git clone/pull + re-import + KB overview refresh with polled live status

This commit is contained in:
2026-08-25 21:39:38 -04:00
parent 0654b304e1
commit 52136fe307
13 changed files with 1621 additions and 2 deletions
+59
View File
@@ -313,6 +313,48 @@ html::after {
.auth-link:disabled { opacity: 0.6; cursor: wait; }
.auth-link svg { width: 16px; height: 16px; display: none; }
/* Phase 32: the admin-only "Sync sources" pill (Sources header) — the
same ghost pill as New chat / the auth links, so the bar keeps one
visual language. ink-soft on surface ≈6.9:1 (WCAG AA); hover pair
brand-ink/brand-soft ≈6.9:1. The refresh icon is always visible (it
doubles as the running-state spinner); icon-only below 640px like
the other pills (aria-label keeps the accessible name). ≥44px touch
target at every width; :focus-visible via the global rule. */
.sync-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
min-height: 44px;
padding: 0.5rem 0.9rem;
border-radius: 999px;
border: 1px solid var(--line);
background: transparent;
color: var(--ink-soft);
font: inherit;
font-weight: 600;
font-size: 0.95rem;
white-space: nowrap;
cursor: pointer;
}
.sync-btn:hover { background: var(--brand-soft); color: var(--brand-ink); }
.sync-btn:disabled { opacity: 0.6; cursor: wait; }
.sync-icon { width: 16px; height: 16px; display: block; flex: 0 0 auto; }
/* Running state: the refresh icon spins (reuses the shared spin
keyframes) — the visible half of "Syncing…" while the 2 s poll waits. */
.sync-btn .sync-icon.is-spinning { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.sync-btn .sync-icon.is-spinning { animation: none; }
}
/* The aria-live last-result announcer ("2 added · 1 pruned") — soft ink
on the header surface (≈6.9:1), small mono to match the stat cards. */
.sync-result {
color: var(--ink-soft);
font-family: var(--mono);
font-size: 0.8rem;
white-space: nowrap;
}
/* "Tuning" toggle (phase 15): ghost pill like New chat + a mono count
badge (brand-ink on brand-soft ≈6.9:1). The label is visually-hidden
(not removed) below 640px so the accessible name keeps the word.
@@ -1598,6 +1640,23 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
.auth-link { padding: 0.4rem 0.55rem; }
.auth-label { display: none; }
.auth-link svg { display: block; }
/* Phase 32: the sync pill goes icon-only like the other pills (the
aria-label keeps the accessible name); the spinning icon is the
visible running state on a touch screen. */
.sync-btn { padding: 0.4rem 0.55rem; }
.sync-label { display: none; }
/* The last-result counts stay ANNOUNCED (aria-live is untouched) but
go visually hidden — the 58px bar has no room for the text; the
icon carries the visible state. Same clip recipe as .steering-label. */
.sync-result {
position: absolute !important;
width: 1px; height: 1px;
margin: -1px; padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.steering-toggle { padding: 0.4rem 0.55rem; }
/* Visually hidden, NOT display:none — the accessible name keeps the
word "Tuning" next to the count badge. */