From a0b47aed55ff671ffc4a13ce59cd897fc7045987 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Mon, 17 Nov 2025 16:06:00 +0000 Subject: [PATCH 1/8] Proof of concept, using RPF's friendly error msg package --- .yarnrc.yml | 6 ++++ package.json | 1 + .../Editor/ErrorMessage/ErrorMessage.jsx | 32 +++++++++++++++++-- webpack.config.js | 6 ++++ yarn.lock | 8 +++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/.yarnrc.yml b/.yarnrc.yml index 6ff085359..1c67a217d 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -3,3 +3,9 @@ nodeLinker: node-modules yarnPath: .yarn/releases/yarn-3.4.1.cjs checksumBehavior: "update" + +npmScopes: + raspberrypifoundation: + npmRegistryServer: "https://npm.pkg.github.com" + npmAuthToken: "${GITHUB_TOKEN}" + diff --git a/package.json b/package.json index ac09c63b5..aad4161bc 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@juggle/resize-observer": "^3.3.1", "@lezer/highlight": "^1.0.0", "@raspberrypifoundation/design-system-react": "^2.7.0", + "@raspberrypifoundation/python-friendly-error-messages": "^0.1.2", "@react-three/drei": "9.114.3", "@react-three/fiber": "^8.0.13", "@reduxjs/toolkit": "^1.6.2", diff --git a/src/components/Editor/ErrorMessage/ErrorMessage.jsx b/src/components/Editor/ErrorMessage/ErrorMessage.jsx index 1d50af989..166ff57c1 100644 --- a/src/components/Editor/ErrorMessage/ErrorMessage.jsx +++ b/src/components/Editor/ErrorMessage/ErrorMessage.jsx @@ -1,18 +1,44 @@ -import React, { useContext, useEffect, useRef } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; + import "../../../assets/stylesheets/ErrorMessage.scss"; + import { useSelector } from "react-redux"; + import { SettingsContext } from "../../../utils/settings"; +import { + loadCopydeckFor, + registerAdapter, + pyodideAdapter, + explain, +} from "@raspberrypifoundation/python-friendly-error-messages"; + const ErrorMessage = () => { const message = useRef(); const error = useSelector((state) => state.editor.error); const settings = useContext(SettingsContext); + const [isReady, setIsReady] = useState(false); useEffect(() => { - if (message.current) { + loadCopydeckFor(navigator.language).then(() => { + registerAdapter("pyodide", pyodideAdapter); + setIsReady(true); + }); + }, []); + + useEffect(() => { + if (!message.current || !error || !isReady) return; + + try { + const result = explain({ error }); + const explained = result.html || result.summary; + message.current.innerHTML = + error + (explained ? `

${explained}` : ""); + } catch { message.current.innerHTML = error; } - }, [error]); + }, [error, isReady]); + return error ? (

diff --git a/webpack.config.js b/webpack.config.js
index df68e746c..2dfd3e4ad 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -82,6 +82,12 @@ module.exports = {
     ],
   },
   resolve: {
+    alias: {
+      "@raspberrypifoundation/python-friendly-error-messages": path.resolve(
+        __dirname,
+        "node_modules/@raspberrypifoundation/python-friendly-error-messages/dist/index.browser.js",
+      ),
+    },
     extensions: [".*", ".js", ".jsx", ".css"],
     fallback: {
       stream: require.resolve("stream-browserify"),
diff --git a/yarn.lock b/yarn.lock
index 296305fca..c01b515a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2963,6 +2963,7 @@ __metadata:
     "@lezer/highlight": ^1.0.0
     "@pmmmwh/react-refresh-webpack-plugin": 0.4.3
     "@raspberrypifoundation/design-system-react": ^2.7.0
+    "@raspberrypifoundation/python-friendly-error-messages": ^0.1.2
     "@react-three/drei": 9.114.3
     "@react-three/fiber": ^8.0.13
     "@react-three/test-renderer": 8.2.1
@@ -3116,6 +3117,13 @@ __metadata:
   languageName: unknown
   linkType: soft
 
+"@raspberrypifoundation/python-friendly-error-messages@npm:^0.1.2":
+  version: 0.1.3
+  resolution: "@raspberrypifoundation/python-friendly-error-messages@npm:0.1.3::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40raspberrypifoundation%2Fpython-friendly-error-messages%2F0.1.3%2F28a2b5f9e9279bf51282cf58135cffca9d79c511"
+  checksum: e5147fbbf49e5e497ef0e3b52b1031ced34e1cb6d103da28af0a1a7d844597d20576fdcb8b34f74d4a7d848d3239f845091a61400650dc79a725bcc5e211d725
+  languageName: node
+  linkType: hard
+
 "@react-spring/animated@npm:~9.6.1":
   version: 9.6.1
   resolution: "@react-spring/animated@npm:9.6.1"

From 533067ad36d6873a852b46ed315853c566c5b704 Mon Sep 17 00:00:00 2001
From: Greg Annandale 
Date: Tue, 18 Nov 2025 14:24:06 +0000
Subject: [PATCH 2/8] Basic styles for error explanation output

---
 src/assets/stylesheets/ErrorMessage.scss | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/assets/stylesheets/ErrorMessage.scss b/src/assets/stylesheets/ErrorMessage.scss
index fbbf14acb..b64e34515 100644
--- a/src/assets/stylesheets/ErrorMessage.scss
+++ b/src/assets/stylesheets/ErrorMessage.scss
@@ -22,3 +22,14 @@
     @include font-size-2(regular);
   }
 }
+
+.error-explanation {
+  margin-block-start: $space-1-25;
+  padding: $space-1-25;
+  background-color: #fff;
+
+  .pfe-summary {
+    margin-block-start: $space-1-25;
+    margin-block-end: $space-1-25;
+  }
+}

From ef13056801af56a3baa798a206e68aed48a14bd4 Mon Sep 17 00:00:00 2001
From: Greg Annandale 
Date: Tue, 18 Nov 2025 14:24:29 +0000
Subject: [PATCH 3/8] Set error line (ie. code) and line number, pass to error
 explainer

---
 .../Editor/ErrorMessage/ErrorMessage.jsx      | 33 +++++++++++++++----
 .../PyodideRunner/PyodideRunner.jsx           | 27 +++++++++++++++
 .../SkulptRunner/SkulptRunner.jsx             | 30 +++++++++++++++++
 .../Runners/PythonRunner/VisualOutputPane.jsx |  9 ++++-
 src/components/Modals/ErrorModal.jsx          |  9 ++++-
 src/redux/EditorSlice.js                      | 10 ++++++
 src/utils/externalLinkHelper.js               |  9 ++++-
 7 files changed, 118 insertions(+), 9 deletions(-)

diff --git a/src/components/Editor/ErrorMessage/ErrorMessage.jsx b/src/components/Editor/ErrorMessage/ErrorMessage.jsx
index 166ff57c1..747068a61 100644
--- a/src/components/Editor/ErrorMessage/ErrorMessage.jsx
+++ b/src/components/Editor/ErrorMessage/ErrorMessage.jsx
@@ -15,12 +15,18 @@ import {
 
 const ErrorMessage = () => {
   const message = useRef();
+  const errorExplanation = useRef();
   const error = useSelector((state) => state.editor.error);
+  const errorLine = useSelector((state) => state.editor.errorLine);
+  // TODO: highlight the error line in the code editor
+  // const errorLineNumber = useSelector((state) => state.editor.errorLineNumber);
   const settings = useContext(SettingsContext);
   const [isReady, setIsReady] = useState(false);
 
   useEffect(() => {
     loadCopydeckFor(navigator.language).then(() => {
+      // TODO: adapt based on runner
+      // state.editor.activeRunner (and/or loadedRunner) will provide either "pyodide" or "skulpt"
       registerAdapter("pyodide", pyodideAdapter);
       setIsReady(true);
     });
@@ -28,20 +34,35 @@ const ErrorMessage = () => {
 
   useEffect(() => {
     if (!message.current || !error || !isReady) return;
-
     try {
-      const result = explain({ error });
-      const explained = result.html || result.summary;
-      message.current.innerHTML =
-        error + (explained ? `

${explained}` : ""); + const explanation = explain({ + error: error, + code: errorLine, + // TODO: set dynamically (based on what?) + audience: "beginner", + verbosity: "guided", + }); + const explained = explanation.html || explanation.summary; + message.current.innerHTML = error; + if (explained) { + errorExplanation.current.innerHTML += `${explained}`; + } } catch { message.current.innerHTML = error; } - }, [error, isReady]); + }, [error, errorLine, isReady]); return error ? (

+      
+
+
) : null; }; diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 0d39afe80..468d2d099 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -6,6 +6,8 @@ import { useTranslation } from "react-i18next"; import classNames from "classnames"; import { setError, + setErrorLine, + setErrorLineNumber, codeRunHandled, setLoadedRunner, updateProjectComponent, @@ -192,6 +194,8 @@ const PyodideRunner = ({ active, outputPanels = ["text", "visual"] }) => { const handleError = (file, line, mistake, type, info) => { let errorMessage; + let errorLine = ""; + let errorLineNumber = null; if (type === "KeyboardInterrupt") { errorMessage = t("output.errors.interrupted"); @@ -203,6 +207,25 @@ const PyodideRunner = ({ active, outputPanels = ["text", "visual"] }) => { errorMessage += `:\n${mistake}`; } + if (line && file) { + errorLineNumber = line; + const lastDotIndex = file.lastIndexOf("."); + const fileName = + lastDotIndex > 0 ? file.substring(0, lastDotIndex) : file; + const fileExtension = + lastDotIndex > 0 ? file.substring(lastDotIndex + 1) : ""; + const component = projectCode.find( + (item) => item.name === fileName && item.extension === fileExtension, + ); + if (component && component.content) { + const lines = component.content.split("\n"); + // line numbers are 1-indexed, array is 0-indexed + if (line > 0 && line <= lines.length) { + errorLine = lines[line - 1]; + } + } + } + const { createError } = ApiCallHandler({ reactAppApiEndpoint, }); @@ -210,6 +233,8 @@ const PyodideRunner = ({ active, outputPanels = ["text", "visual"] }) => { } dispatch(setError(errorMessage)); + dispatch(setErrorLine(errorLine)); + dispatch(setErrorLineNumber(errorLineNumber)); disableInput(); }; @@ -256,6 +281,8 @@ const PyodideRunner = ({ active, outputPanels = ["text", "visual"] }) => { const handleRun = async () => { output.current.innerHTML = ""; dispatch(setError("")); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); setVisuals([]); stdinClosed.current = false; diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index 0e19c63c7..a0abf4439 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -10,6 +10,8 @@ import classNames from "classnames"; import { setError, setErrorDetails, + setErrorLine, + setErrorLineNumber, codeRunHandled, stopDraw, setSenseHatEnabled, @@ -161,6 +163,8 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { input.removeAttribute("id"); input.removeAttribute("contentEditable"); dispatch(setError(t("output.errors.interrupted"))); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); dispatch(codeRunHandled()); } }, [codeRunStopped]); @@ -329,6 +333,9 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { let errorDetails = {}; let errorMessage; let explanation; + let errorLine = ""; + let errorLineNumber = null; + if (err.message === t("output.errors.interrupted")) { errorMessage = err.message; errorDetails = { @@ -343,6 +350,25 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { const lineNumber = err.traceback[0].lineno; const fileName = err.traceback[0].filename.replace(/^\.\//, ""); + if (lineNumber && fileName) { + errorLineNumber = lineNumber; + const lastDotIndex = fileName.lastIndexOf("."); + const name = + lastDotIndex > 0 ? fileName.substring(0, lastDotIndex) : fileName; + const extension = + lastDotIndex > 0 ? fileName.substring(lastDotIndex + 1) : ""; + const component = projectCode.find( + (item) => item.name === name && item.extension === extension, + ); + if (component && component.content) { + const lines = component.content.split("\n"); + // line numbers are 1-indexed, array is 0-indexed + if (lineNumber > 0 && lineNumber <= lines.length) { + errorLine = lines[lineNumber - 1]; + } + } + } + if (errorType === "ImportError" && window.crossOriginIsolated) { const articleLink = `https://help.editor.raspberrypi.org/hc/en-us/articles/30841379339924-What-Python-libraries-are-available-in-the-Code-Editor`; const moduleName = errorDescription.replace(/No module named /, ""); @@ -375,6 +401,8 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { dispatch(setError(errorMessage)); dispatch(setErrorDetails(errorDetails)); + dispatch(setErrorLine(errorLine)); + dispatch(setErrorLineNumber(errorLineNumber)); dispatch(stopDraw()); if (getInput()) { const input = getInput(); @@ -387,6 +415,8 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { // clear previous output dispatch(setError("")); dispatch(setErrorDetails({})); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); if (output.current) { output.current.innerHTML = ""; } diff --git a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx index 86e405b2d..c75e4a2be 100644 --- a/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx +++ b/src/components/Editor/Runners/PythonRunner/VisualOutputPane.jsx @@ -4,7 +4,12 @@ import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import Sk from "skulpt"; import AstroPiModel from "../../../AstroPiModel/AstroPiModel"; -import { codeRunHandled, setError } from "../../../../redux/EditorSlice"; +import { + codeRunHandled, + setError, + setErrorLine, + setErrorLineNumber, +} from "../../../../redux/EditorSlice"; const VisualOutputPane = () => { const codeRunTriggered = useSelector( @@ -68,6 +73,8 @@ const VisualOutputPane = () => { if (error === "") { dispatch(setError(t("output.errors.interrupted"))); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); } dispatch(codeRunHandled()); } diff --git a/src/components/Modals/ErrorModal.jsx b/src/components/Modals/ErrorModal.jsx index 9a3918b97..5a299e683 100644 --- a/src/components/Modals/ErrorModal.jsx +++ b/src/components/Modals/ErrorModal.jsx @@ -5,7 +5,12 @@ import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; import Button from "../Button/Button"; -import { closeErrorModal, setError } from "../../redux/EditorSlice"; +import { + closeErrorModal, + setError, + setErrorLine, + setErrorLineNumber, +} from "../../redux/EditorSlice"; import "../../assets/stylesheets/Modal.scss"; const ErrorModal = ({ errorType, additionalOnClose }) => { @@ -21,6 +26,8 @@ const ErrorModal = ({ errorType, additionalOnClose }) => { additionalOnClose(); } dispatch(setError(null)); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); }; return ( diff --git a/src/redux/EditorSlice.js b/src/redux/EditorSlice.js index c53751b09..0c4fe1c7f 100644 --- a/src/redux/EditorSlice.js +++ b/src/redux/EditorSlice.js @@ -137,6 +137,8 @@ export const editorInitialState = { sidebarShowing: true, modals: {}, errorDetails: {}, + errorLine: "", + errorLineNumber: null, runnerBeingLoaded: null | "pyodide" | "skulpt", }; @@ -310,6 +312,12 @@ export const EditorSlice = createSlice({ setError: (state, action) => { state.error = action.payload; }, + setErrorLine: (state, action) => { + state.errorLine = action.payload; + }, + setErrorLineNumber: (state, action) => { + state.errorLineNumber = action.payload; + }, triggerCodeRun: (state) => { state.codeRunTriggered = true; state.codeHasBeenRun = true; @@ -452,6 +460,8 @@ export const { setBrowserPreview, setCascadeUpdate, setError, + setErrorLine, + setErrorLineNumber, setIsSplitView, setNameError, setHasShownSavePrompt, diff --git a/src/utils/externalLinkHelper.js b/src/utils/externalLinkHelper.js index 9730b9574..ac8e23cf6 100644 --- a/src/utils/externalLinkHelper.js +++ b/src/utils/externalLinkHelper.js @@ -1,7 +1,12 @@ import { useState } from "react"; import { useDispatch } from "react-redux"; -import { setError, triggerCodeRun } from "../redux/EditorSlice"; +import { + setError, + setErrorLine, + setErrorLineNumber, + triggerCodeRun, +} from "../redux/EditorSlice"; const domain = "https://rpf.io/"; const host = process.env.PUBLIC_URL || "http://localhost:3011"; @@ -27,6 +32,8 @@ const useExternalLinkState = (showModal) => { const handleExternalLinkError = () => { dispatch(setError("externalLink")); + dispatch(setErrorLine("")); + dispatch(setErrorLineNumber(null)); showModal(); }; From 8cc11a7fa097a1a53224429cc95d4299140f2a1f Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Tue, 13 Jan 2026 15:20:12 +0000 Subject: [PATCH 4/8] Bump Python FEM version, pass code into error message context --- package.json | 2 +- src/assets/stylesheets/ErrorMessage.scss | 11 ----------- .../Editor/ErrorMessage/ErrorMessage.jsx | 8 +++++--- src/redux/EditorSlice.js | 16 ++++++++++++++++ yarn.lock | 10 +++++----- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index aad4161bc..06fd008e8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@juggle/resize-observer": "^3.3.1", "@lezer/highlight": "^1.0.0", "@raspberrypifoundation/design-system-react": "^2.7.0", - "@raspberrypifoundation/python-friendly-error-messages": "^0.1.2", + "@raspberrypifoundation/python-friendly-error-messages": "^0.1.4", "@react-three/drei": "9.114.3", "@react-three/fiber": "^8.0.13", "@reduxjs/toolkit": "^1.6.2", diff --git a/src/assets/stylesheets/ErrorMessage.scss b/src/assets/stylesheets/ErrorMessage.scss index b64e34515..fbbf14acb 100644 --- a/src/assets/stylesheets/ErrorMessage.scss +++ b/src/assets/stylesheets/ErrorMessage.scss @@ -22,14 +22,3 @@ @include font-size-2(regular); } } - -.error-explanation { - margin-block-start: $space-1-25; - padding: $space-1-25; - background-color: #fff; - - .pfe-summary { - margin-block-start: $space-1-25; - margin-block-end: $space-1-25; - } -} diff --git a/src/components/Editor/ErrorMessage/ErrorMessage.jsx b/src/components/Editor/ErrorMessage/ErrorMessage.jsx index 747068a61..e73a04a7c 100644 --- a/src/components/Editor/ErrorMessage/ErrorMessage.jsx +++ b/src/components/Editor/ErrorMessage/ErrorMessage.jsx @@ -18,6 +18,8 @@ const ErrorMessage = () => { const errorExplanation = useRef(); const error = useSelector((state) => state.editor.error); const errorLine = useSelector((state) => state.editor.errorLine); + const code = useSelector((state) => state.editor.code); + console.log("ErrorMessage render", { error: error, code: code }); // TODO: highlight the error line in the code editor // const errorLineNumber = useSelector((state) => state.editor.errorLineNumber); const settings = useContext(SettingsContext); @@ -37,8 +39,8 @@ const ErrorMessage = () => { try { const explanation = explain({ error: error, - code: errorLine, - // TODO: set dynamically (based on what?) + code: code || errorLine, + // TODO: set dynamically (but based on what? maybe not really needed) audience: "beginner", verbosity: "guided", }); @@ -50,7 +52,7 @@ const ErrorMessage = () => { } catch { message.current.innerHTML = error; } - }, [error, errorLine, isReady]); + }, [error, code, errorLine, isReady]); return error ? (
diff --git a/src/redux/EditorSlice.js b/src/redux/EditorSlice.js index 0c4fe1c7f..920d7dc91 100644 --- a/src/redux/EditorSlice.js +++ b/src/redux/EditorSlice.js @@ -93,6 +93,17 @@ export const loadProjectList = createAsyncThunk( }, ); +// Helper function to extract all Python code from components +const getAllPythonCode = (components) => { + if (!components || !Array.isArray(components)) { + return ""; + } + return components + .filter((component) => component.extension === "py") + .map((component) => component.content || "") + .join("\n"); +}; + export const editorInitialState = { project: {}, cascadeUpdate: false, @@ -139,6 +150,7 @@ export const editorInitialState = { errorDetails: {}, errorLine: "", errorLineNumber: null, + code: "", runnerBeingLoaded: null | "pyodide" | "skulpt", }; @@ -201,6 +213,7 @@ export const EditorSlice = createSlice({ content: action.payload.content || "", }); state.saving = "idle"; + state.code = getAllPythonCode(state.project.components); }, setPage: (state, action) => { state.page = action.payload; @@ -237,6 +250,7 @@ export const EditorSlice = createSlice({ state.openFiles[firstPanelIndex].push("main.py"); } state.justLoaded = true; + state.code = getAllPythonCode(state.project.components); }, setProjectInstructions: (state, action) => { state.project.instructions = action.payload; @@ -285,6 +299,7 @@ export const EditorSlice = createSlice({ }); state.project.components = mapped; state.cascadeUpdate = cascadeUpdate; + state.code = getAllPythonCode(state.project.components); }, updateProjectName: (state, action) => { state.project.name = action.payload; @@ -305,6 +320,7 @@ export const EditorSlice = createSlice({ state.openFiles[panelIndex][fileIndex] = `${name}.${extension}`; } state.saving = "idle"; + state.code = getAllPythonCode(state.project.components); }, setCascadeUpdate: (state, action) => { state.cascadeUpdate = action.payload; diff --git a/yarn.lock b/yarn.lock index c01b515a3..26cb0d6e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2963,7 +2963,7 @@ __metadata: "@lezer/highlight": ^1.0.0 "@pmmmwh/react-refresh-webpack-plugin": 0.4.3 "@raspberrypifoundation/design-system-react": ^2.7.0 - "@raspberrypifoundation/python-friendly-error-messages": ^0.1.2 + "@raspberrypifoundation/python-friendly-error-messages": ^0.1.4 "@react-three/drei": 9.114.3 "@react-three/fiber": ^8.0.13 "@react-three/test-renderer": 8.2.1 @@ -3117,10 +3117,10 @@ __metadata: languageName: unknown linkType: soft -"@raspberrypifoundation/python-friendly-error-messages@npm:^0.1.2": - version: 0.1.3 - resolution: "@raspberrypifoundation/python-friendly-error-messages@npm:0.1.3::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40raspberrypifoundation%2Fpython-friendly-error-messages%2F0.1.3%2F28a2b5f9e9279bf51282cf58135cffca9d79c511" - checksum: e5147fbbf49e5e497ef0e3b52b1031ced34e1cb6d103da28af0a1a7d844597d20576fdcb8b34f74d4a7d848d3239f845091a61400650dc79a725bcc5e211d725 +"@raspberrypifoundation/python-friendly-error-messages@npm:^0.1.4": + version: 0.1.4 + resolution: "@raspberrypifoundation/python-friendly-error-messages@npm:0.1.4::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40raspberrypifoundation%2Fpython-friendly-error-messages%2F0.1.4%2F4ceb89b9a5c7a2b16dbe1008e7762064dd4b753a" + checksum: 7095a502fc88c3ca91b59de4f867512ab55e05585fe73ad563813d39375aa8ea5a1dd95a30af69bd5ad6a807608d8e75de2cc1890d32546e1dd1e08f508da1a9 languageName: node linkType: hard From 553141b48220aa596f2e8f588015f5d8dd143f0a Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 5 Feb 2026 10:07:56 +0000 Subject: [PATCH 5/8] Remove need to auth for Python FEM npm package --- .yarnrc.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.yarnrc.yml b/.yarnrc.yml index 1c67a217d..6ff085359 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -3,9 +3,3 @@ nodeLinker: node-modules yarnPath: .yarn/releases/yarn-3.4.1.cjs checksumBehavior: "update" - -npmScopes: - raspberrypifoundation: - npmRegistryServer: "https://npm.pkg.github.com" - npmAuthToken: "${GITHUB_TOKEN}" - From f3fb6e1291e799d8a28269c4027e7cea063ad77a Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 5 Mar 2026 15:57:23 +0000 Subject: [PATCH 6/8] Error explanation styles --- src/assets/stylesheets/ErrorMessage.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/assets/stylesheets/ErrorMessage.scss b/src/assets/stylesheets/ErrorMessage.scss index fbbf14acb..eb560bc7f 100644 --- a/src/assets/stylesheets/ErrorMessage.scss +++ b/src/assets/stylesheets/ErrorMessage.scss @@ -22,3 +22,13 @@ @include font-size-2(regular); } } + +.error-explanation__content { + margin: 0.5rem; + padding: 1rem; + background-color: #f8f8f8; + + div { + margin-bottom: 0.5rem; + } +} From 9dc7dda03d46a9f9b86837a27d1ce5f64a44d44b Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 7 May 2026 11:09:26 +0100 Subject: [PATCH 7/8] Update PFEM to 0.1.6 --- package.json | 2 +- src/components/Editor/ErrorMessage/ErrorMessage.jsx | 8 +++----- yarn.lock | 10 +++++----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d97328b98..f6e839210 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@juggle/resize-observer": "^3.3.1", "@lezer/highlight": "^1.0.0", "@raspberrypifoundation/design-system-react": "^2.7.0", - "@raspberrypifoundation/python-friendly-error-messages": "^0.1.4", + "@raspberrypifoundation/python-friendly-error-messages": "^0.1.6", "@react-three/drei": "9.114.3", "@react-three/fiber": "^8.0.13", "@reduxjs/toolkit": "^1.6.2", diff --git a/src/components/Editor/ErrorMessage/ErrorMessage.jsx b/src/components/Editor/ErrorMessage/ErrorMessage.jsx index e73a04a7c..a28198854 100644 --- a/src/components/Editor/ErrorMessage/ErrorMessage.jsx +++ b/src/components/Editor/ErrorMessage/ErrorMessage.jsx @@ -10,7 +10,7 @@ import { loadCopydeckFor, registerAdapter, pyodideAdapter, - explain, + friendlyExplain, } from "@raspberrypifoundation/python-friendly-error-messages"; const ErrorMessage = () => { @@ -37,12 +37,10 @@ const ErrorMessage = () => { useEffect(() => { if (!message.current || !error || !isReady) return; try { - const explanation = explain({ + const explanation = friendlyExplain({ error: error, code: code || errorLine, - // TODO: set dynamically (but based on what? maybe not really needed) - audience: "beginner", - verbosity: "guided", + runtime: "pyodide", }); const explained = explanation.html || explanation.summary; message.current.innerHTML = error; diff --git a/yarn.lock b/yarn.lock index 2451cd6f2..62622d978 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2963,7 +2963,7 @@ __metadata: "@lezer/highlight": ^1.0.0 "@pmmmwh/react-refresh-webpack-plugin": 0.4.3 "@raspberrypifoundation/design-system-react": ^2.7.0 - "@raspberrypifoundation/python-friendly-error-messages": ^0.1.4 + "@raspberrypifoundation/python-friendly-error-messages": ^0.1.6 "@react-three/drei": 9.114.3 "@react-three/fiber": ^8.0.13 "@react-three/test-renderer": 8.2.1 @@ -3118,10 +3118,10 @@ __metadata: languageName: unknown linkType: soft -"@raspberrypifoundation/python-friendly-error-messages@npm:^0.1.4": - version: 0.1.4 - resolution: "@raspberrypifoundation/python-friendly-error-messages@npm:0.1.4::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40raspberrypifoundation%2Fpython-friendly-error-messages%2F0.1.4%2F4ceb89b9a5c7a2b16dbe1008e7762064dd4b753a" - checksum: 7095a502fc88c3ca91b59de4f867512ab55e05585fe73ad563813d39375aa8ea5a1dd95a30af69bd5ad6a807608d8e75de2cc1890d32546e1dd1e08f508da1a9 +"@raspberrypifoundation/python-friendly-error-messages@npm:^0.1.6": + version: 0.1.6 + resolution: "@raspberrypifoundation/python-friendly-error-messages@npm:0.1.6" + checksum: 5270aefc908c5c78cc01ab820f45d19ab6d035081b2a5465ba577103486bb4392d1cd42fec04cf3834b9ff5c6af4a1c22c271d691a2f3cf367ba32cbfc01df9f languageName: node linkType: hard From 1dbea586eb28d763aa9693ce3a584f619667b1b7 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 7 May 2026 11:19:08 +0100 Subject: [PATCH 8/8] fix: prevent error explanation executing with every code input --- src/components/Editor/ErrorMessage/ErrorMessage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Editor/ErrorMessage/ErrorMessage.jsx b/src/components/Editor/ErrorMessage/ErrorMessage.jsx index a28198854..af8307b3d 100644 --- a/src/components/Editor/ErrorMessage/ErrorMessage.jsx +++ b/src/components/Editor/ErrorMessage/ErrorMessage.jsx @@ -50,7 +50,7 @@ const ErrorMessage = () => { } catch { message.current.innerHTML = error; } - }, [error, code, errorLine, isReady]); + }, [error, errorLine, isReady]); return error ? (