ui: ProblemCard/ConcludeCard redesigned — the solution is the hero
Owner round (2026-10-05), Slice C:
- ProblemCard: ALWAYS OPEN — the answered card's container + eyebrow
discipline ('Problem' label, lh-prose statement), no chevron, no
collapsed bar. The dock mirrors it in Slice D.
- ConcludeCard: the reveal is the modelSolution committed at POSE time,
shown verbatim as the hero — never re-authored (the slim schema's
law). The verdict rides below in the quiz-explanation register
(small, muted 'Verdict:' row). Nothing collapsible. Legacy inputs
with their own solutionReveal fall back to it.
- The reveal join: modelSolutionForConclude (native-messages.ts) — the
newest cleanly-posed problem before the conclude call; refused poses
reveal nothing. projectView enriches tool-conclude parts with
part.reveal at projection; ArtifactPartProps carries the field
(contracts/chat.ts).
Pinned: always-open problem card; hero-first conclude layout with the
verdict after it; legacy fallback; the join (clean pose, refused pose,
multi-problem records) and the view enrichment.
This commit is contained in:
@@ -328,6 +328,40 @@ function cleanToolCallIds(messages: readonly LhMessage[]): Set<string> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
/** The conclusion's reveal (owner round, 2026-10-05): the hero of a
|
||||
* conclude card is the modelSolution committed at POSE time — the newest
|
||||
* cleanly-posed problem BEFORE this conclude call. The reveal is never
|
||||
* re-authored (the slim schema dropped solutionReveal); legacy conclude
|
||||
* inputs that still carry their own solutionReveal fall back to it at the
|
||||
* card. Undefined when the pose carries no string solution. */
|
||||
export function modelSolutionForConclude(
|
||||
messages: readonly LhMessage[],
|
||||
concludeCallId: string,
|
||||
): string | undefined {
|
||||
let mi = messages.findIndex(
|
||||
(m) =>
|
||||
(m as { role?: string }).role === "assistant" &&
|
||||
(m as AssistantMessage).content.some(
|
||||
(b) => b.type === "toolCall" && b.id === concludeCallId,
|
||||
),
|
||||
);
|
||||
if (mi < 0) return undefined;
|
||||
const clean = cleanToolCallIds(messages);
|
||||
for (; mi >= 0; mi--) {
|
||||
const m = messages[mi]!;
|
||||
if ((m as { role?: string }).role !== "assistant") continue;
|
||||
const assistant = m as AssistantMessage;
|
||||
for (let bi = assistant.content.length - 1; bi >= 0; bi--) {
|
||||
const block = assistant.content[bi]!;
|
||||
if (block.type !== "toolCall" || block.name !== PRACTICE_PROBLEM_TOOL) continue;
|
||||
if (!clean.has(block.id)) continue; // a refused pose revealed nothing
|
||||
const solution = (block.arguments as { modelSolution?: unknown } | undefined)?.modelSolution;
|
||||
return typeof solution === "string" ? solution : undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function activeProblemOf(
|
||||
messages: readonly LhMessage[],
|
||||
): { toolCallId: string; input: unknown } | null {
|
||||
|
||||
Reference in New Issue
Block a user