Adopt @microbit/ui - #1248
Draft
microbit-matt-hillsdon wants to merge 127 commits into
Draft
Conversation
Census (July 2026, moved from ml-trainer's RAC-MIGRATION.md), the semantic-token pre-work plan (startable while still on Chakra), and the app-specific migration items. Method lives in the @microbit/ui repo's docs/migration-playbook.md.
Plus two status-log caveats: the revived kit scripts are unexercised (first differ run doubles as validation), and the private theme package pulled changes postdating the census.
Semantic-token pre-work for the RAC/Panda migration (lockstep with the private theme package). The OSS theme now carries the same component structure as the private theme, with brand divergence expressed through a new semanticTokens block rather than forked component configs: - Button: adopt 2rem radius, add baseStyle + unstyled/outline/language variants (language via languageText* tokens) - Alert: toast variant sets per-status bg via toast*Bg tokens - Container/Tabs: sidebar chrome via sidebar* tokens (incl. gradient) - Tooltip: new OSS config (fontSize md), registered in the theme The resolved-theme differ now shows zero structural divergence between the OSS and private themes. See RAC-MIGRATION.md.
Status-log the semantic-token pre-work: differ run + kit fix, census cross-check, divergence classification, the executed 7-step convergence, and toCSSVar verification of the semantic-token CSS-var emission. Adds parked notes: the brand/brand2-vs-purple/teal colour-scheme finding (teal does not map to brand2), brand-ramp data nits, and the languageText base-preset default question for the shared-ui repo.
Record the headless side-by-side vs python.microbit.org/v/3: the sidebar Tabs gradient renders byte-identical (brand.500 -> blimpTeal.400) to live, confirming the inline-string -> semantic-token refactor preserves the branded appearance. Notes the sandbox setup needed (node_modules reinstall for platform-native binaries, proxy-authed Chromium, CI=true vite).
RAC/Panda migration step 2: install Panda alongside Chakra without porting any components. The app renders unchanged on Chakra; the generated CSS is inert until components port. - Deps: @pandacss/dev, @csstools/postcss-cascade-layers; @microbit/ui linked via file:../ui/packages/ui (ships as source); react-aria-components (peer). - panda.config.ts: preset stack (preset-base -> @microbit/ui/base-preset -> app preset -> optional private brand preset), eject:true, preflight:false, include app + @microbit/ui source. - App preset src/deployment/default/panda-preset.ts: code.* palette + sidebar/ language semantic tokens the base preset lacks. - Coexistence CSS: panda script does codegen + cssgen + unlayer; styled-system alias in tsconfig + vite; styled-system.css imported first in index.tsx; generated output ignored by git/eslint/prettier. - Legacy Safari (<15.4, app targets Safari/iOS 14): postcss.config.cjs runs expandLogicalShorthands + cascade-layers in production only. Providers + i18n are deferred to step 3 (first shared component port). See RAC-MIGRATION.md.
Status-log step 2: preset stack + coexistence CSS wiring, legacy-Safari postcss, verification (panda/typecheck/build/lint clean, app unchanged on Chakra), the section-D differ improvement (base-preset merge; 55->13, residual = known brand-ramp divergence), and the deferral of providers/i18n to step 3.
RAC/Panda migration step 3, pilot area. Replace Chakra AlertDialog/Button/Text with @microbit/ui Modal(role=alertdialog)+Button; onClick->onPress; cancel = default (outline), destructive = warningSolid. Drop leastDestructiveRef (gotcha #15) for autoFocus on cancel. - App preset: add python-editor's outline button variant + defaultVariants (outline), the census-flagged per-app default (base recipe defaults to secondary). staticCss [*] covers it. - vite resolve.dedupe for react/react-dom/react-aria-* — @microbit/ui is dev-linked as source, so its react imports otherwise resolve to the ui monorepo copy (two Reacts -> invalid-hook crash; gotcha #19). Verified byte-identical to live python.microbit.org/v/3: cancel brand.500 outline, replace white-on-red.500, both pill radius.
Log the first coexistence port (ConfirmDialog -> @microbit/ui Modal), the per-app outline button default, gotcha #19 (dual React from dev-linking), and the byte-identical-to-live verification.
RAC/Panda migration step 3, dialog infrastructure. The shared GenericDialog (shell for 8 dialogs) and GenericDialogFooter move onto @microbit/ui Modal + Button + Panda HStack. Props API preserved, so the consumer dialogs are untouched and render their (still-Chakra) content inside the RAC Modal during coexistence. - Modal/ModalOverlay/ModalContent -> Modal + ModalCloseButton/Header/Body/ Footer; minWidth via contentCss + isCentered. - returnFocusOnClose is now a no-op (RAC restores focus; gotcha #15), kept for API compat; finalFocusRef still wired. - Footer: HStack from styled-system/jsx; don't-show-again Link->Button variant=link; close solid->primary. Verified typecheck/build clean and the PostSaveDialog (exercises shell + footer) visually identical to live python.microbit.org/v/3.
RAC/Panda migration step 3. Finish the shared common/ dialogs: - PostSaveDialog/MultipleFilesDialog bodies: Chakra VStack/Text/Link -> Panda styled-system/jsx VStack + @microbit/ui Text/Link. - InputDialog: own Modal -> @microbit/ui Modal + Panda styled.form; shared submit() (form onSubmit for Enter, footer Button onPress); primary action, isDisabled when invalid. - ProgressDialog: own Modal -> @microbit/ui Modal + ProgressBar (aria-label from 'loading'); non-dismissable during the operation. Typecheck/build/lint clean; smoke-tested Save -> InputDialog -> PostSaveDialog on the local branded build (renders, no errors, body matches live).
RAC/Panda migration step 3. All 9 workbench/connect-dialogs/ files (ConnectCableDialog, Overlay, WebUSBErrorDialog, TransferHexDialog, ConnectDialog, WebUSBDialog, FirmwareDialog, NotFoundDialog, ConnectHelpDialog) move off Chakra: Text/Link/Image -> @microbit/ui, layout -> styled-system/jsx, Button solid->primary / default->outline, onClick->onPress, useMediaQuery->useBreakpointValue, useDisclosure->useState. FirmwareDialog uses an <a> with button() recipe classes (no LinkButton primitive yet - flagged for the library). No @chakra-ui imports remain in the folder; tsc/build/lint clean; smoke-tested 'Send to micro:bit' -> ConnectDialog renders correctly.
RAC/Panda migration step 3. WelcomeDialog, FeedbackForm, AboutDialog move off Chakra; common/ModalCloseButton deleted (all dialogs use @microbit/ui's). - AboutDialog <Table> -> Panda styled HTML table (app-side, owner decision). - AspectRatio -> native aspectRatio CSS (gotcha #11). - Kept Chakra Collapse (AboutDialog read-more; library gap), inlined useClipboard, SimpleGrid->Grid, useDisclosure->useState. - Modals without a header got a (localized) aria-label for RAC. tsc/build/lint clean; smoke-tested Help->About renders correctly (table, images, buttons, collapse), no console errors.
- ToastProvider mounted inside TranslationProvider; bin/compile-lang.mjs merges @microbit/ui's lang catalogs into the per-locale message chunks (i18n:compile). - use-action-feedback, language-server toast plumbing and TranslationProvider move to the library's context-free useToast/ToastFn. Toast id dedupe replaces the manual isActive guard; the XTerm paste toast's bottom-right position is dropped (single top region). - common/ leaves ported: MaybeLink, AreaHeading, ErrorBoundary, Placeholder, Spinner, YoutubeVideoEmbed, ScrollablePanel, HeadedScrollablePanel, FileDropTarget, ExpandCollapseIcon; BoxProps forwarding dropped where consumers never used it. - OSS panda preset gains this app's gray.10/25 values.
…okens - CollapsibleButton: icon/button modes on library IconButton/Button; _collapsed becomes a Panda SystemStyleObject; dead buttonWidth dropped. - FileInputButton: library Tooltip + plain hidden file input. - Consumer prop translation across project buttons, SerialBar, SideBarHeader search, HideSplitViewButton (+ its three call sites). ProjectAreaNav Reset colorScheme=red becomes variant=warning. - App preset gains a zIndex token scale mirroring zIndex.ts: imported constants in Panda styles are not statically extractable (the previous breadcrumb z-index only worked via a coincidental literal elsewhere).
- Menus (FileRow, SendButton, SaveMenuButton, ChooseMainScriptQuestion, MoreMenuButton) onto MenuTrigger/MenuList/MenuItem; split buttons keep the attached look via ButtonGroup isAttached. Portal + explicit menu z-index constants dropped (RAC popovers portal natively at the dropdown z-index). - ChooseMainScriptQuestion uses the new library MenuOptionGroup/ MenuItemOption (menuitemradio semantics). - ProjectNameQuestion/NewFileNameQuestion onto the library TextField. - ProjectArea/ProjectAreaNav/ProjectNameEditable/ProjectActionBar onto Panda layout + library Text/List/Tooltip; ProjectActionBar renders a styled.section (no as-prop polymorphism in Panda). - project-actions dialog/toast JSX onto library Text/Link/List. - SendButton's tooltip onFocus suppression hack removed: RAC tooltips only open on keyboard focus-visible.
- SettingsDialog/LanguageDialog onto the library Modal; language cards use the base preset's language variant; SimpleGrid becomes Panda Grid. - SettingsMenu onto MenuTrigger with a new app-preset sidebar button variant (the Chakra ghost-based variant with the callers' white folded into the variant base). - SettingsArea onto the new library NumberField (react-aria NumberField, Chakra NumberInput look), library Checkbox and NativeSelect. - SelectFormControl renders a styled label row + NativeSelect.
- SerialArea renders a styled.section; XTerm/SerialBar use inline style + runtime token() for dynamic values (terminal bg, three-way status bg, compactSize-derived height). - XTerm's useToken(fonts, code) becomes Panda token(fonts.code), backed by a new fonts.code token in the OSS app preset. - SerialMenu/SerialBar onto MenuTrigger + the sidebar variant; SerialHelp onto the library Modal with the new library Kbd/Code components. - TracebackLink/MaybeTracebackLink onto library Link/Text.
Owner decision: SplitView and the sidebar Tabs stay app components — RAC provides tab behaviour and the split-view value is app layout machinery; a generic library Tabs is parked until a second family consumer exists. Runtime-computed pane dimensions move to inline styles; static conditionals stay extractable ternaries; zIndex constant becomes the splitViewDivider token; BoxProps forwarding dropped.
Chakra-era <Divider borderWidth="1px"> drew both edges of the zero-height <hr> — a visually 2px rule — and the port flattened these to 1px (same class as the top-level item dividers, found by pickaxing history for Divider borderWidth). Expressed via the library's new thickness="thick"; DocumentationTopLevelItem's hand-rolled 2px moves to it too. The search-result divider's Chakra color="gray.400" never reached the border (dividers inherited the global gray.200); keep what shipped rather than the unrendered intent.
These were Chakra <Link as="button">: no size scale, so they inherited the dialog's 16px document size. The port to Button variant="link" brought the Button size scale with it (md = 14.4px on this app's shrunken fontSizes), silently shrinking them. size="lg" (16.2px) is the nearest scale size; the link variant still zeroes lg's height/padding. Verified against production: box centres within 0.25px. ModuleOverlay's inline link is the other Link-conversion but sits in md Text where inherited and variant sizes coincide — unchanged.
The connection library signals completion by calling the progress callback with an undefined value. Since the connection library v1.0.0-beta.0 upgrade (e6b4a7d) we rendered that as 0%, so the progress bar visibly jumped backwards while the dialog stayed open during post-flash reinit/reset (most noticeable after partial flash). Hold the bar at its last value until the dialog closes instead.
classroom's Chakra theme carries the identical × 0.88 spacing/sizes grid and × 0.9 fontSizes (md+) — same 2022 change, byte-identical values — so the scale moved into the shared library as an optional preset rather than being replicated in a second app preset. Drops common-sizes.ts/font-sizes.ts and the local toTokens plumbing. Verified a no-op: regenerated CSS has an identical set of custom properties with identical values and byte-identical rules; only the emission order of :root declarations shifts (the categories now come from an earlier preset in the stack, and no token var is declared twice). The keep-vs-align-with-the-family-scale question is unchanged, just now answerable in one place. Needs the next @microbit/ui publish (the dense-preset export); the pin here still says 0.1.0-alpha.14 and wants bumping with it.
classroom's migration is extending the library, and this app tracks those changes, so @microbit/ui is source-linked here the same way the private theme package is. Both need re-creating after npm install.
The pin catches up with the library work classroom's migration added, so the `../ui` symlink this app has been carrying since `dense-preset` landed is no longer needed. AGENTS.md now describes linking as a local-development arrangement rather than as the current state, since which mode is in force will change often. Three library renderings change here, all deliberate corrections: Tooltip takes Chakra's colour, padding and radius (fba05fb), Icon sets Chakra's vertical-align itself rather than relying on Panda's preflight (2f7e18a), and Avatar shows its fallback until a photo loads (c80530d). The Icon change is what moves `va_middle` earlier in the documentation-content snapshot: the same three atomic classes in a different order, since the component now supplies the property the call site used to be alone in setting. Modal's isOpen/onClose became a union in the library and broke prop-forwarding shells, but this app has none — the eight affected files were all in ml-trainer. Verified: typecheck, lint, 217 unit tests, production build.
Its migration is done, and the harness was a migration instrument: the baseline that gave it meaning was the pre-flip Chakra build, on both sides of the OSS/private split. Against an ordinary previous commit it is a general visual-regression suite instead, and it cannot see a dependency bump at all, since it symlinks one node_modules into the baseline worktree. Reasoning and the recoverable refs are in the playbook's "Fidelity harness" section; this tree's copy — the newer generation, with the --baseline-only/--compare-only halves for paired-package runs — is at f322b68. Out with it: the Playwright fidelity project and its snapshot path, the FIDELITY/FIDELITY_NO_WEBSERVER switches, the npm script and the .fidelity ignore. The HTTPS_PROXY passthrough stays — it serves the ordinary suite in sandboxed environments, not just the harness. `playwright test --list` still enumerates 64 tests across 12 files.
Raises Safari/iOS from 14/14 to 14.1/14.5, which is a correctness fix rather than a tidy-up: flexbox `gap` landed in exactly those two versions, and this app's layout is built on the Panda stack patterns, which are gap-based. The built CSS has 18 gap declarations against 24 display:flex, so on Safari 14.0 or iOS 14.0–14.4 that spacing collapses to zero — nothing can polyfill it. The floor claimed support the layout could not honour, and no device is stranded there anyway: Safari 14.1 shipped for the same macOS range as 14.0, and every iOS 14.0 device can reach 14.5. Chrome/Edge come down from 98 to 90 to match the other two apps. The 98 was not load-bearing: it arrived wholesale in the CRA→Vite switch (4425d16), replacing CRA's usage-share default, with no stated reason, and Chrome 98 predates that commit by two years. Nothing in src uses a Chrome 91–98 era API — no structuredClone, .at(), findLast, Object.hasOwn or error.cause; the only modern method is replaceAll, which is Chrome 85. The device-connection code lives in a dependency and feature-detects, so it was never governed by this floor. The two lists also disagreed with each other, which is what let them drift: browserslist entries are OR'd, so with no explicit Firefox entry `defaults` supplied Firefox 140 while the build targeted firefox104, and the Vite switch had dropped CRA's `not op_mini all` / `not ie >0`, leaving Opera Mini and KaiOS in the resolved set. There is now one BUILD_TARGETS const mirrored into build.target and build.cssTarget, with browserslist as documentation pointing at it — the same shape as the other two apps. Deliberately not aligned: cssMinify stays esbuild rather than lightningcss. That is a rendering change, not a support declaration — lightningcss rewrites logical border-radius longhands into fragile :lang()-based physical rules — and this app's PostCSS chain is documented around the current choice. Verified: typecheck, lint, production build, and the production CSS still has its logical shorthands expanded (421 longhands, no var()-bearing shorthands), its layers flattened, and its 18 gap rules intact.
The data-microbit-org migration's library changes, released: Button isLoading keeps the label in the layout (Chakra parity), Field helper text regains its block box, NativeSelect chevron/cursor/padding parity, Heading label/subtitle hold their font size, and SharedUIProvider now passes the app locale to react-aria's I18nProvider. Mounted here inside TranslationProvider so react-aria's built-in strings follow the app language rather than the browser.
- Font size NumberField: labelPosition="side" replaces the four css props. The whole dialog now sits at md, so the number input grows to match the selects beside it (the sm was a Chakra-era accident; the labels are unchanged). - SelectFormControl deleted for NativeSelectField; createOptions becomes a local helper returning <option>s. - The two checkbox info texts become helperText: same look bar gray.700 -> gray.600, and they now reach aria-describedby.
PAIRS with a gray-ramp-v2 @microbit/ui build and the matching python-editor-v3-microbit (its gray override shrinks to the designed light stops, 10-300). Muted text written against the branded #a9aaa9 '600' moves to gray.500 (#767676 - AA, visibly darker: search headings, module overlay, radio composer - eyeball each); the zoom pills' hover/ press move 400/500 -> 300/400, the nearest stops to the branded values they were written against. The panel surfaces (gray.25 sidebar and simulator, gray.50 project pane) deliberately do not move - no renames, no light-stop changes. Left on gray.600 for the visual pass: the editor gutter numbers and xterm scrollbar (darker with the ink grade, arguably better; re-point to a light stop if not).
… press Inactive line numbers move down to gray.500 (#767676) - the lightest text-safe stop. Lighter than that is an audit-level 1.4.3 fail: gutter numbers are functional text (tracebacks point at them). The active line's number keeps gray.800. Note 4.5:1 is measured against white; on the gutter's #fcfcfc it is ~4.44:1 - gray.600 is the retreat if an audit quibbles. The zoom/undo pills' press state takes the new gray.350 press-weight stop (#b4b4b4, within a bit of the branded #b0b0b0 press) instead of the re-graded 400. Pairs with @microbit/ui gray-ramp-v2 (which adds the stop).
…value Restores the light inactive line numbers (#b4b4b4 vs the branded #a9aaa9). A known 1.4.3 exception - line numbers are functional text and the AA floor is gray.500 - tracked in an issue rather than here.
On the terminal's #333 track the family gray.600 ink (#575757) was the low-contrast option; 350 (#b4b4b4, = the parked gutter grey and near the branded #a9aaa9) reads ~6:1 against it.
Includes the two raw var(--colors-gray-25) inline-SVG fills in SideBarTab (the sites the v1 spike's rename missed). PAIRS with @microbit/ui and python-editor-v3-microbit builds that carry the rename; against older builds the 75 sites fail black/transparent.
Recompiled the message catalogs: alpha.19 adds seven ui.* ids for the react-aria strings a prop can replace (NumberField steppers, toast region, ComboBox trigger/listbox, Select placeholder and row action). Without the recompile they would fall back to English in every locale.
…n-editor-v3 into experiment-rai
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces Chakra UI v2 with microbit UI library based on react-aria-components and Panda CSS.
Accepted differences