Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions .github/workflows/playground-wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Playground WASM

# Builds the website playground's WASM bundle whenever the generator changes,
# publishes it as a public release asset, and bumps website/playground-wasm.lock.
# The lock-bump commit triggers Vercel's git auto-deploy, whose build fetches
# the asset via website/scripts/fetch-playground-wasm.mjs. No Vercel secrets.
#
# Self-retrigger is impossible by construction: the lock file lives under
# website/, outside this workflow's path filter.

on:
push:
branches: [main]
paths:
- "src/**"
- "wasm/**"
- "Cargo.toml"
- "Cargo.lock"
- "scripts/build-playground-wasm.sh"
workflow_dispatch:

permissions:
contents: write

concurrency:
group: playground-wasm
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always

jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
with:
tool: wasm-pack

- name: Build playground bundle
run: ./scripts/build-playground-wasm.sh

- name: Package and publish release asset
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "openapi-to-rust") | .version')
tag="playground-wasm-${version}-${GITHUB_SHA::7}"
tar czf playground-pkg.tar.gz -C website/public/playground pkg
gh release create "$tag" playground-pkg.tar.gz \
--prerelease \
--title "Playground WASM $version (${GITHUB_SHA::7})" \
--notes "WASM bundle for openapi-to-rust.dev/playground, built from ${GITHUB_SHA}."
echo "$tag" > website/playground-wasm.lock

- name: Commit lock bump
# Pushes back to the ref the workflow ran on: main for the normal
# push-triggered flow, the dispatched branch for pre-merge test runs.
run: |
set -euo pipefail
if git diff --quiet -- website/playground-wasm.lock; then
echo "lock unchanged; nothing to deploy"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add website/playground-wasm.lock
git commit -m "chore: bump playground wasm to $(cat website/playground-wasm.lock) [skip ci]"
git pull --rebase origin "$GITHUB_REF_NAME"
git push origin "HEAD:$GITHUB_REF_NAME"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@
/website/dist/
/website/.astro/
/website/.vercel/

# Playground WASM bundle: built by scripts/build-playground-wasm.sh locally,
# published as a GitHub release asset by .github/workflows/playground-wasm.yml
# and fetched by website/scripts/fetch-playground-wasm.mjs on Vercel.
/website/public/playground/pkg/
54 changes: 54 additions & 0 deletions .impeccable/critique/2026-07-18T05-06-10Z__website-src.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
target: website/src (playground + site)
total_score: 25
p0_count: 2
p1_count: 3
timestamp: 2026-07-18T05-06-10Z
slug: website-src
---
Method: dual-agent (A: design review sub-agent · B: detector/browser sub-agent)

# Design Health Score

| # | Heuristic | Score | Key Issue |
|---|-----------|-------|-----------|
| 1 | Visibility of System Status | 2 | Generate/example pick before WASM ready is a silent no-op; stale example dropdown label |
| 2 | Match System / Real World | 3 | YAML parser jargon in errors; otherwise excellent domain copy |
| 3 | User Control and Freedom | 2 | Failed generate destroys previous result; example pick clobbers pasted spec |
| 4 | Consistency and Standards | 2 | Unstyled UA-default tab buttons; nested <main>; tablist roles without keyboard pattern |
| 5 | Error Prevention | 2 | No guard on clobbering user spec; pre-ready clicks eaten |
| 6 | Recognition Rather Than Recall | 3 | REQUIRED_DEPS.toml leads tab row; otherwise good hints |
| 7 | Flexibility and Efficiency | 3 | Cmd+Enter, URL fetch, crate download; no permalink |
| 8 | Aesthetic and Minimalist Design | 3 | 6rem "Playground" h1 pushes Generate below the fold at 900px |
| 9 | Error Recovery | 2 | line/col given but no editor line numbers; no aria-live |
| 10 | Help and Documentation | 3 | Strong footer + docs cross-links |
| **Total** | | **25/40** | **Acceptable** |

# Anti-Patterns Verdict
Not slop overall: varied composition, evidence-driven copy, coherent print-terminal register. Static detector: 0 findings. In-page detector: 13 findings across 5 pages — hero-eyebrow-chip on 4/5 pages, tiny-text 11.52px on 3 pages, cramped .table-wrap padding on 2, oversized 84px compare h1, long line-length on /playground (~160ch) and /compare (~110ch). The one "looks broken/AI" moment: unstyled native tab buttons on the playground result.

# Priority Issues
- [P0] Playground output pane clipped ≤900px: tabs/Copy/Download unreachable; `.playground { grid-template-columns: 1fr }` at ≤900px loses minmax(0,…) + `main{overflow:clip}` makes it unscrollable (playground.astro:575, global.css:65).
- [P0] Docs-layout pages truncate text at 390px: `.doc-shell { grid-template-columns: 1fr }` (global.css:508) needs minmax(0,1fr); verified live.
- [P1] Result tabs unstyled: JS-created buttons never get data-astro-cid-*, scoped .tab-row rules can't match (playground.astro:212, 528-543). Active tab indistinguishable, ~24px targets.
- [P1] Silent no-op before worker ready (generate() early-return, playground.astro:243); flagship example path fills textarea then nothing happens.
- [P1] Nested <main> landmarks (BaseLayout.astro:64 + playground.astro:19).
- [P2] Homepage never sells the playground; hero CTAs go to docs + anchor only.
- [P2] Errors invisible to AT (no aria-live/role=alert) and destroy prior result (playground.astro:74-75, 233-240).
- [P2] Tablist semantics without keyboard behavior; REQUIRED_DEPS.toml first; "1 schemas" pluralization (playground.astro:81, 210-226).
- [P2] Compare h1 84px breaks product name across lines; page-hero h1 clamp oversized (global.css:338).
- [P2] Tiny text 11.52px (0.72rem figcaption/footer labels) on 3 pages; doc-pagination 0.66rem.
- [P3] No JSON-LD on /playground; shared generic og-card. Stale example dropdown label. DocsLayout hardcoded updated-date default. Avenir-only personality on macOS.

# Persona Red Flags
- Alex: Cmd+Enter swallowed during load → "it's broken" in 5s; role=tab promises arrow keys that don't exist; no shareable permalink.
- Jordan: REQUIRED_DEPS.toml + "2 schemas · 4 operations" vocabulary; parser-speak errors with no line numbers; example pick destroys pasted work.
- Casey: mobile playground generates but result is clipped + no scroll-into-view — can't copy/download; docs pages unreadable at 390.

# Minor Observations
Warning palette untokenized; playground-footer inline-code wraps oddly; 404 page is great; a11y baseline (skip-link, focus-visible, reduced-motion) above average; dev-toolbar 504 is a dev-only artifact.

# Questions
- Should the playground be the homepage hero's primary demo ("run it on your spec now, nothing uploaded")?
- Could the error state keep the last good result visible instead of a 480px void?
- Is "Playground" worth 230px of h1, or should the tool start at the top?
33 changes: 33 additions & 0 deletions .impeccable/critique/2026-07-18T05-19-10Z__website-src.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
target: website/src (playground + site)
total_score: 37
p0_count: 0
p1_count: 1
timestamp: 2026-07-18T05-19-10Z
slug: website-src
---
Method: dual-agent (A: design review sub-agent · B: detector/browser sub-agent) — round 2, post-fix verification

# Design Health Score
| # | Heuristic | Score | Key Issue |
|---|-----------|-------|-----------|
| 1 | Visibility of System Status | 4 | Loading notice, stale dim, auto-scroll — exemplary |
| 2 | Match System / Real World | 4 | |
| 3 | User Control and Freedom | 3 | No clear/reset affordance |
| 4 | Consistency and Standards | 3 | Site-wide compressHTML missing-space text bug |
| 5 | Error Prevention | 3 | |
| 6 | Recognition Rather Than Recall | 4 | |
| 7 | Flexibility and Efficiency | 4 | |
| 8 | Aesthetic and Minimalist Design | 4 | |
| 9 | Error Recovery | 4 | Prev output kept dimmed; exact parse errors |
| 10 | Help and Documentation | 4 | |
| **Total** | | **37/40** | **Excellent** |

# Verification
All 11 round-1 fixes VERIFIED live (incl. pre-WASM-ready notice under 6× CPU throttle). Zero horizontal overflow at 390/768. Detector: static scan clean; oversized-h1 fixed; tiny-text down to 1 element (#comparison-scroll-hint 11.84px); line-length on .playground-footer (~160ch) and .verification-note (~110ch); table-wrap + eyebrow findings accepted in ignore.md.

# Priority Issues
- [P1] compressHTML strips newlines before inline <a>/<code> → words jam together ("theschema support notes"); 12+ instances / 6 pages. Fix: compressHTML: false in astro.config.mjs.
- [P3] Compare h1 hyphen-breaks "openapi-to-rust" at 390 → nowrap span.
- [P3] Screen-reader announcement of playground states (role=alert/status present in source — verify implicit live regions suffice).
- [P3] scroll-hint 11.84px; playground-footer + verification-note line length.
10 changes: 10 additions & 0 deletions .impeccable/critique/ignore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Accepted / false-positive detector findings

- **hero-eyebrow-chip** (all pages): the mono kicker above page-hero h1s is a deliberate,
named brand system used exactly once per page (category label above the h1 in
DocsLayout, `.hero-kicker` on the homepage). It is not the per-section eyebrow
scaffold the rule targets. Re-flag only if eyebrows start appearing above body
sections.
- **cramped-padding on `.table-wrap`** (/docs/getting-started, /compare/openapi-generator):
a table inside a horizontal-scroll container is conventionally flush with the
container edges; cells carry their own 0.9rem/1rem padding. Judged intentional.
54 changes: 39 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 13 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[workspace]
members = ["wasm"]
# Scratch crates (spec-compile.sh, ad-hoc generator runs) live under tmp/;
# they must never be adopted into this workspace.
exclude = ["tmp"]

[package]
name = "openapi-to-rust"
version = "0.7.0"
Expand Down Expand Up @@ -26,12 +32,13 @@ include = [
]

[dependencies]
clap = { version = "4.5", features = ["derive"] }
clap = { version = "4.5", features = ["derive"], optional = true }
once_cell = "1.19"
prettyplease = "0.2"
proc-macro2 = "1.0"
quote = "1.0"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"], optional = true }
url = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
Expand All @@ -50,14 +57,17 @@ insta = { version = "1.41", features = ["yaml"] }
tempfile = "3.0"

[features]
default = []
default = ["cli"]
cli = ["dep:clap", "dep:reqwest", "http-error"]
http-error = ["dep:reqwest"]
internal-tools = []
specta = ["dep:specta"]
test-helpers = ["dep:insta", "dep:tempfile"]

[[bin]]
name = "openapi-to-rust"
path = "src/bin/openapi-to-rust.rs"
required-features = ["cli"]

[[bin]]
name = "catalog-gen"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

Read the [guides and documentation](https://openapi-to-rust.dev/) for the quickest path from an OpenAPI document to compiling Rust.

## 5-second trial

Paste your spec into the **[browser playground](https://openapi-to-rust.dev/playground)** —
the real generator compiled to WebAssembly. See the generated Rust instantly and
download it as a complete, compilable crate. Nothing is uploaded.

## 30-second trial

Install the CLI from crates.io, then generate a tiny client from a stable,
Expand Down
24 changes: 24 additions & 0 deletions scripts/build-playground-wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Build the playground WASM bundle and stage it for the website.
#
# Output: website/public/playground/pkg/ (gitignored build artifact).
# wasm-pack runs wasm-opt -Oz itself (configured in wasm/Cargo.toml), and the
# env vars below give the release profile size-focused settings without
# touching the workspace profile used by the CLI.
set -euo pipefail
cd "$(dirname "$0")/.."

DEST="website/public/playground/pkg"

CARGO_PROFILE_RELEASE_OPT_LEVEL=z \
CARGO_PROFILE_RELEASE_LTO=true \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1 \
wasm-pack build wasm --release --target web --out-dir "../$DEST"

# Mark the bundle as a local build so fetch-playground-wasm.mjs never
# clobbers it with the lock-pinned release asset.
echo "local" > "$DEST/.source-tag"

echo
echo "Playground WASM staged in $DEST:"
ls -lh "$DEST" | awk 'NR>1 {print " " $9 " (" $5 ")"}'
4 changes: 4 additions & 0 deletions scripts/spec-compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ EOF
continue
fi
{
# Empty [workspace] keeps the scratch crate out of the repo's workspace;
# without it cargo walks up, finds the root manifest, and refuses.
echo "[workspace]"
echo
echo "[package]"
echo "name = \"spec-compile-$name\""
echo "version = \"0.0.0\""
Expand Down
Loading
Loading