fix(MULTIVERSION-0001): fix version indexing pipeline state and UI reactivity
- Add updateVersion() helper to IndexingPipeline that writes to repository_versions - Set version state to indexing/indexed/error at the appropriate pipeline stages - Add computeVersionStats() to count snippets for a specific version - Replace Map<string,string> with Record<string,string|undefined> for activeVersionJobs to fix Svelte 5 reactivity edge cases - Remove premature loadVersions() call from handleIndexVersion (oncomplete fires it instead) - Add refreshRepo() to version oncomplete callback so stat badges update after indexing - Disable Index button when activeVersionJobs has an entry for that tag (not just version.state) - Add three pipeline test cases covering versionId indexing, error, and no-touch paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,9 @@
|
||||
let showDiscoverPanel = $state(false);
|
||||
let registerBusy = $state(false);
|
||||
|
||||
// Active version indexing jobs: tag -> jobId
|
||||
let activeVersionJobs = $state<Record<string, string | undefined>>({});
|
||||
|
||||
// Remove confirm
|
||||
let removeTag = $state<string | null>(null);
|
||||
|
||||
@@ -115,6 +118,16 @@
|
||||
activeJobId = d.job.id;
|
||||
}
|
||||
const versionCount = d.versionJobs?.length ?? 0;
|
||||
if (versionCount > 0) {
|
||||
let next = { ...activeVersionJobs };
|
||||
for (const vj of d.versionJobs) {
|
||||
const matched = versions.find((v) => v.id === vj.versionId);
|
||||
if (matched) {
|
||||
next = { ...next, [matched.tag]: vj.id };
|
||||
}
|
||||
}
|
||||
activeVersionJobs = next;
|
||||
}
|
||||
successMessage =
|
||||
versionCount > 0
|
||||
? `Re-indexing started. Also queued ${versionCount} version job${versionCount === 1 ? '' : 's'}.`
|
||||
@@ -157,6 +170,10 @@
|
||||
const d = await res.json();
|
||||
throw new Error(d.error ?? 'Failed to add version');
|
||||
}
|
||||
const d = await res.json();
|
||||
if (d.job?.id) {
|
||||
activeVersionJobs = { ...activeVersionJobs, [tag]: d.job.id };
|
||||
}
|
||||
addVersionTag = '';
|
||||
await loadVersions();
|
||||
} catch (e) {
|
||||
@@ -177,7 +194,10 @@
|
||||
const d = await res.json();
|
||||
throw new Error(d.error ?? 'Failed to queue version indexing');
|
||||
}
|
||||
await loadVersions();
|
||||
const d = await res.json();
|
||||
if (d.job?.id) {
|
||||
activeVersionJobs = { ...activeVersionJobs, [tag]: d.job.id };
|
||||
}
|
||||
} catch (e) {
|
||||
errorMessage = (e as Error).message;
|
||||
}
|
||||
@@ -244,8 +264,9 @@
|
||||
registerBusy = true;
|
||||
errorMessage = null;
|
||||
try {
|
||||
await Promise.all(
|
||||
[...selectedDiscoveredTags].map((tag) =>
|
||||
const tags = [...selectedDiscoveredTags];
|
||||
const responses = await Promise.all(
|
||||
tags.map((tag) =>
|
||||
fetch(`/api/v1/libs/${encodeURIComponent(repo.id)}/versions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -253,6 +274,15 @@
|
||||
})
|
||||
)
|
||||
);
|
||||
const results = await Promise.all(responses.map((r) => (r.ok ? r.json() : null)));
|
||||
let next = { ...activeVersionJobs };
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const result = results[i];
|
||||
if (result?.job?.id) {
|
||||
next = { ...next, [tags[i]]: result.job.id };
|
||||
}
|
||||
}
|
||||
activeVersionJobs = next;
|
||||
showDiscoverPanel = false;
|
||||
discoveredTags = [];
|
||||
selectedDiscoveredTags = new Set();
|
||||
@@ -346,7 +376,13 @@
|
||||
{#if activeJobId}
|
||||
<div class="mt-4 rounded-xl border border-blue-100 bg-blue-50 p-4">
|
||||
<p class="mb-2 text-sm font-medium text-blue-700">Indexing in progress</p>
|
||||
<IndexingProgress jobId={activeJobId} />
|
||||
<IndexingProgress
|
||||
jobId={activeJobId}
|
||||
oncomplete={() => {
|
||||
activeJobId = null;
|
||||
refreshRepo();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if repo.state === 'error'}
|
||||
<div class="mt-4 rounded-xl border border-red-100 bg-red-50 p-4">
|
||||
@@ -461,31 +497,44 @@
|
||||
{:else}
|
||||
<div class="divide-y divide-gray-100">
|
||||
{#each versions as version (version.id)}
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="font-mono text-sm font-medium text-gray-900">{version.tag}</span>
|
||||
<span
|
||||
class="rounded-full px-2 py-0.5 text-xs font-medium {stateColors[version.state] ??
|
||||
'bg-gray-100 text-gray-600'}"
|
||||
>
|
||||
{stateLabels[version.state] ?? version.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={() => handleIndexVersion(version.tag)}
|
||||
disabled={version.state === 'indexing'}
|
||||
class="rounded-lg border border-blue-200 px-3 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{version.state === 'indexing' ? 'Indexing...' : 'Index'}
|
||||
</button>
|
||||
<button
|
||||
onclick={() => (removeTag = version.tag)}
|
||||
class="rounded-lg border border-red-100 px-3 py-1 text-xs font-medium text-red-500 hover:bg-red-50"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
<div class="py-2.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="font-mono text-sm font-medium text-gray-900">{version.tag}</span>
|
||||
<span
|
||||
class="rounded-full px-2 py-0.5 text-xs font-medium {stateColors[version.state] ??
|
||||
'bg-gray-100 text-gray-600'}"
|
||||
>
|
||||
{stateLabels[version.state] ?? version.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={() => handleIndexVersion(version.tag)}
|
||||
disabled={version.state === 'indexing' || !!activeVersionJobs[version.tag]}
|
||||
class="rounded-lg border border-blue-200 px-3 py-1 text-xs font-medium text-blue-600 hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{version.state === 'indexing' || !!activeVersionJobs[version.tag] ? 'Indexing...' : 'Index'}
|
||||
</button>
|
||||
<button
|
||||
onclick={() => (removeTag = version.tag)}
|
||||
class="rounded-lg border border-red-100 px-3 py-1 text-xs font-medium text-red-500 hover:bg-red-50"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#if !!activeVersionJobs[version.tag]}
|
||||
<IndexingProgress
|
||||
jobId={activeVersionJobs[version.tag]!}
|
||||
oncomplete={() => {
|
||||
const { [version.tag]: _, ...rest } = activeVersionJobs;
|
||||
activeVersionJobs = rest;
|
||||
loadVersions();
|
||||
refreshRepo();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user