fix: the marker-only first turn — dead send + empty bubble
Owner report, 2026-10-04: starting a session with map picks showed ONE empty user bubble, no agent turn; a follow-up woke the agent with the right material but the marker never rendered. Two breaks, one root — the record no longer starts empty and user-side data parts had no render lane: - The first-send gate checked initialMessages.length === 0; the graph-context marker at message[0] made that false, so the ?prompt turn NEVER fired. New pure helper initialPromptOwed (the escAbortTarget pattern): the prompt is owed while no user/assistant message exists — scaffolding is not conversation. Pinned in tests/graph-context.test.ts. - The transcript's user branch rendered text parts only; a data-only message (graph-context, practice-thinking) fell through as an EMPTY bubble — the data-practice-thinking case I anchored to lived in the ASSISTANT switch, dead on arrival. Data-only user messages now hoist to a bubble-free card lane (the subagent-steer precedent); the two dead assistant-switch cases are gone. Gates: 1296 pass / 0 fail; typecheck, lint, build clean.
This commit is contained in:
@@ -29,6 +29,7 @@ const { toLlmMessages } = await import("../src/core/native-messages.ts");
|
||||
import type { LhMessage } from "../src/core/native-messages.ts";
|
||||
const { projectView } = await import("../src/core/view.ts");
|
||||
const { POST } = await import("../app/api/chats/route.ts");
|
||||
const { initialPromptOwed } = await import("../components/chat.tsx");
|
||||
const { chatStore } = await import("../src/store/chats.ts");
|
||||
const { graphStore } = await import("../src/store/graph.ts");
|
||||
|
||||
@@ -80,6 +81,26 @@ describe("newGraphContextMessage", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── the first-send gate (the dead first turn, owner report 2026-10-04) ──────
|
||||
|
||||
describe("initialPromptOwed", () => {
|
||||
it("a marker-only record still owes the prompt — scaffolding is not conversation", () => {
|
||||
const marker = newGraphContextMessage(PICKS, []);
|
||||
assert.equal(initialPromptOwed([marker as never], "drill me on logic"), true);
|
||||
});
|
||||
|
||||
it("once a user or assistant message exists, the prompt is spent", () => {
|
||||
const marker = newGraphContextMessage(PICKS, []);
|
||||
const user = { role: "user" } as never;
|
||||
assert.equal(initialPromptOwed([marker as never, user], "drill me"), false);
|
||||
assert.equal(initialPromptOwed([{ role: "assistant" } as never], "drill me"), false);
|
||||
});
|
||||
|
||||
it("no prompt → nothing owed (the empty-draft case)", () => {
|
||||
assert.equal(initialPromptOwed([], undefined), false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── the route (picks ride the creation) ─────────────────────────────────────
|
||||
|
||||
describe("POST /api/chats — graphContext becomes message[0]", () => {
|
||||
|
||||
Reference in New Issue
Block a user