+ {ownerLabel && (
+
+ {ownerLabel}
+
+ )}
{openFiles.map((file) => (
void;
@@ -70,6 +72,7 @@ async function readFileAsBase64(file: File): Promise
{
export const BoardOptionsModal = ({
isOpen,
boardId,
+ boardName,
boardKind,
currentOptions,
spiffsFiles,
@@ -85,6 +88,12 @@ export const BoardOptionsModal = ({
const [draft, setDraft] = useState(seed);
const fileInputRef = useRef(null);
+ const update = useCallback(
+ (patch: Partial) => setDraft((d) => ({ ...d, ...patch })),
+ [],
+ );
+
+ // Hooks must run on every render — bail out only AFTER all of them.
if (!isOpen) return null;
const showPsram = boardSupportsPsram(boardKind);
@@ -97,11 +106,6 @@ export const BoardOptionsModal = ({
const nonDio = draft.flashMode !== 'dio';
const sameCore = draft.eventsRunOnCore === draft.arduinoRunsOnCore;
- const update = useCallback(
- (patch: Partial) => setDraft((d) => ({ ...d, ...patch })),
- [],
- );
-
const handleApply = () => {
// Strip PSRAM for boards that don't support it (e.g. C3) so a stale
// value can't smuggle through to the backend.
@@ -146,7 +150,7 @@ export const BoardOptionsModal = ({
Board Options
- {BOARD_KIND_LABELS[boardKind]} - id {boardId}
+ {boardName?.trim() || BOARD_KIND_LABELS[boardKind]} - id {boardId}
))}
{/* Board label */}
{selectedBoard && (
-
{BOARD_KIND_LABELS[selectedBoard.boardKind]}
+
{boardDisplayName(selectedBoard)}
)}
{/* Pin grid */}
diff --git a/frontend/src/components/simulator/SerialMonitor.tsx b/frontend/src/components/simulator/SerialMonitor.tsx
index e77be17f..38587f70 100644
--- a/frontend/src/components/simulator/SerialMonitor.tsx
+++ b/frontend/src/components/simulator/SerialMonitor.tsx
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useSimulatorStore } from '../../store/useSimulatorStore';
import { getTabSessionId } from '../../simulation/Esp32Bridge';
import type { BoardKind } from '../../types/board';
-import { BOARD_KIND_LABELS } from '../../types/board';
+import { boardDisplayName } from '../../types/board';
// Short labels for tabs
const BOARD_SHORT_LABEL: Partial
> = {
@@ -207,12 +207,12 @@ export const SerialMonitor: React.FC = () => {
...(isActive ? { ...styles.tabActive, borderBottomColor: color, color } : {}),
}}
onClick={() => handleTabClick(board.id)}
- title={BOARD_KIND_LABELS[board.boardKind]}
+ title={boardDisplayName(board)}
>
{BOARD_ICON[board.boardKind] ?? '●'}
- {BOARD_SHORT_LABEL[board.boardKind] ?? board.boardKind}
+ {board.name?.trim() || BOARD_SHORT_LABEL[board.boardKind] || board.boardKind}
{hasUnread && !isActive && }
);
diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx
index f0823686..253fdb56 100644
--- a/frontend/src/components/simulator/SimulatorCanvas.tsx
+++ b/frontend/src/components/simulator/SimulatorCanvas.tsx
@@ -42,7 +42,7 @@ import {
import { useIsCoarsePointer } from '../../utils/useTouchDevice';
import type { ComponentMetadata } from '../../types/component-metadata';
import type { BoardKind } from '../../types/board';
-import { BOARD_KIND_FQBN, BOARD_KIND_LABELS } from '../../types/board';
+import { BOARD_KIND_FQBN, boardDisplayName } from '../../types/board';
import { boardGateDecision, proBoardFeatureName, triggerProUpgradePrompt } from '../../lib/proBoardGate';
import { FlashModal } from './FlashModal';
import { isTauri as isTauriRuntimeFn } from '../../desktop/tauriBridge';
@@ -2066,7 +2066,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
>
{boards.map((b) => (
))}
@@ -2607,7 +2607,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
let title = pinPicker.kind === 'board' ? 'Board' : 'Component';
if (pinPicker.kind === 'board') {
const b = boards.find((x) => x.id === id);
- if (b) title = BOARD_KIND_LABELS[b.boardKind] ?? b.boardKind;
+ if (b) title = boardDisplayName(b);
} else {
const c = components.find((x) => x.id === id);
const meta = c ? registry.getById(c.metadataId) : null;
@@ -2809,7 +2809,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
{boardContextMenu &&
(() => {
const board = boards.find((b) => b.id === boardContextMenu.boardId);
- const label = board ? BOARD_KIND_LABELS[board.boardKind] : 'Board';
+ const label = board ? boardDisplayName(board) : 'Board';
const connectedWires = wires.filter(
(w) =>
w.start.componentId === boardContextMenu.boardId ||
@@ -3045,6 +3045,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
{
{boardToRemove &&
(() => {
const board = boards.find((b) => b.id === boardToRemove);
- const label = board ? BOARD_KIND_LABELS[board.boardKind] : t('editor.canvas.removeConfirm.boardFallback');
+ const label = board ? boardDisplayName(board) : t('editor.canvas.removeConfirm.boardFallback');
const connectedWires = wires.filter(
(w) => w.start.componentId === boardToRemove || w.end.componentId === boardToRemove,
).length;
diff --git a/frontend/src/pages/ProjectByIdPage.tsx b/frontend/src/pages/ProjectByIdPage.tsx
index 92419533..90a5f188 100644
--- a/frontend/src/pages/ProjectByIdPage.tsx
+++ b/frontend/src/pages/ProjectByIdPage.tsx
@@ -152,6 +152,7 @@ export function buildLoadPayload(project: RawProject) {
if (Array.isArray(parsed) && parsed.length > 0) {
boards = parsed.map((b: Partial & { id: string; boardKind: string }) => ({
id: b.id,
+ name: b.name,
boardKind: b.boardKind as BoardKind,
x: b.x ?? 50,
y: b.y ?? 50,
diff --git a/frontend/src/store/useSimulatorStore.ts b/frontend/src/store/useSimulatorStore.ts
index c24d2483..d14cd175 100644
--- a/frontend/src/store/useSimulatorStore.ts
+++ b/frontend/src/store/useSimulatorStore.ts
@@ -1277,11 +1277,12 @@ export const useSimulatorStore = create((set, get) => {
payload.boards.forEach((b) => {
addBoard(b.boardKind, b.x, b.y, b.id);
// Apply the rest of the saved fields that addBoard doesn't set.
- if (b.languageMode && b.languageMode !== 'arduino') {
+ const patch: Partial = {};
+ if (b.languageMode && b.languageMode !== 'arduino') patch.languageMode = b.languageMode;
+ if (b.name && b.name.trim()) patch.name = b.name;
+ if (Object.keys(patch).length > 0) {
set((s) => ({
- boards: s.boards.map((bb) =>
- bb.id === b.id ? { ...bb, languageMode: b.languageMode } : bb,
- ),
+ boards: s.boards.map((bb) => (bb.id === b.id ? { ...bb, ...patch } : bb)),
}));
}
});
diff --git a/frontend/src/types/board.ts b/frontend/src/types/board.ts
index 7d24db97..18b43b37 100644
--- a/frontend/src/types/board.ts
+++ b/frontend/src/types/board.ts
@@ -75,6 +75,10 @@ export interface BleStatus {
export interface BoardInstance {
id: string; // unique in canvas, e.g. 'arduino-uno', 'raspberry-pi-3'
+ /** Optional user-given display name. Falls back to the kind label when
+ * empty. Lets the user tell two same-kind boards apart in the file
+ * explorer, the compile console, and the canvas selector. */
+ name?: string;
boardKind: BoardKind;
x: number;
y: number;
@@ -129,6 +133,13 @@ export const BOARD_KIND_LABELS: Record = {
attiny85: 'ATtiny85',
};
+/** Display name for a board instance: the user's custom name if set, else the
+ * kind label. Route every user-facing board label through this so renamed
+ * boards show their name everywhere (file explorer, compile console, canvas). */
+export function boardDisplayName(board: Pick): string {
+ return board.name?.trim() || BOARD_KIND_LABELS[board.boardKind];
+}
+
export const BOARD_KIND_FQBN: Record = {
'arduino-uno': 'arduino:avr:uno',
'arduino-nano': 'arduino:avr:nano:cpu=atmega328',
diff --git a/frontend/src/utils/projectPayload.ts b/frontend/src/utils/projectPayload.ts
index 8191b5f9..e253fc7a 100644
--- a/frontend/src/utils/projectPayload.ts
+++ b/frontend/src/utils/projectPayload.ts
@@ -31,6 +31,7 @@ function chipGroupIdsWithFiles(): string[] {
function serialisableBoard(b: BoardInstance) {
return {
id: b.id,
+ name: b.name,
boardKind: b.boardKind,
x: b.x,
y: b.y,
diff --git a/frontend/src/utils/vlxFile.ts b/frontend/src/utils/vlxFile.ts
index b7f9138a..f050749d 100644
--- a/frontend/src/utils/vlxFile.ts
+++ b/frontend/src/utils/vlxFile.ts
@@ -45,6 +45,7 @@ export interface VlxPayload {
name?: string;
boards: Array<{
id: string;
+ name?: string;
boardKind: string;
x: number;
y: number;
@@ -61,6 +62,7 @@ export interface VlxPayload {
function serialisableBoard(b: BoardInstance) {
return {
id: b.id,
+ name: b.name,
boardKind: b.boardKind,
x: b.x,
y: b.y,