From e947f1e600ee3f7babe6d177962030889e162600 Mon Sep 17 00:00:00 2001 From: David Montero Date: Sun, 7 Jun 2026 00:10:15 +0200 Subject: [PATCH] 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. --- .../src/components/editor/EditorToolbar.tsx | 6 ++++- frontend/src/services/compilation.ts | 7 ++++++ frontend/src/store/useLibraryManifestStore.ts | 24 +++++++++++++++++++ frontend/src/utils/loadExample.ts | 6 +++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 frontend/src/store/useLibraryManifestStore.ts diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index c332272a..4bc7b103 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -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); diff --git a/frontend/src/services/compilation.ts b/frontend/src/services/compilation.ts index 62b35ed3..fcc445bd 100644 --- a/frontend/src/services/compilation.ts +++ b/frontend/src/services/compilation.ts @@ -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 }, ); diff --git a/frontend/src/store/useLibraryManifestStore.ts b/frontend/src/store/useLibraryManifestStore.ts new file mode 100644 index 00000000..6300b94f --- /dev/null +++ b/frontend/src/store/useLibraryManifestStore.ts @@ -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((set) => ({ + libraries: null, + setLibraries: (libs) => set({ libraries: libs && libs.length ? libs : null }), +})); diff --git a/frontend/src/utils/loadExample.ts b/frontend/src/utils/loadExample.ts index 1fa1edd2..05599f79 100644 --- a/frontend/src/utils/loadExample.ts +++ b/frontend/src/utils/loadExample.ts @@ -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);