@@ -137,10 +137,7 @@ function LinearProgress({ value, barColor }) {
137137
138138function DeviceState ( { serial } ) {
139139 return (
140- < div
141- className = "absolute bottom-0 m-0 lg:m-4 p-4 w-full sm:w-auto sm:min-w-[350px] sm:border sm:border-gray-200 dark:sm:border-gray-600 bg-white dark:bg-gray-700 text-black dark:text-white rounded-md flex flex-row gap-2"
142- style = { { left : '50%' , transform : 'translate(-50%, -50%)' } }
143- >
140+ < div className = "mt-8 p-4 sm:min-w-[350px] border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-black dark:text-white rounded-md flex flex-row gap-2" >
144141 < div className = "flex flex-row gap-2" >
145142 < svg
146143 xmlns = "http://www.w3.org/2000/svg"
@@ -222,7 +219,7 @@ function LandingPage({ onStart }) {
222219 < div className = "text-center" >
223220 < h1 className = "text-4xl font-bold dark:text-white mb-4" > flash.comma.ai</ h1 >
224221 < p className = "text-xl text-gray-600 dark:text-gray-300 max-w-md" >
225- Flash your comma back to a factory state
222+ Restore your comma device back to a fresh factory state
226223 </ p >
227224 </ div >
228225 < button
@@ -304,22 +301,22 @@ function ConnectInstructions({ deviceType, onNext }) {
304301
305302 < ol className = "text-left space-y-3 text-lg dark:text-white" >
306303 < li className = "flex gap-3" >
307- < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > a </ span >
304+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > A </ span >
308305 < span > Unplug the device</ span >
309306 </ li >
310307 { ! isCommaFour && (
311308 < li className = "flex gap-3" >
312- < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > b </ span >
309+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > B </ span >
313310 < span > Wait for the light on the back to fully turn off</ span >
314311 </ li >
315312 ) }
316313 < li className = "flex gap-3" >
317- < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > { isCommaFour ? 'b ' : 'c ' } </ span >
318- < span > Connect the < strong > { isCommaFour ? 'right' : 'lower' } </ strong > USB-C port < strong > (port 1) </ strong > to your computer</ span >
314+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > { isCommaFour ? 'B ' : 'C ' } </ span >
315+ < span > Connect < strong > port 1 </ strong > to your computer</ span >
319316 </ li >
320317 < li className = "flex gap-3" >
321- < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > { isCommaFour ? 'c ' : 'd ' } </ span >
322- < span > Connect power to the < strong > { isCommaFour ? 'left' : 'upper' } </ strong > port < strong > (port 2) </ strong > </ span >
318+ < span className = "flex-shrink-0 w-7 h-7 rounded-full bg-[#51ff00] text-black flex items-center justify-center font-bold text-sm" > { isCommaFour ? 'C ' : 'D ' } </ span >
319+ < span > Connect < strong > port 2 </ strong > to your computer or a power brick </ span >
323320 </ li >
324321 </ ol >
325322 </ div >
@@ -356,7 +353,7 @@ function LinuxUnbind({ onNext }) {
356353 < h2 className = "text-3xl font-bold dark:text-white mb-2" > Unbind from qcserial</ h2 >
357354 < p className = "text-gray-600 dark:text-gray-300 max-w-lg" >
358355 On Linux, devices in QDL mode are bound to the kernel's qcserial driver.
359- Run this command to unbind it:
356+ Run this command in a terminal to unbind it:
360357 </ p >
361358 </ div >
362359
@@ -382,6 +379,29 @@ function LinuxUnbind({ onNext }) {
382379 )
383380}
384381
382+ // WebUSB connection screen - shows while waiting for user to select device
383+ function WebUSBConnect ( { onConnect } ) {
384+ return (
385+ < div className = "wizard-screen flex flex-col items-center justify-center h-full gap-6 p-8" >
386+ < div className = "p-8 rounded-full bg-yellow-500" >
387+ < img src = { cable } alt = "connect" width = { 128 } height = { 128 } className = "invert" />
388+ </ div >
389+ < div className = "text-center" >
390+ < h2 className = "text-3xl font-bold dark:text-white mb-2" > Select your device</ h2 >
391+ < p className = "text-gray-600 dark:text-gray-300 max-w-lg" >
392+ Click the button below to open the device selector, then choose < code className = "px-2 py-0.5 bg-[#51ff00] rounded font-mono text-black font-semibold" > QUSB_BULK_CID</ code > from the list.
393+ </ p >
394+ </ div >
395+ < button
396+ onClick = { onConnect }
397+ className = "px-8 py-3 text-xl font-semibold rounded-full bg-[#51ff00] hover:bg-[#45e000] active:bg-[#3acc00] text-black transition-colors"
398+ >
399+ Connect
400+ </ button >
401+ </ div >
402+ )
403+ }
404+
385405// Device picker component
386406function DevicePicker ( { onSelect } ) {
387407 const [ selected , setSelected ] = useState ( null )
@@ -402,8 +422,8 @@ function DevicePicker({ onSelect }) {
402422 : 'border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500'
403423 } `}
404424 >
405- < img src = { comma3XProduct } alt = "comma 3/ 3X" className = "h-32 object-contain" />
406- < span className = "text-xl font-semibold dark:text-white" > comma 3 / 3X</ span >
425+ < img src = { comma3XProduct } alt = "comma three or comma 3X" className = "h-32 object-contain" />
426+ < span className = "text-xl font-semibold dark:text-white" > comma three < br /> comma 3X</ span >
407427 </ button >
408428
409429 < button
@@ -434,16 +454,8 @@ function DevicePicker({ onSelect }) {
434454 )
435455}
436456
437- // Wizard step names for the stepper (UI-level steps, not flash manager steps)
438- // Steps are inserted dynamically based on OS and device
439- function getWizardSteps ( needsZadig , needsUnbind ) {
440- const steps = [ 'Device' ]
441- if ( needsZadig ) steps . push ( 'Driver' )
442- steps . push ( 'Connect' )
443- if ( needsUnbind ) steps . push ( 'Unbind' )
444- steps . push ( 'Flash' )
445- return steps
446- }
457+ // Fixed wizard steps - always show the same steps regardless of platform/device
458+ const WIZARD_STEPS = [ 'Device' , 'Setup' , 'Flash' ]
447459
448460export default function Flash ( ) {
449461 const [ step , setStep ] = useState ( StepCode . INITIALIZING )
@@ -459,13 +471,9 @@ export default function Flash() {
459471 const qdlManager = useRef ( null )
460472 const imageManager = useImageManager ( )
461473
462- // Determine which optional steps are needed
474+ // Determine which optional setup screens are needed
463475 const needsZadig = isWindows
464476 const needsUnbind = isLinux && selectedDevice === DeviceType . COMMA_3
465- const wizardSteps = getWizardSteps ( needsZadig , needsUnbind )
466-
467- // Get the index of a step in the wizard
468- const getStepIndex = ( stepName ) => wizardSteps . indexOf ( stepName )
469477
470478 useEffect ( ( ) => {
471479 if ( ! imageManager . current ) return
@@ -494,72 +502,79 @@ export default function Flash() {
494502
495503 // Transition to flash screen when connected
496504 useEffect ( ( ) => {
497- if ( connected && ( wizardScreen === 'connect' || wizardScreen === 'unbind' ) ) {
505+ if ( connected && wizardScreen === 'webusb' ) {
498506 setWizardScreen ( 'flash' )
499- setWizardStep ( getStepIndex ( 'Flash' ) )
500507 }
501508 } , [ connected , wizardScreen ] )
502509
510+ // Wizard step indices (fixed)
511+ const STEP_DEVICE = 0
512+ const STEP_SETUP = 1
513+ const STEP_FLASH = 2
514+
503515 // Handle user clicking start on landing page
504516 const handleStart = ( ) => {
505517 setStep ( StepCode . DEVICE_PICKER )
506518 setWizardScreen ( 'device' )
507- setWizardStep ( getStepIndex ( 'Device' ) )
519+ setWizardStep ( STEP_DEVICE )
508520 }
509521
510522 // Handle device selection
511523 const handleDeviceSelect = ( deviceType ) => {
512524 setSelectedDevice ( deviceType )
525+ setWizardStep ( STEP_SETUP )
513526 if ( isWindows ) {
514527 setWizardScreen ( 'zadig' )
515- setWizardStep ( getStepIndex ( 'Driver' ) )
516528 } else {
517529 setWizardScreen ( 'connect' )
518- setWizardStep ( getStepIndex ( 'Connect' ) )
519530 }
520531 }
521532
522533 // Handle zadig done
523534 const handleZadigDone = ( ) => {
524535 setWizardScreen ( 'connect' )
525- setWizardStep ( getStepIndex ( 'Connect' ) )
526536 }
527537
528538 // Handle connect instructions next
529539 const handleConnectNext = ( ) => {
530540 // On Linux with comma 3/3X, need to unbind qcserial BEFORE showing WebUSB picker
531541 if ( isLinux && selectedDevice === DeviceType . COMMA_3 ) {
532542 setWizardScreen ( 'unbind' )
533- setWizardStep ( getStepIndex ( 'Unbind' ) )
534543 } else {
535- // Start connection - stay on connect screen until connected
536- qdlManager . current ?. start ( )
544+ // Go to WebUSB connection screen
545+ setWizardScreen ( 'webusb' )
546+ setWizardStep ( STEP_FLASH )
537547 }
538548 }
539549
540550 // Handle linux unbind done
541551 const handleUnbindDone = ( ) => {
542- // Start connection - stay on unbind screen until connected
552+ // Go to WebUSB connection screen
553+ setWizardScreen ( 'webusb' )
554+ setWizardStep ( STEP_FLASH )
555+ }
556+
557+ // Handle WebUSB connect button
558+ const handleWebUSBConnect = ( ) => {
543559 qdlManager . current ?. start ( )
544560 }
545561
546562 // Handle going back in wizard
547563 const handleWizardBack = ( toStep ) => {
548- const stepName = wizardSteps [ toStep ]
564+ const stepName = WIZARD_STEPS [ toStep ]
549565 if ( stepName === 'Device' ) {
550566 setStep ( StepCode . DEVICE_PICKER )
551567 setWizardScreen ( 'device' )
552- setWizardStep ( toStep )
568+ setWizardStep ( STEP_DEVICE )
553569 setSelectedDevice ( null )
554- } else if ( stepName === 'Driver' ) {
555- setWizardScreen ( 'zadig' )
556- setWizardStep ( toStep )
557- } else if ( stepName === 'Connect' ) {
558- setWizardScreen ( 'connect' )
559- setWizardStep ( toStep )
560- } else if ( stepName === 'Unbind' ) {
561- setWizardScreen ( 'unbind' )
562- setWizardStep ( toStep )
570+ } else if ( stepName === 'Setup' ) {
571+ // Go back to the appropriate setup screen based on platform
572+ setWizardStep ( STEP_SETUP )
573+ if ( isWindows ) {
574+ setWizardScreen ( 'zadig' )
575+ } else {
576+ setWizardScreen ( 'connect' )
577+ }
563578 }
564579 }
565580
@@ -575,7 +590,7 @@ export default function Flash() {
575590 if ( wizardScreen === 'device' && ! error ) {
576591 return (
577592 < div className = "relative h-full" >
578- < Stepper steps = { wizardSteps } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
593+ < Stepper steps = { WIZARD_STEPS } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
579594 < DevicePicker onSelect = { handleDeviceSelect } />
580595 </ div >
581596 )
@@ -585,7 +600,7 @@ export default function Flash() {
585600 if ( wizardScreen === 'zadig' && ! error ) {
586601 return (
587602 < div className = "relative h-full" >
588- < Stepper steps = { wizardSteps } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
603+ < Stepper steps = { WIZARD_STEPS } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
589604 < WindowsZadig onNext = { handleZadigDone } />
590605 </ div >
591606 )
@@ -595,7 +610,7 @@ export default function Flash() {
595610 if ( wizardScreen === 'connect' && ! error ) {
596611 return (
597612 < div className = "relative h-full" >
598- < Stepper steps = { wizardSteps } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
613+ < Stepper steps = { WIZARD_STEPS } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
599614 < ConnectInstructions deviceType = { selectedDevice } onNext = { handleConnectNext } />
600615 </ div >
601616 )
@@ -605,12 +620,21 @@ export default function Flash() {
605620 if ( wizardScreen === 'unbind' && ! error ) {
606621 return (
607622 < div className = "relative h-full" >
608- < Stepper steps = { wizardSteps } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
623+ < Stepper steps = { WIZARD_STEPS } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
609624 < LinuxUnbind onNext = { handleUnbindDone } />
610625 </ div >
611626 )
612627 }
613628
629+ // Render WebUSB connection screen
630+ if ( wizardScreen === 'webusb' && ! error ) {
631+ return (
632+ < div className = "relative h-full" >
633+ < Stepper steps = { WIZARD_STEPS } currentStep = { wizardStep } onStepClick = { handleWizardBack } />
634+ < WebUSBConnect onConnect = { handleWebUSBConnect } />
635+ </ div >
636+ )
637+ }
614638
615639 const uiState = steps [ step ] || { }
616640 if ( error ) {
@@ -649,7 +673,7 @@ export default function Flash() {
649673 < div id = "flash" className = "wizard-screen relative flex flex-col gap-8 justify-center items-center h-full" >
650674 { wizardStep >= 0 && (
651675 < Stepper
652- steps = { wizardSteps }
676+ steps = { WIZARD_STEPS }
653677 currentStep = { wizardStep }
654678 onStepClick = { canGoBack ? handleWizardBack : ( ) => { } }
655679 />
0 commit comments