From b33f83b149f507894552579971707cd284f64948 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Mon, 24 Aug 2026 15:22:04 +0200 Subject: [PATCH] fix(examples): one malformed example must not take the gallery down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typing anything into the /examples search box threw "Cannot read properties of undefined (reading 'map')" and blanked the page. The haystack the search matches against is built from `example.components.map(...)`, and `components` was undefined on 27 examples. `components` is required by `ExampleProject`, so this reads as impossible — but the gallery is assembled from overlay example modules whose factories end in `as unknown as ExampleProject`, and the compiler stops checking there. The field is a promise the type makes and cannot keep, so the three places that read it now cope with its absence. CircuitPreview is the one that matters most: it renders as the fallback whenever an example's thumbnail 404s, so it meets exactly the examples nobody has generated art for yet. --- .../src/components/examples/CircuitPreview.tsx | 17 +++++++++++------ .../src/components/examples/ExamplesGallery.tsx | 7 ++++++- frontend/src/pages/ExampleDetailPage.tsx | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/examples/CircuitPreview.tsx b/frontend/src/components/examples/CircuitPreview.tsx index 93e43521..5b52b089 100644 --- a/frontend/src/components/examples/CircuitPreview.tsx +++ b/frontend/src/components/examples/CircuitPreview.tsx @@ -274,17 +274,22 @@ export const CircuitPreview: React.FC = ({ const boardKind = example.boardType ?? (isBoardless ? null : 'arduino-uno'); const boardDef = boardKind ? BOARD_DEFS[boardKind] : undefined; + // Same defensive read as the gallery's search haystack: overlay example + // factories cast their result, so a missing `components` reaches here as + // undefined and this preview is exactly where it lands — it renders as the + // fallback whenever an example's thumbnail 404s. + const components = example.components ?? []; + // Check if the board is explicitly listed in components[] - const boardInComponents = example.components.some((c) => isBoardType(c.type)); + const boardInComponents = components.some((c) => isBoardType(c.type)); if (boardDef && !boardInComponents && !isBoardless) { // Board is implicit (pico, esp32, nano examples) — inject at default position // Position it to the left of the other components - const minCompX = - example.components.length > 0 ? Math.min(...example.components.map((c) => c.x)) : 400; + const minCompX = components.length > 0 ? Math.min(...components.map((c) => c.x)) : 400; const avgCompY = - example.components.length > 0 - ? example.components.reduce((s, c) => s + c.y, 0) / example.components.length + components.length > 0 + ? components.reduce((s, c) => s + c.y, 0) / components.length : 150; const boardX = Math.max(40, minCompX - boardDef.w - 60); const boardY = Math.max(40, avgCompY - boardDef.h / 2); @@ -292,7 +297,7 @@ export const CircuitPreview: React.FC = ({ } // Add all components from the example - example.components.forEach((c) => { + components.forEach((c) => { // For board components that ARE in components[], use the board SVG def const def = isBoardType(c.type) && boardDef ? boardDef : getCompDef(c.type, c.properties ?? {}); diff --git a/frontend/src/components/examples/ExamplesGallery.tsx b/frontend/src/components/examples/ExamplesGallery.tsx index 0d1e90c7..7d7d8e06 100644 --- a/frontend/src/components/examples/ExamplesGallery.tsx +++ b/frontend/src/components/examples/ExamplesGallery.tsx @@ -133,7 +133,12 @@ export const ExamplesGallery: React.FC = ({ onLoadExample example.difficulty, getBoardFilter(example), ...(example.tags ?? []), - ...example.components.map((c) => c.type), + // Defensive. `components` is required by ExampleProject, but overlay + // example sets are built by factories that cast their result, so the + // compiler is not actually guarding this. One entry that omitted it took + // the entire gallery down with a TypeError the first time anyone typed in + // the search box — a whole page lost to one malformed example. + ...(example.components ?? []).map((c) => c.type), ] .join(' ') .toLowerCase(); diff --git a/frontend/src/pages/ExampleDetailPage.tsx b/frontend/src/pages/ExampleDetailPage.tsx index c77fa13d..5f9a2bdd 100644 --- a/frontend/src/pages/ExampleDetailPage.tsx +++ b/frontend/src/pages/ExampleDetailPage.tsx @@ -300,7 +300,7 @@ export const ExampleDetailPage: React.FC = () => { }} > ✓ - {example.components.length > 0 + {(example.components?.length ?? 0) > 0 ? `${example.components.length} interactive component${example.components.length > 1 ? 's' : ''} on the canvas` : 'Interactive simulation canvas'}