mirror your GitHub repos to tangled.org automatically synchub.to
mirror sync tangled github git
92

Configure Feed

Select the types of activity you want to include in your feed.

fix: treat mirrored repos as synced regardless of relayed-push count

Daniel Roe (Jul 10, 2026, 12:41 PM +0200) 6575d143 4b4288a1

+32 -30
+6 -9
app/pages/dashboard.vue
··· 184 184 const HEALTH_ORDER: Record<string, number> = { 185 185 error: 0, 186 186 enrolling: 1, 187 - waiting: 2, 188 - info: 3, 189 - paused: 4, 190 - ok: 5, 187 + info: 2, 188 + paused: 3, 189 + ok: 4, 191 190 } 192 191 const sortedRepos = computed<DashboardRepo[]>(() => { 193 192 const list = data.value?.repos ?? [] ··· 359 358 <span> 360 359 {{ data.summary.needsAttention }} 361 360 {{ data.summary.needsAttention === 1 ? 'repository needs' : 'repositories need' }} attention. 362 - <template v-if="data.summary.waiting > 0">{{ data.summary.waiting }} still catching up.</template> 361 + <template v-if="data.summary.enrolling > 0">{{ data.summary.enrolling }} still setting up.</template> 363 362 </span> 364 363 </div> 365 - <div v-else-if="data.summary.waiting > 0" class="summary" role="status"> 364 + <div v-else-if="data.summary.enrolling > 0" class="summary" role="status"> 366 365 <span aria-hidden="true">◐</span> 367 - <span>{{ data.summary.waiting }} {{ data.summary.waiting === 1 ? 'repository is' : 'repositories are' }} still catching up. This is normal right after installing.</span> 366 + <span>{{ data.summary.enrolling }} {{ data.summary.enrolling === 1 ? 'repository is' : 'repositories are' }} still setting up on tangled. This is normal right after installing.</span> 368 367 </div> 369 368 <div v-else-if="data.summary.total > 0" class="summary summary--ok" role="status"> 370 369 <span aria-hidden="true">●</span> ··· 850 849 851 850 .badge-ok { border-color: var(--color-ok); color: var(--color-ok); } 852 851 .badge-ok::before { content: "●"; } 853 - .badge-waiting, 854 852 .badge-enrolling { border-color: var(--color-warn); color: var(--color-warn); } 855 - .badge-waiting::before, 856 853 .badge-enrolling::before { content: "◐"; } 857 854 .badge-info { border-color: var(--color-accent-dim); color: var(--color-accent-dim); } 858 855 .badge-info::before { content: "ℹ"; }
+2 -3
server/api/me/dashboard.get.ts
··· 23 23 export interface DashboardSummary { 24 24 total: number 25 25 ok: number 26 - waiting: number 26 + enrolling: number 27 27 needsAttention: number 28 28 } 29 29 ··· 108 108 lastError: row.lastError, 109 109 disabledAt, 110 110 tangledRepoDid: row.tangledRepoDid, 111 - refCount: refKeys.length, 112 111 }), 113 112 } 114 113 }) ··· 116 115 const summary: DashboardSummary = { 117 116 total: repos.length, 118 117 ok: repos.filter(r => r.health.state === 'ok').length, 119 - waiting: repos.filter(r => r.health.state === 'waiting' || r.health.state === 'enrolling').length, 118 + enrolling: repos.filter(r => r.health.state === 'enrolling').length, 120 119 needsAttention: repos.filter(r => r.health.needsAttention).length, 121 120 } 122 121
+10 -11
server/utils/repo-health.ts
··· 10 10 */ 11 11 12 12 export type RepoHealthState = 13 - /** Enrolled and at least one ref has synced. Nothing to do. */ 13 + /** Mirrored on tangled (the knot holds the repo). Nothing to do. */ 14 14 | 'ok' 15 - /** Enrolled but nothing has synced yet: backfill/first push still in flight. */ 16 - | 'waiting' 17 - /** Not yet mirrored on tangled (enrolment hasn't completed). */ 15 + /** Enrolment hasn't produced a tangled mirror yet. */ 18 16 | 'enrolling' 19 17 /** User paused sync from the dashboard. */ 20 18 | 'paused' ··· 36 34 lastError: string | null 37 35 disabledAt: string | null 38 36 tangledRepoDid: string | null 39 - refCount: number 40 37 } 41 38 42 39 /** ··· 97 94 } 98 95 } 99 96 100 - if (!input.tangledRepoDid) { 97 + // The knot clones the whole repo from `source` at enrolment, so a mapping 98 + // with a `tangledRepoDid` and `active` status is mirrored regardless of 99 + // whether we've relayed a push since. `refCount` counts only pushes we've 100 + // observed after enrolment, so it is NOT a completeness signal: a repo that 101 + // simply hasn't been pushed to since enrolment still has `refCount === 0` 102 + // while being fully up to date on the knot. 103 + if (!input.tangledRepoDid || input.status !== 'active') { 101 104 return { state: 'enrolling', message: 'Setting up the mirror on tangled. This can take a minute.', needsAttention: false } 102 105 } 103 106 104 - if (input.refCount === 0) { 105 - return { state: 'waiting', message: 'Enrolled, waiting for the first sync. New installs backfill in the background.', needsAttention: false } 106 - } 107 - 108 - return { state: 'ok', message: 'Mirrored and up to date.', needsAttention: false } 107 + return { state: 'ok', message: 'Mirrored and syncing.', needsAttention: false } 109 108 }
+14 -7
test/unit/repo-health.spec.ts
··· 7 7 lastError: null, 8 8 disabledAt: null, 9 9 tangledRepoDid: 'did:plc:repo', 10 - refCount: 1, 11 10 ...overrides, 12 11 } 13 12 } 14 13 15 14 describe('repoHealth', () => { 16 - it('reports ok for an active mapping with synced refs', () => { 15 + it('reports ok for an active mapping with a tangled mirror', () => { 17 16 const h = repoHealth(input()) 18 17 expect(h.state).toBe('ok') 19 18 expect(h.needsAttention).toBe(false) 20 19 }) 21 20 22 - it('reports waiting for an enrolled mapping that has synced nothing yet', () => { 23 - const h = repoHealth(input({ refCount: 0 })) 24 - expect(h.state).toBe('waiting') 25 - expect(h.needsAttention).toBe(false) 21 + it('reports ok for a mirrored repo even when no push has been relayed since enrolment', () => { 22 + // The knot clones the whole repo at enrolment, so an active mapping with a 23 + // repoDid is up to date regardless of relayed-push count. This is the 24 + // majority case (repos not pushed to since install) and must not read as 25 + // "waiting for the first sync". 26 + const h = repoHealth(input({ tangledRepoDid: 'did:plc:repo' })) 27 + expect(h.state).toBe('ok') 26 28 }) 27 29 28 30 it('reports enrolling before the tangled repo exists', () => { 29 - const h = repoHealth(input({ tangledRepoDid: null, refCount: 0 })) 31 + const h = repoHealth(input({ tangledRepoDid: null })) 32 + expect(h.state).toBe('enrolling') 33 + }) 34 + 35 + it('reports enrolling while status is still pending', () => { 36 + const h = repoHealth(input({ status: 'pending', tangledRepoDid: null })) 30 37 expect(h.state).toBe('enrolling') 31 38 }) 32 39