diff --git a/frontend/src/__tests__/new-project-starter-sections.test.ts b/frontend/src/__tests__/new-project-starter-sections.test.ts index ae2345fb..ddd0568b 100644 --- a/frontend/src/__tests__/new-project-starter-sections.test.ts +++ b/frontend/src/__tests__/new-project-starter-sections.test.ts @@ -2,9 +2,10 @@ * NewProjectDialog starter sections — display order and overlay cards. * * The dialog hardcodes its sections; overlay-registered kinds (M5Stack, - * XIAO C6, XIAO RP2040) surface only when the private overlay registered - * them. Guards the operator's requested order: M5Stack (Cardputer ADV, then - * Core) sits ahead of STM32, and an OSS build shows no empty M5Stack block. + * the partner sections, XIAO C6, XIAO RP2040) surface only when the private + * overlay registered them. Guards the operator's requested order: the + * partner sections (M5Stack, Seeed Studio, DFRobot, Pimoroni, Espressif) + * sit between ESP32 and STM32, and an OSS build shows none of them empty. */ import { describe, it, expect } from 'vitest'; import { buildStarterSections } from '../components/editor/NewProjectDialog'; @@ -45,6 +46,45 @@ describe('NewProjectDialog starter sections', () => { expect(m5.entries[0].blurb).toBe('ESP32-S3 card computer'); }); + it('full partner overlay: sections sit between M5Stack and STM32, catalog order', () => { + const defs = [ + def('cardputer-adv', 'M5 Cardputer ADV', 'x'), + def('m5stack-core', 'M5Stack Core', 'x'), + def('xiao-esp32s3-sense', 'XIAO ESP32-S3 Sense', 'x'), + def('unihiker-m10', 'UNIHIKER M10', 'x'), + def('pimoroni-pico-plus-2w', 'Pico Plus 2 W', 'x'), + def('badger-2350', 'Badger 2350', 'x'), + def('stellar-unicorn', 'Stellar Unicorn', 'x'), + def('esp32-c3-lcdkit', 'ESP32-C3-LCDkit', 'x'), + ]; + expect(titles(defs)).toEqual([ + 'Arduino', + 'ESP32', + 'M5Stack', + 'Seeed Studio', + 'DFRobot', + 'Pimoroni', + 'Espressif', + 'STM32', + 'Raspberry Pi', + ]); + const pim = buildStarterSections(defs).find((s) => s.title === 'Pimoroni')!; + expect(pim.entries.map((e) => e.kind)).toEqual([ + 'pimoroni-pico-plus-2w', + 'badger-2350', + 'stellar-unicorn', + ]); + }); + + it('embargoed partner boards (not registered) leave their section unrendered', () => { + // Pimoroni launched, Espressif still under embargo: no Espressif block. + const defs = [ + def('pimoroni-pico-plus-2w', 'Pico Plus 2 W', 'x'), + def('badger-2350', 'Badger 2350', 'x'), + ]; + expect(titles(defs)).toEqual(['Arduino', 'ESP32', 'Pimoroni', 'STM32', 'Raspberry Pi']); + }); + it('a partial overlay (only one M5 kind) still renders the section with that card', () => { const m5 = buildStarterSections([def('m5stack-core', 'M5Stack Core', 'x')]).find( (s) => s.title === 'M5Stack', diff --git a/frontend/src/components/editor/NewProjectDialog.tsx b/frontend/src/components/editor/NewProjectDialog.tsx index 17df9405..98289a30 100644 --- a/frontend/src/components/editor/NewProjectDialog.tsx +++ b/frontend/src/components/editor/NewProjectDialog.tsx @@ -156,6 +156,21 @@ const PREFERRED_BLINK_EXAMPLE: Record = { // is the first run. Both ids carry a captured gallery thumb. 'cardputer-adv': 'cardputer-adv-hello', 'm5stack-core': 'm5stack-core-m5-helloworld', + // Partner boards (overlay kinds), one representative first-run each. The + // Stellar/Badger entries are REQUIRED, not just preferred: their gallery + // examples use `boards[]` + boardFilter, which the generic single-board + // search above never matches. + 'xiao-esp32s3-sense': 'xiao-esp32s3-sense-hello', + 'unihiker-m10': 'unihiker-blink-p2', + 'pimoroni-pico-plus-2w': 'pimoroni-pico-plus-2w-blink', + 'badger-2350': 'badger-2350-badgeos', + 'stellar-unicorn': 'stellar-unicorn-rainbow', + // Espressif devkits — launch-embargoed; cards appear when the boards do. + 'esp32-c3-lcdkit': 'esp32-c3-lcdkit-knob-dial', + 'esp32-s3-eye': 'esp32-s3-eye-camera-lcd', + 'esp-vocat': 'esp-vocat-face', + 'esp-sensairshuttle': 'esp-sensair-lcd-dash', + 'esp32-p4': 'esp32-p4-blink', }; /** Dynamic import keeps the (large) gallery data out of the editor bundle @@ -255,6 +270,31 @@ export function buildStarterSections(defs: ProBoardDef[]): StarterSection[] { // they run on Free and are a friendlier first pick than the Pro-gated // STM32 family. Cardputer first, then the Core. { title: 'M5Stack', entries: [...pro('cardputer-adv'), ...pro('m5stack-core')] }, + // Hardware partners (overlay kinds, same shape as M5Stack): each section + // lists only the boards the overlay actually registered, so an OSS build + // and launch-embargoed items (e.g. the Espressif devkits pre-launch) + // simply render nothing. The XIAO S3/C3/C6 and XIAO RP2040 stay in the + // chip-family sections above as the generation representatives. + { title: 'Seeed Studio', entries: [...pro('xiao-esp32s3-sense')] }, + { title: 'DFRobot', entries: [...pro('unihiker-m10')] }, + { + title: 'Pimoroni', + entries: [ + ...pro('pimoroni-pico-plus-2w'), + ...pro('badger-2350'), + ...pro('stellar-unicorn'), + ], + }, + { + title: 'Espressif', + entries: [ + ...pro('esp32-c3-lcdkit'), + ...pro('esp32-s3-eye'), + ...pro('esp-vocat'), + ...pro('esp32-p4'), + ...pro('esp-sensairshuttle'), + ], + }, { title: 'STM32', entries: STM32_BOARDS.map(oss) }, { title: 'Raspberry Pi',