From f2046ff0fc99a5af5368877af0601e01cd216cb0 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Sun, 16 Aug 2026 21:54:33 +0000 Subject: [PATCH 1/5] Add dev:link-ui and dev:link-ui-patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit classroom and data-microbit-org already had dev:link-ui; this app did not, which made working on @microbit/ui against it awkward. dev:link-ui-patterns is new to both: the LanguageDialog pattern reads the library's semantic tokens, so linking only @microbit/ui leaves a published ui-patterns referencing token names the linked library may have renamed. No source changes were needed for the role-token restructure (Phase 3, ui-private docs/role-tokens.md) — this preset only defines its own sidebar* tokens, and the 15 raw var(--colors-gray-*) references in the CodeMirror themes are unaffected because the ramps stay public and unrenamed. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 93f52e2a3..4adf5bc42 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,8 @@ "prepare": "npm run panda", "panda": "panda codegen", "panda:watch": "panda codegen --watch", + "dev:link-ui": "rm -rf node_modules/@microbit/ui && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && rm -rf styled-system && npm run panda", + "dev:link-ui-patterns": "rm -rf node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && rm -rf styled-system && npm run panda", "serve": "npx serve --no-clipboard -l 3000 -- build/", "prestart": "npm run panda", "start": "vite dev", From b34d2fef6f4119e77f7c5ce2941221980f6fd3d8 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 17 Aug 2026 12:13:57 +0000 Subject: [PATCH 2/5] Add dev:link-theme for the private brand preset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symlinks ../python-editor-v3-microbit into node_modules, which is the switch both panda.config.ts and vite.config.ts test for, so one link moves the Panda codegen and the Vite alias together. Builds the theme package first — both resolve it through dist. dev:unlink-theme reverses it. Completes the set alongside dev:link-ui and dev:link-ui-patterns. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 4adf5bc42..60feff7ba 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,8 @@ "panda:watch": "panda codegen --watch", "dev:link-ui": "rm -rf node_modules/@microbit/ui && ln -s ../../../ui/packages/ui node_modules/@microbit/ui && rm -rf styled-system && npm run panda", "dev:link-ui-patterns": "rm -rf node_modules/@microbit/ui-patterns && ln -s ../../../ui/packages/ui-patterns node_modules/@microbit/ui-patterns && rm -rf styled-system && npm run panda", + "dev:link-theme": "npm --prefix ../python-editor-v3-microbit run build && mkdir -p node_modules/@microbit-foundation && rm -rf node_modules/@microbit-foundation/python-editor-v3-microbit && ln -s ../../../python-editor-v3-microbit node_modules/@microbit-foundation/python-editor-v3-microbit && rm -rf styled-system && npm run panda", + "dev:unlink-theme": "rm -rf node_modules/@microbit-foundation/python-editor-v3-microbit && rm -rf styled-system && npm run panda", "serve": "npx serve --no-clipboard -l 3000 -- build/", "prestart": "npm run panda", "start": "vite dev", From fc3aa21e371cae74f525ebc14c57112e3e5c895e Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 17 Aug 2026 12:14:07 +0000 Subject: [PATCH 3/5] Point links at fg.link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the twenty link call sites onto `fg.link`, replacing the hardcoded brand stops they each carried. The private preset sets `fg.link` to brand.500 (#6c4bc1) — the value seventeen of them already used, and the purple the non-app web properties use. Two changes fall out. The three Sanity documentation link marks were brand.600 (#50388f) rather than the brand.500 everywhere else. That is 8.43:1 against the gray.75 sidebar they render on, but only 1.66:1 against the body text beside them, so they read as bold-ish grey until hovered. brand.500 is 5.69:1 on that background and 2.46:1 against body text — still short of the 3:1 a colour-only link wants, which the underline work addresses separately, but a clear improvement and consistent with the rest of the app. On the OSS build (no private preset) links move from brand.500 #3182ce to the base preset's brand.600 #2b6cb0. The OSS brand ramp is the family blue, whose 500 is a fill grade at 4.03:1 — below AA as text. Nobody ships that build, but it should not carry a failing default. Links that deliberately inherit are untouched: the sidebar logo and the serial traceback link sit inside dark surfaces, the search result row is itself the link, and the MakeCode link in the load-error toast takes the toast's white. --- src/common/GenericDialog.tsx | 2 +- src/common/PostSaveDialog.tsx | 2 +- src/documentation/api/ApiDocumentation.tsx | 2 +- src/documentation/common/DocumentationContent.tsx | 6 +++--- src/documentation/ideas/IdeasDocumentation.tsx | 2 +- src/editor/ModuleOverlay.tsx | 2 +- src/workbench/AboutDialog/AboutDialog.tsx | 10 +++++----- src/workbench/WelcomeDialog.tsx | 2 +- src/workbench/connect-dialogs/ConnectDialog.tsx | 2 +- src/workbench/connect-dialogs/FirmwareDialog.tsx | 4 ++-- src/workbench/connect-dialogs/NotFoundDialog.tsx | 10 +++------- 11 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/common/GenericDialog.tsx b/src/common/GenericDialog.tsx index fc4310ceb..febea7a64 100644 --- a/src/common/GenericDialog.tsx +++ b/src/common/GenericDialog.tsx @@ -78,7 +78,7 @@ export const GenericDialogFooter = ({ variant="link" size="lg" onPress={onCloseDontShowAgain} - css={{ color: "brand.500", mr: "auto" }} + css={{ color: "fg.link", mr: "auto" }} > diff --git a/src/common/PostSaveDialog.tsx b/src/common/PostSaveDialog.tsx index 53cdc2127..f2494438e 100644 --- a/src/common/PostSaveDialog.tsx +++ b/src/common/PostSaveDialog.tsx @@ -125,7 +125,7 @@ const PostSaveDialogBody = ({ values={{ link: (chunks: ReactNode) => ( ( - + {chunks} ), diff --git a/src/documentation/common/DocumentationContent.tsx b/src/documentation/common/DocumentationContent.tsx index df5984499..573354231 100644 --- a/src/documentation/common/DocumentationContent.tsx +++ b/src/documentation/common/DocumentationContent.tsx @@ -73,7 +73,7 @@ const DocumentationApiLinkMark = ( const [, setState] = useRouterState(); return ( { e.preventDefault(); setState({ @@ -93,7 +93,7 @@ const DocumentationInternalLinkMark = ( const [state, setState] = useRouterState(); return ( { e.preventDefault(); setState( @@ -118,7 +118,7 @@ const DocumentationExternalLinkMark = ( ) => { return ( ( { link: (chunks: ReactNode) => ( diff --git a/src/workbench/connect-dialogs/FirmwareDialog.tsx b/src/workbench/connect-dialogs/FirmwareDialog.tsx index d1b61d98e..0e7052bbf 100644 --- a/src/workbench/connect-dialogs/FirmwareDialog.tsx +++ b/src/workbench/connect-dialogs/FirmwareDialog.tsx @@ -69,7 +69,7 @@ const FirmwareDialogBody = () => { values={{ link: (chunks: ReactNode) => ( { ( - + {chunks} ), @@ -109,7 +105,7 @@ const NotFoundDialogBody = ({ values={{ link: (chunks: ReactNode) => ( Date: Mon, 17 Aug 2026 13:53:07 +0000 Subject: [PATCH 4/5] Assert the app and brand presets' semantic-token keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panda accepts an unknown `semanticTokens` key silently — the override never applies and nothing fails, so a rename in @microbit/ui would leave this app on the library's colours with a green typecheck. Covers the private brand preset too, when it is linked. That preset is the reason the check exists: its four `toast*Bg` overrides went dead when the library renamed them to `surface.*`, and the brand build's toasts fell back to the library's teal. Checking it here rather than in its own repo keeps that repo free of a dependency on @microbit/ui, and runs the check where the merge actually happens. --- src/deployment/default/panda-preset.test.ts | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/deployment/default/panda-preset.test.ts diff --git a/src/deployment/default/panda-preset.test.ts b/src/deployment/default/panda-preset.test.ts new file mode 100644 index 000000000..23d79ad3c --- /dev/null +++ b/src/deployment/default/panda-preset.test.ts @@ -0,0 +1,49 @@ +/** + * (c) 2026, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { unknownSemanticTokens } from "@microbit/ui/preset-lint"; +import { createRequire } from "node:module"; +import { describe, expect, it } from "vitest"; +import { appPreset } from "./panda-preset"; + +// Panda accepts an unknown `semanticTokens` key silently — the override +// never applies and nothing fails, so a rename in @microbit/ui would leave +// this app on the library's colours with a green typecheck. See the +// CSS-variable contract in @microbit/ui's README. + +// This app's own semantic tokens, which the base preset does not define. +const introduces = [ + "colors.sidebarHeaderBg", + "colors.sidebarTablistBg", + "colors.sidebarTabSelectedText", + "colors.sidebarTabSelectedBg", +]; + +it("app preset overrides only semantic tokens @microbit/ui defines", () => { + expect(unknownSemanticTokens(appPreset, { introduces })).toEqual([]); +}); + +// The private brand preset is optional — present only when linked +// (`npm run dev:link-theme`) or installed in a brand build, exactly as +// panda.config.ts treats it. Checked here rather than in its own repo so +// that repo stays free of a dependency on this library, and because the +// merge this guards against happens on this side. +const require = createRequire(import.meta.url); +let brandPreset: unknown; +try { + brandPreset = ( + require("@microbit-foundation/python-editor-v3-microbit/panda-preset") as { + default: unknown; + } + ).default; +} catch { + brandPreset = undefined; +} + +describe.skipIf(!brandPreset)("private brand preset", () => { + it("overrides only semantic tokens @microbit/ui defines", () => { + expect(unknownSemanticTokens(brandPreset, { introduces })).toEqual([]); + }); +}); From c4232774bd23a4fe3cf69853d5d8659c19e2c49e Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Tue, 18 Aug 2026 10:21:33 +0000 Subject: [PATCH 5/5] Guard the condition-object tokens in both presets Assert @microbit/ui's droppedConditionTokens alongside the existing key check, for the app preset and (when linked) the brand preset: a flat override of a { base, _onDark } token merges wholesale and silently drops the dark-surface flip. --- src/deployment/default/panda-preset.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/deployment/default/panda-preset.test.ts b/src/deployment/default/panda-preset.test.ts index 23d79ad3c..37bfeeb64 100644 --- a/src/deployment/default/panda-preset.test.ts +++ b/src/deployment/default/panda-preset.test.ts @@ -3,7 +3,10 @@ * * SPDX-License-Identifier: MIT */ -import { unknownSemanticTokens } from "@microbit/ui/preset-lint"; +import { + droppedConditionTokens, + unknownSemanticTokens, +} from "@microbit/ui/preset-lint"; import { createRequire } from "node:module"; import { describe, expect, it } from "vitest"; import { appPreset } from "./panda-preset"; @@ -25,6 +28,12 @@ it("app preset overrides only semantic tokens @microbit/ui defines", () => { expect(unknownSemanticTokens(appPreset, { introduces })).toEqual([]); }); +// A flat override of a { base, _onDark } token merges wholesale and +// silently drops the dark-surface flip — the same failure, one door over. +it("app preset keeps every condition the base preset's tokens carry", () => { + expect(droppedConditionTokens(appPreset)).toEqual([]); +}); + // The private brand preset is optional — present only when linked // (`npm run dev:link-theme`) or installed in a brand build, exactly as // panda.config.ts treats it. Checked here rather than in its own repo so @@ -46,4 +55,8 @@ describe.skipIf(!brandPreset)("private brand preset", () => { it("overrides only semantic tokens @microbit/ui defines", () => { expect(unknownSemanticTokens(brandPreset, { introduces })).toEqual([]); }); + + it("keeps every condition the base preset's tokens carry", () => { + expect(droppedConditionTokens(brandPreset)).toEqual([]); + }); });