CodeNomad/packages/ui/scripts/capture-readme.mjs
Pascal André d873cfd2ee
docs: refresh README and workspace screenshot for 0.20 (#782)
## Summary

- Update the main README for the shared OpenCode V2 service, current
features, managed desktop setup, minimum/recommended OpenCode versions,
and explicit service restart behavior.
- Correct release links, actual Electron/Tauri artifact formats and
architectures, Node guidance, remote authentication and SideCar
configuration fields.
- Align server and contributor documentation with the 0.20 baseline.
- Replace the old hero screenshot with a current 1600x900 workspace
rendered using real Solid components and synthetic demo data.
- Include a reproducible Chromium capture script and provenance notes;
external traffic is blocked and no user backend, daemon or provider is
used.

## Preview

![CodeNomad 0.20
workspace](https://raw.githubusercontent.com/NeuralNomadsAI/CodeNomad/docs/readme-0.20/docs/screenshots/workspace-0.20.png)

## Validation

- UI typecheck passes.
- Chromium capture passes transcript, connected-state, agent, timeline
and MCP readiness checks, with no uncaught renderer errors.
- Resulting image inspected visually.
- 20 local Markdown/image targets verified.
- Staged git diff --check passes.

Regenerate with: node packages/ui/scripts/capture-readme.mjs

Documentation and capture tooling only; CI is not being monitored.
2026-09-26 04:13:30 +02:00

64 lines
3.6 KiB
JavaScript

import assert from "node:assert/strict"
import { fileURLToPath } from "node:url"
import { mkdir } from "node:fs/promises"
import path from "node:path"
import { chromium } from "playwright"
import { createServer } from "vite"
import solid from "vite-plugin-solid"
const root = fileURLToPath(new URL("..", import.meta.url))
const output = path.resolve(root, "../../docs/screenshots/workspace-0.20.png")
const server = await createServer({ configFile: false, root, logLevel: "error",
plugins: [solid(), { name: "readme-capture", configureServer(server) {
server.middlewares.use("/api/events", (req, res) => {
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" })
res.write(": documentation fixture\n\n")
req.on("close", () => res.end())
})
server.middlewares.use("/readme", async (_req, res) => {
res.setHeader("Content-Type", "text/html")
res.end(await server.transformIndexHtml("/readme", '<html lang="en"><head><title>CodeNomad — demo workspace</title></head><body><div id="root"></div><script type="module" src="/tests/browser/fixtures/readme-workspace.tsx"></script></body></html>'))
})
} }], resolve: { dedupe: ["solid-js"] }, optimizeDeps: { exclude: ["lucide-solid"] },
server: { host: "127.0.0.1", port: 0, hmr: false, watch: null },
})
let browser
try {
await server.listen()
const origin = `http://127.0.0.1:${server.httpServer.address().port}`
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
const page = await browser.newPage({ viewport: { width: 1600, height: 900 }, deviceScaleFactor: 1, colorScheme: "dark", timezoneId: "UTC" })
const errors = []
page.on("pageerror", error => { errors.push(error.message); console.error(error.message) })
page.on("console", message => { if (message.type() === "error") console.error(message.text()) })
// No user backend, shared daemon, provider, or external website is contacted.
await page.route("**/*", async route => {
const url = new URL(route.request().url())
if (url.origin !== origin) return route.abort()
if (url.pathname === "/api/events") return route.continue()
if (url.pathname.includes("/api/") || url.pathname.startsWith("/workspaces/")) {
return route.fulfill({ contentType: "application/json", body: "{}" })
}
return route.continue()
})
await page.goto(`${origin}/readme`)
await page.getByText("Keyboard navigation is ready", { exact: true }).waitFor()
await page.getByText("Connected", { exact: true }).waitFor()
await page.getByText("project-docs", { exact: true }).waitFor()
await page.getByRole("button", { name: "Agent: Build", exact: true }).waitFor()
assert.equal(await page.locator("[data-app-tab-id]").count(), 8, "Show all eight demo projects")
await page.getByText("Add workspace notifications", { exact: true }).waitFor()
await page.waitForFunction(() => Number(document.querySelector(".message-timeline")?.getAttribute("data-segment-count")) >= 180)
await page.locator('.message-timeline-segment[data-message-id="msg_01"]').waitFor()
await page.evaluate(() => document.fonts.ready)
await page.waitForTimeout(1500)
assert.equal(await page.locator(".keyboard-hints:visible, .kbd-hint:visible").count(), 0,
"Keyboard shortcut hints must remain hidden with the default preference")
assert.deepEqual(errors, [], "The documentation fixture must render without uncaught errors")
await mkdir(path.dirname(output), { recursive: true })
await page.screenshot({ path: output, animations: "disabled" })
console.log(output)
} finally {
await browser?.close()
await server.close()
}