test(e2e): fixture-seeded transcripts return no content hits from thread search
#2.305 aberto em 6 de ago. de 2026
Métricas do repositório
- Stars
- (1 estrela)
- Métricas de merge de PR
- (Métricas PR pendentes)
Description
What happens
In an e2e-fixture window, search:thread returns title hits but no content hits for a fixture-seeded transcript. The same window renders all 24 turns of that transcript on screen.
Measured in the long-transcript fixture window (Electron, MAKA_E2E=1, MAKA_E2E_FIXTURE=long-transcript), calling the renderer bridge directly so the modal's own filtering is out of the picture:
await window.maka.search.thread({ source: 'thread', query, limit: 10 })
| query | result |
|---|---|
长会话 |
1 hit — {"title":"超长会话滚动几何","summary":"会话标题","target":{"kind":"thread","sessionId":"e2e-fixture-long-transcript"}} |
长会话问题 |
[] |
长会话问题 3 |
[] |
长会话回答 3 |
[] |
占位正文 |
[] |
Every one of those strings is in the seeded messages (longTranscriptMessages in apps/desktop/src/main/e2e-fixture/scenarios-sessions.ts): each turn is a 长会话问题 N user message and a 长会话回答 N assistant message with 60 filler lines of … 用于撑高单个 turn 的占位正文内容。.
Where the two paths diverge
The renderer and search do not read messages the same way in fixture mode.
// apps/desktop/src/main/sessions-ipc-main.ts — the renderer's transcript load
ipcMain.handle('sessions:readMessages', async (_event, sessionId: string) => {
if (e2eFixture) return store.readMessages(sessionId); // ← fixture bypass
messages = await runtime.getMessages(sessionId);
…
});
// same file — search's dependency, no such bypass
return runThreadSearch(request, {
listSessions: () => runtime.listSessions(),
readMessages: (sessionId: string) => runtime.getMessages(sessionId),
…
});
resolveE2eFixture returns non-null only when a scenario is set, so the bypass is active in exactly the windows that seed transcripts. The renderer therefore renders from the store the fixture wrote, while search asks the runtime — which is why the session is visible to search (it comes from listSessions, hence the title hit) but its messages are not.
runThreadSearch also swallows the difference between "threw" and "returned nothing":
try { messages = await deps.readMessages(session.id); } catch { continue; }
so from outside I can't tell which of the two happens for a seeded id — only that the search path never reaches the store the fixture wrote to. That distinction is what decides the fix (register seeded sessions with the runtime, or give search the same fixture-mode store read), so whoever takes this should establish it before changing anything.
Not the cause: the backend !== 'fake' filter. Fixture headers default to backend: 'ai-sdk' (seed-helpers.ts), and the title hit above proves the session reaches the scan.
Why it matters
A title-only hit carries no turnId:
// thread-search.ts, title branch
target: { kind: 'thread', sessionId: session.id }
// content branch
target: { kind: 'thread', sessionId: session.id, ...(turnId ? { turnId } : {}) }
and the shell turns that into a plain session switch:
// app-shell.tsx
if (turnId) setSearchScrollTarget({ sessionId, turnId, nonce: Date.now() });
else setSearchScrollTarget(null);
So no e2e can drive search → navigate-to-a-specific-turn against a seeded fixture. That is what blocked the turn-navigation coverage discussed in #2239 (use-chat-scroll's scroll-target effect, which releases the arrival pin when the reader picks a position); the gap is recorded there with this issue as its reason.
Two things that do not work around it: a message written by the runtime inside a fixture window might well be searchable — I did not establish that, my one attempt didn't produce a reply and I didn't chase why — but it would land at the tail of the transcript, and a target that coincides with the bottom cannot distinguish "navigated to the hit" from "followed to the latest turn", which is the whole point of such a test.
Contrast
Content search works when the runtime itself created the session: e2e/floating-layers.spec.ts → search closes before navigating and focusing the matched turn sends a message through the composer and then finds it by content (re-ran it while writing this — green). Note that spec's window has no fixture scenario, so e2eFixture is null there and both paths read through the runtime — it is the contrast case for the divergence above, not the same window in a different state.
Repro
test('probe', async ({ longTranscriptWindow: page }) => {
for (const query of ['长会话', '长会话问题', '占位正文']) {
console.log(query, await page.evaluate(async (q) =>
JSON.stringify(await window.maka.search.thread({ source: 'thread', query: q, limit: 10 })), query));
}
});
Measured on 2b9719eb7 (branch of #2239), which is f8d2ac4ee plus that PR's commits — none of which touch search, storage, or the fixture.