Skip to content

Commit 9fb2fde

Browse files
committed
Fix things around processing pipeline
1 parent a516add commit 9fb2fde

3 files changed

Lines changed: 80 additions & 9 deletions

File tree

src/app.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,21 @@ body::before {
805805
font-weight: 500;
806806
}
807807

808+
.phase-info-estimate {
809+
margin: 8px 0;
810+
padding: 6px 10px;
811+
font-family: var(--font-mono);
812+
font-size: 0.6875rem;
813+
font-variant-numeric: tabular-nums;
814+
letter-spacing: 0.01em;
815+
line-height: 1.6;
816+
color: var(--color-foreground-secondary);
817+
background-color: var(--color-canvas-secondary);
818+
border: 1px solid var(--color-border);
819+
border-left: 2px solid var(--color-accent);
820+
border-radius: 4px;
821+
}
822+
808823
/* Footer total time */
809824
.phase-footer-time {
810825
display: inline-flex;

src/lib/components/PipelineProgress.svelte

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
processTotal: number
1313
processDate: string
1414
processElapsedMs: number
15+
repoSizeBytes?: number | null
1516
oncancel: () => void
1617
}
1718
@@ -26,6 +27,7 @@
2627
processTotal,
2728
processDate,
2829
processElapsedMs,
30+
repoSizeBytes,
2931
oncancel,
3032
}: Props = $props()
3133
@@ -94,10 +96,37 @@ ${statBlock}`
9496
: ''
9597
statBlock = `<span class="phase-info-stat"><span class="phase-info-stat-value">${formatBytes(snap.loaded)}</span> received${speed}</span>`
9698
}
99+
100+
// Size estimate block — when we know the repo size, estimate compressed download range
101+
const packRatioLow = 0.1
102+
const packRatioHigh = 0.8
103+
let sizeEstimateBlock = ''
104+
if (repoSizeBytes != null && repoSizeBytes > 0) {
105+
const repoMb = formatMb(repoSizeBytes)
106+
const lowBytes = repoSizeBytes * packRatioLow
107+
const highBytes = repoSizeBytes * packRatioHigh
108+
const lowMb = formatMb(lowBytes)
109+
const highMb = formatMb(highBytes)
110+
const lowPctLabel = Math.round(packRatioLow * 100)
111+
const highPctLabel = Math.round(packRatioHigh * 100)
112+
113+
const loaded = snap?.loaded ?? 0
114+
const progressLine =
115+
loaded > 0
116+
? (() => {
117+
const lowPct = Math.min(Math.round((loaded / lowBytes) * 100), 100)
118+
const highPct = Math.min(Math.round((loaded / highBytes) * 100), 100)
119+
return ` With <span class="phase-info-stat-value">${formatMb(loaded)} MB</span> downloaded, that\u2019s somewhere between <span class="phase-info-stat-value">${highPct}%</span> and <span class="phase-info-stat-value">${lowPct}%</span> done. That's all we know.`
120+
})()
121+
: ''
122+
123+
sizeEstimateBlock = `<p class="phase-info-estimate">The repo is <span class="phase-info-stat-value">${repoMb} MB</span> uncompressed. Git packs are typically <span class="phase-info-stat-value">${lowPctLabel}\u2013${highPctLabel}%</span> of that, so expect roughly <span class="phase-info-stat-value">${lowMb}\u2013${highMb} MB</span> to download.${progressLine}</p>`
124+
}
125+
97126
return `<p>This is the actual download \u2014 bytes flowing from the server through a CORS proxy into the browser. Everything runs in a Web Worker so the page stays responsive while data streams in.</p>
98127
<p>The pack file contains all the compressed objects from the previous step. Once it arrives, the objects still need to be unpacked and reconstructed.</p>
99128
<p>GitHub and other hosts often send data in bursts with long pauses between them \u2014 sometimes several minutes of silence. This is normal server behavior, not a connection problem.</p>
100-
${statBlock}`
129+
${sizeEstimateBlock}${statBlock}`
101130
}
102131
103132
case 'resolve':
@@ -335,6 +364,11 @@ ${statBlock}`
335364
336365
const formatNumber = (n: number): string => n.toLocaleString()
337366
367+
const formatMb = (bytes: number): string => {
368+
const mb = bytes / (1024 * 1024)
369+
return mb >= 100 ? Math.round(mb).toLocaleString() : mb.toFixed(1)
370+
}
371+
338372
const getPhaseDetail = (phaseDef: PhaseDefinition, state: PhaseState): string => {
339373
const snap = snapshots[phaseDef.id]
340374
if (!snap) return ''

src/routes/+page.svelte

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,22 @@
113113
return parsed ? parsed.url : ''
114114
})
115115
116-
let autoStartedRepo = $state('')
116+
// Auto-start from URL on initial page load only (e.g. shared links).
117+
// Must not re-trigger on programmatic goto — goto is async, so comparing
118+
// the URL-derived initialRepo with a stored string races and can start the wrong repo.
119+
let hasAutoStarted = $state(false)
117120
118121
$effect(() => {
119-
if (browser && initialRepo && initialRepo !== autoStartedRepo) {
120-
autoStartedRepo = initialRepo
122+
if (browser && initialRepo && !hasAutoStarted) {
123+
hasAutoStarted = true
121124
void startAnalysis(initialRepo)
122125
}
123126
})
124127
128+
// Generation counter: incremented on each startAnalysis call so stale async
129+
// flows (cache lookups, fire-and-forget fetches) bail out when superseded.
130+
let analysisGeneration = 0
131+
125132
const startTimer = (kind: 'clone' | 'process') => {
126133
stopTimer()
127134
const startTime = Date.now()
@@ -296,19 +303,22 @@
296303
cancel()
297304
resetState()
298305
lastRepoInput = repoInput
306+
const generation = ++analysisGeneration
299307
300308
try {
301309
const parsed = parseRepoUrl(repoInput)
302-
autoStartedRepo = repoInput // Prevent the URL-watching effect from re-triggering
310+
hasAutoStarted = true // Prevent the URL-watching effect from re-triggering
303311
updateUrl(repoInput)
304312
305-
// Fetch repo size for display (fire-and-forget, non-blocking)
313+
// Fetch repo size for display (fire-and-forget, non-blocking).
314+
// Guarded by generation to avoid setting size for the wrong repo.
306315
void fetchRepoSizeBytes(parsed.host, parsed.owner, parsed.repo).then((size) => {
307-
repoSizeBytes = size
316+
if (generation === analysisGeneration) repoSizeBytes = size
308317
})
309318
310319
// Check local cache first
311320
const cached = await getResult(parsed.url)
321+
if (generation !== analysisGeneration) return
312322
if (cached) {
313323
cachedResult = cached
314324
result = cached
@@ -319,6 +329,7 @@
319329
320330
// Check shared server cache on local miss
321331
const serverEntry = await fetchServerResult(parsed.url)
332+
if (generation !== analysisGeneration) return
322333
if (serverEntry) {
323334
cachedResult = serverEntry.result
324335
result = serverEntry.result
@@ -332,6 +343,7 @@
332343
// Pre-clone size gate (GitHub only, skipped on force or API failure)
333344
if (!force) {
334345
const sizeBytes = await fetchRepoSizeBytes(parsed.host, parsed.owner, parsed.repo)
346+
if (generation !== analysisGeneration) return
335347
if (sizeBytes !== null && sizeBytes > sizeGateThresholdBytes) {
336348
sizeGateBytes = sizeBytes
337349
return
@@ -343,6 +355,7 @@
343355
await cancelCleanup
344356
cancelCleanup = undefined
345357
}
358+
if (generation !== analysisGeneration) return
346359
347360
phase = 'cloning'
348361
startTimer('clone')
@@ -356,6 +369,7 @@
356369
357370
await analyzer.analyze(repoInput, corsProxy, handleProgress)
358371
} catch (e) {
372+
if (generation !== analysisGeneration) return
359373
stopTimer()
360374
if (phase !== 'error') {
361375
phase = 'error'
@@ -371,6 +385,7 @@
371385
372386
const dismissSizeGate = () => {
373387
sizeGateBytes = 0
388+
updateUrl('')
374389
}
375390
376391
let cancelCleanup: Promise<void> | undefined
@@ -384,6 +399,12 @@
384399
phase = 'idle'
385400
}
386401
402+
/** User-facing cancel: also resets URL so stale repo path doesn't linger. */
403+
const handleUserCancel = () => {
404+
cancel()
405+
updateUrl('')
406+
}
407+
387408
const retry = () => {
388409
if (pendingRefresh) {
389410
// Re-attempt the failed refresh
@@ -621,7 +642,8 @@
621642
{processTotal}
622643
{processDate}
623644
processElapsedMs={processElapsed}
624-
oncancel={cancel}
645+
{repoSizeBytes}
646+
oncancel={handleUserCancel}
625647
/>
626648
</div>
627649
{/if}
@@ -655,7 +677,7 @@
655677
</p>
656678
<div class="mt-3 flex items-center gap-3">
657679
<button onclick={dismissSizeWarning} class="btn-primary text-sm"> Continue </button>
658-
<button onclick={cancel} class="btn-link text-sm"> Cancel </button>
680+
<button onclick={handleUserCancel} class="btn-link text-sm"> Cancel </button>
659681
</div>
660682
</div>
661683
</div>

0 commit comments

Comments
 (0)