diff --git a/HANDOFF.md b/HANDOFF.md index 10dab10..72351cf 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -217,7 +217,7 @@ git hooks (simple-git-hooks): pre-commit → lint-staged ; pre-push → typeche **세션 5 (2026-05-12) — Devvit Web 공식 문서 정독 + 정합화 (PR `feat/devvit-web-conformance`)**: - 📚 `developers.reddit.com`은 WebFetch 차단 → **Playwright로 비-게임 문서 58페이지 크롤** → `docs/devvit-reference.md`(440KB 스냅샷)에 저장. 발견사항/수정내역은 `docs/devvit-conformance-notes.md`. -- ✅ **`devvit build`가 실패했을 버그 수정**: `devvit.json`에 필수 `server` 블록 없음 + CJS 서버 번들 빌드 없음(`tsconfig`는 `noEmit`+ESM). → `vite.config.ts`(SSR 빌드, `format:'cjs'` → `dist/server/index.cjs`, `noExternal:true`, minify) + `devvit.json`에 `"server":{"entry":"dist/server/index.cjs"}` + `"scripts":{"dev":"vite build --watch","build":"vite build"}` + `"dev":{"subreddit":"vibe-mod-playtest"}` 추가. `vite` devDep 추가. `npm run build` = `tsc --noEmit && vite build`. `dist/`는 gitignore. +- ✅ **`devvit build`가 실패했을 버그 수정**: `devvit.json`에 필수 `server` 블록 없음 + CJS 서버 번들 빌드 없음(`tsconfig`는 `noEmit`+ESM). → `vite.config.ts`(SSR 빌드, `format:'cjs'` → `dist/server/index.cjs`, `noExternal:true`, minify) + `devvit.json`에 `"server":{"dir":"dist/server","entry":"index.cjs"}` + `"scripts":{"dev":"vite build --watch","build":"vite build"}` + `"dev":{"subreddit":"vibemod_playtest"}` 추가. `vite` devDep 추가. `npm run build` = `tsc --noEmit && vite build`. `dist/`는 gitignore. - ✅ **deps 정리**: Devvit Web은 `@devvit/web` 하나 + submodule import만 사용 → `@devvit/reddit`/`@devvit/redis`를 `dependencies`에서 제거(transitive로 남음), `import {redis} from '@devvit/redis'` → `'@devvit/web/server'`. `TaskBody/TaskAck` 로컬 타입 → 진짜 `TaskRequest/TaskResponse`(`@devvit/web/server` 재익스포트). CLI devDep: `@devvit/cli` → `devvit`. - ✅ **`context` 사용**: 서브레딧 이름은 `reddit.getCurrentSubreddit()`(API 호출) 대신 `context.subredditName`/`subredditId`(요청 컨텍스트, 호출 0). `devvit-helpers.ts`가 `context.*` 기반·동기. 호출부 `await`/`.catch` 제거. (테스트에 `fakeContext` 더블 추가.) - ✅ **publish 순서 정정**: `devvit settings set`은 "최소 1개 설치" 후에만 → `npm run dev`/`devvit upload`를 *먼저*. (이전 HANDOFF Step 2가 순서 틀림 — 위 Step 1.5/2/3에서 정정.) diff --git a/README.md b/README.md index da91c50..eb057ac 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,7 @@ > and keeps **30-day rollback** on every action it ever takes. The LLM is used **only at rule-edit > time** — zero AI calls per post or comment, and the LLM never sees Reddit content. - - - - +_Screenshots (compose form · dry-run preview · audit log) are added once the app is running in a test community — see [`docs/devvit-setup-guide.md`](./docs/devvit-setup-guide.md)._ --- diff --git a/devvit.json b/devvit.json index b5b716e..95c5272 100644 --- a/devvit.json +++ b/devvit.json @@ -1,8 +1,7 @@ { "$schema": "https://developers.reddit.com/schema/config-file.v1.json", "name": "vibe-mod", - "version": "0.1.0", - "server": { "entry": "dist/server/index.cjs" }, + "server": { "dir": "dist/server", "entry": "index.cjs" }, "marketingAssets": { "icon": "assets/icon.png" }, "permissions": { "reddit": { "enable": true, "scope": "moderator" }, @@ -15,8 +14,7 @@ "type": "string", "label": "Developer OpenAI API Key (sk-…)", "isSecret": true, - "defaultValue": "", - "helpText": "Devvit constraint: secrets must be global. This key is shared across all subreddit installs of vibe-mod (per-install BYOK override available via subredditOpenaiApiKey below). Daily quota per sub is enforced to bound cost." + "helpText": "Devvit constraint: secrets must be global. This key is shared across all subreddit installs of vibe-mod (per-install BYOK override available via subredditOpenaiApiKey below). Daily quota per sub is enforced to bound cost. Set it after the first upload: `npx devvit settings set openaiApiKey`." }, "openaiModel": { "type": "select", @@ -125,6 +123,6 @@ "build": "vite build" }, "dev": { - "subreddit": "vibe-mod-playtest" + "subreddit": "vibemod_playtest" } } diff --git a/docs/devvit-conformance-notes.md b/docs/devvit-conformance-notes.md index 1b55c1e..b3eca0b 100644 --- a/docs/devvit-conformance-notes.md +++ b/docs/devvit-conformance-notes.md @@ -11,7 +11,7 @@ fixed. Anything still open is at the bottom. - **`devvit.json` was missing the required `server` block.** A Devvit Web app must declare `post` and/or `server`; vibe-mod is server-only → it needs `"server": { "entry": "" }`. Added `"server": { "entry": "dist/server/index.cjs" }`. - **No build step produced a CommonJS server bundle.** `tsconfig` had `noEmit: true` and ESM output; the Devvit Web runtime requires **CJS** server output. Added `vite.config.ts` (SSR build, `format: 'cjs'`, `entryFileNames: 'index.cjs'`, `target: 'node22'`, `ssr.noExternal: true` so `@devvit/web`/`hono`/`zod` are bundled in, only Node builtins external, `minify: 'esbuild'` → ~2.1 MB). Added `vite` as a devDep. - **`devvit.json` had no `scripts`.** `devvit playtest` runs `scripts.dev`, `devvit upload` runs `scripts.build`. Added `"scripts": { "dev": "vite build --watch", "build": "vite build" }`. `package.json`'s `build` is now `tsc --noEmit && vite build`. -- Added `"dev": { "subreddit": "vibe-mod-playtest" }` so `devvit playtest` doesn't need a generated sub (overridable via `DEVVIT_SUBREDDIT`). +- Added `"dev": { "subreddit": "vibemod_playtest" }` so `devvit playtest` doesn't need a generated sub (overridable via `DEVVIT_SUBREDDIT`). - `dist/` is git-ignored (build artifact). ### 2. Dependency / import hygiene (the canonical Devvit Web shape) diff --git a/docs/devvit-setup-guide.md b/docs/devvit-setup-guide.md index 9ccff03..3a67da3 100644 --- a/docs/devvit-setup-guide.md +++ b/docs/devvit-setup-guide.md @@ -193,12 +193,12 @@ npm run dev # = `devvit playtest` → Ctrl+C to end What `devvit playtest` does: installs the app on your test subreddit, re-installs a new version every time you save a code change, and streams logs to the terminal. It picks the subreddit from, in order: -`DEVVIT_SUBREDDIT` env var → `dev.subreddit` in `devvit.json` (currently `"vibe-mod-playtest"`) → the +`DEVVIT_SUBREDDIT` env var → `dev.subreddit` in `devvit.json` (currently `"vibemod_playtest"`) → the playtest sub stored for your app → otherwise it auto-creates one. -⚠️ `devvit.json` currently has `"dev": { "subreddit": "vibe-mod-playtest" }`. If you don't actually +⚠️ `devvit.json` currently has `"dev": { "subreddit": "vibemod_playtest" }`. If you don't actually moderate a sub by that name, either: -- create `r/vibe-mod-playtest` (private, you as mod, keep < 200 subscribers), **or** +- create `r/vibemod_playtest` (private, you as mod, keep < 200 subscribers), **or** - change that value to your test sub's name, **or** - delete the `dev.subreddit` field and let `devvit upload`/`playtest` auto-create one (recommended if you don't care about the name). diff --git a/scripts/devvit-doctor.ts b/scripts/devvit-doctor.ts index 661f125..565dc92 100644 --- a/scripts/devvit-doctor.ts +++ b/scripts/devvit-doctor.ts @@ -7,9 +7,8 @@ // HARD checks (exit 1 on failure): devvit.json is well-formed; every external // host the server code fetch()es is declared under permissions.http.domains; // node satisfies package.json engines. -// SOFT checks (warn only): Devvit CLI logged in; .devvit-app-id present; the -// other tooling files exist. Soft because they depend on the human having done -// the wizard / `devvit login`. +// SOFT checks (warn only): Devvit CLI logged in; the other tooling files exist. +// Soft because they depend on the human having run `devvit login`. import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; @@ -36,7 +35,7 @@ console.log('devvit.json'); let devvit: { $schema?: string; name?: string; - version?: string; + server?: { dir?: string; entry?: string }; permissions?: { http?: { enable?: boolean; domains?: string[] }; reddit?: unknown; redis?: unknown }; menu?: { items?: Array<{ endpoint?: string }> }; forms?: Record; @@ -54,8 +53,17 @@ if (devvit) { else warn('$schema not set — add "https://developers.reddit.com/schema/config-file.v1.json" for editor validation'); if (devvit.name) ok(`name: ${devvit.name}`); else fail('name missing'); - if (devvit.version) ok(`version: ${devvit.version}`); - else fail('version missing'); + // NB: the official devvit.json schema does NOT allow a top-level `version` + // (versioning is managed by `devvit upload --bump` / package.json) — having + // one makes `devvit upload` fail validation, so flag it. + if ((devvit as Record).version !== undefined) + fail( + 'remove the top-level "version" field from devvit.json — not allowed by the schema (managed by `devvit upload`)', + ); + if (devvit.server?.entry && devvit.server.entry.includes('/')) + fail( + `server.entry must be a bare filename within server.dir (default dist/server), not a path — got "${devvit.server.entry}"`, + ); if (devvit.permissions) ok('permissions block present'); else fail('permissions block missing'); } @@ -146,10 +154,6 @@ try { } catch { warn('not logged in — run `npx devvit login` before `npm run dev` / `upload` / `publish`'); } -if (exists('.devvit-app-id')) ok('.devvit-app-id present'); -else - warn('.devvit-app-id not found — run the Devvit "Mod Tool" wizard at https://developers.reddit.com/new (creates it)'); - // ── tooling files (soft) ────────────────────────────────────────────────────── console.log('\nTooling'); for (const f of [ diff --git a/vite.config.ts b/vite.config.ts index 2af8ac2..3e2913b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,11 @@ // vite.config.ts — builds the Devvit Web *server* bundle. // vibe-mod is server-only (no webview/post), so there's just one build target: // the Node server endpoints in src/server/index.ts, compiled to a single -// CommonJS file at dist/server/index.cjs (the path `devvit.json`'s `server.entry` -// points at). The Devvit Web runtime requires CJS — ESM server output is not +// CommonJS file at dist/server/index.cjs. devvit.json declares this as +// `server: { dir: "dist/server", entry: "index.cjs" }` — `entry` is the filename +// *within* `dir`, not a path from the project root, so it must stay in sync with +// `outDir` + `entryFileNames` below. The Devvit Web runtime requires CJS — ESM +// server output is not // supported. Run via `vite build` (= devvit.json `scripts.build`, used by // `devvit upload`) or `vite build --watch` (= `scripts.dev`, used by `devvit playtest`). //