fix(boards): a QEMU-Linux overlay board is not a Raspberry Pi
Two answers about the UNIHIKER M10 came from the family whose run path it borrows rather than from the board itself. The upgrade prompt told a free user that "Raspberry Pi emulation is a Pro feature" about a DFRobot SBC: proBoardFeatureName named the family, so every piFamily overlay board inherited the Pi's name. It now uses the board's own label whenever the kind is registered; the OSS families keep theirs. loadExample gated the guest-script branch on isPiBoardKind(), which reads a registry the overlay only fills at mount. A direct /example/<id> link could load before that: the branch was skipped and the editor opened on the board group's Arduino blink default instead of the example's script.py. vfsFiles is itself the declaration that this board's code is a guest script, so the branch no longer consults the registry. The server-side gate message names the two families instead of one board, since the QEMU-Linux lane also carries boards that are not Raspberry Pis. Test covers both, with the unregistered-kind race reproduced.
This commit is contained in:
parent
0430e8e0d4
commit
3348e1fa48
|
|
@ -30,9 +30,14 @@ BoardAccessGate = Callable[[object, str], Awaitable[bool]]
|
|||
|
||||
_gate: Optional[BoardAccessGate] = None
|
||||
|
||||
# Deliberately names the families, not one board: the QEMU-Linux lane also
|
||||
# carries boards that are not Raspberry Pis (the UNIHIKER M10 boots through
|
||||
# the same bridge), and telling their user that "Raspberry Pi emulation" is
|
||||
# gated is a message about a board they did not place.
|
||||
PRO_BOARD_MESSAGE = (
|
||||
'STM32 and Raspberry Pi emulation are Velxio Pro features. '
|
||||
'Use them on velxio.dev with a paid plan, or install Velxio Desktop.'
|
||||
'STM32 and QEMU-emulated Linux boards (Raspberry Pi, UNIHIKER) are '
|
||||
'Velxio Pro features. Use them on velxio.dev with a paid plan, or '
|
||||
'install Velxio Desktop.'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Overlay QEMU-Linux boards (ProBoardDef.piFamily) in the example loader and
|
||||
* in the Pro upgrade prompt.
|
||||
*
|
||||
* Both used to answer as if the board were a Raspberry Pi, because both read
|
||||
* a registry the overlay only fills at mount:
|
||||
*
|
||||
* - `loadExample` gated the guest-script branch on `isPiBoardKind()`. A
|
||||
* direct /example/<id> link can load before the overlay registers its
|
||||
* kinds (the pro overlay registered the partner examples first), so the
|
||||
* branch was skipped and the editor opened on the board group's Arduino
|
||||
* blink default instead of the example's script.py.
|
||||
* - `proBoardFeatureName` named the FAMILY, so a free user clicking Run on
|
||||
* a UNIHIKER M10 was told "Raspberry Pi emulation is a Pro feature" about
|
||||
* a board that is not a Raspberry Pi.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import { useEditorStore } from '../store/useEditorStore';
|
||||
import { useElectricalStore } from '../store/useElectricalStore';
|
||||
import { registerProBoards } from '../lib/proBoardRegistry';
|
||||
import { proBoardFeatureName } from '../lib/proBoardGate';
|
||||
import { loadExample } from '../utils/loadExample';
|
||||
import type { ExampleProject } from '../data/examples';
|
||||
import type { BoardKind } from '../types/board';
|
||||
|
||||
// Never registered — stands in for a board whose overlay import has not
|
||||
// landed yet at load time.
|
||||
const UNREGISTERED = 'linux-test-unregistered';
|
||||
// Registered below, as the overlay does at mount.
|
||||
const REGISTERED = 'linux-test-registered';
|
||||
|
||||
const SCRIPT = '# guest script\nprint("hello from the guest")\n';
|
||||
|
||||
const exampleFor = (kind: string): ExampleProject =>
|
||||
({
|
||||
id: `example-${kind}`,
|
||||
title: `${kind} example`,
|
||||
description: 'guest script uploaded to the QEMU-Linux board',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardType: kind,
|
||||
code: '',
|
||||
boards: [{ boardKind: kind, x: 40, y: 40, vfsFiles: { 'script.py': SCRIPT } }],
|
||||
components: [],
|
||||
wires: [],
|
||||
}) as unknown as ExampleProject;
|
||||
|
||||
function resetStores() {
|
||||
const sim = useSimulatorStore.getState();
|
||||
for (const id of sim.boards.map((b) => b.id)) sim.removeBoard(id);
|
||||
useElectricalStore.getState().setPaused(false);
|
||||
}
|
||||
|
||||
describe('overlay QEMU-Linux boards', () => {
|
||||
beforeEach(resetStores);
|
||||
|
||||
it('loads the example guest script even before the kind is registered', async () => {
|
||||
await loadExample(exampleFor(UNREGISTERED));
|
||||
|
||||
const sim = useSimulatorStore.getState();
|
||||
expect(sim.boards).toHaveLength(1);
|
||||
const files = useEditorStore.getState().fileGroups[sim.boards[0].activeFileGroupId] ?? [];
|
||||
expect(files.map((f) => f.name)).toContain('script.py');
|
||||
expect(files.find((f) => f.name === 'script.py')?.content).toBe(SCRIPT);
|
||||
// The board group's Arduino default must be gone, not sitting next to it.
|
||||
expect(files.some((f) => (f.content ?? '').includes('Arduino Blink Example'))).toBe(false);
|
||||
});
|
||||
|
||||
it('names the board itself in the upgrade prompt, not the family it rides', () => {
|
||||
registerProBoards([
|
||||
{
|
||||
kind: REGISTERED,
|
||||
label: 'Test Linux SBC',
|
||||
fqbn: null,
|
||||
description: 'overlay QEMU-Linux board',
|
||||
tag: 'velxio-linux-test',
|
||||
size: { w: 100, h: 100 },
|
||||
piFamily: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(proBoardFeatureName(REGISTERED)).toBe('Test Linux SBC emulation');
|
||||
// The OSS families keep their family names — every kind in them is one.
|
||||
expect(proBoardFeatureName('raspberry-pi-4' as BoardKind)).toBe('Raspberry Pi emulation');
|
||||
expect(proBoardFeatureName('stm32-bluepill' as BoardKind)).toBe('STM32 emulation');
|
||||
expect(proBoardFeatureName('arduino-uno' as BoardKind)).toBe('this board');
|
||||
});
|
||||
});
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
import type { BoardKind } from '../types/board';
|
||||
import { isPiBoardKind, isStm32BoardKind } from '../types/board';
|
||||
import { getProBoard } from './proBoardRegistry';
|
||||
|
||||
export type BoardGateDecision = 'allow' | 'block';
|
||||
|
||||
|
|
@ -82,8 +83,18 @@ export function triggerProUpgradePrompt(featureName: string): void {
|
|||
);
|
||||
}
|
||||
|
||||
/** Human label for the upgrade prompt, per board family. */
|
||||
/**
|
||||
* Human label for the upgrade prompt.
|
||||
*
|
||||
* An overlay board is named by its own label, not by the family whose run
|
||||
* path it borrows: `piFamily` routes the UNIHIKER M10 through the Raspberry
|
||||
* Pi bridge, and the prompt told its user that "Raspberry Pi emulation is a
|
||||
* Pro feature" about a board that is not a Raspberry Pi. The OSS families
|
||||
* keep their family names — every kind in them really is one.
|
||||
*/
|
||||
export function proBoardFeatureName(kind: BoardKind | string): string {
|
||||
const def = typeof kind === 'string' ? getProBoard(kind) : undefined;
|
||||
if (def) return `${def.label} emulation`;
|
||||
if (isStm32BoardKind(kind)) return 'STM32 emulation';
|
||||
if (isPiBoardKind(kind)) return 'Raspberry Pi emulation';
|
||||
return 'this board';
|
||||
|
|
|
|||
|
|
@ -189,7 +189,14 @@ export async function loadExample(
|
|||
useSimulatorStore.getState().updateBoard(boardId, { sdFiles: eb.sdFiles });
|
||||
}
|
||||
|
||||
if (eb.vfsFiles && isPiBoardKind(eb.boardKind)) {
|
||||
// `vfsFiles` IS the declaration that this board's code is a guest
|
||||
// script — no isPiBoardKind() check. That guard read the Pi-family
|
||||
// registry, which an overlay fills at mount: a direct /example/<id>
|
||||
// link to an overlay QEMU-Linux board (UNIHIKER M10) could load before
|
||||
// its kind was registered, the branch was skipped, and the editor
|
||||
// opened on the board group's Arduino blink default instead of the
|
||||
// example's script. Only QEMU-Linux examples carry vfsFiles.
|
||||
if (eb.vfsFiles) {
|
||||
// Pi example scripts go into the board's REGULAR file group — they
|
||||
// are edited in Monaco like any other board's code, and the run
|
||||
// path uploads the group into the guest home. (The old separate
|
||||
|
|
|
|||
Loading…
Reference in New Issue