feat: auto-compile when pressing Play if code changed or no hex loaded

Closes #81

- Add `codeChangedSinceLastCompile` dirty flag to useEditorStore
- Play button now triggers compilation automatically when no compiled
  program exists or code has changed since last compile
- Show compilation progress during auto-compile before run
- If compilation fails, show errors instead of running stale code
- Keep separate Compile button for manual compile-only workflow
- Play button always enabled (disabled only while running/compiling)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pull/82/head
David Montero Crespo 2026-03-29 01:46:07 -03:00
parent e99ded70b5
commit 67f9bc70c6
2 changed files with 56 additions and 9 deletions

View File

@ -44,7 +44,7 @@ const BOARD_PILL_COLOR: Record<BoardKind, string> = {
};
export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compileLogs, setCompileLogs }: EditorToolbarProps) => {
const { files } = useEditorStore();
const { files, codeChangedSinceLastCompile, markCompiled } = useEditorStore();
const {
boards,
activeBoardId,
@ -137,6 +137,7 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
compileBoardProgram(activeBoardId, program);
}
setMessage({ type: 'success', text: 'Compiled successfully' });
markCompiled();
setMissingLibHint(false);
} else {
const errText = result.error || result.stderr || 'Compile failed';
@ -155,24 +156,62 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
}
};
const handleRun = () => {
// Track whether we should auto-run after compilation completes
const autoRunAfterCompile = useRef(false);
const handleRun = async () => {
if (activeBoardId) {
const board = boards.find((b) => b.id === activeBoardId);
const isQemuBoard = board?.boardKind === 'raspberry-pi-3' || board?.boardKind === 'esp32' || board?.boardKind === 'esp32-s3';
if (isQemuBoard || board?.compiledProgram) {
// QEMU boards don't need compilation
if (isQemuBoard) {
trackRunSimulation(board?.boardKind);
startBoard(activeBoardId);
setMessage(null);
return;
}
// Auto-compile if no program or code changed since last compile
if (!board?.compiledProgram || codeChangedSinceLastCompile) {
autoRunAfterCompile.current = true;
await handleCompile();
// After compile, check if it succeeded and run
const updatedBoard = useSimulatorStore.getState().boards.find((b) => b.id === activeBoardId);
if (autoRunAfterCompile.current && updatedBoard?.compiledProgram) {
autoRunAfterCompile.current = false;
trackRunSimulation(updatedBoard.boardKind);
startBoard(activeBoardId);
setMessage(null);
} else {
autoRunAfterCompile.current = false;
}
return;
}
trackRunSimulation(board?.boardKind);
startBoard(activeBoardId);
setMessage(null);
return;
}
// legacy fallback
if (compiledHex) {
// Legacy fallback
if (!compiledHex || codeChangedSinceLastCompile) {
autoRunAfterCompile.current = true;
await handleCompile();
const hex = useSimulatorStore.getState().compiledHex;
if (autoRunAfterCompile.current && hex) {
autoRunAfterCompile.current = false;
trackRunSimulation();
startSimulation();
setMessage(null);
} else {
autoRunAfterCompile.current = false;
}
} else {
trackRunSimulation();
startSimulation();
setMessage(null);
} else {
setMessage({ type: 'error', text: 'Compile first' });
}
};
@ -337,9 +376,9 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
{/* Run */}
<button
onClick={handleRun}
disabled={running || (!['raspberry-pi-3','esp32','esp32-s3'].includes(activeBoard?.boardKind ?? '') && !compiledHex && !activeBoard?.compiledProgram)}
disabled={running || compiling}
className="tb-btn tb-btn-run"
title="Run"
title="Run (auto-compiles if needed)"
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<polygon points="5,3 19,12 5,21" />

View File

@ -89,6 +89,10 @@ interface EditorState {
setTheme: (theme: 'vs-dark' | 'light') => void;
setFontSize: (size: number) => void;
// Dirty flag — tracks whether code changed since last compilation
codeChangedSinceLastCompile: boolean;
markCompiled: () => void;
// Legacy compat — sets content of the active file
setCode: (code: string) => void;
}
@ -108,6 +112,9 @@ export const useEditorStore = create<EditorState>((set, get) => ({
activeGroupFileId: { [DEFAULT_GROUP_ID]: MAIN_ID },
openGroupFileIds: { [DEFAULT_GROUP_ID]: [MAIN_ID] },
codeChangedSinceLastCompile: true,
markCompiled: () => set({ codeChangedSinceLastCompile: false }),
// ── File operations (legacy API — operate on active group) ──────────────
createFile: (name: string) => {
@ -178,6 +185,7 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return {
files: s.files.map(mapper),
fileGroups: { ...s.fileGroups, [groupId]: (s.fileGroups[groupId] ?? []).map(mapper) },
codeChangedSinceLastCompile: true,
};
});
},