PCR νλΌμ΄λ¨Έ μ€κ³ μν¬νλ‘μ°λ₯Ό μν Next.js νλ‘ νΈμλμ
λλ€.
4λ¨κ³ Wizard UI, λ°±μλ μ°λ, κ²°κ³Ό μΊλ²μ€ μκ°νλ₯Ό μ 곡ν©λλ€.
- Next.js App Router κΈ°λ° λ¨μΌ νμ΄μ§ Wizard(
app/page.tsx) - ν νλ¦Ώ μνμ€ μ λ ₯(FASTA/Raw) μ κ·ν λ° μ ν¨μ± κ²μ¬
/api/designAPI νΈμΆ ν κ²°κ³Όλ₯Ό μ ν(/result)μΌλ‘ νμ- κ²°κ³Όλ₯Ό
localStorageμ μ μ₯/볡μν΄ ν κ° λ°μ΄ν° μ λ¬ - Canvas κΈ°λ° genome track λ λλ§(μ€/ν¨λ ν¬ν¨)
- FASTA ν€λ μ κ±°, 곡백 μ κ±°,
ATGCμ΄μΈ λ¬Έμ νν°λ§/λλ¬Έμν - νμΌ μ λ‘λ, ν΄λ¦½λ³΄λ λΆμ¬λ£κΈ°, ν° μ λ ₯(30,000μ μ΄κ³Ό) ν리뷰 λͺ¨λ μ§μ
- Step 2/Step 4 λλΆλΆ νλλ UI κΈ°λ³Έκ° νμ λ¨κ³μ λλ€.
- Step 3μμλ
restriction_enzymesμ λ ₯λ§ μ€μ μμ² payloadμ λ°μλ©λλ€. - ν
νλ¦Ώ μνμ€(
target_sequence)λ μ€μ API μμ²μ λ°μλ©λλ€.
Generate Primersν΄λ¦ μ μ ν(/result)μ λ¨Όμ μ΄κ³ API μμ² μ€ν- μ±κ³΅ μ κ²°κ³Ό ν€(
resultKey)λ₯Ό 쿼리μ€νΈλ§μΌλ‘ μ λ¬ν΄ κ²°κ³Ό νμ΄μ§ λ λλ§ - μ€ν¨ μ μλ¬ λ©μμ§ νμ λ° μμ ν μ 리
resultKeyλ‘localStorageλ°μ΄ν° 볡μ- Primer/Template focus κΈ°λ° μ΄κΈ° μ€ μν μλ κ³μ°
- Canvasμμ κ²°κ³Ό track μκ°ν λ° μ€/리μ μ‘°μ μ§μ
- Framework: Next.js 16 (App Router)
- Language: TypeScript (strict)
- UI: React 19, Tailwind CSS 4, lucide-react
- State: Zustand
- Data: Axios, TanStack Query
- Test: Vitest
PrimerFlow-FE/
ββ app/
β ββ page.tsx # 4-step wizard λ©μΈ νλ©΄
β ββ result/
β β ββ page.tsx # κ²°κ³Ό λΌμ°νΈ(ν΄λΌμ΄μΈνΈ λμ import)
β β ββ ResultClientPage.tsx # resultKey κΈ°λ° κ²°κ³Ό 볡μ/νμ
β ββ layout.tsx
β ββ providers.tsx # QueryClientProvider
ββ components/
β ββ canvas/GenomeCanvas.tsx # κ³΅μ© μΊλ²μ€ μΈν°λμ
(ν¨λ/ν μ€)
β ββ steps/ # Step1~Step4 UI
β ββ ui/ # ν€λ/νΈν°/λ€λΉκ²μ΄μ
β ββ PrimerResultModal.tsx
ββ src/
β ββ lib/ # μκ³ λ¦¬μ¦, API ν΄λΌμ΄μΈνΈ, νμ, storage
β ββ services/analysisService.ts
β ββ types/
ββ store/useViewStore.ts
ββ hooks/
ββ tests/
ββ docs/
- Node.js 20.x μ΄μ
- npm
- λ‘컬 λ°±μλ μλ²(
http://127.0.0.1:8000)
# 1) μμ‘΄μ± μ€μΉ
npm ci
# 2) κ°λ° μλ² μ€ν
npm run devλΈλΌμ°μ μμ http://localhost:3000μ μ΄μ΄ νμΈν©λλ€.
νμ¬ next.config.tsλ μλ rewriteλ₯Ό κ³ μ μΌλ‘ μ¬μ©ν©λλ€.
/api/:path*->http://127.0.0.1:8000/:path*
μ¦, νλ‘ νΈμλλ POST /api/designμΌλ‘ νΈμΆνκ³ μ€μ μμ²μ λ‘컬 λ°±μλλ‘ νλ‘μλ©λλ€.
.env.exampleλ μλ΄μ©μ΄λ©°, νμ¬ μ½λ κΈ°μ€μΌλ‘ λ°±μλ λΌμ°ν
μ νμ νκ²½ λ³μλ μμ΅λλ€.
npm run dev # κ°λ° μλ²
npm run build # νλ‘λμ
λΉλ
npm run start # νλ‘λμ
μλ² μ€ν
npm run lint # eslint .
npm test # vitest runνμ¬ ν¬ν¨λ ν΅μ¬ ν μ€νΈ:
tests/step1TemplateSequence.test.ts- FASTA/Raw μ λ ₯ μ κ·ν, invalid λ¬Έμ νμ§/μ κ±° κ²μ¦
tests/visibleRange.test.ts- prefix sum λ° visible range κ³μ° κ²μ¦
GitHub Actionsμμ PR κΈ°μ€μΌλ‘ μλ κ²μ¦μ΄ μ€νλ©λλ€.
- Lint
- Test
- Build
λν main λΈλμΉ λμ PRμ develop λΈλμΉμμλ§ νμ©νλλ‘ μ μ±
μ΄ μ€μ λμ΄ μμ΅λλ€.
- μμ
λ΄μ:
- κΈ°μ μ€ν μ μ
- Next.js: λ©μΈ νμ΄μ§(
app/page.tsx)μ μ μ λ μ΄μμμ ꡬμ±, μ μ 리μμ€ κ΄λ¦¬ λ° ν€λ μ€μ - TypeScript: μ»΄ν¬λνΈ, μ μ μ€ν μ΄, μ νΈ νμ μ λͺ μ
- Canvas API: μΊλ²μ€ 컨ν μ€νΈ μ§μ μ²λ¦¬, λμ©λ μμ΄ λ λλ§, μ€/ν¨λ λ³ν, ν μ€νΈ/λ° λν 그리기
- Zustand: μΊλ²μ€ λ·° μν, 리μ /μ λ°μ΄νΈ μ‘μ μ μ μ κ΄λ¦¬
- Vercel: Next.js μ± λ°°ν¬
- Next.js: λ©μΈ νμ΄μ§(
- νλ‘μ νΈ κΈ°λ³Έ μν€ν μ² λ° μ€μΌλ ν€ μ½λ ꡬμ±
- κΈ°μ μ€ν μ μ
- AI νμ©:
- Geminiλ‘ μμΈ ν둬ννΈ μμ±
- Codexλ‘ νλ‘μ νΈ μν€ν μ² λ° μ€μΌλ ν€ μ½λ μμ±
- λ€μ μ£Ό κ³ν:
page.tsx,layout.tsxꡬν, λͺ© λ°μ΄ν° μΆλ ₯ νμΈ
- μμ
λ΄μ:
- λλ―Έ λ°μ΄ν°λ‘ νμ΄μ§ μ°κ²°
- λ·° μν(Zustand)μ μ€/ν¨λ λμ μ λ
- AI νμ©:
- Codexλ‘
layout.tsx,page.tsxμΈλΆ ꡬν λ° λλ²κΉ
- Codexλ‘
- μλ£ κΈ°λ₯:
- λͺ© λ°μ΄ν° μΆλ ₯ μν νμΈ
- ν μ€νΈ κ²°κ³Ό:
- λ€μ μ£Ό κ³ν:
- μ€ν κΈ°λ° μ΄κΈ° μ λ ₯ νΌκ³Ό κ²μ¦ λ‘μ§ μ°©μ
- μμ
λ΄μ:
- PCR νλΌμ΄λ¨Έ λμμΈ μ€ν μμ±(1-based κ·μΉ, IUPAC μ ν, μ±λ₯ λͺ©ν ν¬ν¨)
- λ©μΈ UI λμμΈ κ²°μ λ° λ€ν¬ ν€ 4λ¨κ³ μ€ν νλ‘μ°λ‘ 리μν¬
- Genome νμ λΆλ¦¬
- AI νμ©:
- Stitch, Figmaμ λμΌ ν둬ννΈλ₯Ό λ£μ΄ λμμΈ λΉκ΅ ν μ±ν
- μλ£ κΈ°λ₯:
- λ¨κ³λ³ UI ꡬν μλ£
- 1λ¨κ³: μνμ€ μ λ ₯(FASTA/raw textarea)
- 2λ¨κ³: Primer Properties (GC% λ²μ, μ΅λ Tm μ°¨μ΄, GC Clamp, Poly-X μ ν, λλ/μΌ μ‘°κ±΄)
- 3λ¨κ³: Binding Location (κ²μ λ²μ, Exon junction μ΅μ , Intron ν¬ν¨/λ²μ, Restriction enzyme μ λ ₯)
- 4λ¨κ³: κ²°κ³Όλ¬Ό μΆλ ₯
- ν μ€νΈ κ²°κ³Ό:
- λ€μ μ£Ό κ³ν:
- μ€μ λ°μ΄ν° μ°λ, GenomeCanvas 미리보기 λ° μ»¨νΈλ‘€ λ§λ¬΄λ¦¬
- μμ
λ΄μ:
- λ°±μλ λͺ¨νΉ μλΉμ€ ꡬν λ° κ²°κ³Ό μκ°ν
- Step 1 μνμ€ μ λ ₯ νΈμμ± κ°μ
- μ»΄ν¬λνΈ μν€ν μ² κ°μ λ° UI μ λ°μ΄νΈ
- AI νμ©:
- Codexλ‘ μΊλ²μ€ νμ λͺ¨λ¬ ꡬν
- Paste λ± λ²νΌ κΈ°λ₯ ꡬν
- μλ£ κΈ°λ₯:
- λͺ© λ°μ΄ν°λ₯Ό λͺ¨λ¬λ‘ νμ
- Step 1μμ DNA μμ΄ μ λ ₯ μ FASTA μ λ‘λ, ν΄λ¦½λ³΄λ λΆμ¬λ£κΈ° μ§μ
- ν μ€νΈ κ²°κ³Ό:
- λ€μ μ£Ό κ³ν:
- μμ±λ λ°±μλμ μ°λνμ¬ κ²°κ³Ό νμ λ° λλ²κΉ
- μμ
λ΄μ:
- νλ‘ νΈμλ-λ°±μλ κ° API ν΅μ κ·κ²©(Spec) μ μ λ° μ°λ ꡬν
- AI νμ©:
- Codexλ‘ Nested Objectλ₯Ό UI μ μ© μν(Flat Object)λ‘ λ³ννλ μ΄λν° ν¨ν΄ μ½λ μμ±
- μλ£ κΈ°λ₯:
- νλΌμ΄λ¨Έ μ€κ³ μμ² νλ‘μΈμ€ ꡬν: μ λ ₯κ° -> μ΄λν° -> API νΈμΆ
- κ²°κ³Ό λͺ¨λ¬ λ°μ΄ν° λ°μΈλ©: Mock λ°μ΄ν° κΈ°λ° μΊλ²μ€/리μ€νΈ λ λλ§
- λ€μ μ£Ό κ³ν:
- μ¬μ©μ μ λ ₯ DNA μμ΄ μ μ²λ¦¬(Sanitization) λ° μ ν¨μ± κ²μ¦ λ‘μ§ κ΅¬ν
- μμ
λ΄μ:
- λμ©λ λ°μ΄ν°(10,000bp+) λ λλ§ μ±λ₯ μ΅μ νλ₯Ό μν λ·°ν¬νΈ νμ λ‘μ§ κ°μ
- μΊλ²μ€ μ€ν¬λ‘€ μ λ°°κ²½μ΄ ν¨κ» λ°λ¦¬λ Jittering λ²κ·Έ μμ λ° λ μ΄μ΄ κ³ μ μ²λ¦¬
- AI νμ©:
- Codexλ‘ binary search μκ³ λ¦¬μ¦ λ‘μ§ κ²μ¦ λ° μ΅μ ν
- Geminiλ‘ λ¬Έμ μν© μ€λͺ ν둬ννΈ μμ±, Codexλ‘ μμ μ μ©
- μλ£ κΈ°λ₯:
- Binary Search λ λλ§ μ΅μ ν:
O(N)->O(log N)κ°μ μΌλ‘ κ³ BP κ΅¬κ° νλ μ λλ μν - Canvas λ°°κ²½ κ³ μ λ λλ§ μ²λ¦¬
- Binary Search λ λλ§ μ΅μ ν:
- λ€μ μ£Ό κ³ν:
- μ λ ₯ λ°μ΄ν° validator ꡬν
- μμ
λ΄μ:
- Step1 μνμ€ μ λ ₯ μ κ·ν λ° κ²μ¦ UX κ°μ
- ATGC λμλ¬Έμ μ²λ¦¬ λ° λΉμ μ λ¬Έμ(N, μ«μ, νΉμλ¬Έμ) νν°λ§ λ‘μ§ μ 립
- λΆμ¬λ£κΈ°/νμΌ μ λ‘λ μ μ¬μ©μ λμ UX μΌκ΄μ± ν보
- AI νμ©:
- 4λ¨κ³ ν둬ννΈ(Phase 1~4) κΈ°λ° λ¨κ³λ³ λ‘μ§ κ³ λν λ° νΈλ¬λΈμν
- Next.js Turbopack import κ²½λ‘ μ΄μ λΆμ λ° ν΄κ²°
- λλ λ¬Έμμ΄ λΆμ¬λ£κΈ° μ κ³Όλν μμ λ¬Έμ μ λν sanitize λ°©μ κ°μ μ μ μ©
- μλ£ κΈ°λ₯:
- μ€μκ° μ κ·ν: μ λ ₯ μ¦μ λλ¬Έμ ATGC λ³ν λ° μ€μκ° νν°λ§
- μμΈ μ²λ¦¬: Upload/Paste/Ctrl+V μ λΉμ μ λ¬Έμ κ°μ§ μ νμΈ ν μ κ±°
- chunk λ¨μ sanitize μ νμΌλ‘ μ±λ₯ κ°μ λ° μ€λ³΅ κ²μ¦ μ κ±°
- λ€μ μ£Ό κ³ν:
- λͺ© λ°μ΄ν° μ κ±° λ° λ°°ν¬ λ°±μλ μ°κ²°
- μμ
λ΄μ:
- Mock Data μλ΅ μ κ±° λ° μ€μλ² μλ΅ κ΅¬μ‘° κΈ°μ€μΌλ‘ νλ‘ νΈ λ‘μ§ μ ν
- μμ² νλΌλ―Έν°λ₯Ό λ°±μλ μ€νμ λ§μΆ° μ 리, μμ²/μλ΅ λ§€ν μ κ²
- API νΈμΆ μ€ν¨ μν©(λ€νΈμν¬/μλ² μ€λ₯) λ©μμ§ λ° μν μ²λ¦¬ 보κ°
- λμμΈ/UI 리λ΄μΌ
- Poppins ν°νΈ λμ
- ν€λ 리λ΄μΌ(
PFν μ€νΈ -> λ‘κ³ μ΄λ―Έμ§,Primer Designer by SeqLab) - Step 1 μΉ΄λ ν€λ ν¨λ© μ‘°μ (
py-4 -> py-2) - Step 4 λΆνμ μμ(
Quality notes) μ κ±° - λΆνμ λΉ νμΌ(
tailwind.config.ts) μμ
- μ½λ νμ§ μλ κ²μ¦ PR λ°μ
- GitHub Actions PR μ Lint/Test/Build μλ μ€ν
- Vitest λμ
λ°
npm testμ€ν¬λ¦½νΈ μΆκ° - λ¦°νΈ μ€ν¬λ¦½νΈ
eslint .λ‘ νμ₯ visibleRangeκΈ°μ΄ ν μ€νΈ μμ±
- AI νμ©:
- Codexλ‘ API ν΄λΌμ΄μΈνΈ κ²½λ‘(
/api/design)μ μλΉμ€ λ μ΄μ΄ λ§€ν κ²μ¦ - μλ΅ λ°μ΄ν° λ³ν κ³Όμ μ νμ μμ μ± μ κ² λ° κ°μ
- Codexλ‘ API ν΄λΌμ΄μΈνΈ κ²½λ‘(
- μλ£ κΈ°λ₯:
- λ°°ν¬ λ°±μλ API μ°λ μλ£
- λ°±μλ μλ΅μ Result Modal/Canvas λ λλ§ κ°λ₯ν ννλ‘ λ³νν΄ νμ
- Mock μμ‘΄ μ κ±°, μ€λ°μ΄ν° κΈ°λ°μΌλ‘ μ ν
- PR νμ§ κ²μ΄νΈ(Lint/Test/Build) μλ κ²μ¦ μ²΄κ³ κ΅¬μΆ
visibleRangeν΅μ¬ λ‘μ§ ν μ€νΈ κΈ°λ° ν보
- μμ
λ΄μ:
- μ·¨μ½μ κ²½κ³ λμ
npm audit fix --forceλ‘ μμ‘΄μ± λ° lockfile μ λ°μ΄νΈ- 보μ ν¨μΉ μ μ© ν Lint/Test/Build κΈ°μ€ λμ μ κ²
- AI νμ©:
- Codexλ‘
package.json/package-lock.jsondiff κ²ν λ° λ²μ μν₯ λ΄μ μ 리 - μ λ°μ΄νΈ μ΄ν CI νκ· μν 체ν¬λ¦¬μ€νΈ μ κ²
- Codexλ‘
- μλ£ κΈ°λ₯:
- Next.js:
16.1.1 -> 16.1.6 - eslint:
9 -> 9.39.2 - eslint-config-next:
16.1.1 -> 16.1.6 - npm audit μ·¨μ½μ λμ μμ‘΄μ± μ λ°μ΄νΈ μλ£
- Next.js:
-
μμ λ΄μ:
- Step 3 μ νν¨μ(Restriction enzyme) μ€μ μ λ ₯μ΄ λμ§ μλ μ΄μ μμ
- κ²°κ³Ό νμ λ°©μμ λͺ¨λ¬μμ μ ν κΈ°λ°μΌλ‘ μ ν
- λλ©μΈ μ©μ΄ μ λΉ:
amplicon->templateλ‘ ν΅μΌ
-
AI νμ©:
- Github Copilotμ μ½λ리뷰λ₯Ό ν΅ν΄ μ νν¨μ μ λ ₯μ΄ λμ§ μκ³ μμμ νμΈ
- νμμ Codexμκ² μ€λͺ λ° ν΄κ²° μμ²
- λͺ¨λ¬μ νμλλ UIλ₯Ό μ νμ μ΄μ΄μ νμνλλ‘ Codexμκ² μ§μ
-
μλ£ κΈ°λ₯:
- Step 3 μ νν¨μ μ λ ₯ μ μ λμ
- κ²°κ³Ό νλ©΄ μ ν νμ μ μ©(λͺ¨λ¬ λ°©μ μ κ±°)
- UI/λ¬Έμ μ©μ΄λ₯Ό
templateκΈ°μ€μΌλ‘ ν΅μΌ





