All jobs

Review Accessibility on Every Nuxt Pull Request

Yes. On each pull request, an agent builds your Nuxt app, runs the changed pages through a browser with axe, reads components for labels and focus, and comments.

Run this on RushIn a Project that has your repository.

What the agent does

The agent reads what the project already checks so its review adds to it rather than repeating it. In an app like nuxt/movies, nuxt.config.ts loads @unocss/nuxt, @nuxt/image, @nuxtjs/i18n with more than a dozen locales including fa-IR, and @nuxtjs/html-validator with the html-validate recommended and standard rule sets but failOnError: false, so markup errors are logged and never block. app/app.vue sets htmlAttrs.lang from the active locale through useHead. tests/e2e/homepage.spec.ts already queries by role, getByRole('navigation', { name: 'Primary' }) and getByRole('heading', { level: 2 }), and .github/workflows runs the Vitest component tests, lint, nuxt typecheck and the Playwright suite across desktop Chrome, Safari and a mobile Chrome project. What nobody runs is an accessibility engine, and the review fills that gap.

Then it reads the components the pull request touches, with the whole app as context for what is normal here. app/components/NavBar.vue is a <nav aria-label="Primary"> whose four links are icon <div>s with a :title and no text, so a screen reader gets the title and a keyboard user gets no visible label. carousel/Base.vue has two scroll buttons with title only, styled op0 hover:op100, so they are invisible until the mouse arrives and a focused button stays invisible unless a focus style is added. photo/Modal.vue is a fixed overlay with close, previous and next buttons that have data-testid attributes but no accessible names, an image marked aria-hidden with no alternative text elsewhere, keyboard handling for Escape and the arrow keys through useEventListener, and no role="dialog", no aria-modal, no focus trap and no return of focus when it closes. StarsRate.vue is two aria-hidden images, meaningful only because media/Card.vue prints formatVote(item.vote_average) beside it, and pages/search.vue has an input with a placeholder and no label. LanguageSwitcher.vue, by contrast, pairs its <select> with a <label for="langSwitcher">, and media/Card.vue gives its NuxtImg an alt from the title.

It also runs the app. In the sandbox it installs with pnpm, builds with nuxt build, starts the output with node .output/server/index.mjs, and drives a browser to the pages the pull request changes, running axe against each one at desktop and mobile widths and in fa-IR as well as English, because the app sets lang per locale but never sets dir, so right-to-left text renders in a left-to-right layout. It tabs through each changed page and records where focus goes, whether it is visible, and whether the modal keeps it. The axe engine is installed in the sandbox only; the repository is not changed.

The result is one review comment on the pull request. Each finding names the component and line, the WCAG criterion it fails, what a screen reader or keyboard user experiences, and the fix in this codebase's style: an aria-label bound to the same $t() string the title uses, role="dialog" and aria-modal="true" on the modal with focus moved to the close button on open and back to the trigger on close, a focus-visible:op100 class beside hover:op100 on the carousel buttons, a visually hidden <label> for the search input, and dir set from the locale in the same useHead call that sets lang. Findings that axe reported get the rule id; findings from reading get the reasoning. The agent pushes nothing.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Review this pull request to a Nuxt app for accessibility without changing any code. Read nuxt.config.ts, app/app.vue, i18n.config.ts, the Playwright and Vitest configs, tests/e2e/homepage.spec.ts, every component and page under app that the pull request changes, and NavBar.vue, carousel/Base.vue, photo/Modal.vue, StarsRate.vue, LanguageSwitcher.vue, media/Card.vue and pages/search.vue for the conventions the app follows. Install with pnpm, run nuxt build, start the output server, and for each changed page run axe in a browser at a desktop and a mobile width and in English and in fa-IR, then tab through the page and record where focus goes, whether it is visible, and whether any overlay traps and returns it. Report controls with no accessible name, icon-only links and buttons that rely on title, images with aria-hidden and no text alternative, inputs with only a placeholder, overlays without dialog semantics or focus management, hover-only visibility that hides focused controls, and a lang attribute without a matching dir for right-to-left locales. Post one review comment on the pull request: each finding with the component and line, the WCAG criterion, the axe rule id where one applies, what a screen reader or keyboard user experiences, and a fix in the style of this codebase, ordered by severity, ending with what the change does well. Push nothing.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/123"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a review comment on the pull request. It opens with what was tested: the pages the pull request changes, the two widths, the two locales, and the axe rule set, so a reader knows the scope before the findings. Each finding then names the component and line, the WCAG criterion and the axe rule id where axe found it, what a screen reader announces or a keyboard user sees, and the fix written with the same UnoCSS attributes and $t() strings the component already uses.

Findings that the browser run produced carry the axe output; findings from reading, such as a modal without role="dialog" or focus that does not return to the trigger, carry the tab sequence the agent recorded. The comment ends with what the change does well, such as a labelled control or an image with a real alt, so the review is a fair account rather than a list of faults.

A reviewer checks the findings against the design, decides which ones block the merge, and asks the author for the fixes, or sends a follow-up message to the same session asking the agent to push them to the branch, since the sandbox still has the app built and the browser ready to rerun axe on the result.

Run it on every pull request that changes a page or component

In the Project that has your repository, add an automation with this prompt, mark it read-only so its runs comment but never push, and give it a GitHub trigger on pull_request.opened and pull_request.synchronize with branch_patterns set to main. Each pull request to main starts one run with the pull request appended to the prompt as the event, and each new push reruns the review on the new commits. Only work by an owner, member or collaborator fires it; pull requests from forks never do.

The trigger cannot filter on paths, so the prompt tells the agent to stop early when the diff touches nothing under app, and the automation's max_runs_per_hour caps how many reviews a busy day can start.

The use case this job follows, with its automation and trigger: Review every pull request.

Why run it on Rush?

An accessibility review needs a built app in a real browser, a screen reader's view of each control, a keyboard pass, and a second locale, and it needs all of that on every pull request, not once a quarter when someone remembers. In a Rush sandbox the agent builds the branch, runs axe at two widths and in two locales, tabs through the changed pages, and keeps the axe output and the tab sequence in the transcript, so each finding is reproducible.

The comment reads like a colleague's review rather than a tool's dump: it knows that LanguageSwitcher.vue already labels its select and that NavBar.vue relies on title, it writes fixes with the project's own UnoCSS attributes and translation strings, and it says what the change got right. Every pull request gets the same pass, whoever opened it.

Frequently asked questions

Does it change my components or push to the branch?

No. The automation runs read-only, so the result is a review comment. If you want the fixes applied, a follow-up message to the same session asks the agent to push them, and the browser in the sandbox reruns axe on the result before it does.

The project already runs html-validator. Why is this different?

@nuxtjs/html-validator checks markup validity and runs with failOnError: false, so it logs and never blocks, and it does not run a browser. The review adds axe against the rendered page, a keyboard pass, and a reading of each component for names, roles, focus and direction, which validity checks do not cover.

Why test in fa-IR as well as English?

Because app/app.vue sets lang from the active locale but nothing sets dir, so Farsi renders in a left-to-right layout with mirrored carousel arrows. Running the changed pages in a right-to-left locale catches that and anything else that only appears when the text direction flips.

Will it flag every pull request, even ones with no UI change?

The prompt tells the agent to read the diff first and stop with a one-line comment when nothing under app changed. The automation's max_runs_per_hour keeps the cost of those short runs bounded.

Does it add axe to the repository?

No. The engine is installed in the sandbox for the run and the repository is left as it was. If the team wants the check in CI, the comment can sketch a Playwright test using the same engine next to tests/e2e/homepage.spec.ts, but adding it is a separate change.