diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index f7a0c888..b69aecd0 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -529,6 +529,26 @@ export const EditorToolbar = ({ blog('info', `Starting compilation for ${boardLabel} (${fqbn})...`); try { + // Reconcile the two "active group" pointers before reading sources. + // If the editor drifted to another BOARD's group (dangling pointer + // after a board delete/add, a restore race), Monaco edits — and the + // agent's write_file calls — land in a group this compile silently + // ignores, shipping a stale same-named sketch.ino instead of the code + // on screen. Re-point the editor at the group being compiled. Chip + // program groups are legitimate cross-edits and stay untouched. + const edGroup = useEditorStore.getState().activeGroupId; + if ( + activeBoard?.activeFileGroupId && + edGroup !== activeBoard.activeFileGroupId && + !edGroup.startsWith('group-chip-') + ) { + blog( + 'warning', + `Editor file group (${edGroup}) diverged from the compiled board group ` + + `(${activeBoard.activeFileGroupId}) — switching the editor to the compiled group.`, + ); + useEditorStore.getState().setActiveGroup(activeBoard.activeFileGroupId); + } const groupFiles = activeBoard?.activeFileGroupId ? useEditorStore.getState().getGroupFiles(activeBoard.activeFileGroupId) : files; diff --git a/frontend/src/store/useEditorStore.ts b/frontend/src/store/useEditorStore.ts index 9013ca1b..6b69fca5 100644 --- a/frontend/src/store/useEditorStore.ts +++ b/frontend/src/store/useEditorStore.ts @@ -517,11 +517,19 @@ export const useEditorStore = create((set, get) => ({ const { [groupId]: _a, ...restActive } = s.activeGroupFileId; const { [groupId]: _o, ...restOpen } = s.openGroupFileIds; const { [groupId]: _f, ...restFolders } = s.folderGroups; + // Never leave activeGroupId dangling at the deleted group: Monaco (and + // every agent write_file) keeps targeting it, while compile reads the + // board's group — two same-named sketch.ino files silently diverge and + // the build ships the WRONG one (the "leftover template Blink compiled + // instead of my code" class). Re-point at any surviving group. + const nextActive = + s.activeGroupId === groupId ? Object.keys(rest)[0] ?? '' : s.activeGroupId; return { fileGroups: rest, activeGroupFileId: restActive, openGroupFileIds: restOpen, folderGroups: restFolders, + activeGroupId: nextActive, }; }); }, diff --git a/frontend/src/utils/projectPayload.ts b/frontend/src/utils/projectPayload.ts index 5715640a..2e158d41 100644 --- a/frontend/src/utils/projectPayload.ts +++ b/frontend/src/utils/projectPayload.ts @@ -38,7 +38,12 @@ function serialisableBoard(b: BoardInstance) { activeFileGroupId: b.activeFileGroupId, languageMode: b.languageMode, serialBaudRate: b.serialBaudRate, - compiledProgram: b.compiledProgram, + // compiledProgram is deliberately NOT persisted. The firmware blob was + // 99.99% of a typical ESP32 save payload (~1.04 MB base64 for a bare + // board), pushing every real project against the request-size ceiling, + // and a restored binary is stale the moment the code differs from what + // was compiled. Sources are saved; the binary is recompiled on demand. + // (Loaders still accept compiledProgram from old rows.) // ESP32 board options + uploaded SPIFFS files. Optional, only present // after the user has opened Board Options... at least once. Both ride // inside boards_json so there's no DB migration.