Adopt @microbit/ui - #1248
Draft
microbit-matt-hillsdon wants to merge 104 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.
GenericDialog's returnFocusOnClose has been a no-op since the RAC port (react-aria restores focus to the trigger by default); drop the prop and the returnFocus state its two remaining callers threaded into it. MoreButton.tsx had no importers (everything uses ShowMoreButton).
Chakra passed aria-valuetext="21 °C" etc.; react-aria's Slider has no valuetext passthrough, so SRs heard bare numbers since the port. Use formatOptions for the two sanctioned units: temperature (celsius, long) and compass heading (degree). Gated on Intl unit-style support (Safari 14.1/iOS 14.5) because react-aria's fallback throws for anything except degree/narrow, which is what compass heading uses below the gate. The accelerometer/compass axes' mg/nT have no Intl equivalent and stay bare; light/sound/pins never had units. Also corrects RangeSensor.unit to string|undefined per the simulator's own types.
Replaces the formatOptions approach (ead613b): the unit is constant per slider, so it belongs in the name - announced once on focus - rather than repeated on every value change. This also covers mg/nT (which Intl's sanctioned unit list cannot express), reads uniformly across all sliders and devices, and drops the Safari 14.1/iOS 14.5 feature gate entirely. Unit names are intl messages ("milli-g", "nanotesla", ...) so screen readers aren't left guessing at "mg", and they'll pick up translations via Crowdin (English fallback meanwhile, per convention).
AGENTS.md now points at the playbook's gotcha catalog (which is where the retired doc's transferable lessons moved); the fidelity harness comments cite the playbook; the density preset comment carries its revisit context inline.
Transferable lessons moved to ../ui/docs/migration-playbook.md (gotchas 23-26, fidelity worked method and harness notes, roadmap/vocabulary updates); the parked brand-ramp material to the private repo's docs/brand-ramp-review.md; the pre-merge manual review checklist to python-manual-review.md (untracked); drop historical content.
The Safari <15.4 cascade-layer flattening (production-only PostCSS)
emulates layers with specificity boosts, and can only see the CSS it
processes per file. The base preset's Chakra-parity global reset
(* { border-color: gray.200 }) therefore ends up beating both
CodeMirror's runtime-injected styles and other app .css files in
production, while dev (real @layer) renders correctly:
- editor caret and drop cursor went gray.200 (the report)
- autocomplete/hover tooltip borders lost gray.400
- structure-highlighting l-shapes lost their code.blockBorder teal
- lint severity bars/marker triangles would lose red/orange
- docs-tooltip code chips lost blimpTeal.100
!important is the one lever that still wins from unprocessed CSS;
applied to exactly these border colours with pointers to the
explanation in CodeMirror.css. Verified in a local production preview:
caret black, l-shapes teal, autocomplete border gray.400. CM base-theme
gutter/panel #ddd borders also lose to gray.200 - imperceptible,
accepted.
alpha.7 moves the Chakra-parity * { border-color; word-wrap } defaults
from globalCss (base layer, specificity-boosted by the production
cascade-layer flattening) to @microbit/ui/reset.css, imported here into
the reset layer via layers.css. That systemic fix supersedes the
interim per-site !importants (7e02fff), reverted here: verified in a
production preview - caret black, structure-highlight l-shapes teal,
autocomplete tooltip border gray.400 - with the built CSS's global
reset rule confirmed unboosted.
The Chakra original was Link as="button" - content-sized, left-aligned, inheriting the docs context font size. The port's Button variant="unstyled" kept the recipe's centring, md height (2.2rem - the taller focus box) and md font size (14.4px vs 14px). Undo all three in the css override; probed identical to the live Chakra app (h=21px, flex-start, 14px). Slipped past the fidelity dual-run because the docs port predates the pre-flip baseline - only the live site shows the Chakra original.
'content' is a Panda utility name (the CSS property), and Panda extracts utility-named props from any capitalised JSX component (playbook gotcha #17) - the unresolvable PortableText values emitted two broken empty rules, the long-standing 'sheet:process' errors in every codegen run. 'blocks' matches the BlockContent API it feeds. panda cssgen now runs error-free.
Brings the Collapse flow-root measurement fix: expanded API docs no longer clip their last line (gc.mem_alloc repro verified against the published package - 74px measured, nothing clipped).
Chakra's Tabs hid all panels at index -1; react-aria retains the previous selection, so the selected panel's content (e.g. the pre-release notice) bled out of the 86px compact pane over the editor.
Drop the Chakra-era gray.100/gray.200 hover/active restatements and the padding that widened the button: the plain ghost recipe (as used by SearchDialog's clear button) gives the lighter blackAlpha hover, and the square button's hover circle nests into the pill's rounded end.
…size The alternatives select in reference documentation was size="sm" under Chakra; the port dropped it while the input recipe was single-size. alpha.9 also fixes the invisible unselected checkbox border and copy buttons in unpermissioned/insecure contexts (useClipboard fallback).
These two were Chakra Button variant="link" (semibold via the Button base style); the shared recipe's link variant is normal-weight (ported from ml-trainer and matching this app's other link buttons, which were Chakra <Link as="button">). Site-local bold to match the adjacent "Beta release" label.
Chakra's <Divider borderWidth="1px"> set all four borders on the zero-height <hr>, so the top and bottom 1px borders stacked into a visually 2px rule — heavier than the plain 1px dividers in the inner lists. The port (bfe5f30) dropped the prop as apparently redundant. Ask for the 2px explicitly.
The Chakra theme's components.Text defaulted Text to md — 0.9rem on this app's shrunken fontSize scale — and the port to @microbit/ui's defaultless Text left unsized <Text> inheriting the 16px document size (seen on the minimised simulator data-log row count and traceback summary). Set via the new text recipe's defaultVariants. Requires @microbit/ui > 0.1.0-alpha.9 (the text recipe).
With @microbit/ui's NativeSelect wrapper now width:100% (restoring the Chakra Select fill that the simulator gesture select lost), width constraints belong on the wrapper — where Chakra's layout-prop split put them — not the select, which fills it. Requires @microbit/ui > 0.1.0-alpha.9 (wrapperCss).
Text recipe (app preset defaults Text to md), Chakra slider root geometry, and NativeSelect full-width + wrapperCss — the releases the last three commits depend on.
Divider thickness variant + decorative-by-default, and the ButtonGroup attached-children border overlap (restores the Save/Send split buttons' 1px seam).
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.
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
TODO
danger.500red vs Chakra'sred.600outline— slight shade change, accepted provisionally.