What the agent does
The agent clones the site into its own cloud sandbox and reads where the colours live before it adds a theme. In a blog built from gatsbyjs/gatsby-starter-blog, src/style.css opens with a :root block that defines the whole palette as custom properties: --color-primary: #005b99, --color-text: #2e353f, --color-text-light: #4f5969, --color-heading: #1a202c, --color-heading-black: black and --color-accent: #d1dce5, next to the spacing, font and type-scale tokens. Every rule below reads those variables; body uses --color-text, links use --color-primary, blockquote uses --color-text-light with a primary-coloured border, and table headers draw their line in --color-accent. There is no prefers-color-scheme query and no data-theme attribute anywhere in src/. gatsby-browser.js imports the two fontsource packages, normalize.css, style.css and prismjs/themes/prism.css, a light code theme; gatsby-ssr.js has one hook, onRenderBody, which sets lang on the html element.
That layout decides the approach. The agent adds a second block in style.css, :root[data-theme="dark"], that reassigns the six colour tokens plus a background variable it introduces for body, and sets color-scheme: dark so form controls and scrollbars follow. Code blocks need their own treatment because prism.css hard-codes light token colours: the agent adds the dark Prism token overrides under the same selector rather than importing a second theme, so there is one source of truth. In gatsby-ssr.js it extends onRenderBody with setHeadComponents to inject a small inline script that reads localStorage.theme, falls back to matchMedia('(prefers-color-scheme: dark)'), and sets document.documentElement.dataset.theme before the first paint, which is what stops the light flash on a static site.
The toggle goes in src/components/layout.js, the one component every page renders. Its Layout wraps global-wrapper, a global-header holding the main-heading link on the index or the header-link-home link elsewhere, main and a footer. The agent adds a button beside the header link with aria-pressed and an accessible label, a useEffect that reads the attribute set by the head script so the server-rendered markup and the hydrated state agree, and a click handler that flips data-theme and writes localStorage.theme. gatsby-config.js keeps theme_color commented out in gatsby-plugin-manifest, and the agent leaves it so, since a manifest colour cannot follow the toggle.
There are no tests to run: package.json has no test runner and its test script prints a reminder to write some and exits 1, and the repository has no .github/ directory. So the check is the build. The agent runs npm run build, serves public/ with npm run serve, opens the index, a post such as content/blog/hello-world and the 404 page in the sandbox browser in both themes, screenshots each, and reads the contrast of body text, links and blockquote text against the new background from the computed styles. It runs npm run format, which applies the .prettierrc settings arrowParens: avoid and semi: false, before it opens the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add dark mode to this Gatsby blog. Read src/style.css, src/components/layout.js, gatsby-browser.js, gatsby-ssr.js, gatsby-config.js and src/pages/index.js. Add a :root[data-theme=\"dark\"] block in style.css that reassigns the colour custom properties and a body background, sets color-scheme, and overrides the Prism token colours so code blocks are readable; keep the light values as they are. In gatsby-ssr.js inject a head script that applies localStorage.theme or the prefers-color-scheme match to document.documentElement before paint. Add a toggle button to the header in layout.js with aria-pressed that flips the attribute and stores the choice. Run npm run build and npm run serve, open the index, one post and the 404 page in both themes in the browser, take screenshots and attach them, check text, link and blockquote contrast against the dark background, run npm run format, and open a pull request with the screenshots in its description. Do not change fonts or spacing.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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.
- 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.
- 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 pull request on its own branch with three changed files: src/style.css gains the dark block and Prism overrides, src/components/layout.js gains the toggle, and gatsby-ssr.js gains the pre-paint script. The description shows the index and a post in light and dark side by side, from the screenshots the agent took, and lists the colour values it chose for each token.
The screenshots are on the session as artifacts, each with a url that downloads it for an hour, and the transcript at GET /o/{org}/sessions/{id}/messages keeps the gatsby build output and every page the browser opened. GET /o/{org}/p/{project}/sessions/{id}/changes shows the diff per file.
Before merging, a reviewer loads the preview, reloads a post with the dark theme stored to confirm there is no flash, checks the toggle with a keyboard, and reads the Prism overrides against a post with a code block. A follow-up on the same session changes a colour or moves the toggle without starting over.
Run it on every design review in the team
This is work a team steers rather than fires and forgets. One person starts the run from the console or with POST /o/{org}/sessions, and everyone who can see the Project follows the live stream at GET /o/{org}/sessions/{id}/events and the screenshots as they land. When the first pass is up, a designer sends a follow-up with POST /o/{org}/sessions/{id}/messages while the turn is finished, such as a different accent colour or a toggle in the footer instead of the header, and the sandbox wakes with the same checkout and node_modules.
For a site with several themes to try, a script sends one request per palette with a request_id made from the palette name, so each variant gets its own sandbox and pull request and a retried call returns the run already started. The key's webhook reports run.completed with data.session_url for each.
The use case this job follows, with its automation and trigger: Steer a run as a team.
Why run it on Rush?
Dark mode on a static site is judged by looking at it: whether the first paint flashes, whether code blocks stay readable, whether the toggle survives a reload. The agent builds the site with the repository's own gatsby build, opens the real pages in a browser in its sandbox, and attaches the screenshots, so the pull request is reviewed from pictures of the result rather than from a description of it.
The run is shared from the start. The stream, the screenshots and the diff are readable by the whole Project while it works, and the next request from a teammate goes to the same parked sandbox, which keeps a design discussion on one session instead of a chain of laptops.
Frequently asked questions
Will the page flash light before going dark?
The point of the script in gatsby-ssr.js is that it does not. It is injected into the head of every server-rendered page and sets data-theme on the root element from localStorage or the media query before the first paint, so the dark values from style.css apply on the first frame. The agent checks this by reloading a post in the sandbox browser with the dark choice stored.
What happens to code blocks?
gatsby-browser.js imports prismjs/themes/prism.css, whose token colours are set for a light background. The agent overrides those tokens under :root[data-theme="dark"] in style.css instead of importing a second theme, so the dark palette for code lives beside the dark palette for text.
Does it follow the operating system setting?
Yes, until the visitor chooses. With nothing in localStorage the head script uses prefers-color-scheme; the toggle writes the choice, and from then on the stored value wins. A reviewer can clear the stored value in the browser to see the system default again.
Can it run the tests?
There are none to run: the starter's test script exits with an error and prints a reminder to write some. The build and the browser check are the verification, and the pull request says so. Writing Playwright checks for the toggle is a separate job on a separate page.