feat(pi-family): guestSetup seam + board-branded workspace texts

Overlay QEMU-Linux boards are not Raspberry Pis: ProBoardDef.guestSetup
lets a board send one shell line at the boot prompt (hostname/PS1/clear)
to de-brand the generic image, sent before piBooted flips so uploads
cannot interleave; the workspace start button and power-on title carry
the board's own label for non raspberry-pi kinds; the compile console
line uses the board label instead of 'Raspberry Pi 3B'.
This commit is contained in:
David Montero Crespo 2026-07-28 14:36:13 +02:00
parent 7bb54e8d32
commit 55dd25eeba
4 changed files with 34 additions and 9 deletions

View File

@ -502,9 +502,9 @@ export const EditorToolbar = ({
const blog = (type: CompilationLog['type'], message: string) =>
addLog({ timestamp: new Date(), type, message, target: boardTarget });
// Raspberry Pi 3B doesn't need arduino-cli compilation
// QEMU-Linux boards don't need arduino-cli compilation
if (isPiBoardKind(kind)) {
blog('info', 'Raspberry Pi 3B: no compilation needed — run Python scripts directly.');
blog('info', `${boardLabel}: no compilation needed — run Python scripts directly.`);
setMessage({ type: 'success', text: 'Ready (no compilation needed)' });
setCompiling(false);
return;

View File

@ -75,6 +75,15 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
// Display the active board's real name (Pi 3B / 4B / 5 / Zero / …) instead of
// a hardcoded "Raspberry Pi 3B" — the workspace serves the whole Pi family.
const boardLabel = board ? boardDisplayName(board) : 'Raspberry Pi';
// Overlay QEMU-Linux boards (piFamily kinds) are not Raspberry Pis — their
// start button / power title must carry the board's own name.
const isRaspberry = !board || board.boardKind.startsWith('raspberry-pi');
const startLabel = isRaspberry
? t('editor.pi.startPi')
: t('editor.pi.startBoard', { board: boardLabel, defaultValue: 'Start {{board}}' });
const powerOnTitle = isRaspberry
? t('editor.pi.powerOnTitle')
: t('editor.pi.powerOnBoardTitle', { board: boardLabel, defaultValue: 'Power on {{board}}' });
// Three display states: offline (!running) → booting (running, guest Linux
// still coming up) → ready (running + booted shell). `running` flips on click
@ -193,9 +202,9 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
<button
style={{ ...styles.toolbarBtn, color: '#4caf50', borderColor: '#4caf50' }}
onClick={() => startBoard(boardId)}
title={t('editor.pi.powerOnTitle')}
title={powerOnTitle}
>
<PlayIcon />{t('editor.pi.startPi')}
<PlayIcon />{startLabel}
</button>
) : (
<>
@ -268,7 +277,7 @@ export const RaspberryPiWorkspace: React.FC<RaspberryPiWorkspaceProps> = ({ boar
<div style={styles.offlineTitle}>{t('editor.pi.offlineTitle', { board: boardLabel })}</div>
<div style={styles.offlineSubtitle}>{t('editor.pi.offlineSubtitle')}</div>
<button style={styles.startBtn} onClick={() => startBoard(boardId)}>
<PlayIcon />{t('editor.pi.startPi')}
<PlayIcon />{startLabel}
</button>
<div style={styles.offlineNote}>
{t('editor.pi.offlineNote1')}

View File

@ -59,6 +59,10 @@ export interface ProBoardDef {
* bridge (backend qemu WebSocket, VFS panel, boot terminal). The overlay
* must also register a matching backend profile for the kind. */
piFamily?: boolean;
/** One shell line sent to the guest right after it reaches the boot
* prompt (piFamily boards only). Lets a board de-brand the generic
* image, e.g. set its own hostname/PS1 and clear the stock motd. */
guestSetup?: string;
/** Canvas renderer. Receives the placed board's props; return a React node.
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
render?: (props: { id: string; x: number; y: number; running: boolean }) => React.ReactNode;

View File

@ -1244,11 +1244,23 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
};
// Guest Linux finished booting (shell prompt reached). Flip piBooted so
// the workspace swaps the "Booting…" overlay for the live terminal and
// uploads know the shell is ready.
// uploads know the shell is ready. Overlay boards may declare a
// guestSetup line (hostname/PS1/clear) to de-brand the generic image;
// it runs before piBooted flips so the VFS upload (gated on piBooted)
// cannot interleave with it.
bridge.onBooted = () => {
set((s) => ({
boards: s.boards.map((b) => (b.id === id ? { ...b, piBooted: true } : b)),
}));
const setup = getProBoard(boardKind)?.guestSetup;
const flip = () =>
set((s) => ({
boards: s.boards.map((b) => (b.id === id ? { ...b, piBooted: true } : b)),
}));
if (setup) {
void bridge
.sendAndWaitForPrompt(setup.endsWith('\n') ? setup : setup + '\n')
.then(flip);
} else {
flip();
}
};
bridge.onDisconnected = () => {
set((s) => {