feat(phase1): "Download the board app" button + App Lab import steps

Replace BoardClaim's old `curl | bash` setup command with a download
button (→ /download/apess-onboard.zip, served same-origin by the web
container) and rewrite the steps to the App Lab flow: download → Import an
app → Run → type the code the matrix scrolls. Works on prod and the LAN box
(relative /download URL). Added a test asserting the link + download attr.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-22 14:23:22 -07:00
co-authored by Claude Opus 4.8
parent c02898a02d
commit b335875509
2 changed files with 22 additions and 16 deletions
+15 -16
View File
@@ -16,14 +16,16 @@ export interface BoardClaimProps {
initialCode?: string initialCode?: string
} }
/** The self-service bring-up steps — the board is already set up from the week. */ /** Self-service bring-up: download the App Lab app, import it, Run it, enter the code. */
const STEPS = [ const STEPS = [
'On your board, open a terminal and run the workshop setup script (below).', 'Download the board app below.',
'It checks your node is ready, registers it, and scrolls a code across the LED matrix.', 'On your Uno Q, open App Lab → “Import an app” → pick the downloaded file.',
'Type the code your board is showing to bind it to your team.', 'Click Run. The node comes up and scrolls a code across the LED matrix.',
'Type that code below to bind the board to your team.',
] ]
const SETUP_CMD = 'curl -fsSL https://apess.redclaw.dev/setup.sh | bash' // Served same-origin from the web container (see deploy/nginx: location /download/).
const DOWNLOAD_URL = '/download/apess-onboard.zip'
/** /**
* The board bring-up wizard for pre-deployed devices: the attendee runs the * The board bring-up wizard for pre-deployed devices: the attendee runs the
@@ -84,17 +86,14 @@ export function BoardClaim({ teamId, teamName, members, connected, port, onClaim
</li> </li>
))} ))}
</ol> </ol>
<div className="flex items-center gap-2 rounded-md border border-border bg-muted/40 px-3 py-2"> <a
<code className="font-mono text-[11px] text-foreground/90 select-all flex-1 truncate">{SETUP_CMD}</code> href={DOWNLOAD_URL}
<button download
type="button" data-testid="download-board-app"
aria-label="Copy setup command" className="flex items-center justify-center gap-2 rounded-md border border-primary/40 bg-primary/5 px-3 py-2.5 text-sm font-medium text-primary hover:bg-primary/10 transition-colors"
onClick={() => navigator.clipboard?.writeText(SETUP_CMD)} >
className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground hover:text-foreground shrink-0" <span aria-hidden>↓</span> Download the board app
> </a>
Copy
</button>
</div>
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input
aria-label="Claim code" aria-label="Claim code"
+7
View File
@@ -28,6 +28,13 @@ describe('TeamRegistration', () => {
expect(screen.getByRole('heading', { name: /team registration/i })).toBeInTheDocument() expect(screen.getByRole('heading', { name: /team registration/i })).toBeInTheDocument()
}) })
it('offers the board-app download (same-origin /download route)', () => {
renderPage()
const link = screen.getByTestId('download-board-app')
expect(link).toHaveAttribute('href', '/download/apess-onboard.zip')
expect(link).toHaveAttribute('download')
})
it('persists the team name to the session store on input', async () => { it('persists the team name to the session store on input', async () => {
const user = userEvent.setup() const user = userEvent.setup()
renderPage() renderPage()