Major refactoring of worksheet parsing to use centralized state management: New architecture: - WorksheetParsingContext: React context provider for parsing state - state-machine.ts: Typed reducer with actions for streaming lifecycle - sse-parser.ts: Shared SSE parsing utility for OpenAI Responses API - usePartialJsonParser.ts: Progressive JSON extraction during streaming Streaming UI improvements: - ParsingProgressOverlay: Dark overlay on photo tile during parsing - ParsingProgressPanel: Collapsible reasoning text panel - ProgressiveHighlightOverlay: Problem boxes light up as LLM parses - New streaming API routes: /parse/stream and /parse-selected/stream Bug fixes during testing: - Fix TypeScript error: cast event.response for id access in sse-parser - Fix reparse reasoning display: preserve "processing" status for reparse - Fix concurrent parsing: revert previous attachment status when switching - Fix problem count: track dispatched problems to prevent duplicates Components updated to use context: - SummaryClient: Wrapped with WorksheetParsingProvider - OfflineWorkSection: Uses context instead of local streaming state - PhotoViewerEditor: Uses context for coordinated parsing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import {
|
|
DIFFICULTY_PROFILES,
|
|
makeHarder,
|
|
makeEasier,
|
|
findRegroupingIndex,
|
|
findScaffoldingIndex,
|
|
REGROUPING_PROGRESSION,
|
|
SCAFFOLDING_PROGRESSION,
|
|
} from "../src/app/create/worksheets/addition/difficultyProfiles";
|
|
|
|
// Start from beginner
|
|
let state = {
|
|
pAnyStart: DIFFICULTY_PROFILES.beginner.regrouping.pAnyStart,
|
|
pAllStart: DIFFICULTY_PROFILES.beginner.regrouping.pAllStart,
|
|
displayRules: DIFFICULTY_PROFILES.beginner.displayRules,
|
|
};
|
|
|
|
console.log("=== MAKE HARDER PATH ===\n");
|
|
console.log("Format: (regroupingIdx, scaffoldingIdx) - description\n");
|
|
|
|
const harderPath: Array<{ r: number; s: number; desc: string }> = [];
|
|
|
|
// Record starting point
|
|
let rIdx = findRegroupingIndex(state.pAnyStart, state.pAllStart);
|
|
let sIdx = findScaffoldingIndex(state.displayRules);
|
|
harderPath.push({ r: rIdx, s: sIdx, desc: "START (beginner)" });
|
|
console.log(`(${rIdx}, ${sIdx}) - START (beginner)`);
|
|
|
|
// Click "Make Harder" 30 times or until max
|
|
for (let i = 0; i < 30; i++) {
|
|
const result = makeHarder(state);
|
|
|
|
const newR = findRegroupingIndex(result.pAnyStart, result.pAllStart);
|
|
const newS = findScaffoldingIndex(result.displayRules);
|
|
|
|
if (newR === rIdx && newS === sIdx) {
|
|
console.log(`\n(${newR}, ${newS}) - ${result.changeDescription} (STOPPED)`);
|
|
break;
|
|
}
|
|
|
|
rIdx = newR;
|
|
sIdx = newS;
|
|
state = result;
|
|
|
|
harderPath.push({ r: rIdx, s: sIdx, desc: result.changeDescription });
|
|
console.log(`(${rIdx}, ${sIdx}) - ${result.changeDescription}`);
|
|
}
|
|
|
|
console.log("\n\n=== PATH VISUALIZATION ===\n");
|
|
console.log("Regrouping Index →");
|
|
console.log("Scaffolding ↓\n");
|
|
|
|
// Create 2D grid visualization
|
|
const grid: string[][] = [];
|
|
for (let s = 0; s <= 12; s++) {
|
|
grid[s] = [];
|
|
for (let r = 0; r <= 18; r++) {
|
|
grid[s][r] = " ·";
|
|
}
|
|
}
|
|
|
|
// Mark path
|
|
harderPath.forEach((point, idx) => {
|
|
if (idx === 0) {
|
|
grid[point.s][point.r] = " S"; // Start
|
|
} else if (idx === harderPath.length - 1) {
|
|
grid[point.s][point.r] = " E"; // End
|
|
} else {
|
|
grid[point.s][point.r] = `${idx.toString().padStart(3)}`;
|
|
}
|
|
});
|
|
|
|
// Mark presets
|
|
const presets = [
|
|
{ label: "BEG", profile: DIFFICULTY_PROFILES.beginner },
|
|
{ label: "EAR", profile: DIFFICULTY_PROFILES.earlyLearner },
|
|
{ label: "INT", profile: DIFFICULTY_PROFILES.intermediate },
|
|
{ label: "ADV", profile: DIFFICULTY_PROFILES.advanced },
|
|
{ label: "EXP", profile: DIFFICULTY_PROFILES.expert },
|
|
];
|
|
|
|
presets.forEach((preset) => {
|
|
const r = findRegroupingIndex(
|
|
preset.profile.regrouping.pAnyStart,
|
|
preset.profile.regrouping.pAllStart,
|
|
);
|
|
const s = findScaffoldingIndex(preset.profile.displayRules);
|
|
|
|
// Only mark if not already part of path
|
|
const onPath = harderPath.some((p) => p.r === r && p.s === s);
|
|
if (!onPath) {
|
|
grid[s][r] = preset.label;
|
|
}
|
|
});
|
|
|
|
// Print grid (inverted so scaffolding increases upward)
|
|
console.log(
|
|
" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18",
|
|
);
|
|
for (let s = 12; s >= 0; s--) {
|
|
console.log(`${s.toString().padStart(2)} ${grid[s].join("")}`);
|
|
}
|
|
|
|
console.log("\nLegend:");
|
|
console.log(" S = Start (beginner)");
|
|
console.log(" E = End (maximum)");
|
|
console.log(" 1-29 = Step number");
|
|
console.log(" BEG/EAR/INT/ADV/EXP = Preset profiles");
|
|
console.log(" · = Not visited");
|