Skip to content

Add the intermediate level: The Checkout Trap - #92

Merged
sinduri-g merged 16 commits into
off-on-dev:mainfrom
sindurigf:feat/accessibility-nightmare-intermediate
Sep 15, 2026
Merged

sinduri-g merged 16 commits into
off-on-dev:mainfrom
sindurigf:feat/accessibility-nightmare-intermediate

Conversation

@sinduri-g

Copy link
Copy Markdown
Contributor

What this adds

  • The intermediate level of The Accessibility Nightmare, where the axe-core scan is green before you start and green when you finish
  • Three broken components: a size picker with no role, name or value; a basket confirmation that is not a dialog and loses the keyboard; a checkout form whose errors never reach a screen reader
  • verify.sh with six checks, driven by Playwright and the Guidepup Virtual Screen Reader
  • npm run listen and a ?listen panel, so players can observe announcements while they work rather than only when they run the grader
  • Level docs and an architecture diagram

Shared files, worth a reviewer's eye

  • lib/scripts/node.sh (new) adds check_playwright_tests and check_npm_dependencies, sourced by loader.sh. No existing adventure calls them.
  • lib/scripts/loader.sh gains one line, sourcing the above.
  • .gitignore: tools/ was unanchored, so it silently swallowed tools/listen.mjs and git add -A skipped it without a word. Now /tools/, which still covers the OpenTelemetry jar that adventure 04 downloads to the repo root.

Verified

From a fresh git clone in a real dev container, as the vscode user:

  • post-create and post-start succeed, and the server stays up with no intervention
  • the forwarded *.app.github.dev host returns 200
  • verify.sh reports 4 fail / 2 pass, and is idempotent
  • applying the solution gives all 6 checks passing
  • axe-core reports zero violations across all six reachable states, in dev and in a production build
  • the production bundle contains none of the dev tooling

Notes

  • Uses base:bookworm. Playwright has dropped Debian 11, so bullseye cannot install Chromium. Same fix as Fix: beginner devcontainer cannot install Playwright on Debian 11 #91 for beginner.
  • The solution walkthrough is held locally and goes to docs/solutions/ in a separate PR after the deadline.
  • community_url and the index.yaml deadline are still empty, matching beginner.

Beginner still needs its own PR

It cannot currently be completed in a Codespace: post-start.sh needs setsid, it has no vite.config.js so the forwarded port is blocked by Vite's host check, onAutoForward is openBrowser where every other config uses notify, and the docs point at localhost.

🤖 Generated with Claude Code

sindurigf and others added 16 commits September 13, 2026 15:14
Signed-off-by: sindurigf <sinduri.g@gmail.com>
…ghtmare-intermediate

Signed-off-by: sindurigf <sinduri.g@gmail.com>

# Conflicts:
#	Makefile
#	adventures/accessibility-nightmare/docs/intermediate.yaml
Signed-off-by: sindurigf <sinduri.g@gmail.com>
…ed preview

post-start.sh backgrounded the dev server with a plain `nohup ... &`, which
Codespaces kills when the lifecycle command's process group is torn down. The
health check passed because it polled while the process was briefly alive, so
the script reported success and the port was dead moments later. Players saw a
502 from the forwarded URL. setsid puts the server in its own session.

Port 5173 no longer auto-opens. openBrowser launched an external window;
openPreview used the editor's Simple Browser, which intercepts Tab and Escape
and makes a keyboard-driven level impossible to work. notify leaves the choice
to the player, and the docs now say to use a real browser tab and why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The panel reserved 22rem of body padding-right beside itself. In a narrow
window, an editor preview pane or a phone viewport that left almost nothing for
the storefront: measured at a 420px viewport, the page rendered 68px wide.

It now floats over the page instead of reserving space, scales with
min(20rem, 38vw), and sets pointer-events: none so every click reaches the page
underneath. Measured after the change, the page keeps its full width at 420,
800 and 1400px, and Tab still walks the page at each.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The claim that the embedded preview intercepts Tab and Escape was wrong. It was
written from a report of the keyboard not working there, but the real cause was
Safari: on macOS it does not move focus to links or buttons unless keyboard
navigation is enabled. The product page has no form fields, so Tab looked
completely dead. Verified once the setting was on: Tab walks the page correctly
in the editor's Simple Browser.

Port 5173 goes back to openPreview, and the docs now carry the Safari note
instead, which is the issue players will actually hit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
Simple Browser fails two different ways here. In Safari it renders the page in
a small corner of the pane, a webview sizing bug in VS Code. In Chrome it
refuses to connect, because a private forwarded port redirects through GitHub's
auth proxy and an iframe cannot complete that flow.

Making the port public would fix only the Chrome half, exposes the storefront to
anyone holding the URL, and leaves Safari users with the corner rendering. The
repo has that pattern in 02-building-cloudhaven_03-expert via postAttachCommand
if it is ever wanted here.

notify works in every browser and costs one click. The docs now say to use a
browser tab and why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
… option

A side rail could not win. Reserving width squeezed the storefront, and
floating over it hid the header and nav on the right. Either way the page lost
the very controls the level asks you to reach.

The panel is now a full-width strip along the bottom, with matching padding on
<body> so it never covers the page. Measured at 420, 800 and 1400px: the page
keeps its full width and the header, nav and menu are all fully in view at each.

The start step also now explains that a private forwarded port cannot load in
the editor preview, and how to make it public if you would rather work there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The forwarded port URL cannot carry a query: portsAttributes allows only label,
protocol, onAutoForward, requireLocalPort and elevateIfNeeded, and the schema
sets additionalProperties false. So players add ?listen themselves, and the
obvious move is to append it to whatever is in the address bar.

That used to fail silently. Routes live in the hash, so ?listen had to precede
it, and appending put the flag somewhere main.jsx never looked. Worse, the route
matcher then read 'product/running-shoes?listen' as the path and fell through to
the homepage, so appending it quietly sent you to the wrong page.

main.jsx now checks the query and the hash, and the router strips anything after
a '?'. Verified: the panel opens and the route resolves for /?listen#/product/...,
/#/product/...?listen and /#/checkout?listen, and stays closed without the flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
.gitignore had an unanchored "tools/", so it matched a tools directory at any
depth. adventures/accessibility-nightmare/intermediate/tools/listen.mjs was
therefore never committed, and `git add -A` skipped it silently. npm run listen
failed for anyone who cloned the repo:

    Error: Cannot find module '.../intermediate/tools/listen.mjs'

The rule exists for the OpenTelemetry agent jar that
04-blind-by-design_03-expert downloads to $REPO_ROOT/tools, which is the only
tools directory in the repo, so anchoring it to /tools/ keeps that ignored and
stops it reaching into challenge levels.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The footer counted steps whose spoken log was empty, so opening the basket
dialog reported "button, Add to basket" and passed unremarked, as did a
rejected submission and a completed order. Only Escape produced an empty log,
so the summary read "1 step said nothing at all" on a page with four separate
announcement faults. A player could reasonably conclude only one thing was
broken.

Steps that change the page without moving focus there now declare what a
screen reader should learn from them, and the step reports when nothing said
it. Broken build flags three; the repaired build flags none.

It still only reports what was and was not said, never what to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The picker is the largest of the three repairs and had the worst failure mode.
A native <select> is the correct answer for a size control on real work, and it
failed with "element(s) not found: getByRole('listbox')", which tells a player
nothing about why a reasonable choice was refused.

Three changes, none of which make the repair easier:

- The picker tests now detect a native <select> and say plainly that it is right
  on real work, out of scope here because the exercise is understanding what it
  does for you, and link the pattern to build instead.
- SizePicker.jsx carries that link in a comment, where someone deep in the file
  will actually see it, along with a note that it is the largest repair.
- The docs recommend an order: form, then confirmation, then picker. Three
  visible wins before the long one, rather than the wall first.

Verified: the shape check rejects a native select and passes the APG combobox;
broken still 4 fail / 2 pass; solution still 12/12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
Found by auditing a fresh clone in a real dev container rather than a copied
working tree.

- what_you_learn linked https://www.w3.org/WAI/ARIA/apg/practices/live-regions/
  which returns 404. Every other documented URL was checked and returns 200.
- The architecture section still said the checkout was on the product page, and
  post-start.sh printed the same thing. It has been its own page since the
  restructure.
- The explore step described npm run listen as marking steps that said nothing,
  which undersold it: it now names steps that changed the page without
  announcing anything, which on the broken build is three rather than one.
- openFiles opened SizePicker first, contradicting the docs' own advice to take
  the repairs smallest first.

Verified end to end in the dev container, as the vscode user: the server is up
with no intervention, the forwarded host returns 200, verify.sh gives 4 fail /
2 pass and is idempotent, npm run listen names three unannounced steps, and
applying the solution gives all 6 checks passing with listen flagging nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
…uessing motive

Both levels of this adventure serve on port 5173 and playwright.config.js sets
reuseExistingServer, so whatever is already listening gets graded. That misled
this work twice: it hid a dead server by starting its own, and it made a native
select appear to pass three tests that should have rejected it. A player running
both levels locally would get failures that describe the wrong code entirely.

A global setup now loads the product page before any test runs and refuses to
continue unless it is this level, naming the likely cause. Verified by starting
the beginner level on 5173 and running this suite against it: it stops with
"Something other than this level is serving http://127.0.0.1:5173" instead of
eleven misleading failures.

The dependency check also assumed anyone who added a package was smuggling in a
headless UI library. It now reports which packages were added or removed rather
than printing the whole list, and the hint covers both cases. Adding prettier
now says "added: prettier" instead of a lecture about Radix.

Broken still 4 fail / 2 pass, solution still 12/12.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
The docs and post-start.sh both pointed at http://localhost:5173. In a
browser-based Codespace there is no localhost to visit: the port is forwarded to
an address ending in .app.github.dev, reached from the Ports tab. The wording
came from the beginner level and was never questioned.

The start step now says to open the Ports tab and click ShopSmart, and shows the
route as something to append to whatever address that gives you. The services
block declares port 5173 rather than a localhost URL, matching dead-reckoning,
which is the only other adventure that lists services this way. post-start.sh
prints the Ports tab instruction and both routes.

127.0.0.1 references inside tools/listen.mjs and the Playwright config are
untouched: those run in the container, where they are correct.

Beginner has the same wording and the same services block, for its own PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
Follows the dead-reckoning convention: docs/diagrams/<adventure>-<level>.svg,
with the filename in architecture_diagram.

It shows the purchase as three surfaces, names the component and the fault each
one carries, then the three checks that look at the same page, with axe-core
marked as reporting zero violations before and after. That contrast is the
level, so the diagram says it rather than leaving it to the prose.

Hand-authored rather than exported, so it is small (6KB against 167KB for the
existing ones) and diffs as text. It carries a title and a desc and is marked
role=img, which seemed the least we could do on this particular adventure.

Checked: valid XML, nothing overflows the viewBox at any text length, and the
arrow labels no longer collide with the cards after narrowing the first two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: sindurigf <sinduri.g@gmail.com>
@sinduri-g
sinduri-g merged commit beee01a into off-on-dev:main Sep 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants