CodingOpen SourceFreeActiveMachine-verified· advanced · ~30 min setup

Oh My Design: evidence-led UI work with deterministic delivery gates

Turn an AI-assisted interface request into an evidence-backed brief, traceable copy, deliberate structure, one production build, and a blind rendered critique, while using Oh My Design's local CLI gates to catch contract drift, stale source, unsafe stack replacement, and reviewable UI-risk signals before delivery.

FlowStacks verification by Shilpa Mitra·Submitted by @3x-haust· verified today· v1.0.0

Run this workflow

CI-verified, 7/7 fixtures passing.

Build this with your agent

One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.

Intended Use

Product and marketing UI work where a coding agent should make its framing, evidence, copy, structural choice, source freshness, and rendered review traceable instead of jumping directly to polished code. Use the pinned OMD process with explicit human scope and host approvals, run deterministic gates in the real project, inspect desktop and mobile renders, and keep the final design decision and release authority with a human.

Not for

  • Treating a green CLI or CI result as proof that an interface is beautiful, original, accessible, correct, legally cleared, or approved by users; the most important design and critique judgments remain agent-authored and require human review
  • Treating source-scan candidates as an authorship detector or automatic rejection: OMD documents them as advisory signals for contextual triage, and the source scanner exits successfully even when candidates exist
  • Assuming a successful render or lint covers real product behavior, authenticated states, keyboard/focus behavior, assistive technology, responsive edge cases, performance, security, or destructive flows
  • Installing directly into a shared host without reviewing the pinned source and backup plan: the installer copies plugin/agent payloads, patches Claude or Codex configuration, may refresh an existing Claude plugin cache, and can install a checksum-pinned browser-rs helper
  • Giving untrusted reference pages, imported files, screenshots, or tool output authority to execute commands or disclose credentials; OMD is a workflow layer running with its host's permissions, not an OS sandbox
  • Assuming `/tmp` is a supported render destination on current main: guarded writes stay inside the project root and render commands also append project-local `.omd/history.jsonl`
  • Treating pre-1.0 single-maintainer main as a stable release channel; pin exact commits and re-audit source, generated payloads, dependencies, and host mutations before upgrades

The Stack

Tested Against

3x-haust/oh-my-design@6b502884990dab63a5534d4d7fff81dd9caddaccsource archive@sha256:f96d8f973b417ff110d898802eec2038e9211a423f89e8546bef0448591fb56bNode.js@22.23.2smol-toml@1.8.0Playwright@1.61.1 (render gates independently re-audited; browser runtime is outside the published CI fixtures)

Side effects & data flow

Network
codeload.github.com, nodejs.org, and registry.npmjs.org/security endpoints in CI, the selected model/agent provider, explicitly chosen public reference sites, and optional Figma or browser-provider endpoints during a real run
Writes
./source.tar.gz, ./source/, ./node-runtime/, ./npm-cache/, and source/node_modules/ inside the disposable CI directory, project-local .omd records, PNG evidence, and user-approved application files during a real run, host plugin, agent, configuration, backup, and optional browser-helper files only when the user explicitly runs the installer
Credentials
A model-provider or coding-agent credential for agent-assisted work; optional FIGMA_TOKEN only for explicitly selected Figma operations; none for the FlowStacks CI gate

Data privacy

  • GitHub codeload, Node.js distribution, and npm advisory services CI requests only public pinned source/runtime bytes and lockfile dependency metadata; it sends no user project, prompt, screenshot, or credential (retention: per the respective operators' published policies)
  • the model or coding-agent provider selected by the user During a real run: authorized project code, briefs, copy, design context, and prompts; none is sent by FlowStacks CI (retention: per the selected provider and account policy)
  • public reference sites and any explicitly selected browser/Figma provider During real research or rendering: requested URLs, browser requests, page content, screenshots, and optional Figma document data; none is accessed by the browser-free CI gate (retention: per each provider's policy and the user's local evidence-retention choices)

Prerequisites

  • Node.js 22.19+ and a reviewed, commit-pinned Oh My Design installation for a real project run
  • Codex, Claude Code, or a compatible Pi host with narrowly scoped filesystem and command authority
  • A launchable supported browser provider for rendered evidence, plus human inspection of desktop/mobile output
  • Authorized public references and appropriate rights for every retained image, copy source, and design influence
  • curl, tar, xz, and a supported x64 Linux or Apple-silicon macOS host for the independent FlowStacks gate

Steps

  1. 1

    Pin the signed source and reproduce the Node 22 deterministic gates

    Download the exact owner-supplied commit archive, verify its byte size, SHA-256, path set, member count, and sole safe `CLAUDE.md -> AGENTS.md` symlink before extraction. Audit the MIT/package/lockfile/read-only scanner boundary as inert source, download an official Node.js 22.23.2 binary for the CI host and verify its release checksum, install the exact graph with lifecycle scripts disabled, require a clean moderate-severity audit, run the submitted 135-test slice, check the exact source-scan and stack-routing JSON, and pass TypeScript. No Skill, installer, MCP server, model, browser, reference site, host configuration, user project, or credential is invoked.

    set -eu
    curl --proto '=https' --tlsv1.2 --retry 3 --max-filesize 5242880 -fsSL   https://codeload.github.com/3x-haust/oh-my-design/tar.gz/6b502884990dab63a5534d4d7fff81dd9caddacc   -o source.tar.gz
    node <<'NODE'
    const crypto = require("crypto");
    const fs = require("fs");
    const { execFileSync } = require("child_process");
    function bad(message) { console.error("BAD: " + message); process.exit(1); }
    const archive = fs.readFileSync("source.tar.gz");
    if (archive.length !== 3748602) bad("source archive size changed");
    const digest = crypto.createHash("sha256").update(archive).digest("hex");
    if (digest !== "f96d8f973b417ff110d898802eec2038e9211a423f89e8546bef0448591fb56b") bad("source archive digest changed");
    const root = "oh-my-design-6b502884990dab63a5534d4d7fff81dd9caddacc/";
    const names = execFileSync("tar", ["-tzf", "source.tar.gz"], { encoding: "utf8" }).trim().split("\n");
    if (names.length !== 1128) bad("source archive member count changed");
    if (names.some((name) => !name.startsWith(root) || name.startsWith("/") || name.split("/").includes(".."))) bad("unsafe source archive path");
    const verbose = execFileSync("tar", ["-tvzf", "source.tar.gz"], { encoding: "utf8" }).trim().split("\n");
    const links = verbose.filter((line) => line[0] === "l");
    if (links.length !== 1 || !links[0].endsWith(root + "CLAUDE.md -> AGENTS.md")) bad("source archive symlink set changed");
    if (verbose.some((line) => !["-", "d", "l"].includes(line[0]))) bad("source archive contains a special member");
    console.log("Oh My Design source pinned OK: commit 6b50288, 1128 members, sha256 " + digest);
    NODE
    mkdir source
    tar -xzf source.tar.gz -C source --strip-components=1
    node <<'NODE'
    const fs = require("fs");
    function bad(message) { console.error("BAD: " + message); process.exit(1); }
    function read(name) { return fs.readFileSync("source/" + name, "utf8"); }
    const license = read("LICENSE");
    if (!license.includes("MIT License") || !license.includes("Copyright (c) 2026 3x-haust")) bad("MIT identity changed");
    const pkg = JSON.parse(read("package.json"));
    if (pkg.name !== "@3xhaust/oh-my-design" || pkg.version !== "0.20.0" || pkg.engines?.node !== ">=22.19" || pkg.license !== "MIT") bad("package identity/runtime changed");
    const expectedDependencies = { playwright: "1.61.1", "smol-toml": "^1.8.0", tldts: "7.4.13", tsx: "^4.23.1", yaml: "^2.6.0" };
    if (JSON.stringify(pkg.dependencies) !== JSON.stringify(expectedDependencies)) bad("direct dependency set changed");
    const lock = JSON.parse(read("package-lock.json"));
    if (lock.lockfileVersion !== 3 || Object.keys(lock.packages || {}).length !== 61) bad("lockfile graph changed");
    for (const [name, meta] of Object.entries(lock.packages)) {
      if (name && (!meta.integrity || !String(meta.resolved || "").startsWith("https://registry.npmjs.org/"))) bad("unlocked or non-npm dependency " + name);
    }
    if (lock.packages["node_modules/smol-toml"]?.version !== "1.8.0") bad("smol-toml is below the audited fix");
    const installScripts = Object.entries(lock.packages).filter(([, meta]) => meta.hasInstallScript).map(([name]) => name).sort();
    const expectedScripts = ["node_modules/esbuild", "node_modules/fsevents", "node_modules/tsx/node_modules/fsevents"];
    if (JSON.stringify(installScripts) !== JSON.stringify(expectedScripts)) bad("dependency lifecycle-script set changed");
    const readOnlyModules = ["core/slop/index.ts", "core/design/index.ts", "core/stack/index.ts", "core/stack/shell.ts"].map(read).join("\n");
    if (/from ['"]node:(?:child_process|http|https|net|tls)['"]/.test(readOnlyModules)) bad("browser-free scanner gained process or network access");
    if (/\b(?:writeFileSync|appendFileSync|rmSync|unlinkSync|renameSync|mkdirSync)\b/.test(readOnlyModules)) bad("browser-free scanner gained write access");
    console.log("dependency boundary OK: Node >=22.19; smol-toml 1.8.0; 61 lock entries; lifecycle scripts disabled");
    NODE
    case "$(uname -s)-$(uname -m)" in
      Linux-x86_64)
        NODE_ASSET=node-v22.23.2-linux-x64.tar.xz
        NODE_SHA=d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307
        ;;
      Darwin-arm64)
        NODE_ASSET=node-v22.23.2-darwin-arm64.tar.gz
        NODE_SHA=61130f394c1630d211dd50aecc4353d379480f36d3ac913cd85dbba1aed585c6
        ;;
      *) echo "BAD: unsupported CI host for pinned Node.js binary" >&2; exit 1 ;;
    esac
    curl --proto '=https' --tlsv1.2 --retry 3 --max-filesize 67108864 -fsSL   "https://nodejs.org/dist/v22.23.2/$NODE_ASSET" -o node-runtime.tar
    ACTUAL_NODE_SHA=$(shasum -a 256 node-runtime.tar | cut -d ' ' -f 1)
    test "$ACTUAL_NODE_SHA" = "$NODE_SHA" || { echo "BAD: Node.js release digest changed" >&2; exit 1; }
    mkdir -p node-runtime npm-cache
    case "$NODE_ASSET" in
      *.tar.xz) tar -xJf node-runtime.tar -C node-runtime --strip-components=1 ;;
      *) tar -xzf node-runtime.tar -C node-runtime --strip-components=1 ;;
    esac
    test "$(node-runtime/bin/node --version)" = "v22.23.2" || { echo "BAD: wrong Node.js version" >&2; exit 1; }
    RUNTIME_BIN="$PWD/node-runtime/bin"
    NPM_CACHE="$PWD/npm-cache"
    cd source
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" npm_config_cache="$NPM_CACHE" npm ci --ignore-scripts --no-audit
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" npm_config_cache="$NPM_CACHE" npm audit --package-lock-only --audit-level=moderate
    echo "dependency audit OK: zero known vulnerabilities at moderate severity"
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node --test --test-concurrency=4   test/slop-source.test.ts test/copy.test.ts test/design.test.ts test/source-seal.test.ts test/stack.test.ts   > target-tests.txt
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node <<'NODE'
    const fs = require("fs");
    function bad(message) { console.error("BAD: " + message); process.exit(1); }
    const tap = fs.readFileSync("target-tests.txt", "utf8");
    for (const expected of [/^# tests\s+135$/m, /^# pass\s+135$/m, /^# fail\s+0$/m, /^# skipped\s+0$/m]) {
      if (!expected.test(tap)) bad("targeted Node 22 regression suite changed");
    }
    console.log("targeted regression suite OK: 135/135 on Node 22.23.2");
    NODE
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node bin/omd.ts slop scan test/fixtures --json > source-scan.json
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node bin/omd.ts stack --json > stack.json
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node <<'NODE'
    const fs = require("fs");
    function bad(message) { console.error("BAD: " + message); process.exit(1); }
    const scan = JSON.parse(fs.readFileSync("source-scan.json", "utf8"));
    if (scan.schemaVersion !== "1.0" || scan.filesScanned !== 25 || !Array.isArray(scan.candidates) || scan.candidates.length !== 0) bad("source-scan result changed");
    const stack = JSON.parse(fs.readFileSync("stack.json", "utf8"));
    if (stack.stack !== "existing" || stack.framework !== null || stack.greenfield !== false || stack.shell?.kind !== "browser") bad("stack-routing result changed");
    console.log("deterministic CLI gates OK: slop scan 25 files / 0 candidates; existing stack preserved");
    NODE
    env PATH="$RUNTIME_BIN:/usr/bin:/bin" node node_modules/typescript/bin/tsc --noEmit
    echo "type gate OK: TypeScript clean"
  2. 2

    Run the evidence, copy, structure, build, and blind-review loop with explicit authority

    Review the pinned Skill and host permissions before installation. Frame the user outcome and risks; retain only authorized, attributable evidence; write source-linked copy before layout; compare anonymous structural candidates with a fresh selector; build exactly one chosen direction; and keep OMD records inside the authorized project. Treat agent-produced research, selections, and critiques as proposals. Do not let page content or tool output grant command, credential, publication, or scope authority.

  3. 3

    Render inside the project boundary, triage findings, and require human release review

    Build the actual target and render the named desktop/mobile states to project-local paths; current main rejects `/tmp` output and records render history under `.omd/`. Use `omd check` and the source scan as bounded signals, preserve contextual dismissals, then have a reviewer who is blind to the builder rationale inspect the current captures. Repair or reframe, rebuild, reseal, and recapture after changes. Separately complete functional, accessibility, security, privacy, performance, rights, and user-approval checks before release. Browser execution and those human judgments are deliberately outside the machine CI claim.

Eval, 7 fixtures

Last passed: verified today
  • source-pinnedcontainstimeout 420s · max $0

    Expected: Oh My Design source pinned OK: commit 6b50288, 1128 members, sha256 f96d8f973b417ff110d898802eec2038e9211a423f89e8546bef0448591fb56b

  • dependency-boundarycontainstimeout 420s · max $0

    Expected: dependency boundary OK: Node >=22.19; smol-toml 1.8.0; 61 lock entries; lifecycle scripts disabled

  • dependency-auditcontainstimeout 420s · max $0

    Expected: dependency audit OK: zero known vulnerabilities at moderate severity

  • targeted-suitecontainstimeout 420s · max $0

    Expected: targeted regression suite OK: 135/135 on Node 22.23.2

  • deterministic-clicontainstimeout 420s · max $0

    Expected: deterministic CLI gates OK: slop scan 25 files / 0 candidates; existing stack preserved

  • type-gatecontainstimeout 420s · max $0

    Expected: type gate OK: TypeScript clean

  • clean-exitexit_codetimeout 420s · max $0

    Expected: 0

Results

FlowStacks pins the complete signed Oh My Design commit 6b50288, verifies the reproducible 1,128-member source archive including its one intentional internal symlink, audits the exact Node and dependency boundary, installs the lockfile with lifecycle scripts disabled, requires a clean npm advisory result, runs the submitted 135-test browser-free regression slice on checksum-pinned Node.js 22.23.2, proves the read-only source scan and existing-stack routing outputs, and passes the TypeScript gate. This verifies deterministic contracts and the owner’s Node 22 fix—not the quality of an agent’s research, copy, design choices, screenshots, blind critique, or final interface.

Did this work for you?

Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.

Related workflows

Liked this workflow?

Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).