From 36c414df8fac6eb7ee116002dca9df204680a2ed Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Tue, 4 Aug 2026 19:58:31 +0200 Subject: [PATCH] feat(sd): source files stay off the card - only project DATA files auto-copy On real hardware your code lives in flash, not on the microSD; a sketch listing the card should see its data (csv, json, txt, wav...), not its own source. buildProjectSdImage now skips .ino/.h/.hpp/.c/.cpp/.py from the workspace auto-copy. Explicit uploads are exempt - an upload always lands whatever its extension. Panel hint updated; tests cover both the exclusion and the upload exemption (15 green). --- frontend/src/__tests__/fat-image.test.ts | 31 ++++++++++++++++--- .../src/components/simulator/SdCardPanel.tsx | 5 +-- frontend/src/utils/sdCardFiles.ts | 7 +++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/frontend/src/__tests__/fat-image.test.ts b/frontend/src/__tests__/fat-image.test.ts index b8a5978f..e42d7203 100644 --- a/frontend/src/__tests__/fat-image.test.ts +++ b/frontend/src/__tests__/fat-image.test.ts @@ -141,16 +141,34 @@ describe('buildFat16Image', () => { }); describe('buildProjectSdImage', () => { - it('auto-copies workspace (text) files into the image', () => { + it('auto-copies workspace DATA files; source files stay off the card', () => { const ws = [ { name: 'sketch.ino', content: 'void setup(){}' }, + { name: 'helper.h', content: '#define X 1' }, + { name: 'impl.cpp', content: 'int x;' }, + { name: 'script.py', content: 'print(1)' }, { name: 'notes.txt', content: 'hello from the card' }, + { name: 'config.json', content: '{}' }, ]; const got = readFat16(buildProjectSdImage(ws)); + const names = got.map((g) => g.name.toLowerCase()); const notes = got.find((g) => g.name.toLowerCase() === 'notes.txt'); expect(notes).toBeTruthy(); expect(new TextDecoder().decode(notes!.data)).toBe('hello from the card'); - expect(got.some((g) => g.name.toLowerCase() === 'sketch.ino')).toBe(true); + expect(names).toContain('config.json'); + // Code lives in flash, not on the microSD. + expect(names).not.toContain('sketch.ino'); + expect(names).not.toContain('helper.h'); + expect(names).not.toContain('impl.cpp'); + expect(names).not.toContain('script.py'); + }); + + it('an explicit upload lands even with a source extension', () => { + const uploaded = decodeSdFiles([ + { name: 'ref.py', contentB64: bytesToB64(new TextEncoder().encode('print(2)')) }, + ]); + const got = readFat16(buildProjectSdImage([], uploaded)); + expect(got.some((g) => g.name.toLowerCase() === 'ref.py')).toBe(true); }); it('uploaded (paid) files override same-named project files', () => { @@ -161,11 +179,14 @@ describe('buildProjectSdImage', () => { expect(Array.from(f.data)).toEqual([0xde, 0xad, 0xbe, 0xef]); // binary, not "TEXT" }); - it('adds uploaded binary files alongside project files', () => { - const ws = [{ name: 'main.ino', content: 'x' }]; + it('adds uploaded binary files alongside project data files', () => { + const ws = [ + { name: 'main.ino', content: 'x' }, // source: stays off the card + { name: 'readme.txt', content: 'data' }, + ]; const uploaded = [{ name: 'logo.bmp', data: new Uint8Array(800).fill(0x42) }]; const got = readFat16(buildProjectSdImage(ws, uploaded)); - expect(got.length).toBe(2); + expect(got.length).toBe(2); // readme.txt + logo.bmp, no main.ino const logo = got.find((g) => g.name.toLowerCase() === 'logo.bmp')!; expect(logo.data.length).toBe(800); expect(logo.data.every((b) => b === 0x42)).toBe(true); diff --git a/frontend/src/components/simulator/SdCardPanel.tsx b/frontend/src/components/simulator/SdCardPanel.tsx index 5e59a318..17ae8316 100644 --- a/frontend/src/components/simulator/SdCardPanel.tsx +++ b/frontend/src/components/simulator/SdCardPanel.tsx @@ -118,8 +118,9 @@ export const SdCardPanel: React.FC = ({ files, onChange, board {files.length === 0 && (
- Upload your own files (images, audio, data). Project files are added - automatically. + Upload your own files (images, audio, data). The project's data files + are added automatically; source files (.ino, .h, .cpp, .py) stay off + the card.
)} {files.map((f) => ( diff --git a/frontend/src/utils/sdCardFiles.ts b/frontend/src/utils/sdCardFiles.ts index 775170ce..55422ba8 100644 --- a/frontend/src/utils/sdCardFiles.ts +++ b/frontend/src/utils/sdCardFiles.ts @@ -59,6 +59,12 @@ export function decodeSdFiles(raw: unknown): SdFile[] { return out; } +/** Source files never land on the card: on real hardware your code lives in + * flash, not on the microSD, and a sketch listing the card should see its + * DATA (csv, json, txt, wav...), not its own source. Uploads are exempt — + * an explicit upload always lands, whatever its extension. */ +const SD_EXCLUDED_SOURCES = /\.(ino|h|hpp|c|cpp|py)$/i; + /** * Build the SD image from the project workspace files (auto-copied, free) plus * any uploaded files (paid). Uploaded files override same-named project files. @@ -70,6 +76,7 @@ export function buildProjectSdImage( ): Uint8Array { const byName = new Map(); for (const f of workspaceFiles) { + if (SD_EXCLUDED_SOURCES.test(f.name)) continue; byName.set(f.name.toLowerCase(), { name: f.name, data: new TextEncoder().encode(f.content) }); } for (const f of uploaded) {