Didactic Substeps Development
Documents how to add step-by-step intermediate expressions to ExpliCAS engine rules.
Install
mkdir -p .claude/skills/didactic-substeps-development && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13871" && unzip -o skill.zip -d .claude/skills/didactic-substeps-development && rm skill.zipInstalls to .claude/skills/didactic-substeps-development
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
How to develop step-by-step didactic substeps for ExpliCAS engine rules, showing concrete expressions with minimal performance impactKey capabilities
- →Add pedagogical substeps to ExpliCAS engine rules
- →Attach lightweight metadata to Rewrites
- →Compute rich post-processing substeps
- →Gate expensive didactic computations behind proof data
- →Use `new_compact` for non-display steps
- →Register new substep generation functions
How it works
The skill details how to add two types of substeps: engine-level (lightweight metadata) and didactic-layer (rich post-processing with expressions), ensuring expensive computations only run when step-by-step display is active.
Inputs & outputs
When to use Didactic Substeps Development
- →Add pedagogy to engine rules
- →Visualize intermediate expressions
- →Enrich math step-by-step output
About this skill
Didactic Substeps Development
This skill documents how to add pedagogical substeps to ExpliCAS engine rules so users can see concrete intermediate expressions in the step-by-step view.
Architecture Overview
There are two independent SubStep systems that both feed into the JSON output:
graph TD
Rule["Rule (apply)"] --> Rewrite["Rewrite"]
Rewrite -->|"poly_proof / substeps"| Step["Step (step.rs)"]
Step -->|"Post-processing"| DidacticEnrich["didactic::enrich_steps()"]
DidacticEnrich --> EnrichedStep["EnrichedStep { base_step, sub_steps }"]
EnrichedStep -->|"Serialization"| JSON["eval_json.rs → SubStepJson"]
1. Engine-Level SubSteps (step.rs::SubStep)
Lightweight metadata attached to a Rewrite during rule application via Rewrite::substep(). These are title + bullet lines format (no LaTeX). They are always computed but are cheap.
// In a rule's apply() method:
Rewrite::new(before, after, "Rule description")
.substep(step::SubStep::new(
"Pattern Recognition",
vec!["Detected a² - b²".into(), "a = x, b = 3".into()],
))
2. Didactic-Layer SubSteps (didactic::SubStep)
Rich post-processing substeps computed by didactic::enrich_steps(). These have before/after expressions with optional LaTeX. They are the ones shown in the web REPL with MathJax rendering.
// In a generate_*_substeps() function:
SubStep {
description: "Expand products".to_string(),
before_expr: "(t+1)(t+2)".to_string(),
after_expr: "t² + 3t + 2".to_string(),
before_latex: Some("(t+1)(t+2)".to_string()),
after_latex: Some("{t}^{2} + 3t + 2".to_string()),
}
Performance Contract
[!IMPORTANT] Didactic computations MUST NOT run during normal simplification. They should only execute when step-by-step display is active.
Pattern: Gate Expensive Computation Behind Proof Data
Expensive didactic work (e.g., computing expanded polynomial forms) should be done in the rule itself only when proof data is already being computed. The rule already produces proof data as part of its correctness verification, so the marginal cost is low.
// In a rule like try_opaque_zero():
// The polynomial expansion is already computed for verification.
// We just need to also store it for display:
let expanded_form_expr =
crate::multipoly_display::expand_additive_terms(ctx, display_expr, &display_vars);
let proof = PolynomialProofData {
// ... standard fields ...
expanded_form_expr, // ← Piggyback on existing computation
};
Pattern: Post-Processing Only When Steps Are Requested
The enrich_steps() function is only called by the JSON/CLI presentation layer when steps_mode == "on". The StepMeta field in Step is Option<Box<StepMeta>> — when steps are not needed, it stays None and no allocation occurs.
Pattern: Use new_compact for Non-Display Steps
When step recording is in compact mode, use Step::new_compact() which skips the format!() call and sets meta: None.
How to Add Substeps for a New Rule
Step 1: Identify the Proof/Metadata Data
Determine what intermediate data the rule computes that would be pedagogically valuable. Common examples:
| Rule Type | Proof Data | Display Value |
|---|---|---|
| Polynomial identity | PolynomialProofData | Expanded terms before cancellation |
| Fraction sum | FractionSumInfo | LCD, numerator construction |
| Rationalization | Conjugate pair | Before/after multiplication |
| Nested fraction | Multiply-through structure | Step-by-step clearing |
Step 2: Attach Proof Data to the Rewrite
Store the intermediate data in the Rewrite so it flows to the Step:
// Option A: Use existing poly_proof mechanism
let rewrite = Rewrite::new(before, after, "description")
.poly_proof(proof_data);
// Option B: Use engine-level substeps (for simple title+lines)
let rewrite = Rewrite::new(before, after, "description")
.substep(step::SubStep::new("Title", vec!["line1".into()]));
Step 3: Create a generate_*_substeps() Function
Add a new function in either didactic/nested_fractions.rs or didactic/fraction_steps.rs:
pub fn generate_my_rule_substeps(ctx: &Context, step: &Step) -> Vec<SubStep> {
let mut sub_steps = Vec::new();
// Extract proof data from the step
let proof = match step.poly_proof() {
Some(p) => p,
None => return sub_steps,
};
// Build substeps with CONCRETE expressions
if let Some(expanded_id) = proof.expanded_form_expr {
let expanded_latex = cas_ast::LaTeXExpr {
context: ctx,
id: expanded_id,
}.to_latex();
sub_steps.push(SubStep {
description: "Expand products".to_string(),
before_expr: format!("{}", DisplayExpr { context: ctx, id: original_id }),
after_expr: format!("{}", DisplayExpr { context: ctx, id: expanded_id }),
before_latex: Some(original_latex),
after_latex: Some(expanded_latex),
});
}
sub_steps
}
Step 4: Register in the Dispatcher
In didactic/mod.rs::enrich_steps(), add a condition to call your new function:
// In the step loop inside enrich_steps():
if step.rule_name.contains("MyNewRule") {
sub_steps.extend(generate_my_rule_substeps(ctx, step));
}
Step 5: Gate by is_chained()
If the rule uses ChainedRewrite (multi-step decomposition already visible as separate engine Steps), skip substep generation to avoid duplication:
if step.rule_name.contains("GCD") && !step.is_chained() {
sub_steps.extend(generate_gcd_substeps(ctx, step));
}
LaTeX Rendering Contract
[!WARNING] Plain text in substeps is automatically wrapped in
\text{}by the JSON layer ifbefore_latex/after_latexareNone. If you want MathJax to render math formulas, you MUST set the LaTeX fields explicitly.
Converting ExprId to LaTeX
let latex = cas_ast::LaTeXExpr {
context: ctx,
id: expr_id,
}.to_latex();
Converting ExprId to Plain Text
let plain = format!("{}", cas_ast::DisplayExpr {
context: ctx,
id: expr_id,
});
Reference: Existing Substep Generators
| Generator | File | Triggered By |
|---|---|---|
generate_fraction_sum_substeps | fraction_steps.rs | Fraction sums in original expr |
generate_gcd_factorization_substeps | fraction_steps.rs | "Simplified fraction by GCD" |
generate_nested_fraction_substeps | nested_fractions.rs | "complex/nested fraction" rule names |
generate_rationalization_substeps | nested_fractions.rs | "Rationalize" in description/rule |
generate_polynomial_identity_substeps | nested_fractions.rs | step.poly_proof().is_some() |
generate_sum_three_cubes_substeps | nested_fractions.rs | "Sum of Three Cubes" rule name |
generate_root_denesting_substeps | nested_fractions.rs | "Root Denesting" rule name |
Concrete Example: Opaque Polynomial Identity
For (sin(u)+1)·(sin(u)+2) - sin(u)² - 3·sin(u) - 2:
Step 1 (in rule try_opaque_zero): Compute expanded_form_expr using expand_additive_terms() which splits into signed terms, expands each through multipoly independently, and recombines — gives t² + 3t + 2 - t² - 3t - 2.
Step 2 (in generate_polynomial_identity_substeps): Build three substeps:
- "Sustitución para simplificar": Show original →
Sea t = sin(u) - "Expresión sustituida": Show
(t+1)(t+2) - t² - 3t - 2→t² + 3t + 2 - t² - 3t - 2 - "Todos los términos se cancelan": Show "Expandir y agrupar" →
= 0
Each substep has both before_latex and after_latex so MathJax renders the math correctly.
Key Files
cas_engine/src/didactic/mod.rs— Entry point, dispatcher,EnrichedStep/SubSteptypescas_engine/src/didactic/nested_fractions.rs— Polynomial identity, rationalization, nested fraction generatorscas_engine/src/didactic/fraction_steps.rs— Fraction sum, GCD factorization generatorscas_engine/src/step.rs—Step,StepMeta, engine-levelSubStepcas_engine/src/rule.rs—Rewrite::poly_proof(),Rewrite::substep()cas_engine/src/multipoly_display.rs—PolynomialProofData,expand_additive_terms()cas_cli/src/commands/eval_json.rs— JSON serialization,\text{}wrapping
When not to use it
- →When didactic computations should run during normal simplification
Limitations
- →Didactic computations MUST NOT run during normal simplification.
- →The `enrich_steps()` function is only called by the JSON/CLI presentation layer when `steps_mode == "on"`.
- →When step recording is in compact mode, use `Step::new_compact()` which skips the `format!()` call and sets `meta: None`.
How it compares
This skill provides a structured approach to adding pedagogical substeps with concrete expressions, ensuring performance by gating computations, unlike an unstructured method that might impact simplification speed.
Compared to similar skills
Didactic Substeps Development side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| Didactic Substeps Development (this skill) | 0 | 6mo | No flags | Advanced |
| streamlit | 86 | 9mo | No flags | Intermediate |
| manim | 29 | 6mo | Review | Intermediate |
| d3-visualization | 7 | 4mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
streamlit
sverzijl
When working with Streamlit web apps, data dashboards, ML/AI app UIs, interactive Python visualizations, or building data science applications with Python
manim
davila7
Comprehensive guide for Manim Community - Python framework for creating mathematical animations and educational videos with programmatic control
d3-visualization
lyndonkl
Use when creating custom, interactive data visualizations with D3.js—building bar/line/scatter charts from scratch, creating network diagrams or geographic maps, binding changing data to visual elements, adding zoom/pan/brush interactions, animating chart transitions, or when chart libraries (Highcharts, Chart.js) don't support your specific visualization design and you need low-level control over data-driven DOM manipulation, scales, shapes, and layouts.
threejs
mrgoonie
Build 3D web apps with Three.js (WebGL/WebGPU). Use for 3D scenes, animations, custom shaders, PBR materials, VR/XR experiences, games, data visualizations, product configurators.
lecture-transcript-slide-matcher
az9713
Combines YouTube lecture transcripts with PDF slides to create an interactive HTML page. Matches each slide to corresponding transcript segments, organized by key concepts. Use when users want to create synchronized lecture notes from transcript text files and slide PDFs.
playground
anthropics
Creates interactive HTML playgrounds — self-contained single-file explorers that let users configure something visually through controls, see a live preview, and copy out a prompt. Use when the user asks to make a playground, explorer, or interactive tool for a topic.