Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
188bead
install pfem
cocomarine May 28, 2026
e8ed800
update webpack config to disable ESM strict resolution for pfem
cocomarine May 28, 2026
cfa91e6
add pfem to skulpt runner WIP pyodide runner
cocomarine May 28, 2026
e594994
Merge branch 'main' into 1448-add-improved-error-messaging
cocomarine Jun 1, 2026
088617a
fix pyodide error
cocomarine Jun 1, 2026
ffb0c84
Merge branch 'main' into 1448-add-improved-error-messaging
cocomarine Jun 1, 2026
3ae80e6
add friendly error enabled attribute
cocomarine Jun 1, 2026
cb9f905
fix errors due to ESM to CJS transform
cocomarine Jun 1, 2026
4bf16ec
remove unnecessary mock
cocomarine Jun 1, 2026
9af9cfe
add friendlyError to redux and show in ErrorMessage
cocomarine Jun 1, 2026
7c05ba7
minor fixes
cocomarine Jun 1, 2026
c1b803f
update editorslice and errormessage tests
cocomarine Jun 2, 2026
0958129
update pyodide and skulpt runner tests
cocomarine Jun 2, 2026
6ff35ec
plain text friendly error for now
cocomarine Jun 2, 2026
0ac2991
add dompurify
cocomarine Jun 2, 2026
64c2c4c
dispatch friendly error html and use css to restrict to title and sum…
cocomarine Jun 2, 2026
4fda69e
update runner tests
cocomarine Jun 2, 2026
3dc2a2e
update editorslice test
cocomarine Jun 2, 2026
5be1299
stub out friendlyerror html in tests
cocomarine Jun 3, 2026
113797f
refactor: remove defaulting to editor styles
maxelkins May 29, 2026
9a5b56c
refactor: move error message to `EditorInput`
maxelkins May 29, 2026
4971474
feat: add initial FriendlyErrorMessage UI component into ErrorMessage
maxelkins May 29, 2026
3dbfc83
revert: move error back to output panel
maxelkins Jun 2, 2026
626bd41
use FriendlyErrorMessage component and update styling
cocomarine Jun 3, 2026
9a8b187
add font size to original error message and minor fixes
cocomarine Jun 3, 2026
8bd51f8
remove fem stub, add friendly test, revert error message test
cocomarine Jun 3, 2026
f309c11
update cypress test helper to new class
cocomarine Jun 3, 2026
aa137a3
update cypress test
cocomarine Jun 3, 2026
c744d3e
intercept the copydeck request before running code
cocomarine Jun 3, 2026
a194986
fix flaky test
cocomarine Jun 3, 2026
d4684b4
Merge branch 'main' into 1448-and-ui-branches-combined
cocomarine Jun 3, 2026
4ad3ef1
Merge branch 'main' into 1448-and-ui-branches-combined
cocomarine Jun 4, 2026
da8c411
resolve conflicts
cocomarine Jun 4, 2026
7a807ff
try using fixture of copydeck for test
cocomarine Jun 4, 2026
b696959
Merge branch 'main' into 1448-and-ui-branches-combined
cocomarine Jun 4, 2026
9bc9022
update pyodide cy
cocomarine Jun 4, 2026
adce42e
Merge branch '1448-and-ui-branches-combined' of github.com:RaspberryP…
cocomarine Jun 4, 2026
454a80d
uncomment test
cocomarine Jun 4, 2026
3ff953e
WIP move pyodide error to input panel
cocomarine Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions cypress/e2e/spec-wc-pyodide.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getEditorShadow,
getErrorMessage,
getFriendlyErrorMessage,
getFileButtonByName,
getProgramInput,
getPyodideOutput,
Expand Down Expand Up @@ -253,3 +254,41 @@ print(text_out)
);
});
});

describe("When friendly errors enabled with pyodide", () => {
beforeEach(() => {
cy.intercept("GET", "**/python-error-copydecks/**", {
fixture: "copydeck.json",
}).as("copydeck");

const params = new URLSearchParams();
params.set("friendly_errors_enabled", "true");

cy.visit({
url: `${origin}?${params.toString()}`,
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
});
cy.window().then((win) => {
Object.defineProperty(win, "crossOriginIsolated", {
value: true,
configurable: true,
});
});
cy.wait("@copydeck");
});

it("shows a friendly error message when an error occurs", () => {
runCode("print(kitten)");
getErrorMessage().should(
"contain",
"NameError: name 'kitten' is not defined on line 1 of main.py",
);
getFriendlyErrorMessage().should(
"contain",
"This variable doesn't exist here",
);
});
});
33 changes: 33 additions & 0 deletions cypress/e2e/spec-wc-skulpt.cy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
getErrorMessage,
getFriendlyErrorMessage,
getP5Canvas,
getPythonConsoleOutput,
getSkulptRunner,
Expand Down Expand Up @@ -149,3 +150,35 @@ describe("Running the code with skulpt", () => {
);
});
});

describe("When friendly errors enabled with skulpt", () => {
beforeEach(() => {
cy.intercept("GET", "**/python-error-copydecks/**", {
fixture: "copydeck.json",
}).as("copydeck");

const params = new URLSearchParams();
params.set("friendly_errors_enabled", "true");

cy.visit(`${origin}?${params.toString()}`);
cy.window().then((win) => {
Object.defineProperty(win, "crossOriginIsolated", {
value: false,
configurable: true,
});
});
cy.wait("@copydeck");
});

it("shows a friendly error message when an error occurs", () => {
runCode("import turtle\nprint(kitten)");
getErrorMessage().should(
"contain",
"NameError: name 'kitten' is not defined on line 2 of main.py",
);
getFriendlyErrorMessage().should(
"contain",
"This variable doesn't exist here",
);
});
});
41 changes: 41 additions & 0 deletions cypress/fixtures/copydeck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"errors": {
"NameError": {
"variants": [
{
"if": {
"not_message": ["is not defined"]
},
"title": "This variable doesn't exist yet",
"summary": "Your code uses the variable {{name}}, but it hasn't been created yet. Check {{loc}}. If you meant to print the text {{name}}, put it in double quotes.",
"why": "Without speech marks Python treats {{name}} as a variable, and this variable does not exist yet.",
"steps": [
"If it is meant to be text, put speech marks around {{name}}.",
"If it is meant to be a variable, make it first (for example: {{name}} = 0).",
"Check spelling and capital letters."
],
"_placeholders": {
"name": "The undefined variable name, e.g. kittens",
"loc": "Where it was used, e.g. line 2 in main.py"
}
},
{
"if": {
"match_message": ["is not defined"]
},
"title": "This variable doesn't exist here",
"summary": "{{name}} might be created somewhere else, but you're using it at {{loc}}. If you meant the text {{name}}, put it in double quotes.",
"why": "A variable created in another place might not be available here.",
"steps": [
"Move the line that makes it to above where you use it.",
"Or set it here just before you use it."
],
"_placeholders": {
"name": "The variable name used out of scope, e.g. total",
"loc": "Where it was referenced, e.g. line 8 in main.py"
}
}
]
}
}
}
5 changes: 4 additions & 1 deletion cypress/helpers/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export const getP5Canvas = () => getEditorShadow().find(".p5Canvas");
export const getTurtleOutput = () => getEditorShadow().find("#turtleOutput");

export const getErrorMessage = () =>
getEditorShadow().find(".error-message__content");
getEditorShadow().find(".error-message__python");

export const getFriendlyErrorMessage = () =>
getEditorShadow().find(".friendly-error-message");

export const getTextOutputTab = () =>
getPyodideOutput().findByLabelText("Text output");
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/cancel_FILL.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 3 additions & 5 deletions src/assets/stylesheets/EditorPanel.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@forward '@raspberrypifoundation/design-system-core/scss/properties/spacing';
@use '@raspberrypifoundation/design-system-core/scss/mixins/typography' as typography;
@forward "@raspberrypifoundation/design-system-core/scss/properties/spacing";
@use "@raspberrypifoundation/design-system-core/scss/mixins/typography" as
typography;

// Scrollbar-width is needed from scrollbar to show in Chrome
.editor-wrapper {
Expand Down Expand Up @@ -31,7 +32,6 @@
overscroll-behavior-x: none;
font-family: var(--wc-font-family-monospace);


.cm-content {
flex: 1;
padding-block-start: var(--space-0-5);
Expand Down Expand Up @@ -60,5 +60,3 @@
display: none;
}
}


32 changes: 21 additions & 11 deletions src/assets/stylesheets/ErrorMessage.scss
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
@use "./rpf_design_system/font-size" as *;
@use "./rpf_design_system/spacing" as *;
@use "@raspberrypifoundation/design-system-core/scss/mixins/typography" as
typography;

.error-message {
color: #7e0305;
background-color: #fde2e1;
padding: $space-0-75 $space-1-25;
color: var(--rpf-red-900);
background-color: var(--rpf-red-100);
border-block-start: 1px solid var(--editor-color-outline);
padding: var(--space-1);
overflow-y: auto;
scrollbar-width: thin;
max-block-size: 50%;

&__content {
&__error {
padding: 0;
margin: 0;
white-space: pre-wrap;
overflow-wrap: break-word;
font-weight: bold;
}

&--medium {
@include font-size-1-5(regular);
}
&__python {
display: flex;
gap: var(--space-1);
@include typography.style-1;

&--large {
@include font-size-2(regular);
&--medium {
@include typography.style-1;
}

&--large {
@include typography.style-2;
}
}

// Only displaying title and summary of friendlyError
Expand Down
27 changes: 27 additions & 0 deletions src/assets/stylesheets/FriendlyErrorMessage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.friendly-error-message {
background-color: var(--rpf-white);
padding: var(--space-2);
color: var(--rpf-text-primary);
border-radius: var(--space-1);
margin-block-start: var(--space-1);

.pfem {
&__title {
display: block;
font-weight: 600;
margin-block-end: var(--space-1);
}

&__summary {
display: block;
}

// Only displaying title and summary of friendlyError
&__why,
&__steps,
&__patch,
&__details {
display: none;
}
}
}
37 changes: 28 additions & 9 deletions src/assets/stylesheets/InternalStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
@use "./EditorFinalStep.scss" as *;
@use "./Loader.scss" as *;
@use "./LoadFailed.scss" as *;
@use "./FriendlyErrorMessage" as *;

@use "./settings/fonts" as fonts;

Expand Down Expand Up @@ -158,7 +159,9 @@ button:focus-visible {
// Primary button
--rpf-button-primary-background-color: var(--editor-color-theme);
--rpf-button-primary-background-color-focus: var(--editor-color-theme);
--rpf-button-primary-background-color-hover: var(--editor-color-theme-secondary);
--rpf-button-primary-background-color-hover: var(
--editor-color-theme-secondary
);
--rpf-button-primary-background-color-active: var(--rpf-navy-600);
--rpf-button-primary-background-color-disabled: var(--rpf-navy-200);
--rpf-button-primary-color-disabled: var(--rpf-grey-600);
Expand All @@ -167,10 +170,16 @@ button:focus-visible {
// Secondary button
--rpf-button-secondary-background-color: var(--editor-color-theme);
--rpf-button-secondary-background-color-focus: var(--rpf-brand-raspberry);
--rpf-button-secondary-background-color-hover: var(--editor-color-theme-tertiary);
--rpf-button-secondary-background-color-hover: var(
--editor-color-theme-tertiary
);
--rpf-button-secondary-border-color: var(--editor-color-theme);
--rpf-button-secondary-border-color-hover: var(--editor-color-theme-secondary);
--rpf-button-secondary-background-color-active: var(--editor-color-theme-secondary);
--rpf-button-secondary-border-color-hover: var(
--editor-color-theme-secondary
);
--rpf-button-secondary-background-color-active: var(
--editor-color-theme-secondary
);
--rpf-button-secondary-background-color-disabled: var(--rpf-grey-50);
--rpf-button-secondary-text-color: var(--editor-color-theme);

Expand Down Expand Up @@ -230,7 +239,9 @@ button:focus-visible {
--rpf-button-secondary-text-color: var(--rpf-white);
--rpf-button-secondary-background-color: var(--editor-color-layer-2);
--rpf-button-secondary-background-color-active: var(--rpf-navy-200);
--rpf-button-secondary-color-disabled-background: var(--editor-color-layer-3);
--rpf-button-secondary-color-disabled-background: var(
--editor-color-layer-3
);
--rpf-button-secondary-background-color-hover: var(--editor-color-outline);
--rpf-button-secondary-border-color: var(--editor-color-theme);
--rpf-button-secondary-border-color-hover: var(--editor-color-theme);
Expand All @@ -240,9 +251,17 @@ button:focus-visible {
// Tertiary button
--rpf-button-tertiary-text-color-hover: var(--rpf-grey-200);
--rpf-button-tertiary-danger-text-color: var(--rpf-red-600);
--rpf-button-tertiary-danger-background-color-hover: rgba(255, 255, 255, 0.1);
--rpf-button-tertiary-danger-background-color-active: rgba(255, 255, 255, 0.15);
--rpf-button-tertiary-danger-background-color-hover: rgba(
255,
255,
255,
0.1
);
--rpf-button-tertiary-danger-background-color-active: rgba(
255,
255,
255,
0.15
);
}
}


2 changes: 2 additions & 0 deletions src/components/Editor/EditorInput/EditorInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import EditorPanel from "../EditorPanel/EditorPanel";
import DraggableTab from "../DraggableTabs/DraggableTab";
import DroppableTabList from "../DraggableTabs/DroppableTabList";
import RunBar from "../../RunButton/RunBar";
import ErrorMessage from "../ErrorMessage/ErrorMessage";

import "../../../assets/stylesheets/EditorInput.scss";
import RunnerControls from "../../RunButton/RunnerControls";
Expand Down Expand Up @@ -186,6 +187,7 @@ const EditorInput = () => {
/>
</TabPanel>
))}
<ErrorMessage />
{isMobile ? null : <RunBar />}
</Tabs>
))}
Expand Down
29 changes: 12 additions & 17 deletions src/components/Editor/ErrorMessage/ErrorMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,25 @@ import "../../../assets/stylesheets/ErrorMessage.scss";
import { useSelector } from "react-redux";
import DOMPurify from "dompurify";
import { SettingsContext } from "../../../utils/settings";
import CancelFillIcon from "../../../assets/icons/cancel_FILL.svg";
import FriendlyErrorMessage from "../FriendlyErrorMessage/FriendlyErrorMessage";

const ErrorMessage = () => {
const error = useSelector((state) => state.editor.error);
const friendlyError = useSelector((state) => state.editor.friendlyError);
const settings = useContext(SettingsContext);

const errorHtml = DOMPurify.sanitize(error);

const friendlyErrorHtml = friendlyError?.html
? DOMPurify.sanitize(friendlyError.html)
: null;

return error ? (
<div className={`error-message error-message--${settings.fontSize}`}>
<pre
className="error-message__content"
dangerouslySetInnerHTML={{ __html: errorHtml }}
/>
{friendlyErrorHtml && (
<div
className="error-message__friendly"
dangerouslySetInnerHTML={{ __html: friendlyErrorHtml }}
<div className="error-message">
<div
className={`error-message__python error-message__python--${settings.fontSize}`}
>
<CancelFillIcon />
<pre
className="error-message__error"
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(error) }}
/>
)}
</div>
<FriendlyErrorMessage />
</div>
) : null;
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/Editor/ErrorMessage/ErrorMessage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe("When error is set", () => {

test("Font size class is set correctly", () => {
const errorMessage = screen.queryByText("Oops").parentElement;
expect(errorMessage).toHaveClass("error-message--myFontSize");
expect(errorMessage).toHaveClass("error-message__python--myFontSize");
});

test("Does not display friendly error elements", () => {
Expand Down
Loading
Loading