Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 42a117fd14387eea5d5e87e85df88356f679c1ab
parent df03d3151dd543329bfd4b341285c10764d3a85e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 18:29:17 -0400

common: a cached or empty leaf reports its progress over the scope it read, so "searched N/M" only climbs (re-review R-L1)

applyCached reported the parent scope as processed. Through it, the
empty-scope short-circuit made a Search in copy read as the whole parent
done, and the fold summed it with the streaming copy: "searched 40/40",
then "20/40", then climbing. It now takes the size of the scope the leaf
read: 0 of 0 when empty (skipped by the session's readout), and a cached
leaf's own effective scope. The real-driver test with the posts scope null
asserts the folded leaf's processed and fraction never fall and the empty
copy adds nothing to the total; it fails on the previous code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/lib/search/searchIn.test.ts | 33++++++++++++++++++++++++++++-----
Mcommon/lib/searchEval.ts | 19+++++++++++++------
2 files changed, 41 insertions(+), 11 deletions(-)

diff --git a/common/lib/search/searchIn.test.ts b/common/lib/search/searchIn.test.ts @@ -267,6 +267,8 @@ function runReal( root: GroupNode, s: SearchIn, scopes: { chat: ReadonlySet<string> | null; posts: ReadonlySet<string> | null }, + // Every folded progress, in order — what the session's readout sees. + seen: TreeProgress[] = [], ): Promise<TreeProgress> { const rewritten = applySearchIn(root, s); return within( @@ -282,7 +284,9 @@ function runReal( concurrency: 2, flushIntervalMs: 5, emit: (p) => { - if (p.done) resolve(foldSearchIn(p, rewritten)); + const folded = foldSearchIn(p, rewritten); + seen.push(folded); + if (p.done) resolve(folded); }, }); }), @@ -291,12 +295,31 @@ function runReal( test("a plain query whose posts copy has nothing to read finishes (posts scope null)", async () => { // What the session passes under a tag filter: no posts in scope at all. - const p = await runReal(ALPHA(), { transcripts: true, posts: true, chat: false }, { - chat: null, - posts: null, - }); + const seen: TreeProgress[] = []; + const p = await runReal( + ALPHA(), + { transcripts: true, posts: true, chat: false }, + { chat: null, posts: null }, + seen, + ); assert.deepEqual([...p.slugs], ["v1"]); assert.equal(p.leafStates.get("a")?.active, false); + // The folded leaf's "searched N/M" only climbs (re-review R-L1): the empty + // copy reports 0 of 0, not its parent scope as done, so the readout does + // not start full and fall back as the transcripts copy streams. + let lastProcessed = 0; + let lastFraction = 0; + for (const q of seen) { + const a = q.leafStates.get("a"); + if (!a || a.totalToProcess === 0) continue; + assert.ok(a.processed >= lastProcessed, `processed fell: ${lastProcessed} → ${a.processed}`); + const f = a.processed / a.totalToProcess; + assert.ok(f >= lastFraction, `searched fell: ${lastFraction} → ${f}`); + lastProcessed = a.processed; + lastFraction = f; + } + assert.equal(p.leafStates.get("a")?.processed, 3); + assert.equal(p.leafStates.get("a")?.totalToProcess, 3, "the empty copy adds nothing to the total"); }); test("… and with an empty posts set (a channel selection with no posts channel)", async () => { diff --git a/common/lib/searchEval.ts b/common/lib/searchEval.ts @@ -353,7 +353,7 @@ async function runLeaf( if (scopeArr.length === 0) { return applyCached( leaf, - parentScope, + 0, { slugs: new Set(), hits: new Map() }, ctx, /*cached*/ false, @@ -363,12 +363,12 @@ async function runLeaf( const key = ctx.runtime.cache.key(canonicalHash(leaf), scopeHash); const cachedSync = ctx.runtime.cache.getSync(key); if (cachedSync) { - return applyCached(leaf, parentScope, cachedSync, ctx, /*cached*/ true); + return applyCached(leaf, scopeArr.length, cachedSync, ctx, /*cached*/ true); } const cached = await ctx.runtime.cache.get(key); if (ctx.cancelled) return new Set(); if (cached) { - return applyCached(leaf, parentScope, cached, ctx, /*cached*/ true); + return applyCached(leaf, scopeArr.length, cached, ctx, /*cached*/ true); } // Cache miss — kick off a real pipeline. Pure-filter leaves (no hit @@ -428,9 +428,16 @@ async function runLeaf( return new Set(result.slugs); } +// `scopeSize` is the size of the scope the leaf READ — its effective scope, +// not its parent's — so its progress is "N of N" for the slugs it covered. The +// parent scope over-counted: a chat leaf narrowed to the videos with chat, or +// a "Search in" copy, reported the whole parent as processed, and the folded +// plain leaf's "searched N/M" started full and then fell back as its other +// copies streamed. An empty scope reports 0 of 0, which the session's +// progress readout skips. function applyCached( leaf: LeafNode, - parentScope: Set<string>, + scopeSize: number, cached: CachedResult, ctx: EvalCtx, isCacheHit: boolean, @@ -452,8 +459,8 @@ function applyCached( setLeafState(ctx, leaf.id, { slugCount: slugs.size, totalHits: countHits(hits), - processed: parentScope.size, - totalToProcess: parentScope.size, + processed: scopeSize, + totalToProcess: scopeSize, capped: false, cached: isCacheHit, active: false,