- One PR = one vertical slice of a single feature (UI + logic + styles together).
- PR size: 50–200 LOC. Reviewable. Easy to roll back.
- One component = single responsibility. Editor handles editing, preview handles preview, parent
page.tsxorchestrates state. - One AI request ≈ one component or one hook. Larger requests blow up consistency and verification cost.
| Unit | Output | Approx size |
|---|---|---|
| Project setup | Next.js + ESLint/Prettier integration + directory structure | ~150 LOC (mostly config) |
| editor-pane | CodeMirror editor + markdown mode + left layout | ~150 LOC |
| preview-pane | react-markdown + plugins (GFM/math/highlight) + right layout | ~120 LOC |
| theme | CSS variables + toggle UI + ThemeContext/Provider | ~180 LOC |
| draft-storage | localStorage hook + debounced autosave + save status indicator | ~80 LOC |
| print-pdf | @media print CSS + ExportButton + print guide modal |
~150 LOC |
| responsive | Mobile tab switch component + media queries | ~100 LOC |
- ❌ One PR with multiple features (theme + localStorage + mobile together) — unreviewable.
- ❌ Stacking component stubs without integration or verification — blows up in the next PR.
- ❌ Separate "setup PR" and "feature PR" — unnecessary round trips.
I work with one primary AI agent (Claude Code) for design and implementation. Three modes:
When: Before starting a new feature, when there is more than one approach. Context to provide: What I'm trying to do + constraints + usable/forbidden libraries + desired form of answer. Format:
"I want to do X. Constraints: A, B, C. Available: D. Off-limits: E. Compare 2–3 options focused on tradeoffs. No code yet."
Example:
"Implementing theme toggle, CSS-variable-based. Constraint: static build, no SSR hydration flash. Restore last theme from localStorage. Compare 2–3 approaches."
When: After design is settled, when writing actual files. Context to provide: Exact file paths + adjacent files (with snippets if needed) + props interface + behavior spec + styling approach. Format:
"Write: [path]. props: [A, B, C]. Behavior: [spec]. Parent usage: [snippet]. Styles: CSS Modules. Deps: only [list]."
Example:
"Write
src/components/EditorPane.tsx. props:value: string,onChange: (next: string) => void. CodeMirror 6 + lang-markdown. Dark theme → oneDark, light → default. Parent:<EditorPane value={md} onChange={setMd} />. CSS Modules. Debounce belongs to parent."
When: Right after the AI returns code, or before opening a PR. Context to provide: Diff (or full file) + what I'm suspicious of + desired output format. Format:
"Diff below. I suspect X. Point out other risks I'm missing. Give me a verification checklist."
Example:
"Diff below. I suspect missing useEffect cleanup — possible CodeMirror instance leak. Is
windowreference SSR-safe? Give me a verification checklist."
- ❌ "Make this nicer" — no criteria → AI makes arbitrary choices. Define "nice" together with the ask.
- ❌ "Just write everything" — verification unit too large → can't trust the output. Chunk to 50–200 LOC.
- ❌ Code snippets with no context — AI fills in assumptions. Wrong assumptions = wasted iteration.
- ❌ Paste back the result as-is — never assume one-shot code is correct. Always go through review mode.
- Did I name the file(s) I want changed?
- Did I share adjacent files / types?
- Did I specify the constraints (libs, static build, mobile, etc.)?
- Did I declare which mode I want (Plan / Code / Review)?
Beyond the primary coding agent, I delegate independent checks to three specialized sub-agents per PR. They get only the diff (or the built site URL) — no context from my chats with the primary agent. Independence is the point.
Role: A non-technical user trying to use rendermd for the first time. Has Chrome (browse, click, screenshot), a shell (curl, lighthouse), AND access to the Playwright capture script. Does not read source code.
Job:
- Launch the dev server or visit the deployed URL.
- Exercise core flows like a real user: paste markdown, switch theme, export PDF, resize to mobile.
- Report what's confusing, broken, or visually off.
- For any PR that touches rendering or print CSS: run
pnpm dlx tsx scripts/capture.ts, thenReadthe produceddocs/screenshots/pdf-*.pdffiles (Claude Code's Read tool renders PDF pages as inline images). Inspect for clipping, alignment, margin, theme correctness. - For any PR: run the corpus harness (
pnpm dlx tsx scripts/render-corpus.ts) to paste each fixture underscripts/fixtures/into the live site and assert minimum render counts (KaTeX displays, hljs blocks, page-break markers, zero console errors). Inspect failed fixtures and the per-fixture screenshots underdocs/screenshots/corpus/.
Prompt template:
You are a non-technical user. You don't know markdown syntax. You opened rendermd at
<URL>. Paste this LLM response into the editor:[...]. Try to save it as a PDF on desktop Chrome and on iOS Safari (mobile DevTools). For any visual/print change: runpnpm dlx tsx scripts/capture.ts, then Read the produced PDFs at/home/wsl/VLSI/rendermd/docs/screenshots/pdf-light.pdfandpdf-dark.pdf— Claude's Read tool renders PDF pages as images. Look at the actual rendered output, not just the CSS. For ANY PR: also runpnpm dlx tsx scripts/render-corpus.ts— this pastes 10 fixture markdowns (math/code/tables/Korean/nested-GFM/long-paragraph/page-break/empty/edge-cases) into the live site and asserts minimum render counts plus zero console errors. Read the per-fixture screenshots underdocs/screenshots/corpus/if the script reports failures. Report every moment you were confused or stuck, plus any visual issues you can see in the captured PDFs and corpus screenshots.
Expected output: A short list of friction points + 1-2 concrete observations from the rendered PDF images + corpus pass/fail summary (and what failed, if anything).
Adding a fixture: drop a new <n>-<topic>.md under scripts/fixtures/ and add an EXPECTATIONS row in scripts/render-corpus.ts keyed by the basename. Unlisted fixtures get a smoke-only run (paste + screenshot, no count assertions).
Role: A senior frontend engineer doing a critical code review. Reads the diff, knows React / TypeScript / Next.js deeply.
Job:
- Flag correctness bugs, perf issues, accessibility violations, type-safety gaps, hidden re-renders, hooks-rules violations, security risks (XSS via
dangerouslySetInnerHTML, etc.), bundle bloat. - Distinguish "must fix to merge" from "follow-up".
- Suggest minimal changes, not rewrites. No stylistic nits unless they affect correctness.
- Verify bundle claim: run
pnpm build, grepout/_next/static/chunks/to confirm the diff's perf claims about chunk splitting / size.
Prompt template:
You are a senior frontend engineer. Review this PR diff against rendermd. Stack: Next.js (static export), TS, CodeMirror 6, react-markdown, CSS Modules, pnpm, Vitest. Focus: correctness, perf, a11y, types, anti-patterns. For each finding give:
file:line, severity (block / nit / follow-up), why, suggested fix. No stylistic comments. If the PR claims a bundle delta, verify it viapnpm buildoutput.
Expected output: Finding list keyed by severity, with file:line refs.
Role: A top-tier UI/UX designer reviewing visual quality, interaction design, micro-interactions, accessibility detail, and brand voice consistency.
Job:
- Read the relevant component + CSS files (skip pure logic / hook code — Agent B covers that).
- Inspect captured PNGs and PDFs via Read tool.
- Flag visual inconsistencies, spacing/scale issues, contrast violations beyond WCAG minimums, loading-state quality, micro-interaction polish, copy tone mismatches.
- Critically evaluate premises (case study: an earlier review flagged dark-theme PDF as "wasting ink" — but PDFs are digital files, not paper. Premise was wrong; finding was rejected.)
Prompt template:
You are a top-tier UI/UX worker reviewing PR #N. Brand voice: Linear / iA Writer / Notion — calm utility, not playful. Read the touched component files + their CSS modules. If the PR changes rendering, read the captured screenshots in
docs/screenshots/(PNG + PDF, both via Read tool). Be opinionated; if a design is weak, say so directly. Skip code-correctness — Agent B covers that. For each finding: severity (block / nit / follow-up), the visual issue in plain language, a concrete fix.
Expected output: Visual / interaction / copy findings; no code-correctness overlap with Agent B.
- Read all three agents' reports.
- Reconcile conflicts (e.g., verifier says "PDF export button is hard to find" + reviewer says "the button code is fine" → the UX issue trumps).
- Test premises before accepting any agent finding. Premise-checking is the orchestrator's job — an agent confident about a finding built on a wrong premise produces a worse recommendation than a "no findings" report. (See dark-theme-ink case above.)
- Decide scope: which findings block merge, which become follow-up issues.
- Approve and merge.
- Independence: an agent that has read the implementation cannot objectively act as a "first-time user".
- Specialization: naive-client / FE / UI/UX prompts drive very different output styles. Mixing dilutes all three.
- Reproducibility: same prompts on the next PR → consistent review quality.
Three real production bugs slipped past agent review when verifiers could only inspect HTML:
- PR #75 era — table clipping in PDF export (user-reported via
reference/export_example.pdf). - PR #82 era — theme hydration mismatch (controlled
<select>doesn't reconcile SSR-bakedselected=""). - PR #88 era — dark theme blue navy cast (only visible against a bright-monitor render).
All three were diagnosable from actually-rendered output but invisible in CSS / React review. The Playwright + PDF-Read pattern (built in PR #80, formalized here) closes that gap.
(Things no AI agent can fully verify on its own.)
- Are the types actually what I intended? (no
any/ overuse ofunknown) - Are the try-catch / defensive paths the AI added actually necessary, or just noise?
- Is each new dependency justified? (
pnpm lsto confirm) - Are component props minimal? Any unused ones?
- Did unrelated refactoring sneak in? (review the full diff — AI loves to "tidy up")
- Do comments explain why, not what? (delete the latter)
- Does the feature actually work? (
pnpm devand try it) - Edge cases:
- Empty input
- Very long input (1MB+)
- Korean / emoji / special chars (
# 한글 🔥) - Nested markdown (
> - **bold _italic_**) - Markdown inside code fences (must NOT render)
- Actually trigger print preview (Cmd/Ctrl+P, not just DevTools) — this is the PDF core flow.
- Mobile viewport (DevTools 360px) doesn't break.
- localStorage quota exceeded (> 5MB) —
QuotaExceededErrorhandled.
-
pnpm buildfinishes with no warnings. - GitHub Pages
basePath: '/rendermd-hyuk'is set — assets resolve correctly. -
out/alone runs the site (no server dependency). - PDF export works on the deployed URL, not just localhost.
- PR size within 50–200 LOC.
- Followed feature → dev → main.
- Commit message answers "why":
- Bad:
fix typo - Good:
fix(editor): crash on empty input — CodeMirror state init missing when value=""
- Bad:
- Pushed only after Husky pre-commit and commit-msg passed.
- Product decisions: what to ship/cut, priority order.
- UX judgment: "is this a good experience for the user?" — AI can't know.
- Final tradeoff selection: AI proposes options; the human picks.
- External system decisions: domains, deployment URLs, secrets.
- Merge timing: AI says "done" → I still verify by hand before merging.