2026-10-21 00:00:20 +03:00
/** Display repair is shared, not a mutation of saved practice/teaching arguments. */
import React from "react" ;
( globalThis as Record < string , unknown >). React = React ;
import assert from "node:assert/strict" ;
import { describe , it } from "node:test" ;
import { renderToStaticMarkup } from "react-dom/server" ;
import { parseHTML } from "linkedom" ;
import { validateToolArguments } from "@earendil-works/pi-ai" ;
import { problemInputObject } from "../src/agent/tools/practice.ts" ;
import { PracticeDock } from "../components/practice-dock.tsx" ;
2026-10-23 17:11:57 +03:00
import { QuestionDock } from "../components/question-dock.tsx" ;
import { LatexGlyph } from "../components/latex-glyph.tsx" ;
2026-10-21 00:00:20 +03:00
import { ProblemCard , ConcludeCard } from "../components/practice-cards.tsx" ;
2026-10-26 04:49:47 +02:00
import { MessageResponse , ToolContentResponse } from "../components/ai-elements/message.tsx" ;
2026-10-21 00:00:20 +03:00
import {
repairMathForDisplay , healOverescapedCommands , restoreMathEscapes ,
parseQuizInput , parseAskInput , parseExplainInput , activeProblemOf , projectView ,
toLlmMessages , type LhMessage , type ArtifactPartProps ,
} from "../src/core/index.ts" ;
const tex = String . raw ` \ nabla f(x) \ in \ mathbb{R}^{m \ times n}` ;
const clean = `$ ${ tex } $` ;
const doubled = clean . replace ( /\\/g , "\\\\" );
const quadrupled = clean . replace ( /\\/g , "\\\\\\\\" );
const debris = "$\forall x \\in \\mathbb{R}$" ; // JSON's form feed consumed the f.
const repairedDebris = String . raw ` \ forall x \ in \ mathbb{R}` ;
const matrix = "$$\n" + String . raw `\\begin{pmatrix}1 & 2 \\ 3 & 4\\end{pmatrix}` + "\n$$" ;
const repairedMatrix = String . raw ` \ begin{pmatrix}1 & 2 \\ 3 & 4 \ end{pmatrix}` ;
const part = ( input : unknown , over : Partial < ArtifactPartProps [ "part" ] > = {}) : ArtifactPartProps [ "part" ] => ({
type : "tool-problem" , state : "input-available" , toolCallId : "call_math" , input , ... over ,
});
const dock = ( input : { statement : string }, expanded = true ) => (
< PracticeDock problemCallId = "call_math" input = { input } expanded = { expanded } onExpand = {() => {}} onSubmit = {() => {}} />
);
function math ( html : string ) {
const { document } = parseHTML ( html );
return {
annotations : [... document . querySelectorAll ( 'annotation[encoding="application/x-tex"]' )]. map (( n ) => n . textContent ),
glyphs : [... document . querySelectorAll ( ".katex-html" )]. map (( n ) => n . textContent ). join ( "" ),
errors : document.querySelectorAll ( ".katex-error" ). length ,
};
}
function assertMath ( html : string , expected = [ tex ], glyphs = [ "∇" , "∈" , "× " ]) {
const actual = math ( html );
assert . deepEqual ( actual . annotations , expected , "exact TeX, not merely absence of errors" );
assert . equal ( actual . errors , 0 );
for ( const glyph of glyphs ) assert . ok ( actual . glyphs . includes ( glyph ), `rendered glyph ${ glyph } ` );
}
describe ( "repairMathForDisplay — the existing composition, unchanged" , () => {
const unchanged = [
clean ,
String . raw `outside \\nabla` ,
String . raw `$a \\ \ nabla b$` ,
String . raw `$a \\\\ \ nabla b$` ,
String . raw `$a \\ 2 \\& 3 \\[6pt] 4$` ,
"$$\na \\\\\\\\ 2\nb\n$$" , // four real row-break slashes survive too.
"`" + doubled + "`" ,
"```tex\n" + doubled + "\n```" ,
"`" + debris + "`" ,
"```tex\n" + debris + "\n```" ,
String . raw `$\\nabla` ,
"$$A = \\\\begin{pmatrix}\n1 & 2\n\\\\end{pmatrix}$$" , // unsupported midline display close.
];
for ( const [ index , value ] of [... unchanged , doubled , quadrupled , debris , matrix ,
String . raw ` \ (\\nabla \ )` , "\\[\n\\\\nabla\n\\]" ]. entries ()) {
it ( `matches the old composition byte-for-byte ( ${ index } )` , () => {
assert . equal ( repairMathForDisplay ( value ), healOverescapedCommands ( restoreMathEscapes ( value )));
});
}
it ( "preserves clean math, odd runs, row breaks, code, prose and existing recognition limits" , () => {
for ( const value of unchanged ) assert . equal ( repairMathForDisplay ( value ), value );
assert . equal ( repairMathForDisplay ( doubled ), clean );
assert . equal ( repairMathForDisplay ( quadrupled ), clean );
assert . equal ( repairMathForDisplay ( debris ), `$ ${ repairedDebris } $` );
assert . equal ( repairMathForDisplay ( matrix ), `$$ \ n ${ repairedMatrix } \ n$$` );
});
});
2026-10-23 17:11:57 +03:00
describe ( "upgraded KaTeX rendering on real question and practice surfaces" , () => {
for ( const [ name , formula , glyphs ] of [
[ "fraction and root" , String . raw ` \ frac{1}{ \ sqrt{x}}` , [ "1" , "x" ]],
[ "accents" , String . raw ` \ hat{x}+ \ bar{y}` , [ "x" , "y" ]],
[ "sum" , String . raw ` \ sum_{i=1}^{n}i` , [ "∑" , "i" , "n" ]],
[ "matrix rows" , String . raw ` \ begin{pmatrix}1&2\\3&4 \ end{pmatrix}` , [ "1" , "2" , "3" , "4" ]],
[ "aligned rows" , String . raw ` \ begin{aligned}a&=b\\ c&=d \ end{aligned}` , [ "a" , "b" , "c" , "d" , "=" ]],
] as const ) {
it ( ` ${ name } : dollar and legacy delimiters preserve glyphs and copied TeX` , () => {
for ( const raw of [ `$ ${ formula } $` , `\\( ${ formula } \\)` , `$$ \ n ${ formula } \ n$$` , `\\[ \ n ${ formula } \ n\\]` ]) {
for ( const element of [
< MessageResponse mode = "static" >{ raw }</ MessageResponse >,
< QuestionDock toolName = "ask_user_question" toolCallId = "security_question" input = {{ question : raw }} expanded onExpand = {() => {}} onSubmit = {() => {}} />,
dock ({ statement : raw }),
]) {
const html = renderToStaticMarkup ( element );
assertMath ( html , [ formula ], [... glyphs ]);
const { document } = parseHTML ( html );
assert . ok ( document . querySelector ( ".katex-base .katex-strut" ), "matching 0.18 internal layout classes" );
if ( name === "fraction and root" ) assert . ok ( document . querySelector ( ".mfrac .sqrt" ));
if ( name === "accents" ) assert . ok ( document . querySelectorAll ( ".katex-accent" ). length >= 2 );
if ( name . endsWith ( "rows" )) assert . ok ( document . querySelectorAll ( ".mtable .mtr" ). length >= 2 || document . querySelectorAll ( ".mtable .vlist > span" ). length >= 2 , "two real rows, not repaired-away slashes" );
}
}
});
}
it ( "preserves escaped currency and code on both dock surfaces" , () => {
const raw = "Costs \\$5 and \\$10. `" + clean + "`\n\n```text\n" + doubled + "\n```" ;
for ( const element of [ dock ({ statement : raw }), < QuestionDock toolName = "ask_user_question" toolCallId = "security_code" input = {{ question : raw }} expanded onExpand = {() => {}} onSubmit = {() => {}} />]) {
const html = renderToStaticMarkup ( element );
assert . deepEqual ( math ( html ). annotations , []);
assert . ok ( parseHTML ( html ). document . firstElementChild ? . textContent ? . includes ( "$5 and $10" ));
assert . ok ( parseHTML ( html ). document . querySelector ( "code" ) ? . textContent ? . includes ( clean ));
}
});
it ( "renders direct LatexGlyph connectors through the upgraded root" , () => {
for ( const [ command , glyph ] of [[ String . raw ` \ Rightarrow` , "⇒" ], [ String . raw ` \ land` , "∧" ], [ String . raw ` \ neg` , "¬" ]]) {
const html = renderToStaticMarkup (< LatexGlyph cmd = { command ! } />);
const { document } = parseHTML ( html );
assert . equal ( document . querySelectorAll ( ".katex-error" ). length , 0 );
assert . ok ( document . querySelector ( ".katex-base .katex-strut" ));
assert . ok ( document . querySelector ( ".katex-html" ) ? . textContent ? . includes ( glyph ! ));
}
});
});
2026-10-21 00:00:20 +03:00
describe ( "practice display surfaces" , () => {
for ( const [ name , raw , expected , glyphs ] of [
[ "two command slashes" , doubled , tex , [ "∇" , "∈" , "× " ]],
[ "four command slashes" , quadrupled , tex , [ "∇" , "∈" , "× " ]],
[ "JSON debris" , debris , repairedDebris , [ "∀" , "∈" ]],
[ "clean math" , clean , tex , [ "∇" , "∈" , "× " ]],
[ "matrix with legitimate row break" , matrix , repairedMatrix , [ "1" , "2" , "3" , "4" ]],
] as const ) {
it ( ` ${ name } : expanded dock, settled statement, verdict, committed reveal and legacy fallback` , () => {
const input = Object . freeze ({ statement : raw , modelSolution : `HIDDEN COMMITMENT ${ raw } ` });
const conclusion = Object . freeze ({ outcome : "solved" , verdict : raw , solutionReveal : raw });
const before = JSON . stringify ({ input , conclusion });
for ( const element of [ dock ( input ), < ProblemCard part = { part ( input )} />]) {
const html = renderToStaticMarkup ( element );
assertMath ( html , [ expected ], [... glyphs ]);
assert . ok ( ! html . includes ( "HIDDEN COMMITMENT" ));
}
assertMath ( renderToStaticMarkup (< ConcludeCard part = { part ( conclusion , { type : "tool-conclude" , reveal : raw })} />),
[ expected , expected ], [... glyphs ]);
assertMath ( renderToStaticMarkup (< ConcludeCard part = { part ({ outcome : "solved" , solutionReveal : raw })} />),
[ expected ], [... glyphs ]);
assert . equal ( JSON . stringify ({ input , conclusion }), before );
});
}
it ( "keeps a boundary-converted numeric statement raw and preserves prior dock passthrough" , () => {
const raw = { statement : 17 , mode : "recall" , modelSolution : "Committed solution." };
const call = { type : "toolCall" , id : "numeric_problem" , name : "problem" , arguments : raw };
const validated = validateToolArguments (
{ name : "problem" , parameters : problemInputObject } as never ,
call as never ,
) as { statement : unknown };
assert . equal ( validated . statement , "17" , "the actual tool boundary converts its cloned arguments" );
assert . equal ( raw . statement , 17 , "saved arguments retain the number" );
const messages = [
{ id : "numeric_pose" , role : "assistant" , content : [ call ], timestamp : 1 },
{ id : "numeric_ack" , role : "toolResult" , toolCallId : call.id , toolName : "problem" , content : [{ type : "text" , text : "Problem posed" }], details : null , isError : false , timestamp : 2 },
] as unknown as LhMessage [];
const before = structuredClone ( messages );
const active = activeProblemOf ( messages );
assert . ok ( active );
assert . equal (( active . input as { statement : unknown }). statement , 17 );
// Mirror the existing runtime cast at the Chat -> PracticeDock seam.
const input = active . input as { statement : string };
let expanded = "" ;
assert . doesNotThrow (() => { expanded = renderToStaticMarkup ( dock ( input )); });
const { document } = parseHTML ( expanded );
assert . equal ( document . querySelector ( ".lh-prose" ) ! . innerHTML ,
parseHTML ( renderToStaticMarkup (< MessageResponse >{ input . statement }</ MessageResponse >)). document . firstElementChild ! . outerHTML ,
"non-string values pass through exactly as before, without string coercion or a new fallback" );
const collapsed = renderToStaticMarkup ( dock ( input , false ));
assert . match ( collapsed , /data-testid="practice-dock-bar"/ );
assert . equal ( parseHTML ( collapsed ). document . querySelector ( "span.truncate" ) ! . textContent , "17" );
assert . deepEqual ( math ( collapsed ). annotations , []);
assert . deepEqual ( messages , before );
assert . equal ( raw . statement , 17 );
});
it ( "preserves code spans/fences across every repaired display surface" , () => {
const raw = "`" + doubled + "`\n\n```text\n" + doubled + "\n```" ;
for ( const element of [ dock ({ statement : raw }), < ProblemCard part = { part ({ statement : raw })} />,
< ConcludeCard part = { part ({ outcome : "solved" , verdict : raw }, { reveal : raw })} />]) {
const html = renderToStaticMarkup ( element );
assert . deepEqual ( math ( html ). annotations , []);
const { document } = parseHTML ( html );
assert . ok ( document . querySelector ( "code" ) ? . textContent ? . includes ( doubled ));
}
});
it ( "does not change the collapsed preview, dismissal or global prose renderer" , () => {
const preview = renderToStaticMarkup ( dock ({ statement : doubled }, false ));
assert . ok ( preview . includes ( doubled ));
assert . deepEqual ( math ( preview ). annotations , []);
const dismissed = renderToStaticMarkup (< ConcludeCard part = { part ({ outcome : "dismissed" , verdict : doubled }, { reveal : doubled })} />);
assert . ok ( dismissed . includes ( "Problem dismissed." ));
assert . deepEqual ( math ( dismissed ). annotations , []);
const plain = math ( renderToStaticMarkup (< MessageResponse >{ doubled }</ MessageResponse >));
assert . deepEqual ( plain . annotations , [ doubled . slice ( 1 , - 1 )]);
assert . ok ( ! plain . glyphs . includes ( "∇" )); // Same unchanged assistant/user MessageResponse path.
});
2026-10-26 04:49:47 +02:00
it ( "teaching shape parsing retains source bytes; presentation alone recovers them" , () => {
2026-10-21 00:00:20 +03:00
const quiz = { question : doubled , options : [{ label : "yes" }, { label : "no" }], correctAnswer : "yes" , explanation : debris };
const ask = { question : doubled };
const explain = { question : doubled , correctAnswer : debris };
const before = structuredClone ({ quiz , ask , explain });
2026-10-26 04:49:47 +02:00
assert . equal ( parseQuizInput ( quiz ) ? . question , doubled );
assert . equal ( parseQuizInput ( quiz ) ? . explanation , debris );
assert . equal ( parseAskInput ( ask ) ? . question , doubled );
assert . equal ( parseExplainInput ( explain ) ? . question , doubled );
assert . equal ( parseExplainInput ( explain ) ? . correctAnswer , debris );
assertMath ( renderToStaticMarkup (< ToolContentResponse >{ parseAskInput ( ask ) ! . question }</ ToolContentResponse >));
2026-10-21 00:00:20 +03:00
assert . deepEqual ({ quiz , ask , explain }, before );
});
it ( "native history, active args and the view join remain raw; commitment appears only at conclusion" , () => {
const stamps = { api : "faux" , provider : "test" , model : "test-model" , timestamp : 1 , stopReason : "toolUse" };
const input = { statement : doubled , modelSolution : `COMMITTED ${ quadrupled } ` };
const messages = [
{ ... stamps , id : "a1" , role : "assistant" , content : [{ type : "toolCall" , id : "p1" , name : "problem" , arguments : input }] },
{ id : "r1" , role : "toolResult" , toolCallId : "p1" , toolName : "problem" , content : [{ type : "text" , text : "Problem posed" }], details : null , isError : false , timestamp : 2 },
] as unknown as LhMessage [];
const concluded = [... messages ,
{ ... stamps , id : "a2" , role : "assistant" , content : [{ type : "toolCall" , id : "c1" , name : "conclude" , arguments : { outcome : "solved" , verdict : doubled } }] },
{ id : "r2" , role : "toolResult" , toolCallId : "c1" , toolName : "conclude" , content : [{ type : "text" , text : "Problem concluded" }], details : null , isError : false , timestamp : 3 },
] as unknown as LhMessage [];
const before = structuredClone ( concluded );
const history = structuredClone ( toLlmMessages ( concluded ));
assert . deepEqual ( activeProblemOf ( messages ) ? . input , input );
const problem = projectView ( messages )[ 0 ] ! . parts . find (( p ) => p . type === "tool-problem" ) ! ;
assert . ok ( problem . state && problem . toolCallId );
assert . deepEqual ( problem . input , input );
const statementHtml = renderToStaticMarkup (< ProblemCard part = {{ ... problem , state : problem.state , toolCallId : problem.toolCallId }} />);
assertMath ( statementHtml );
assert . ok ( ! statementHtml . includes ( "COMMITTED" ));
const conclusion = projectView ( concluded ). flatMap (( m ) => m . parts ). find (( p ) => p . type === "tool-conclude" ) ! ;
assert . ok ( conclusion . state && conclusion . toolCallId );
assert . equal ( conclusion . reveal , input . modelSolution );
const revealHtml = renderToStaticMarkup (< ConcludeCard part = {{ ... conclusion , state : conclusion.state , toolCallId : conclusion.toolCallId }} />);
assert . ok ( revealHtml . includes ( "COMMITTED" ));
assertMath ( revealHtml , [ tex , tex ]);
assert . deepEqual ( concluded , before );
assert . deepEqual ( toLlmMessages ( concluded ), history );
});
});