feat(frontend): send the example library manifest as the compile scope (P2.3)

Activates manifest-scoped ESP-IDF resolution for the gallery. loadExample now
records the example's declared libraries in useLibraryManifestStore; EditorToolbar
passes them to compileCode, which sends them as `libraries` in the compile
request. The backend then merges exactly those libraries (P2.0 scope) instead of
picking a stray same-named lib from the shared dir.

Safe: a core-only example sends null (legacy scan-all); a stale/incomplete
manifest degrades to scan-all via the backend graceful fallback, never a wrong
build. Ignored by the backend for non-ESP32 (arduino-cli) boards. Example
manifests were completed (incl. transitive deps) in c671c9b.
This commit is contained in:
David Montero 2026-06-07 00:10:15 +02:00
parent 472973f05b
commit e947f1e600
4 changed files with 42 additions and 1 deletions

View File

@ -22,6 +22,7 @@ import { clearChipDrives } from '../../simulation/customChips/chipPinDrives';
import { requestElectricalResolve } from '../../simulation/spice/electricalResolveHook';
import { reportRunEvent } from '../../services/metricsService';
import { useProjectStore } from '../../store/useProjectStore';
import { useLibraryManifestStore } from '../../store/useLibraryManifestStore';
import { LibraryManagerModal } from '../simulator/LibraryManagerModal';
import { InstallLibrariesModal } from '../simulator/InstallLibrariesModal';
import { parseCompileResult } from '../../utils/compilationLogger';
@ -170,6 +171,8 @@ export const EditorToolbar = ({
const activeBoard = boards.find((b) => b.id === activeBoardId) ?? boards[0];
const currentProject = useProjectStore((s) => s.currentProject);
// P2.3 — declared library manifest of the loaded example/project (compile scope).
const activeLibraries = useLibraryManifestStore((s) => s.libraries);
// Board-less mode: digital / analog SPICE-only circuits. The Run / Stop
// buttons toggle the SPICE solver's `paused` flag — pausing freezes every
@ -508,6 +511,7 @@ export const EditorToolbar = ({
{
boardOptions: activeBoard?.boardOptions,
spiffsFiles: activeBoard?.spiffsFiles,
libraries: activeLibraries,
},
);
@ -980,7 +984,7 @@ export const EditorToolbar = ({
})),
]);
},
{ boardOptions: board.boardOptions, spiffsFiles: board.spiffsFiles },
{ boardOptions: board.boardOptions, spiffsFiles: board.spiffsFiles, libraries: activeLibraries },
);
const resultLogs = parseCompileResult(result, label, boardTarget);

View File

@ -16,6 +16,10 @@ export interface SketchFile {
export interface CompileExtras {
boardOptions?: ESP32BoardOptions;
spiffsFiles?: SpiffsFile[];
// P2.3 — declared library manifest (the loaded example/project's libraries).
// Sent as the ESP-IDF resolution SCOPE; null/omitted = legacy scan-all.
// Ignored by the backend for non-ESP32 (arduino-cli) boards.
libraries?: string[] | null;
}
export interface CompileResult {
@ -95,6 +99,8 @@ export async function compileCode(
const spiffs_files = extras?.spiffsFiles?.length
? extras.spiffsFiles.map((f) => ({ name: f.name, content_b64: f.contentB64 }))
: null;
// P2.3 — library manifest (resolution scope). null = legacy scan-all.
const libraries = extras?.libraries && extras.libraries.length ? extras.libraries : null;
let jobId: string;
try {
@ -106,6 +112,7 @@ export async function compileCode(
project_id: projectId ?? null,
board_options,
spiffs_files,
libraries,
},
{ withCredentials: true, timeout: 30000 },
);

View File

@ -0,0 +1,24 @@
import { create } from 'zustand';
/**
* Library manifest for the currently-loaded example/project (P2.3).
*
* The declared library set is sent to the backend compiler as the resolution
* SCOPE: ESP-IDF then merges only these libraries (plus the core), so a sketch
* picks the declared lib and never an unrelated one from the shared dir.
*
* `null` = no manifest -> legacy scan-all (unchanged behaviour). Set by
* `loadExample` to the example's declared `libraries` (or null for core-only
* examples). Switching between examples updates it; loading a non-example
* workspace leaves the previous value, but that only degrades to scan-all via
* the backend's graceful fallback never an incorrect build.
*/
interface LibraryManifestState {
libraries: string[] | null;
setLibraries: (libs: string[] | null | undefined) => void;
}
export const useLibraryManifestStore = create<LibraryManifestState>((set) => ({
libraries: null,
setLibraries: (libs) => set({ libraries: libs && libs.length ? libs : null }),
}));

View File

@ -11,6 +11,7 @@ import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../store/useSimulator
import { useElectricalStore } from '../store/useElectricalStore';
import { useProjectStore } from '../store/useProjectStore';
import { useVfsStore } from '../store/useVfsStore';
import { useLibraryManifestStore } from '../store/useLibraryManifestStore';
import { isBoardComponent } from './boardPinMapping';
import { getInstalledLibraries, installLibrary } from '../services/libraryService';
import { trackOpenExample } from './analytics';
@ -115,6 +116,11 @@ export async function loadExample(
// so no PUT goes out.
useProjectStore.getState().clearCurrentProject();
// P2.3 — record this example's declared library manifest as the compile
// scope (null for core-only examples). EditorToolbar sends it with every
// compile so ESP-IDF resolution merges exactly these libraries.
useLibraryManifestStore.getState().setLibraries(example.libraries ?? null);
// Loading a new example always starts unpaused — otherwise the canvas
// would open with every LED frozen at the previous example's state.
useElectricalStore.getState().setPaused(false);