Skip to content

Commit 8c799a9

Browse files
yyodsclaude
andcommitted
[green] render five-profile flash copy from the selected release
Gate the stale 'Planned profiles: Unavailable' cards to builds without a five-profile release and render the accurate five-target provisioning summary instead (four ESP Web Serial profiles + Pico verified-UF2/ BOOTSEL), framed as a visibly pending candidate only in candidate mode. For a five-profile release the visible recovery section now carries all four ESP merged-image esptool commands (Waveshare gated on profile presence, C3 at 0x0) and a separate Pico BOOTSEL re-copy paragraph with no esptool/offset/Web Serial fallback, mirroring RECOVERY.md and browser-flashing.md §8. The component-offset sentence names ESP32-C3 beside ESP32-S3 at 0x0, the module-profile explainer covers all five exact profiles, and 'Before you connect' scopes Web Serial/desktop Chromium to the ESP targets while the Pico flow needs only a secure context with Web Crypto (§7). The installer's exact-B-version Waveshare warning is now gated on the profile's presence in the selected release rather than qualified-public status, the Pico consent affirmation names the actual RP2350+CYW43439 board contract, and the release consent fieldset gains its visible legend. Polish: candidate intro branch, drop 'future' from the no-release body (two pinned regexes updated accordingly), drop aria-disabled from informational cards, drop pre-GP2 jargon, and fix Blockly / Classic ESP32 / ESP Web Tools naming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016KVSKkd7KUMHqKXDhPXTj5 Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent ef798b0 commit 8c799a9

4 files changed

Lines changed: 152 additions & 26 deletions

File tree

‎tools/web/src/app/flash/page.tsx‎

Lines changed: 131 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { ExternalIcon } from "@/components/icons";
55
import { FlashStatus } from "@/components/flash-status";
66
import { PageIntro } from "@/components/page-intro";
77
import { WaveshareBoardPhoto } from "@/components/waveshare-board-photo";
8-
import { releaseIncludesWaveshareLcd147b } from "@/lib/firmware-release";
8+
import {
9+
firmwareVersionUsesFiveProfiles,
10+
releaseIncludesWaveshareLcd147b,
11+
} from "@/lib/firmware-release";
912
import {
1013
firmwareReleaseSelectedAtBuild,
1114
localFirmwarePreviewSelectedAtBuild,
@@ -29,7 +32,18 @@ export default function FlashPage() {
2932
const preview = localFirmwarePreviewSelectedAtBuild();
3033
const release = preview ? null : firmwareReleaseSelectedAtBuild();
3134
const publicBeta = release?.deployment === "public-beta";
35+
const candidate = release?.deployment === "candidate";
3236
const waveshareLcd147b = releaseIncludesWaveshareLcd147b(release);
37+
const fiveProfileRelease =
38+
release !== null && firmwareVersionUsesFiveProfiles(release.version);
39+
const releaseHasWaveshareProfile = Boolean(
40+
release?.profiles.some(
41+
(profile) => profile.id === "waveshare-esp32-s3-lcd-147b",
42+
),
43+
);
44+
const releaseHasPicoProfile = Boolean(
45+
release?.profiles.some((profile) => profile.id === "rpi-pico2-w"),
46+
);
3347
const showWaveshareBoard =
3448
waveshareLcd147b ||
3549
preview?.profiles.some(
@@ -49,7 +63,9 @@ export default function FlashPage() {
4963
? " The current v0.4.2 installer is a hardware-tested firmware beta. Production Chrome erase/install and deliberately interrupted-flash recovery passed on both exact profiles. Complete release qualification is still pending; this is not a qualified release."
5064
: qualifiedPublic
5165
? ` Qualified v${release.version} firmware is available for all ${exactProfileCountLabel(release.profiles.length)} exact release profiles.`
52-
: " The public install action remains unavailable until the final v0.6.0-derived bytes pass hardware validation on every included profile."}
66+
: candidate
67+
? ` This protected, access-controlled v${release.version} release candidate is staged for hardware qualification. Hardware validation is pending on every included profile; the public install action stays unavailable until it passes.`
68+
: " The public install action remains unavailable until the final v0.6.0-derived bytes pass hardware validation on every included profile."}
5369
</p>
5470
</PageIntro>
5571

@@ -126,7 +142,8 @@ export default function FlashPage() {
126142
<span>
127143
Firmware includes the inert <code>pyble_st7789</code>{" "}
128144
user library and <code>pyble_waveshare_lcd147b</code>{" "}
129-
companion for ordinary Python and Blocky TFT programs.
145+
companion for ordinary Python and Blockly TFT
146+
programs.
130147
</span>
131148
</li>
132149
<li>
@@ -169,7 +186,7 @@ export default function FlashPage() {
169186
<li>
170187
<strong>esp32-4mb</strong>
171188
<span>
172-
Classical ESP32 module with 4 MiB flash; no PSRAM required.
189+
Classic ESP32 module with 4 MiB flash; no PSRAM required.
173190
</span>
174191
</li>
175192
<li>
@@ -182,7 +199,7 @@ export default function FlashPage() {
182199
the display boot splash.
183200
</span>
184201
</li>
185-
{showWaveshareBoard ? (
202+
{showWaveshareBoard || releaseHasWaveshareProfile ? (
186203
<li>
187204
<strong>waveshare-esp32-s3-lcd-147b</strong>
188205
<span>
@@ -192,6 +209,25 @@ export default function FlashPage() {
192209
</span>
193210
</li>
194211
) : null}
212+
{fiveProfileRelease ? (
213+
<>
214+
<li>
215+
<strong>esp32-c3-4mb</strong>
216+
<span>
217+
ESP32-C3 revision v0.3 or newer with 4 MiB flash; no
218+
PSRAM required.
219+
</span>
220+
</li>
221+
<li>
222+
<strong>rpi-pico2-w</strong>
223+
<span>
224+
Raspberry Pi Pico 2 W (RP2350 + CYW43439). Provisioned
225+
by a verified UF2 download and a manual BOOTSEL copy,
226+
not a Web Serial flow.
227+
</span>
228+
</li>
229+
</>
230+
) : null}
195231
</ul>
196232
</section>
197233

@@ -215,11 +251,41 @@ export default function FlashPage() {
215251
</li>
216252
</ul>
217253
</section>
254+
) : fiveProfileRelease ? (
255+
<section aria-labelledby="release-methods">
256+
<h2 id="release-methods">Two provisioning methods</h2>
257+
<ul className="requirement-list">
258+
<li>
259+
<strong>ESP Web Tools · Web Serial</strong>
260+
<span>
261+
Direct browser installation for the four exact ESP
262+
profiles — esp32-4mb, esp32-s3-n16r8,
263+
waveshare-esp32-s3-lcd-147b, and esp32-c3-4mb — after
264+
manifest and firmware verification.
265+
</span>
266+
</li>
267+
<li>
268+
<strong>Pico 2 W · BOOTSEL / UF2</strong>
269+
<span>
270+
Verify and download the UF2, then copy it to the mounted
271+
RP2350 BOOTSEL volume. This is not an ESP Web Tools flow.
272+
</span>
273+
</li>
274+
</ul>
275+
{candidate ? (
276+
<p>
277+
All five targets are provisioned from this visibly pending
278+
candidate: hardware qualification is pending on every
279+
profile, and installation is offered only through this
280+
access-controlled candidate build.
281+
</p>
282+
) : null}
283+
</section>
218284
) : (
219285
<section aria-labelledby="planned-profile">
220286
<h2 id="planned-profile">Planned profiles</h2>
221287
<ul className="requirement-list planned-profile-list">
222-
<li className="planned-profile-card" aria-disabled="true">
288+
<li className="planned-profile-card">
223289
<strong>esp32-c3-4mb</strong>
224290
<span>
225291
<b>Unavailable.</b> Exact-profile real-hardware validation
@@ -228,11 +294,12 @@ export default function FlashPage() {
228294
command is offered yet.
229295
</span>
230296
</li>
231-
<li className="planned-profile-card" aria-disabled="true">
297+
<li className="planned-profile-card">
232298
<strong>rpi-pico2-w</strong>
233299
<span>
234-
<b>Unavailable.</b> The Pico 2 W port remains pre-GP2; no
235-
public UF2 download or installer action is offered yet.
300+
<b>Unavailable.</b> The Pico 2 W port has not completed
301+
release qualification; no public UF2 download or installer
302+
action is offered in this release.
236303
</span>
237304
</li>
238305
</ul>
@@ -267,6 +334,33 @@ export default function FlashPage() {
267334
</span>
268335
</li>
269336
</ul>
337+
) : fiveProfileRelease ? (
338+
<ul className="requirement-list">
339+
<li>
340+
<strong>ESP targets use Web Serial</strong>
341+
<span>
342+
The four ESP profiles install through Web Serial and Web
343+
Crypto in a current desktop Chromium browser over HTTPS.
344+
iPadOS cannot perform this wired ESP provisioning step.
345+
</span>
346+
</li>
347+
<li>
348+
<strong>Pico 2 W does not use Web Serial</strong>
349+
<span>
350+
The rpi-pico2-w flow needs only a secure context with Web
351+
Crypto: it verifies and downloads the UF2 for a manual
352+
BOOTSEL-volume copy.
353+
</span>
354+
</li>
355+
<li>
356+
<strong>Data-capable USB and stable power</strong>
357+
<span>
358+
Back up board files and close other serial tools first.
359+
ESP installation erases the device; copying the UF2
360+
overwrites the installed firmware.
361+
</span>
362+
</li>
363+
</ul>
270364
) : (
271365
<ul className="requirement-list">
272366
<li>
@@ -371,7 +465,8 @@ export default function FlashPage() {
371465
</p>
372466
<p>
373467
For advanced merged-image recovery with the same version-matched
374-
bundle bytes, run the command for the exact selected profile:
468+
bundle bytes, run the command for the exact selected ESP
469+
profile:
375470
</p>
376471
<ul className="readiness-list">
377472
<li>
@@ -388,7 +483,7 @@ export default function FlashPage() {
388483
<span>n16r8/firmware.bin</span>
389484
</code>
390485
</li>
391-
{waveshareLcd147b ? (
486+
{releaseHasWaveshareProfile ? (
392487
<li>
393488
<code>
394489
python -m esptool --chip esp32s3 write_flash 0x0{" "}
@@ -397,18 +492,41 @@ export default function FlashPage() {
397492
</code>
398493
</li>
399494
) : null}
495+
{fiveProfileRelease ? (
496+
<li>
497+
<code>
498+
python -m esptool --chip esp32c3 write_flash 0x0{" "}
499+
<span>esp32-c3-</span>
500+
<span>4mb/firmware.bin</span>
501+
</code>
502+
</li>
503+
) : null}
400504
</ul>
401505
<p>
402506
As an advanced diagnostic or recovery alternative, the component
403507
offsets are: bootloader at 0x1000 for classic ESP32 or 0x0 for
404-
ESP32-S3, partition table at 0x8000, and application at 0x10000.
405-
Use only the component files from that same bundle.
508+
ESP32-S3{fiveProfileRelease ? " and ESP32-C3" : ""}, partition
509+
table at 0x8000, and application at 0x10000. Use only the
510+
component files from that same bundle.
406511
</p>
407512
<p>
408513
After flashing, perform a hard reset or power cycle. Expect the
409514
board to advertise as <code>PyBLE-XXXX</code>, then make the
410515
first connection from the PyBLE app.
411516
</p>
517+
{releaseHasPicoProfile ? (
518+
<p>
519+
<strong>Pico 2 W recovery:</strong> the merged-image commands
520+
and component offsets above apply only to the ESP profiles,
521+
never to the Pico 2 W. After an interrupted or failed copy,
522+
disconnect the board, hold BOOTSEL while reconnecting USB,
523+
wait for the RP2350 volume, then re-verify and copy the same
524+
versioned UF2 again. Wait for the automatic reboot and confirm
525+
the <code>PyBLE-XXXX</code> BLE advertisement. Pico
526+
provisioning does not use Web Serial or the ESP ROM recovery
527+
controls.
528+
</p>
529+
) : null}
412530
<p>
413531
Repeated resets, flash-size or PSRAM startup errors, or no BLE
414532
advertisement can indicate a wrong memory profile. Stop instead

‎tools/web/src/components/flash-status.tsx‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ function policyFailure(release: FirmwareReleaseDescriptor | null | undefined) {
156156
if (!release) {
157157
return {
158158
heading: "Installer unavailable",
159-
body: "Hardware validation is still required on every profile included in a future v0.6.0-derived candidate before the public installer can be enabled.",
159+
body: "Hardware validation is still required on every profile included in a v0.6.0-derived candidate before the public installer can be enabled.",
160160
};
161161
}
162162
if (release.deployment !== "public" && release.deployment !== "candidate") {
@@ -472,8 +472,12 @@ function FlashStatusForRelease({
472472
timeZone: "UTC",
473473
year: "numeric",
474474
}).format(new Date(activeRelease.builtAt));
475-
const includesWaveshareLcd147b =
476-
releaseIncludesWaveshareLcd147b(activeRelease);
475+
// The exact-board warning accompanies the profile whenever the selected
476+
// release offers it — candidate builds included — not only after public
477+
// qualification.
478+
const includesWaveshareLcd147b = activeRelease.profiles.some(
479+
({ id }) => id === "waveshare-esp32-s3-lcd-147b",
480+
);
477481

478482
return (
479483
<section className="flash-status" aria-labelledby="installer-status-title">
@@ -562,6 +566,7 @@ function FlashStatusForRelease({
562566
className="consent-list"
563567
aria-label="Installation acknowledgements"
564568
>
569+
<legend>Installation acknowledgements</legend>
565570
{requiredConsentItems.map(({ id, label }) => (
566571
<label key={id}>
567572
<input
@@ -570,10 +575,13 @@ function FlashStatusForRelease({
570575
onChange={(event) => updateConsent(id, event.target.checked)}
571576
/>
572577
<span>
573-
{id === "erase" &&
574-
selectedProfile.provisioningKind === "verified-uf2-bootsel"
575-
? "I understand copying the UF2 overwrites the installed board firmware."
576-
: label}
578+
{selectedProfile.provisioningKind === "verified-uf2-bootsel" &&
579+
id === "profile"
580+
? "My board is a Raspberry Pi Pico 2 W (RP2350 + CYW43439)."
581+
: selectedProfile.provisioningKind ===
582+
"verified-uf2-bootsel" && id === "erase"
583+
? "I understand copying the UF2 overwrites the installed board firmware."
584+
: label}
577585
</span>
578586
</label>
579587
))}
@@ -722,7 +730,7 @@ type PreviewConsentId =
722730

723731
function previewMethodLabel(profile: LocalFirmwarePreviewProfile) {
724732
return profile.method === "esp-web-tools"
725-
? "ESP32 Web Tools · Web Serial direct install"
733+
? "ESP Web Tools · Web Serial direct install"
726734
: "RP2 UF2 download · BOOTSEL copy";
727735
}
728736

@@ -739,7 +747,7 @@ function previewWarning(profileId: LocalFirmwareProfileId) {
739747
if (profileId === "rpi-pico2-w") {
740748
return "Disconnect the Pico 2 W, hold BOOTSEL while you connect USB, wait for the RP2350 volume, then copy the verified UF2.";
741749
}
742-
return "Use only a classical ESP32 module with exactly 4 MiB flash and no PSRAM requirement.";
750+
return "Use only a classic ESP32 module with exactly 4 MiB flash and no PSRAM requirement.";
743751
}
744752

745753
function LocalFlashPreview({

‎tools/web/src/lib/local-firmware-preview.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ export const localFirmwareProfileTable = [
55
{
66
id: "esp32-4mb",
77
label: "ESP32 · 4 MiB flash",
8-
selectLabel: "Classical ESP32 · 4 MiB flash",
8+
selectLabel: "Classic ESP32 · 4 MiB flash",
99
buildTarget: "esp32",
1010
chipFamily: "ESP32",
1111
method: "esp-web-tools",
1212
offset: 4096,
1313
group: "ESP Web Serial",
1414
requirements: "4 MiB external SPI flash · no PSRAM required",
15-
warning: "Use only for a classical ESP32 module with exactly 4 MiB flash.",
15+
warning: "Use only for a classic ESP32 module with exactly 4 MiB flash.",
1616
},
1717
{
1818
id: "esp32-s3-n16r8",

‎tools/web/src/test/flash-installer.test.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ describe("browser firmware installer states", () => {
450450
render(<InstallerUnderTest capabilities={supportedCapabilities} />);
451451

452452
expect(screen.getByRole("status")).toHaveTextContent(
453-
/installer unavailable.*hardware validation.*every profile included in a future v0\.6\.0-derived candidate/i,
453+
/installer unavailable.*hardware validation.*every profile included in a v0\.6\.0-derived candidate/i,
454454
);
455455
expect(
456456
screen.getByRole("button", { name: /installer coming soon/i }),
@@ -1055,7 +1055,7 @@ describe("local five-board engineering preview", () => {
10551055
name: /selected firmware target details/i,
10561056
});
10571057
expect(details).toHaveTextContent(/esp32-c3-4mb/i);
1058-
expect(details).toHaveTextContent(/\besp web tools\b.*web serial/i);
1058+
expect(details).toHaveTextContent(/esp web tools.*web serial/i);
10591059
expect(details).toHaveTextContent(
10601060
/local engineering preview.*unqualified/i,
10611061
);

0 commit comments

Comments
 (0)