Commit Graph

11 Commits

Author SHA1 Message Date
YeonGyu-Kim 85c77007f0 feat(web): add MDX components for documentation rendering 2026-05-08 17:44:40 +09:00
YeonGyu-Kim 9848803f4f fix(web): replace next-mdx-remote with marked + raw HTML to avoid CF Workers eval ban
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.
2026-05-08 16:57:28 +09:00
YeonGyu-Kim 65fc0d9434 ci: generate docs content before web checks; bun prepare hook for local dev
The first build of #3860 failed at type-check because the generated
`lib/docs-content.generated.ts` is gitignored (regenerated on every
build) and CI's `type-check` step runs before `build`. Two fixes:

- web-ci.yml: explicit `Generate docs content from repo-root docs/`
  step right after `bun install` so format-check, lint, and type-check
  all see the file.
- web/package.json: add `prepare` lifecycle script. `bun install`
  invokes it automatically, so a fresh local checkout boots into a
  working state too.

Build still re-runs the generator via prebuild, so docs/ edits land in
the bundle without an explicit dev action.
2026-05-08 16:46:05 +09:00
YeonGyu-Kim 57b9d42537 fix(web): bundle docs sources at build time so the worker has no fs
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.
2026-05-08 16:38:14 +09:00
YeonGyu-Kim e5369f3a63 feat(web): render docs from repo-root docs/ as MDX SoT at build time
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.
2026-05-08 16:14:55 +09:00
YeonGyu-Kim e02166d6b7 fix(web): downgrade Next.js 16.2.6 -> 15.5.18 to unblock Cloudflare deploy
The first auto-deploy from PR #3855 returned HTTP 500 on every page
with the runtime error `TypeError: components.ComponentMod.handler is
not a function`. Captured via `wrangler tail`.

Root cause: Next.js 16.2.6 was published 2026-05-07 19:01 UTC, *after*
@opennextjs/cloudflare 1.19.8 was published earlier the same day at
11:33 UTC. OpenNext 1.19.8's peerDependency declares
`next: '>=15.5.16 <16 || >=16.2.5'` — 16.2.6 falls inside the range
syntactically, but the route component module export shape changed in
that patch and OpenNext has not caught up yet.

Pin Next + eslint-config-next to 15.5.18 (latest 15.x LTS, the other
half of OpenNext's supported range). Revert the migration-only changes
that came with the 16 bump:

- eslint.config.mjs: `nextPlugin.configs["core-web-vitals"]` (v16
  shape) -> `nextPlugin.flatConfig.coreWebVitals` (v15 shape).
- tsconfig.json: `jsx: "react-jsx"` -> `jsx: "preserve"` (Next 15
  default).
- tsconfig.json: add `noUncheckedSideEffectImports: false` because
  TypeScript 6 enabled this option under `strict` and Next 15's
  bundled types do not declare ambient CSS modules (Next 16 does).

All other web/ deps stay at latest. lucide-react remains pinned at
0.577.0 from #3853 for the same brand-icon reason. Re-evaluate Next 16
when @opennextjs/cloudflare ships a release explicitly tested against
\>= 16.2.6.
2026-05-08 15:25:51 +09:00
YeonGyu-Kim 81a1f6668f fix(web): truly pin lucide-react at 0.577.0
"lucide-react": "0" is npm shorthand for >=0.0.0 <1.0.0, so the
dependency was still floating across all 0.x releases. A future
lockfile refresh could pull a newer 0.x that quietly changes brand-icon
inventory.

Pin exact 0.577.0 (no caret) so the lockfile cannot drift until we
explicitly migrate to a brand-icon library compatible with lucide-react
v1.x (which removed Github, X, etc.).

Identified by cubic.
2026-05-08 14:44:29 +09:00
YeonGyu-Kim dff2acf38a chore(web): apply prettier 3.8 + Next 16 tsconfig auto-formatting
Side effects from `bun run format` (prettier 3.8) and `bun run build`
(Next 16) that I missed in the prior commit:

- web/components/ui/badge.tsx, web/components/ui/button.tsx: prettier 3.8
  inlines short interface `extends` lists onto a single line.
- web/tsconfig.json: Next 16's build step auto-modernizes tsconfig:
  `jsx: "preserve"` → `jsx: "react-jsx"` and reformats `lib`/`paths`
  arrays to multi-line. Build is identical either way; committing the
  modernized shape so subsequent `next build` runs do not produce
  spurious diffs.
2026-05-08 14:09:14 +09:00
YeonGyu-Kim e5eda117be chore(web): bump every dependency to latest
Bumped via `bun update --latest` then resolved breakage from two
major-version jumps:

1. Next.js 15.5 → 16.2 + @next/eslint-plugin-next 16: dropped the
   `flatConfig` namespace. Updated web/eslint.config.mjs to use
   `nextPlugin.configs["core-web-vitals"]` per the new export shape.

2. lucide-react 0.553 → 1.x: lucide upstream removed all brand icons
   (Github, etc.) — they are now expected to come from a separate brand
   icon library. Pinned lucide-react at the last 0.x (0.577.0) for now;
   migrating to a brand-icon library is tracked as a follow-up.

All other deps to latest:
- react/react-dom 19.2.4 → 19.2.6
- next-intl 4.8 → 4.11
- motion 12.35 → 12.38
- tailwind-merge 3.4 → 3.5
- geist 1.5 → 1.7
- @radix-ui/* unchanged (already latest within their ranges)
- @opennextjs/cloudflare 1.17 → 1.19.8
- @playwright/test 1.56 → 1.59
- @tailwindcss/postcss + tailwindcss 4.1 → 4.2.4
- @types/node 22 → 25.6
- @types/react 19 → 19.2.14
- eslint 9 → 10.3 (works because we now reference @next/eslint-plugin-next
  configs directly, not eslint-config-next)
- eslint-plugin-prettier 5.5.4 → 5.5.5
- globals 16 → 17.6
- postcss 8.5.6 → 8.5.14
- prettier 3.6.2 → 3.8.3 (no formatting changes detected by --check)
- prettier-plugin-tailwindcss 0.6 → 0.8
- typescript 5.9.3 → 6.0.3
- typescript-eslint 8.56 → 8.59
- wrangler 4.71 → 4.90

Verified locally:
- bun install --frozen-lockfile: 685 packages, no errors
- bun run format:check: pass (no diffs after `bun run format`)
- bun run lint: pass
- bun run type-check: pass (TypeScript 6 + @types/node 25)
- bun run build: pass (Next 16 build, 21 static pages, all 4 locales)
- bunx opennextjs-cloudflare build: pass (.open-next/worker.js produced)

Note: Next 16 `build` log relabels the Middleware row to "Proxy
(Middleware)" — purely cosmetic, no behavior change.
2026-05-08 14:06:25 +09:00
YeonGyu-Kim f7d16c5a99 fix(web): actually ignore .wrangler/ as web/AGENTS.md claims
Code-quality review flagged that web/AGENTS.md ANTI-PATTERNS section
asserts `.wrangler/` is gitignored, but the rule was missing from
web/.gitignore. Adding the rule alongside the other Cloudflare/Next
build-output rules so the doc and the file agree.
2026-05-08 13:52:33 +09:00
YeonGyu-Kim f94714bbdf feat(web): import oh-my-openagent-web Next.js + Cloudflare Workers site
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.
2026-05-08 13:35:00 +09:00