From f73885c47d0795458bce39aa955c26ce6127746d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:06:53 +0000 Subject: [PATCH 1/6] fix(birdsbirdbirds): one Save label, gallery before last step, preview-offered wording --- challenges/birdsbirdbirds/challenge.json | 2 +- challenges/birdsbirdbirds/server.mjs | 2 +- .../skills/start-challenge/SKILL.md | 28 +++++++++++++------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/challenges/birdsbirdbirds/challenge.json b/challenges/birdsbirdbirds/challenge.json index 303414a..2d4703d 100644 --- a/challenges/birdsbirdbirds/challenge.json +++ b/challenges/birdsbirdbirds/challenge.json @@ -19,7 +19,7 @@ { "id": "draw-bird", "title": "Draw your bird", - "hint": "Draw a bird on the page and press Save.", + "hint": "Draw a bird on the page and press Save my bird.", "done_when": "The file bird.png exists in the workspace." }, { diff --git a/challenges/birdsbirdbirds/server.mjs b/challenges/birdsbirdbirds/server.mjs index d046b5d..52404c4 100644 --- a/challenges/birdsbirdbirds/server.mjs +++ b/challenges/birdsbirdbirds/server.mjs @@ -116,7 +116,7 @@ function sendSavedBird(response, workspace) { body = readWorkspaceFile(path.join(workspace, 'bird.png'), MAX_PNG_BYTES) } catch (error) { if (error.code === 'ENOENT') return sendText(response, 404, 'no bird.png yet') - if (error instanceof RefusedFileError) return sendText(response, 422, `${error.message}; press Save on the drawing page again`) + if (error instanceof RefusedFileError) return sendText(response, 422, `${error.message}; press Save my bird on the drawing page again`) throw error } diff --git a/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md b/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md index 8f7a871..c42f027 100644 --- a/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md +++ b/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md @@ -41,14 +41,19 @@ starts (after `session_info`, when you have it): call `show_preview` with `http://127.0.0.1:4322/` without asking first. Without `show_preview`, give them that URL to open right away. +When `show_preview` returns `offered`, a dialog is still asking the +participant whether to open the page: say "Click **Open preview** to start +drawing." Never say the page is open until it returns `navigated`. + When you resume with `draw-bird` not yet complete, first use `bash` to check whether `bird.png` exists in the current directory: a save made just before a restart sends no message. If it exists, go straight to step 2. -Tell them to draw any bird, then press Save. Saving tells you on its own, as -a `[birdsbirdbirds]` message. If they say they saved but no message came, use -`bash` to check that `bird.png` exists in the current directory; if it -doesn't, ask them to press Save and look again. When it exists, go on to +Tell them to draw any bird, then press **Save my bird**; always call the +button that. Saving tells you on its own, as a `[birdsbirdbirds]` message. If +they say they saved but no message came, use `bash` to check that `bird.png` +exists in the current directory; if it doesn't, ask them to press **Save my +bird** and look again. When it exists, go on to step 2. ## 2. Meet the bird @@ -116,13 +121,15 @@ again at step 1. Ask how the flight looks and offer one tweak, such as faster, higher, or more wobbly. If they want it, edit `bird.json`, rerun the checker, and let -them watch again. When they say they've watched it fly, report `fly`. +them watch again. When they say they've watched it fly, go on to step 5; +`fly` is the last step, and reporting it moves the participant's focus to +Finish, so it waits for the gallery answer. ## 5. Gallery -Only with `publish_gallery_item`. Use the display name from `session_info` -and ask: "Want this on the booth screens? It'll show with your name, -[display name], and staff can remove it at any time." +Only with `publish_gallery_item`; without it, skip to step 6. Use the display +name from `session_info` and ask: "Want this on the booth screens? It'll show +with your name, [display name], and staff can remove it at any time." On a yes, run the checker once more, then `read` `bird.json` and call `publish_gallery_item` with: @@ -134,3 +141,8 @@ On a yes, run the checker once more, then `read` `bird.json` and call - `payload`: exactly the object you just read from `bird.json` On a no, move on without publishing. + +## 6. Finish + +Report `fly`. With `report_step`, end with exactly: "Press Finish to claim +your 150 pts." Finish closes the page, so don't invite them to keep watching. From 83d056e516aa56708011d138700fb9049e289196 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:09:42 +0000 Subject: [PATCH 2/6] fix(birdsbirdbirds): fit the drawing page to the station viewport --- challenges/birdsbirdbirds/page/app.mjs | 13 ++++ challenges/birdsbirdbirds/page/index.html | 11 +-- challenges/birdsbirdbirds/page/style.css | 87 ++++++++++++++--------- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/challenges/birdsbirdbirds/page/app.mjs b/challenges/birdsbirdbirds/page/app.mjs index 05040db..5112dae 100644 --- a/challenges/birdsbirdbirds/page/app.mjs +++ b/challenges/birdsbirdbirds/page/app.mjs @@ -42,6 +42,9 @@ const toSkyButton = document.getElementById('to-sky-button') const drawAgainButton = document.getElementById('draw-again-button') const sky = document.getElementById('sky') +const INITIAL_PROMPT = 'Pick a colour and start drawing.' +const DRAWN_PROMPT = 'Looking good! Press Save my bird when it’s done.' + const pen = { colour: COLOURS[0].value, width: PEN_SIZES[1].width, erasing: false } const stroke = { pointerId: undefined, lastPoint: undefined } const undoSnapshots = [] @@ -191,6 +194,16 @@ function endStroke(event) { stroke.pointerId = undefined stroke.lastPoint = undefined + promptToSave() +} + +/** + * Swaps the initial prompt for a nudge to save once there is a drawing. + * Any other message (Undone, Cleared, Saved, an error) is left alone, since + * it reports something the attendee just did. + */ +function promptToSave() { + if (statusLine.textContent === INITIAL_PROMPT) setStatus(DRAWN_PROMPT) } /** Clears the canvas to transparent, undoably. */ diff --git a/challenges/birdsbirdbirds/page/index.html b/challenges/birdsbirdbirds/page/index.html index 4217111..1885783 100644 --- a/challenges/birdsbirdbirds/page/index.html +++ b/challenges/birdsbirdbirds/page/index.html @@ -41,16 +41,19 @@

Draw your bird

+ +
+ +

Pick a colour and start drawing.

+ + +
- -

Pick a colour and start drawing.

- -
diff --git a/challenges/birdsbirdbirds/page/style.css b/challenges/birdsbirdbirds/page/style.css index 4d8a632..58e4ab0 100644 --- a/challenges/birdsbirdbirds/page/style.css +++ b/challenges/birdsbirdbirds/page/style.css @@ -44,39 +44,45 @@ button:disabled { opacity: 0.45; } +/* The booth station's Chrome is about 1000x597, so the whole drawing page, + Save button included, must fit one screen. The floor keeps shorter or + narrower windows usable by letting the page scroll instead of squashing. */ #draw-mode { - max-width: 1000px; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + max-width: 1280px; + height: max(100dvh, 540px); margin: 0 auto; - padding: 24px 20px 40px; + padding: 10px 20px 14px; } .draw-header h1 { - margin: 0 0 6px; - font-size: 2.4rem; + margin: 0 0 2px; + font-size: 1.7rem; + line-height: 1.15; } .lead { - margin: 0 0 20px; - font-size: 1.15rem; + margin: 0 0 8px; + font-size: 1rem; } .draw-layout { display: flex; - flex-wrap: wrap; gap: 24px; - align-items: flex-start; + min-height: 0; } .toolbar { display: flex; flex-direction: column; - gap: 16px; - flex: 0 0 220px; + gap: 8px; + flex: 0 0 280px; } .tool-group { margin: 0; - padding: 12px; + padding: 4px 8px 8px; border: 2px solid var(--slide-text); border-radius: 14px; background: #fff; @@ -91,28 +97,29 @@ button:disabled { .sizes, .tools { display: flex; - flex-wrap: wrap; - gap: 8px; + gap: 4px; } .swatch { - width: 52px; - height: 52px; + flex: 1; + min-width: 0; + aspect-ratio: 1; + max-height: 44px; border: 3px solid #fff; border-radius: 50%; box-shadow: 0 0 0 2px var(--slide-text); } .swatch[aria-pressed="true"] { - box-shadow: 0 0 0 4px var(--slide-text), 0 0 0 7px var(--slide-lime); + box-shadow: 0 0 0 3px var(--slide-text), 0 0 0 5px var(--slide-lime); } .size { display: flex; + flex: 1; align-items: center; justify-content: center; - width: 56px; - height: 56px; + height: 44px; border: 2px solid var(--slide-text); border-radius: 12px; background: #fff; @@ -132,12 +139,12 @@ button:disabled { .tool-button, .action-button { flex: 1; - min-height: 52px; - padding: 8px 14px; + min-height: 44px; + padding: 4px 14px; border: 2px solid var(--slide-text); border-radius: 12px; background: #fff; - font-size: 1.1rem; + font-size: 1.05rem; font-weight: 700; } @@ -146,16 +153,27 @@ button:disabled { gap: 8px; } -.canvas-column { +/* Beside the tools, so Save is never below the fold. */ +.save-area { display: flex; flex-direction: column; - align-items: center; - gap: 14px; - flex: 1 1 512px; + gap: 6px; + align-items: stretch; +} + +/* Sized from the space left over, so the square canvas never overflows. */ +.canvas-column { + display: flex; + flex: 1; + align-items: flex-start; + justify-content: center; + min-width: 0; + container-type: size; } .canvas-frame { - width: min(512px, 100%); + /* Room for the frame's 6px offset shadow. */ + width: min(100cqw - 6px, 100cqh - 6px, 640px); aspect-ratio: 1; border: 3px solid var(--slide-text); border-radius: 16px; @@ -181,14 +199,13 @@ button:disabled { } .save-button { - min-width: 260px; - min-height: 64px; - padding: 12px 32px; + min-height: 56px; + padding: 8px 16px; border: 3px solid var(--slide-text); border-radius: 16px; background: var(--slide-lime); box-shadow: 4px 4px 0 var(--slide-text); - font-size: 1.5rem; + font-size: 1.4rem; font-weight: 800; } @@ -198,9 +215,9 @@ button:disabled { } .status { - min-height: 1.5em; + min-height: 2.6em; margin: 0; - font-size: 1.2rem; + font-size: 1rem; font-weight: 600; text-align: center; } @@ -215,14 +232,16 @@ button:disabled { .waiting { margin: 0; - font-size: 1rem; + font-size: 0.9rem; + text-align: center; opacity: 0.7; } .link-button { + padding: 2px; border: none; background: none; - font-size: 1.1rem; + font-size: 1rem; font-weight: 700; text-decoration: underline; } From c42790bd557b9e62d0505a7fc60b501dab5f5df2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:13:35 +0000 Subject: [PATCH 3/6] fix(birdsbirdbirds): fold the finish line into the base skill's and cover the 422 wording --- .../skills/start-challenge/SKILL.md | 19 ++++++++++++------- test/birdsbirdbirds.test.mjs | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md b/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md index c42f027..29592f4 100644 --- a/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md +++ b/challenges/birdsbirdbirds/skills/start-challenge/SKILL.md @@ -41,9 +41,10 @@ starts (after `session_info`, when you have it): call `show_preview` with `http://127.0.0.1:4322/` without asking first. Without `show_preview`, give them that URL to open right away. -When `show_preview` returns `offered`, a dialog is still asking the -participant whether to open the page: say "Click **Open preview** to start -drawing." Never say the page is open until it returns `navigated`. +Whenever `show_preview` returns `offered`, a dialog is still asking the +participant whether to open the page: tell them to click **Open preview**, +here "Click **Open preview** to start drawing." Never say the page is open +until a call returns `navigated`. When you resume with `draw-bird` not yet complete, first use `bash` to check whether `bird.png` exists in the current directory: a save made just before @@ -116,7 +117,8 @@ checker or edit it. When it passes, report `animate`. The drawing page switches to the sky on its own once a valid `bird.json` exists. Tell the participant to look at it (call `show_preview` again if the -page was closed). They can press "Draw again" to redraw; if they do, start +page was closed; on `offered`, say "Click **Open preview** to watch it +fly."). They can press "Draw again" to redraw; if they do, start again at step 1. Ask how the flight looks and offer one tweak, such as faster, higher, or @@ -127,7 +129,8 @@ Finish, so it waits for the gallery answer. ## 5. Gallery -Only with `publish_gallery_item`; without it, skip to step 6. Use the display +Only with `publish_gallery_item`; without it, or if you already published +this bird in this conversation, skip to step 6. Use the display name from `session_info` and ask: "Want this on the booth screens? It'll show with your name, [display name], and staff can remove it at any time." @@ -144,5 +147,7 @@ On a no, move on without publishing. ## 6. Finish -Report `fly`. With `report_step`, end with exactly: "Press Finish to claim -your 150 pts." Finish closes the page, so don't invite them to keep watching. +Report `fly`. With `report_step`, end with the finish line, naming the +points: "All steps are done. Press Enter, or choose Finish, to claim your 150 +pts." Finish closes the page and clears the bird, so don't invite them to +keep watching it. diff --git a/test/birdsbirdbirds.test.mjs b/test/birdsbirdbirds.test.mjs index dae489a..7141bee 100644 --- a/test/birdsbirdbirds.test.mjs +++ b/test/birdsbirdbirds.test.mjs @@ -721,6 +721,7 @@ test('the server does not serve a symlinked bird.png', (testContext) => withWork const response = await sendRequest({ port, requestPath: '/bird.png' }) assert.equal(response.status, 422) + assert.match(response.body.toString(), /press Save my bird on the drawing page again/) })) test('the server does not serve a bird.png over the size cap', (testContext) => withWorkspace(async ({ workspace }) => { From f7896e2803405a156c07daffdf81cd1ae9a3c6b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:15:43 +0000 Subject: [PATCH 4/6] fix(birdsbirdbirds): stack the drawing page on narrow screens and keep Save out of the toolbar role --- challenges/birdsbirdbirds/page/app.mjs | 3 +- challenges/birdsbirdbirds/page/index.html | 40 ++++++++++++----------- challenges/birdsbirdbirds/page/style.css | 33 +++++++++++++++++-- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/challenges/birdsbirdbirds/page/app.mjs b/challenges/birdsbirdbirds/page/app.mjs index 5112dae..70e45be 100644 --- a/challenges/birdsbirdbirds/page/app.mjs +++ b/challenges/birdsbirdbirds/page/app.mjs @@ -42,7 +42,7 @@ const toSkyButton = document.getElementById('to-sky-button') const drawAgainButton = document.getElementById('draw-again-button') const sky = document.getElementById('sky') -const INITIAL_PROMPT = 'Pick a colour and start drawing.' +const INITIAL_PROMPT = statusLine.textContent const DRAWN_PROMPT = 'Looking good! Press Save my bird when it’s done.' const pen = { colour: COLOURS[0].value, width: PEN_SIZES[1].width, erasing: false } @@ -294,6 +294,7 @@ function showDraw() { skyMode.hidden = true drawMode.hidden = false sky.replaceChildren() + setStatus(INITIAL_PROMPT) toSkyButton.hidden = flight.entry === undefined } diff --git a/challenges/birdsbirdbirds/page/index.html b/challenges/birdsbirdbirds/page/index.html index 1885783..43853c2 100644 --- a/challenges/birdsbirdbirds/page/index.html +++ b/challenges/birdsbirdbirds/page/index.html @@ -18,28 +18,30 @@

Draw your bird

-