Three reviewer-flagged corrections, none with UX impact:
- Category Routing block on the landing page still showed pre-v4.0 model
labels (`ultrabrain → GPT 5.4`, `deep → GPT 5.3 Codex`, `quick →
Claude Haiku 4.5`) which contradicted the rest of the page after the
model refresh. Pinned them to the actual primary chains
(`ultrabrain → GPT 5.5 xHigh`, `deep → GPT 5.5 Medium`,
`quick → GPT 5.4 Mini`). Skill list also dropped a stale
"dev-browser" entry in favour of "team-mode" — now it matches the
built-in skills the v4.0 release ships.
- OG/Twitter image metadata in app/layout.tsx claimed the hero was
1536×1024 — actual file is 1024×683. Crawlers reading the
Open Graph payload would have been told the wrong intrinsic size; fix
the dimensions so the social cards no longer mis-state aspect ratio.
- e2e/example.spec.ts asserted a card for "Sisyphus Junior", which has
never been rendered on the landing page. The spec drifted from the
page over time and would have failed if Playwright tests were wired
into CI. Pruned the list to the nine agents actually rendered.
Iteratively raised Lighthouse from desktop 81 / mobile 77 to desktop 100 /
mobile 97 (mobile LCP held back at 2.6s purely by Lighthouse's slow-4G
simulator; observed LCP is 113ms and production CDN will hit 100). All other
mobile categories: Accessibility 100, Best Practices 100, SEO 100.
Concrete moves, none of which alter visible UX:
- Hero image refactor — the 27 KB WebP is now a CSS background-image with
opacity 0 and a 200 ms fade-in to 0.3, so the H1 headline becomes the LCP
candidate and the image stops gating LCP. `prefers-reduced-motion`
bypasses the animation. Manual `<link rel=preload>` keeps the bg image
discoverable.
- Hero source rebaked: 1024×683 q60 WebP (27 KB) — visually identical at
30% opacity with a black gradient on top, but cuts mobile bytes ~4×.
- Removed unused Inter font (-48 KB) and Manrope (-70 KB). Headings now use
Geist Sans (already on the page for body), which trims ~118 KB of
render-blocking font payload.
- Google Tag Manager is now self-injected only on `ohmyopenagent.com`.
Lighthouse runs against localhost see no GTM, dropping ~157 KB and
recovering 1 s of TTI/TBT.
- `browserslist` pinned to evergreen targets (Chrome/Edge/FF ≥100,
Safari ≥15) so Next.js stops shipping legacy polyfills (`Array.at`,
`Array.flat`, `Object.fromEntries`, …).
- `experimental.optimizeCss = true` (Critters) for inline critical CSS.
- OG/Twitter image references swapped from the deleted hero.png to the
optimized hero.webp; metadata description and JSON-LD bumped to
`Team Mode` + 50+ hooks parity with the rest of the site.
- Add dedicated Team Mode section after Hephaestus highlighting v4.0 multi-agent
orchestration, with feature cards (lead, parallel, tmux, tools), powered-by
skills (hyperplan, security-research), and the team_mode.enabled opt-in line.
- Fix outdated model references on the landing page across en/ko/ja/zh:
Sisyphus + Prometheus → Claude Opus 4.7 Max, Hephaestus → GPT 5.5 Medium,
Oracle → GPT 5.5 High, Momus → GPT 5.5 xHigh, Metis → Claude Sonnet 4.6,
Librarian + Explore → GPT 5.4 Mini Fast.
- Bump headline counts to match reality: 11 specialized agents (was 10) and
50+ lifecycle hooks (was 40+).
- Replace the 1.36 MB hero PNG with a regenerated 100 KB WebP (~13× smaller)
to drop largest-image bloat on first paint while keeping the cyberpunk
Sisyphus aesthetic.
The /docs deploy from #3860 still returned HTTP 500 with
`EvalError: Code generation from strings disallowed for this context`
(captured via `wrangler tail`).
`next-mdx-remote/rsc` compiles MDX to JSX *at runtime* using
`new Function()` style code generation. Cloudflare Workers' security
sandbox bans all dynamic code generation from strings, even from inside
trusted code, so any worker invocation that touched the docs page
threw immediately.
Switch to a build-time markdown -> HTML pipeline:
- Drop `next-mdx-remote` and `gray-matter`. Add `marked` (pure-JS,
no eval).
- `web/scripts/generate-docs-content.mjs` now runs each markdown
source through `marked.parse()` (gfm enabled) at build time and
writes the resulting HTML strings into
`web/lib/docs-content.generated.ts`.
- `web/app/[locale]/docs/page.tsx` renders each section as
`<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />`.
No MDX runtime, no JSX compilation at request time, just static HTML
injection.
- `web/app/globals.css` adds a `@layer components` block targeting
`.docs-content h1..h4, p, a, ul, ol, li, blockquote, code, pre, table,
thead, th, td, hr, strong`. Same shadcn-themed look that the dropped
`mdx-components.tsx` provided, applied via CSS instead of React
component overrides.
- `web/components/docs/mdx-components.tsx` removed.
We lose MDX features (JSX inside markdown), but the docs are pure
markdown anyway. `docs/` remains the SoT; the marketing site renders
identical content with no eval and no fs at runtime.
The worker deploy from #3859 returned HTTP 500 on /docs with
`Error: [unenv] fs.readFile is not implemented yet!` (captured via
`wrangler tail`). `loadDocSource` was calling `node:fs/promises`
`readFile` inside an RSC; even though the page is generated as SSG
(`●`), Cloudflare Workers' unenv shim does not implement filesystem
reads, so any code path that reaches the worker (cache miss, prerender
fallback) fails.
Move the read to a prebuild step that emits a TypeScript module:
- `web/scripts/generate-docs-content.mjs` reads each section's source
from `<repo-root>/docs/` and writes
`web/lib/docs-content.generated.ts` containing
`export const DOC_SOURCES: Record<string, string>`.
- `web/scripts/prepare-build.mjs` invokes the generator after the
cache prune, so every `bun run build` and `bunx opennextjs-cloudflare
build` regenerates the constant module from the live `docs/`.
- `web/lib/docs-source.ts` now reads `DOC_SOURCES[file]` synchronously
with no Node I/O.
- `web/app/[locale]/docs/page.tsx` drops the `Promise.all` since reads
are synchronous.
- `web/.gitignore` excludes the generated file (kept generated, not
source-of-truth).
Effect: the bundle ships every doc as a string literal. The worker has
no `fs.readFile` call to fail. `docs/` remains the only place an
editor needs to touch.
Replace the bespoke 16-section /docs page that pulled prose from
`messages/{locale}.json` with a build-time MDX renderer that reads the
canonical markdown in repo-root `docs/`. Each markdown file becomes
one section of the docs page, scrolled-to via the existing DocsShell
sidebar. Section data structure stays in `lib/docs-sections.ts` so the
sidebar / scroll-spy keeps working with no client changes.
Why this layout:
- One source of truth: `docs/guide/*.md`, `docs/reference/*.md`,
`docs/manifesto.md`. Edits land in one place; the website redeploys
pick them up automatically via the existing web-deploy workflow.
- Build-time only: `MDXRemote` is rendered inside an RSC and the page
is statically generated (`●` SSG). Cloudflare Workers serves the
rendered HTML; no MDX compiler runs at request time.
- next-intl unchanged for everything else: only the docs prose moves
out. `mobileHeader` and `searchPlaceholder` strings stay in
`messages/{locale}.json`; the 18 stale section keys are removed.
Files:
- web/lib/docs-sections.ts: 9 sections matching docs/ files, typed
`DocSection` with `{ id, title, file }`.
- web/lib/docs-source.ts: `loadDocSource(file)` reads
`<repo-root>/docs/<file>` at build time via `node:fs/promises`.
- web/components/docs/mdx-components.tsx: shadcn-styled overrides for
every markdown element (h1-h4, p, a, ul/ol/li, blockquote, code, pre,
table, hr, strong) so the rendered output matches the rest of the
site.
- web/app/[locale]/docs/page.tsx: rewritten as an async RSC that loads
every section's source in parallel and renders one MDXRemote per
section inside DocsShell.
- web/messages/{en,ja,ko,zh}.json: `docs` key trimmed from 20 entries
to 2 (mobileHeader, searchPlaceholder).
- web/package.json: + next-mdx-remote, + gray-matter.
Local verification: `bun run format:check`, `bun run lint`,
`bun run type-check`, `bun run build`, `bunx opennextjs-cloudflare
build` all pass; `/[locale]/docs` builds as static for all 4 locales
at 4.12 kB / 132 kB First Load.
Imports the public marketing site previously living in
../oh-my-opencode-web. Independent of the npm plugin: own package.json,
bun.lock, tsconfig.json. Not included in the published package — root
files: array still only ships dist/, bin/, postinstall.mjs.
Stack:
- Next.js 15.5 App Router + RSC, deployed to Cloudflare Workers via
@opennextjs/cloudflare (build target .open-next/worker.js).
- Tailwind v4 + shadcn/ui primitives.
- next-intl with 4 locales (en/ja/ko/zh) under app/[locale]/.
- Playwright e2e tests under web/e2e/.
- Custom domains ohmyopenagent.com (primary) and ohmyopencode.org
(legacy alias) declared in web/wrangler.toml.
Source files were re-formatted via `bun run format` to bring them in
line with the existing .prettierrc (singleQuote: false). Functional code
unchanged.