diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..15f6ad2
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,11 @@
+# Copy this file to .env.local and set variables as needed.
+#
+# Backend origin used by Next.js rewrite proxy.
+# - Local development default: http://127.0.0.1:8000
+# - Production: BACKEND_URL must be explicitly set.
+BACKEND_URL=
+#
+# Frontend axios base URL.
+# Keep this unset to use "/api" (same-origin via Next.js rewrite).
+# Set only when you intentionally bypass the rewrite proxy.
+NEXT_PUBLIC_API_BASE_URL=
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..06ec980
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,73 @@
+name: CI
+
+on:
+ pull_request:
+ branches:
+ - main
+ - master
+ - develop
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ lint-and-build:
+ name: Lint & Build
+ runs-on: ubuntu-latest
+
+ env:
+ NEXT_TELEMETRY_DISABLED: "1"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Inject BACKEND_URL
+ shell: bash
+ env:
+ BACKEND_URL_PRODUCTION: ${{ secrets.BACKEND_URL_PRODUCTION }}
+ BACKEND_URL_STAGING: ${{ secrets.BACKEND_URL_STAGING }}
+ run: |
+ # fork PR은 보안상 secrets가 전달되지 않음 → 공개 URL(또는 더미)로 빌드만 통과
+ if [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
+ echo "BACKEND_URL=https://primerflow-be.onrender.com" >> "$GITHUB_ENV"
+ exit 0
+ fi
+
+ if [ "${{ github.base_ref }}" = "main" ] || [ "${{ github.base_ref }}" = "master" ]; then
+ selected="$BACKEND_URL_PRODUCTION"
+ key="BACKEND_URL_PRODUCTION"
+ else
+ selected="$BACKEND_URL_STAGING"
+ key="BACKEND_URL_STAGING"
+ fi
+
+ if [ -z "$selected" ]; then
+ echo "::error::Missing secret $key"
+ echo "::error::Set it in Settings -> Secrets and variables -> Actions -> Repository secrets"
+ exit 1
+ fi
+
+ echo "BACKEND_URL=$selected" >> "$GITHUB_ENV"
+
+ - name: Lint
+ run: npm run lint
+
+ - name: Test
+ run: npm test
+
+ - name: Build
+ run: npm run build
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5ef6a52..7b8da95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
+!.env.example
# vercel
.vercel
diff --git a/README.md b/README.md
index e067165..ae435e9 100644
--- a/README.md
+++ b/README.md
@@ -73,13 +73,22 @@ git clone [https://github.com/Seq-Lab/PrimerFlow-FE.git](https://github.com/Seq-
cd PrimerFlow-FE
# 3. 패키지 설치
-npm install
+npm ci
# 4. 환경 변수 설정 (.env.local 생성)
-# (백엔드 API 주소 설정 예시)
-# echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local
+- `.env.example` 파일을 복사하여 `.env.local`을 생성하세요.
+- Next.js `rewrites`에서 백엔드 목적지는 `BACKEND_URL`이 설정되면 해당 값을, 없으면 `http://127.0.0.1:8000`(로컬)로 사용합니다.
+- 로컬 기본값(127.0.0.1:8000)을 사용하려면 `.env.local`을 비워 두어도 무방합니다.
+- 다른 백엔드로 프록시해야 한다면 `.env.local`에 아래처럼 설정하세요:
+
+```env
+BACKEND_URL=[https://api.example.com](https://api.example.com)
+```
+
+- Vercel 등 배포 환경에서도 동일한 환경 변수를 프로젝트 환경 변수로 추가하면 됩니다.
# 5. 개발 서버 실행
+```
npm run dev
```
@@ -97,7 +106,7 @@ npm run dev
- AI 활용: Gemini로 자세한 내용 프롬프트로 작성, codex로 프로젝트 아키텍처 및 스켈레톤 코드 작성.
- 다음 주 계획: page.tsx, layout.tsx 구현, 목 데이터 출력 해보기
-### Week 2 (25.12.29 - 26.01.04)
+### Week 2 (25.12.29 - 26.01.04)z`
- 작업 내역:
- 더미 데이터로 페이지에 연결
- 뷰 상태(Zustand)와 줌·패닝 동작을 정돈
@@ -136,3 +145,83 @@ npm run dev

- 다음 주 계획: 실제 데이터 연동, GenomeCanvas 미리보기·컨트롤 마무리.
+
+### Week 4 (26.01.12 ~ 26.01.18)
+- 작업 내역:
+ - 백엔드 모킹 서비스 구현 및 결과 시각화
+ - Step 1 시퀀스 입력 편의성 개선
+ - 컴포넌트 아키텍처 개선 및 UI 업데이트
+- AI 활용:
+ - codex로 캔버스가 표시되는 모달 구현
+ - paste등 버튼 기능 구현
+- 완료 기능:
+ - 목데이터를 모달을 이용하여 표시
+ - Step1에서 DNA서열 입력 시, fasta파일 업로드, 클립보드에서 붙여넣기 지원
+
+- 테스트 결과:
+ - 목데이터 표시 확인
+
+ 
+- 다음 주 계획: 완성된 백엔드와 연동하여 결과 표시 및 디버깅
+
+### Week 5 (26.01.19 ~ 26.01.25)
+- 작업 내역: 프론트엔드-백엔드 간 API 통신 규격(Spec) 정의 및 연동 구현
+- AI 활용: codex 이용하여 복잡한 Nested Object을 UI 전용 상태(Flat Object)로 변환하는 어댑터 패턴 코드 자동 생성
+- 완료 기능:
+ - 프라이머 설계 요청(Request) 프로세스 구현: 입력값 → 어댑터 → API 호출 흐름 완성
+ - 결과 모달(Result Modal) 데이터 바인딩: Mock 데이터를 활용하여 캔버스 및 리스트에 분석 결과 렌더링
+- 다음 주 계획: 사용자 입력 데이터(DNA 서열)에 대한 전처리(Sanitization) 및 유효성 검증 로직 구현
+
+
+### Week 6 (26.01.26 ~ 26.02.01)
+- 작업 내역:
+ - 대용량 데이터(10,000bp 이상) 렌더링 성능 최적화를 위한 뷰포트 탐색 로직 개선
+ - 캔버스 UI 스크롤 조작 시 배경이 함께 밀리는 버그(Jittering) 수정 및 레이어 고정 처리
+- AI 활용:
+ - codex를 이용하여 binary search 알고리즘 로직 검증 및 최적화
+ - gemini로 현재 발생하고 있는 상황을 정확하게 설명하여 해결을 요구하는 프롬프트 작성 및 codex를 이용한 수정
+- 완료 기능:
+ - Binary Search 렌더링 최적화: $O(N)$ 탐색을 $O(\log N)$으로 개선하여 High BP 구간 프레임 드랍 해결
+ - Canvas Background Fix: 스크롤 이벤트 시 배경 이미지가 고정되도록 렌더링 로직 수정
+- 다음 주 계획:
+ - 입력 데이터 validator 구현
+
+### Week 7 (26.02.02 ~ 26.02.08)
+- Step1 시퀀스 입력 정규화 및 검증 UX 개선
+ - ATGC 대소문자 처리 및 비정상 문자(N, 숫자, 특수문자) 필터링 로직 정립
+ - 붙여넣기 및 파일 업로드 시 사용자 동의 UX 일관성 확보
+
+- AI 활용:
+ - 4단계 프롬프트(Phase 1~4)를 구성하여 AI와 단계별 로직 고도화 및 트러블슈팅 진행
+ - Next.js Turbopack 빌드 에러(Import 경로 이슈) 분석 및 해결
+ - 대량 문자열 붙여넣기 시 발생하는 데이터 손실(과도한 삭제) 문제에 대한 최적화된 Sanitize 접근 방식 제안 및 적용
+
+- 완료 기능:
+ - 실시간 정규화: 입력 즉시 대소문자 구분 없이 대문자 ATGC로 자동 변환 및 실시간 필터링 적용 (안내 캡션 추가)
+ - 사용자 동의 기반 예외 처리: FASTA 파일 업로드, Paste 버튼, Ctrl+V 입력 시 비정상 문자가 감지되면 즉시 삭제하지 않고 window.confirm을 통한 사용자 제거 동의 로직 구현
+ - 로직 최적화: 조각(chunk) 단위 산니타이즈(Sanitize) 방식으로 전환하여 성능 개선 및 Generate 단계의 불필요한 중복 검증 로직 제거
+
+- 다음 주 계획:
+ - 목데이터 제거 및 배포된 백엔드와 연결
+
+### Week 8 (26.02.09 ~ 26.02.15)
+- 작업 내역:
+ - 목데이터(Mock Data) 기반 응답 제거 및 실서버 응답 구조 기준으로 프론트 로직 전환
+ - 프라이머 분석 요청 파라미터를 백엔드 스펙에 맞게 정리하고 요청/응답 매핑 흐름 점검
+ - API 호출 실패 상황(네트워크/서버 오류)에 대한 사용자 메시지 노출 및 상태 처리 보강
+- AI 활용:
+ - Codex를 활용해 API 클라이언트 경로(`/api/design`)와 서비스 레이어 매핑 로직 검증
+ - 응답 데이터 변환(UI 전용 트랙/프라이머 후보 매핑) 과정의 타입 안정성 점검 및 개선
+- 완료 기능:
+ - 프라이머 설계 요청이 배포된 백엔드 API로 전송되도록 연동 완료
+ - 백엔드 응답을 Result Modal/Canvas에 렌더링 가능한 형태로 변환하여 표시
+ - Mock 의존 흐름을 제거하고 실데이터 기반 동작으로 전환
+- 다음 주 계획:
+ - Vercel 환경에 프론트엔드 배포 및 배포 환경 변수(API Base URL) 점검
+
+### Week 9 (26.02.16 ~ 26.02.22)
+- 작업 내역:
+- AI 활용:
+- 완료 기능:
+- 다음 주 계획:
+- 작업 내역:
diff --git a/app/api/v1/primer/design/route.ts b/app/api/v1/primer/design/route.ts
deleted file mode 100644
index cb872d5..0000000
--- a/app/api/v1/primer/design/route.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { NextResponse } from 'next/server';
-
-// 기획서에 명시된 더미 데이터 반환
-export async function POST(request: Request) {
- // 실제 요청 바디를 받아서 로그로 확인 가능
- const body = await request.json();
- console.log("프론트에서 받은 요청:", body);
-
- // 가짜 응답 데이터 (기획서 PrimerDesignResponse 구조)
- const mockResponse = {
- genome: {
- id: "gene_001",
- name: "Test Gene",
- sequence: "ATGC...",
- length_bp: 1000
- },
- candidates: [
- {
- id: "primer_1",
- sequence: "ATGCATGC",
- start_bp: 100,
- end_bp: 120,
- strand: "forward",
- metrics: { tm_c: 60.5, gc_percent: 50 }
- }
- ],
- meta: {
- timestamp: new Date().toISOString()
- }
- };
-
- // 1초 뒤에 응답 (네트워크 지연 시뮬레이션)
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- return NextResponse.json(mockResponse);
-}
\ No newline at end of file
diff --git a/app/globals.css b/app/globals.css
index 17c9ff3..fe4f87a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -45,6 +45,115 @@
font-display: swap;
}
+.poppins-thin {
+ font-family: "Poppins", sans-serif;
+ font-weight: 100;
+ font-style: normal;
+}
+
+.poppins-extralight {
+ font-family: "Poppins", sans-serif;
+ font-weight: 200;
+ font-style: normal;
+}
+
+.poppins-light {
+ font-family: "Poppins", sans-serif;
+ font-weight: 300;
+ font-style: normal;
+}
+
+.poppins-regular {
+ font-family: "Poppins", sans-serif;
+ font-weight: 400;
+ font-style: normal;
+}
+
+.poppins-medium {
+ font-family: "Poppins", sans-serif;
+ font-weight: 500;
+ font-style: normal;
+}
+
+.poppins-semibold {
+ font-family: "Poppins", sans-serif;
+ font-weight: 600;
+ font-style: normal;
+}
+
+.poppins-bold {
+ font-family: "Poppins", sans-serif;
+ font-weight: 700;
+ font-style: normal;
+}
+
+.poppins-extrabold {
+ font-family: "Poppins", sans-serif;
+ font-weight: 800;
+ font-style: normal;
+}
+
+.poppins-black {
+ font-family: "Poppins", sans-serif;
+ font-weight: 900;
+ font-style: normal;
+}
+
+.poppins-thin-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 100;
+ font-style: italic;
+}
+
+.poppins-extralight-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 200;
+ font-style: italic;
+}
+
+.poppins-light-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 300;
+ font-style: italic;
+}
+
+.poppins-regular-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 400;
+ font-style: italic;
+}
+
+.poppins-medium-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 500;
+ font-style: italic;
+}
+
+.poppins-semibold-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 600;
+ font-style: italic;
+}
+
+.poppins-bold-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 700;
+ font-style: italic;
+}
+
+.poppins-extrabold-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 800;
+ font-style: italic;
+}
+
+.poppins-black-italic {
+ font-family: "Poppins", sans-serif;
+ font-weight: 900;
+ font-style: italic;
+}
+
+
:root {
--background: #060b16;
--foreground: #e2e8f0;
diff --git a/app/layout.tsx b/app/layout.tsx
index 20c504b..cfc0af4 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { JetBrains_Mono, Space_Grotesk } from "next/font/google";
+import { JetBrains_Mono, Poppins, Space_Grotesk } from "next/font/google";
import SiteFooter from "@/components/ui/Footer";
import Providers from "./providers";
import "./globals.css";
@@ -14,6 +14,13 @@ const mono = JetBrains_Mono({
subsets: ["latin"],
});
+const poppins = Poppins({
+ variable: "--font-poppins",
+ subsets: ["latin"],
+ weight: ["400", "600", "700"],
+ display: "swap",
+});
+
export const metadata: Metadata = {
title: "PrimerFlow - Primer Design Workbench",
description: "Dark-mode playground for primer visualization and tuning.",
@@ -28,7 +35,7 @@ export default function RootLayout({
-
+
{children}
diff --git a/app/page.tsx b/app/page.tsx
index 2746ee1..22158e1 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,11 +1,17 @@
"use client";
-import { useState } from "react";
+import { useRef, useState } from "react";
import PrimerResultModal from "@/components/PrimerResultModal";
-import { analyzeGenome, type AnalyzeRequest, type AnalyzeResponse } from "@/lib/api/analysisService";
-import { demoGenome } from "@/lib/mocks/demoGenome";
-import type { GenomeData } from "@/lib/types/Genome";
+import {
+ analyzeGenome,
+ type AnalyzeRequestInput,
+} from "@/services/analysisService";
+import type { GenomeData, PrimerDesignResponseUI } from "@/types";
import { useViewStore } from "@/store/useViewStore";
+import {
+ getInvalidStep1TemplateSequenceChar,
+ normalizeStep1TemplateSequence,
+} from "../src/lib/parsers/step1TemplateSequence";
import Step1TemplateEssential from "@/components/steps/Step1TemplateEssential";
import Step2PrimerProperties from "@/components/steps/Step2PrimerProperties";
import Step3BindingLocation from "@/components/steps/Step3BindingLocation";
@@ -13,37 +19,37 @@ import Step4SpecificityPreview from "@/components/steps/Step4SpecificityPreview"
import WizardFooterNav from "@/components/ui/WizardFooterNav";
import WizardHeader from "@/components/ui/WizardHeader";
-const isGenomeFeature = (feature: any) =>
- feature &&
- typeof feature.start === "number" &&
- typeof feature.end === "number" &&
- feature.start <= feature.end;
-
-const isGenomeData = (data: any): data is GenomeData =>
- data &&
- typeof data.length === "number" &&
- Array.isArray(data.tracks) &&
- data.tracks.every(
- (track: any) =>
- track &&
- typeof track.id === "string" &&
- Array.isArray(track.features) &&
- track.features.every(isGenomeFeature),
- );
+const toGenomeDataFromResponse = (response: PrimerDesignResponseUI | null): GenomeData | null => {
+ if (!response?.genome) return null;
+ const length =
+ response.genome.length ??
+ response.genome.length_bp ??
+ response.genome.sequence?.length ??
+ 0;
+ const tracks = response.genome.tracks ?? [];
+ return { length, tracks };
+};
-const toGenomeDataFromResponse = (
- response: AnalyzeResponse | null,
- fallback: GenomeData,
-): GenomeData => {
- if (!response) return fallback;
- const details = response.details;
- if (details && typeof details === "object") {
- const candidate = (details as any).genome ?? details;
- if (isGenomeData(candidate)) {
- return candidate;
- }
- }
- return fallback;
+const DEFAULT_PREVIEW_GENOME: GenomeData = {
+ length: 12000,
+ tracks: [
+ {
+ id: "preview-primer-candidates",
+ name: "Primer 후보군",
+ height: 28,
+ features: [
+ { id: "p-01", start: 400, end: 1200, label: "P-01", color: "#2563eb" },
+ { id: "p-02", start: 1800, end: 2600, label: "P-02", color: "#0ea5e9" },
+ { id: "p-03", start: 3200, end: 4300, label: "P-03", color: "#22c55e" },
+ ],
+ },
+ {
+ id: "preview-target-region",
+ name: "Target 구간",
+ height: 18,
+ features: [{ id: "amplicon", start: 1500, end: 5200, label: "Amplicon", color: "#f97316" }],
+ },
+ ],
};
export default function Home() {
@@ -61,8 +67,8 @@ export default function Home() {
const handleZoomOut = () =>
setViewState({ ...viewState, scale: clampScale(viewState.scale / zoomStep) });
- // 더미 genome 데이터
- const genome = demoGenome;
+ const sequenceInputRef = useRef("");
+ const [resultGenome, setResultGenome] = useState
(null);
const steps = [
{ id: 1, label: "Template & Essential" },
@@ -73,31 +79,107 @@ export default function Home() {
const [step, setStep] = useState<1 | 2 | 3 | 4>(1);
const [isLoading, setIsLoading] = useState(false);
- const [apiResult, setApiResult] = useState(null);
+ const [apiResult, setApiResult] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
+ const [step1WarningMessage, setStep1WarningMessage] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
- const [resultGenome, setResultGenome] = useState(null);
const totalSteps = steps.length;
const handleStepChange = (next: number) => {
const clamped = Math.min(Math.max(next, 1), totalSteps) as 1 | 2 | 3 | 4;
setStep(clamped);
};
- const handleNext = () => handleStepChange(step + 1);
+ const clearStep1Warning = () => {
+ if (step1WarningMessage) {
+ setStep1WarningMessage(null);
+ }
+ };
+
+ const reportStep1ValidationMessage = (
+ mode: "step-transition" | "generate",
+ message: string | null,
+ ) => {
+ // Step 전환 검증은 Step1 인라인 경고를 사용하고,
+ // Generate 직전 검증은 하단 error banner를 사용해 중복 노출을 피한다.
+ if (mode === "step-transition") {
+ setStep1WarningMessage(message);
+ return;
+ }
+ setErrorMessage(message);
+ };
+
+ const validateStep1Sequence = (mode: "step-transition" | "generate") => {
+ const rawInputSequence = sequenceInputRef.current;
+ const normalizedSequence = normalizeStep1TemplateSequence(rawInputSequence);
+ const invalidChar = getInvalidStep1TemplateSequenceChar(rawInputSequence);
+ if (!invalidChar) {
+ if (mode === "generate" && rawInputSequence.trim().length > 0 && !normalizedSequence) {
+ // Generate 직전 검증: 입력이 있었는데 정규화 후 빈 문자열이면 요청 중단.
+ const warningMessage =
+ "전송할 수 있는 유효한 염기서열이 없습니다. A, T, G, C 문자만 입력해 주세요.";
+ reportStep1ValidationMessage(mode, warningMessage);
+ return { isValid: false, normalizedSequence };
+ }
+
+ // 단계 이동 검증: Step1 경고를 초기화하고 다음 동작 진행.
+ reportStep1ValidationMessage(mode, null);
+ return { isValid: true, normalizedSequence };
+ }
+
+ const warningMessage = `대소문자 구분 없이 A, T, G, C만 입력 가능합니다. 잘못된 문자를 제거해 주세요.`;
+ reportStep1ValidationMessage(mode, warningMessage);
+ return { isValid: false, normalizedSequence };
+ };
+ const handleNext = () => {
+ if (step === 1 && !validateStep1Sequence("step-transition").isValid) {
+ return;
+ }
+
+ handleStepChange(step + 1);
+ };
const handleBack = () => handleStepChange(step - 1);
const isLastStep = step === totalSteps;
- const trackCount = genome.tracks.length;
- const featureCount = genome.tracks.reduce(
+ const previewGenome = DEFAULT_PREVIEW_GENOME;
+ const trackCount = previewGenome.tracks.length;
+ const featureCount = previewGenome.tracks.reduce(
(count, track) => count + track.features.length,
0,
);
const handleGenerate = async () => {
- const payload: AnalyzeRequest = {
- target_sequence: "ATGCGTACGTAGCTAGCTAGCTAGCTAATGCGTACGTAGCTAGCTAGCTAGCTA",
+ const validation = validateStep1Sequence("generate");
+ if (!validation.isValid) {
+ return;
+ }
+
+ const targetSeq = validation.normalizedSequence?.trim() ?? "";
+ if (!targetSeq) {
+ setErrorMessage("템플릿 시퀀스를 입력해 주세요.");
+ return;
+ }
+
+ const payload: AnalyzeRequestInput = {
+ target_sequence: targetSeq,
species: "Homo sapiens",
analysis_type: "primer_generation",
- notes: "UI mock request while backend is offline",
+ product_size_min: 100,
+ product_size_max: 300,
+ tm_min: 57,
+ tm_opt: 60,
+ tm_max: 63,
+ gc_content_min: 40,
+ gc_content_max: 60,
+ max_tm_difference: 1,
+ gc_clamp: true,
+ max_poly_x: 5,
+ concentration: 50,
+ check_enabled: true,
+ splice_variant_handling: false,
+ snp_handling: false,
+ mispriming_library: false,
+ end_mismatch_region_size: 5,
+ end_mismatch_min_mismatch: 1,
+ search_start: 1,
};
setIsLoading(true);
@@ -106,8 +188,8 @@ export default function Home() {
try {
const result = await analyzeGenome(payload);
setApiResult(result);
- const genomeFromApi = toGenomeDataFromResponse(result, demoGenome);
- setResultGenome(genomeFromApi);
+ const genomeFromApi = toGenomeDataFromResponse(result);
+ setResultGenome(genomeFromApi ?? null);
setIsModalOpen(true);
} catch (error) {
const message =
@@ -118,7 +200,6 @@ export default function Home() {
setIsModalOpen(false);
// Surface the error for visibility during development.
console.error("Generate Primers failed", error);
- alert(message);
} finally {
setIsLoading(false);
}
@@ -134,7 +215,7 @@ export default function Home() {
- {step === 1 && }
+ {step === 1 && (
+
+ )}
{step === 2 && }
@@ -150,7 +237,7 @@ export default function Home() {
{step === 4 && (
void;
}
@@ -15,30 +19,26 @@ interface PrimerResultModalProps {
const MIN_SCALE = 0.1;
const MAX_SCALE = 50;
const ZOOM_STEP = 1.2;
+const CANVAS_PADDING_X = 20;
-const createInitialViewState = (): GenomeCanvasViewState => ({
- scale: 1,
- offsetX: 0,
- offsetY: 0,
-});
+interface PrimerResultCanvasPanelProps {
+ apiResult: PrimerDesignResponseUI | null;
+ genome: GenomeData;
+ onClose: () => void;
+ initialViewState: GenomeCanvasViewState;
+ canvasShellRef: RefObject;
+}
const clampScaleValue = (scale: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale));
-export default function PrimerResultModal({
- isOpen,
+function PrimerResultCanvasPanel({
apiResult,
genome,
onClose,
-}: PrimerResultModalProps) {
- const [viewState, setViewState] = useState(createInitialViewState());
-
- useEffect(() => {
- if (isOpen) {
- setViewState(createInitialViewState());
- }
- }, [isOpen, genome]);
-
- if (!isOpen || !genome) return null;
+ initialViewState,
+ canvasShellRef,
+}: PrimerResultCanvasPanelProps) {
+ const [viewState, setViewState] = useState(initialViewState);
const handleZoomIn = () =>
setViewState((prev) => ({
@@ -52,7 +52,7 @@ export default function PrimerResultModal({
scale: clampScaleValue(prev.scale / ZOOM_STEP),
}));
- const handleReset = () => setViewState(createInitialViewState());
+ const handleReset = () => setViewState(initialViewState);
return (
@@ -108,7 +108,10 @@ export default function PrimerResultModal({
-
+
{
+ const tracks = Array.isArray(data.tracks) ? data.tracks : [];
+
+ tracks.forEach((track) => {
const trackHeight = (track.height ?? 18) * layoutScale;
ctx.fillStyle = "#a5b4d8";
ctx.font = `${12 * layoutScale}px ui-sans-serif, system-ui`;
- ctx.fillText(track.name ?? track.id, paddingX, y - 10 * layoutScale);
+ const trackLabel = track.name ?? track.id ?? "Track";
+ ctx.fillText(trackLabel, paddingX, y - 10 * layoutScale);
ctx.strokeStyle = "#23324a";
ctx.beginPath();
@@ -187,9 +193,15 @@ export default function PrimerResultModal({
ctx.lineTo(viewport.width - paddingX, y + trackHeight / 2);
ctx.stroke();
- track.features.forEach((feature) => {
- const x = bpToX(feature.start);
- const width = toScreenWidth(feature.start, feature.end);
+ const features = Array.isArray(track.features)
+ ? track.features
+ : [];
+
+ features.forEach((feature) => {
+ const start = Number(feature.start ?? feature.start_bp ?? 0);
+ const end = Number(feature.end ?? feature.end_bp ?? start);
+ const x = bpToX(start);
+ const width = toScreenWidth(start, end);
const radius = Math.min(6, trackHeight / 2);
ctx.fillStyle = feature.color ?? "#38bdf8";
@@ -214,11 +226,17 @@ export default function PrimerResultModal({
drawRoundedRect(ctx, x, y, width, trackHeight, radius);
ctx.fill();
- if (feature.label) {
+ const label =
+ feature.label ||
+ feature.id ||
+ feature.name ||
+ "";
+
+ if (label) {
const labelPaddingX = 6 * layoutScale;
const labelPaddingY = 3 * layoutScale;
ctx.font = `600 ${11 * layoutScale}px ui-sans-serif, system-ui`;
- const metrics = ctx.measureText(feature.label);
+ const metrics = ctx.measureText(label);
const labelWidth = metrics.width + labelPaddingX * 2;
const labelHeight = 16 * layoutScale + labelPaddingY;
const labelX = x + 6 * layoutScale;
@@ -238,11 +256,7 @@ export default function PrimerResultModal({
ctx.stroke();
ctx.fillStyle = "#e2e8f0";
- ctx.fillText(
- feature.label,
- labelX + labelPaddingX,
- labelY + 12 * layoutScale,
- );
+ ctx.fillText(label, labelX + labelPaddingX, labelY + 12 * layoutScale);
}
});
@@ -256,3 +270,105 @@ export default function PrimerResultModal({
);
}
+
+const createInitialViewState = (): GenomeCanvasViewState => ({
+ scale: 1,
+ offsetX: 0,
+ offsetY: 0,
+});
+
+export default function PrimerResultModal({
+ isOpen,
+ apiResult,
+ genome,
+ onClose,
+}: PrimerResultModalProps) {
+ const canvasShellRef = useRef
(null);
+ const [canvasWidth, setCanvasWidth] = useState(() =>
+ typeof window === "undefined" ? 960 : Math.max(320, Math.floor(window.innerWidth * 0.72)),
+ );
+
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const { body, documentElement } = document;
+ const previousBodyOverflow = body.style.overflow;
+ const previousBodyPaddingRight = body.style.paddingRight;
+ const previousBodyOverscrollBehavior = body.style.overscrollBehavior;
+ const previousHtmlOverflow = documentElement.style.overflow;
+ const previousHtmlOverscrollBehavior = documentElement.style.overscrollBehavior;
+
+ const scrollbarWidth = window.innerWidth - documentElement.clientWidth;
+ body.style.overflow = "hidden";
+ body.style.overscrollBehavior = "none";
+ documentElement.style.overflow = "hidden";
+ documentElement.style.overscrollBehavior = "none";
+
+ if (scrollbarWidth > 0) {
+ body.style.paddingRight = `${scrollbarWidth}px`;
+ }
+
+ return () => {
+ body.style.overflow = previousBodyOverflow;
+ body.style.paddingRight = previousBodyPaddingRight;
+ body.style.overscrollBehavior = previousBodyOverscrollBehavior;
+ documentElement.style.overflow = previousHtmlOverflow;
+ documentElement.style.overscrollBehavior = previousHtmlOverscrollBehavior;
+ };
+ }, [isOpen]);
+
+ useEffect(() => {
+ if (!isOpen) return;
+
+ const element = canvasShellRef.current;
+ if (!element) return;
+
+ const updateCanvasWidth = () => {
+ setCanvasWidth(element.clientWidth);
+ };
+
+ updateCanvasWidth();
+
+ const resizeObserver = new ResizeObserver(() => {
+ updateCanvasWidth();
+ });
+
+ resizeObserver.observe(element);
+
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, [isOpen]);
+
+ const fittedInitialViewState = useMemo(() => {
+ if (!genome) return createInitialViewState();
+
+ return createFocusedPrimerViewState({
+ genome,
+ viewportWidth: canvasWidth,
+ minScale: MIN_SCALE,
+ maxScale: MAX_SCALE,
+ paddingX: CANVAS_PADDING_X,
+ });
+ }, [canvasWidth, genome]);
+
+ if (!isOpen || !genome) return null;
+
+ const featureCount = genome.tracks.reduce(
+ (count, track) => count + (Array.isArray(track.features) ? track.features.length : 0),
+ 0,
+ );
+
+ const viewStateKey = `${genome.length}-${genome.tracks.length}-${featureCount}-${Math.round(canvasWidth)}`;
+
+ return (
+
+ );
+}
diff --git a/components/canvas/GenomeCanvas.tsx b/components/canvas/GenomeCanvas.tsx
index 263b275..1d7d0ce 100644
--- a/components/canvas/GenomeCanvas.tsx
+++ b/components/canvas/GenomeCanvas.tsx
@@ -6,7 +6,7 @@ import type {
GenomeCanvasProps,
GenomeCanvasRenderState,
GenomeCanvasViewState,
-} from "@/lib/types/Genome";
+} from "@/types";
const DEFAULT_VIEW_STATE: GenomeCanvasViewState = {
scale: 1,
diff --git a/components/steps/Step1TemplateEssential.tsx b/components/steps/Step1TemplateEssential.tsx
index 97d7967..3930178 100644
--- a/components/steps/Step1TemplateEssential.tsx
+++ b/components/steps/Step1TemplateEssential.tsx
@@ -1,33 +1,204 @@
"use client";
-import { type ChangeEvent, useMemo, useRef, useState } from "react";
+import {
+ type ClipboardEvent,
+ type ChangeEvent,
+ type FormEvent,
+ type MutableRefObject,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
import { SlidersHorizontal } from "lucide-react";
-import TextareaAutosize from "react-textarea-autosize";
+import {
+ getInvalidStep1TemplateSequenceChars,
+ sanitizeStep1TemplateSequenceInput,
+} from "../../src/lib/parsers/step1TemplateSequence";
-export default function Step1TemplateEssential() {
+type Step1TemplateEssentialProps = {
+ sequenceRef: MutableRefObject;
+ validationMessage?: string | null;
+ onSequenceChange?: (value: string) => void;
+};
+
+const countAlphabeticChars = (value: string) => {
+ let count = 0;
+
+ for (let index = 0; index < value.length; index += 1) {
+ const code = value.charCodeAt(index);
+ const isUpperCase = code >= 65 && code <= 90;
+ const isLowerCase = code >= 97 && code <= 122;
+
+ if (isUpperCase || isLowerCase) {
+ count += 1;
+ }
+ }
+
+ return count;
+};
+
+const COUNT_DELAY_SMALL_MS = 80;
+const COUNT_DELAY_LARGE_MS = 240;
+const MAX_EDITOR_CHARS = 30_000;
+const PREVIEW_HEAD_CHARS = 1_200;
+const PREVIEW_TAIL_CHARS = 1_200;
+
+const getPreviewValue = (value: string) => {
+ if (value.length <= MAX_EDITOR_CHARS) return value;
+
+ const head = value.slice(0, PREVIEW_HEAD_CHARS);
+ const tail = value.slice(-PREVIEW_TAIL_CHARS);
+
+ return `${head}\n\n...[large sequence preview: ${value.length.toLocaleString()} chars total]...\n\n${tail}`;
+};
+
+export default function Step1TemplateEssential({
+ sequenceRef,
+ validationMessage,
+ onSequenceChange,
+}: Step1TemplateEssentialProps) {
const fileInputRef = useRef(null);
const textareaRef = useRef(null);
- const [sequenceInput, setSequenceInput] = useState("");
+ const countTimeoutRef = useRef(null);
+ const invalidRemovalResolverRef = useRef<((approved: boolean) => void) | null>(null);
+ const [invalidRemovalDialogMessage, setInvalidRemovalDialogMessage] = useState(
+ null,
+ );
+ const [isLargeSequenceMode, setIsLargeSequenceMode] = useState(
+ sequenceRef.current.length > MAX_EDITOR_CHARS,
+ );
+ const [basePairCount, setBasePairCount] = useState(() =>
+ countAlphabeticChars(sequenceRef.current),
+ );
+
+ const clearPendingCountTimer = () => {
+ if (countTimeoutRef.current !== null) {
+ window.clearTimeout(countTimeoutRef.current);
+ countTimeoutRef.current = null;
+ }
+ };
- const basePairCount = useMemo(
- () => sequenceInput.replace(/[^A-Za-z]/g, "").length,
- [sequenceInput],
+ const scheduleCount = (value: string, isLargeSequence: boolean) => {
+ clearPendingCountTimer();
+ const delay = isLargeSequence ? COUNT_DELAY_LARGE_MS : COUNT_DELAY_SMALL_MS;
+
+ countTimeoutRef.current = window.setTimeout(() => {
+ setBasePairCount(countAlphabeticChars(value));
+ countTimeoutRef.current = null;
+ }, delay);
+ };
+
+ const updateSequence = (value: string, countMode: "defer" | "immediate" = "defer") => {
+ sequenceRef.current = value;
+ onSequenceChange?.(value);
+ const nextIsLargeSequence = value.length > MAX_EDITOR_CHARS;
+ setIsLargeSequenceMode(nextIsLargeSequence);
+
+ if (textareaRef.current) {
+ const nextEditorValue = nextIsLargeSequence ? getPreviewValue(value) : value;
+ if (textareaRef.current.value !== nextEditorValue) {
+ textareaRef.current.value = nextEditorValue;
+ }
+ }
+
+ if (countMode === "immediate") {
+ clearPendingCountTimer();
+ setBasePairCount(countAlphabeticChars(value));
+ return;
+ }
+ scheduleCount(value, nextIsLargeSequence);
+ };
+
+ useEffect(
+ () => () => {
+ clearPendingCountTimer();
+ if (invalidRemovalResolverRef.current) {
+ invalidRemovalResolverRef.current(false);
+ invalidRemovalResolverRef.current = null;
+ }
+ },
+ [],
);
const focusTextarea = () => textareaRef.current?.focus();
- const appendSequence = (next: string) => {
- const sanitized = next.trim();
+ const requestInvalidRemovalConsent = (message: string) =>
+ new Promise((resolve) => {
+ if (invalidRemovalResolverRef.current) {
+ invalidRemovalResolverRef.current(false);
+ }
+ invalidRemovalResolverRef.current = resolve;
+ setInvalidRemovalDialogMessage(message);
+ });
+
+ const resolveInvalidRemovalConsent = (approved: boolean) => {
+ if (invalidRemovalResolverRef.current) {
+ invalidRemovalResolverRef.current(approved);
+ invalidRemovalResolverRef.current = null;
+ }
+ setInvalidRemovalDialogMessage(null);
+ };
+
+ const confirmInvalidRemoval = async (rawSequence: string) => {
+ const invalidChars = getInvalidStep1TemplateSequenceChars(rawSequence);
+ if (invalidChars.length === 0) return true;
+
+ const previewInvalidChars = invalidChars.slice(0, 8).join(", ");
+ const extraKinds = invalidChars.length > 8 ? ` 외 ${invalidChars.length - 8}종` : "";
+ return requestInvalidRemovalConsent(
+ `붙여넣으려는 데이터에 A, T, G, C 이외 문자가 포함되어 있습니다 (${previewInvalidChars}${extraKinds}). 해당 문자를 제거하고 계속할까요?`,
+ );
+ };
+
+ const appendSequence = async (
+ next: string,
+ options: { requireInvalidRemovalConsent?: boolean } = {},
+ ) => {
+ if (options.requireInvalidRemovalConsent && !(await confirmInvalidRemoval(next))) {
+ focusTextarea();
+ return;
+ }
- if (!sanitized) {
+ const sanitizedChunk = sanitizeStep1TemplateSequenceInput(next);
+ if (!sanitizedChunk) {
focusTextarea();
return;
}
- setSequenceInput((prev) => (prev ? `${prev}\n${sanitized}` : sanitized));
+ const currentValue = sequenceRef.current;
+ const appendedValue = `${currentValue}${sanitizedChunk}`;
+
+ updateSequence(appendedValue, "immediate");
focusTextarea();
};
+ const insertChunkAtSelection = (
+ textarea: HTMLTextAreaElement,
+ rawChunk: string,
+ options: { sanitize: boolean },
+ ) => {
+ const nextChunk = options.sanitize
+ ? sanitizeStep1TemplateSequenceInput(rawChunk)
+ : rawChunk;
+ const currentValue = sequenceRef.current;
+ const selectionStart = textarea.selectionStart ?? currentValue.length;
+ const selectionEnd = textarea.selectionEnd ?? currentValue.length;
+ const nextValue =
+ currentValue.slice(0, selectionStart) +
+ nextChunk +
+ currentValue.slice(selectionEnd);
+
+ if (nextValue !== currentValue) {
+ updateSequence(nextValue, "immediate");
+ }
+
+ if (textareaRef.current && nextValue.length <= MAX_EDITOR_CHARS) {
+ const cursor = selectionStart + nextChunk.length;
+ textareaRef.current.selectionStart = cursor;
+ textareaRef.current.selectionEnd = cursor;
+ }
+ };
+
const handleUploadClick = () => fileInputRef.current?.click();
const handleFileChange = async (event: ChangeEvent) => {
@@ -37,7 +208,7 @@ export default function Step1TemplateEssential() {
try {
const text = await file.text();
- appendSequence(text);
+ await appendSequence(text, { requireInvalidRemovalConsent: true });
} catch (error) {
console.error("Failed to read FASTA file", error);
} finally {
@@ -53,26 +224,68 @@ export default function Step1TemplateEssential() {
try {
const text = await navigator.clipboard.readText();
- appendSequence(text);
+ await appendSequence(text, { requireInvalidRemovalConsent: true });
} catch (error) {
console.error("Failed to read from clipboard", error);
}
};
+ const handleTextareaPaste = async (event: ClipboardEvent) => {
+ if (isLargeSequenceMode || event.currentTarget.readOnly) {
+ event.preventDefault();
+ return;
+ }
+
+ const textarea = event.currentTarget;
+ const pastedText = event.clipboardData.getData("text");
+ if (!pastedText) return;
+ event.preventDefault();
+ const shouldSanitize = await confirmInvalidRemoval(pastedText);
+ insertChunkAtSelection(textarea, pastedText, { sanitize: shouldSanitize });
+ focusTextarea();
+ };
+
+ const handleTextareaBeforeInput = (event: FormEvent) => {
+ if (isLargeSequenceMode || event.currentTarget.readOnly) {
+ event.preventDefault();
+ return;
+ }
+
+ const nativeEvent = event.nativeEvent as InputEvent;
+ const inputType = nativeEvent.inputType ?? "";
+ if (inputType === "insertFromPaste") return;
+ if (!inputType.startsWith("insert")) return;
+
+ const rawChunk = nativeEvent.data ?? "";
+ if (!rawChunk) return;
+
+ const sanitizedChunk = sanitizeStep1TemplateSequenceInput(rawChunk);
+ if (sanitizedChunk === rawChunk) return;
+
+ event.preventDefault();
+ insertChunkAtSelection(event.currentTarget, rawChunk, { sanitize: true });
+ };
+
+ const handleTextareaChange = (event: ChangeEvent) => {
+ if (isLargeSequenceMode || event.currentTarget.readOnly) return;
+ updateSequence(sanitizeStep1TemplateSequenceInput(event.currentTarget.value));
+ };
+
const handleCleanClick = () => {
- setSequenceInput("");
+ updateSequence("", "immediate");
focusTextarea();
};
return (
-
-
- Step 1. 템플릿 시퀀스와 기본 설정을 입력하세요.
-
+ <>
+
+
+ Step 1. 템플릿 시퀀스와 기본 설정을 입력하세요.
+
-
+
PCR Template Sequence
@@ -89,23 +302,39 @@ export default function Step1TemplateEssential() {
{basePairCount} bp
- Seq1\nATGCGT..."}
spellCheck={false}
- minRows={10}
- maxRows={20}
- value={sequenceInput}
- onChange={(event) => setSequenceInput(event.target.value)}
+ readOnly={isLargeSequenceMode}
+ defaultValue={getPreviewValue(sequenceRef.current)}
+ onChange={handleTextareaChange}
+ onBeforeInput={handleTextareaBeforeInput}
+ onPaste={handleTextareaPaste}
/>
+ {isLargeSequenceMode && (
+
+ Large sequence mode is enabled for stability. The full sequence is
+ kept in memory and will be used for generation, but the editor
+ shows only a preview.
+
+ )}
+
+ A, T, G, C 이외 문자는 입력 시 자동으로 제거됩니다.
+
+ Paste 버튼, Ctrl+V, Upload as file에서는 제거 전에 확인을 요청합니다.
+
+ {validationMessage && (
+
{validationMessage}
+ )}
@@ -114,7 +343,7 @@ export default function Step1TemplateEssential() {
className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-bold text-slate-300 hover:text-white hover:bg-slate-800 transition-colors border border-transparent hover:border-slate-700"
onClick={handleUploadClick}
>
- Upload FASTA
+ Upload as file
-
+
+ {invalidRemovalDialogMessage && (
+
{
+ if (event.target === event.currentTarget) {
+ resolveInvalidRemovalConsent(false);
+ }
+ }}
+ >
+
+
문자 제거 확인
+
+ {invalidRemovalDialogMessage}
+
+
+ resolveInvalidRemovalConsent(false)}
+ >
+ 취소
+
+ resolveInvalidRemovalConsent(true)}
+ >
+ 제거 후 계속
+
+
+
+
+ )}
+ >
);
}
diff --git a/components/steps/Step4SpecificityPreview.tsx b/components/steps/Step4SpecificityPreview.tsx
index 4bad666..590ee7f 100644
--- a/components/steps/Step4SpecificityPreview.tsx
+++ b/components/steps/Step4SpecificityPreview.tsx
@@ -1,9 +1,17 @@
"use client";
import GenomeCanvas from "@/components/canvas/GenomeCanvas";
+import {
+ getVisibleRange,
+} from "@/lib/algorithms/visibleRange";
import { createBpScale } from "@/lib/math/coords";
-import type { GenomeCanvasViewState, GenomeData } from "@/lib/types/Genome";
+import type {
+ GenomeCanvasRenderState,
+ GenomeCanvasViewState,
+ GenomeData,
+} from "@/types";
import { ShieldCheck } from "lucide-react";
+import { useCallback, useMemo, useRef } from "react";
type Step4SpecificityPreviewProps = {
genome: GenomeData;
@@ -32,6 +40,15 @@ const drawRoundedRect = (
ctx.closePath();
};
+type TrackLayoutMemo = {
+ baseTrackHeightsRef: number[];
+ layoutScale: number;
+ trackGap: number;
+ trackHeights: number[];
+ prefixSums: number[];
+ totalTracksHeight: number;
+};
+
export default function Step4SpecificityPreview({
genome,
viewState,
@@ -40,6 +57,219 @@ export default function Step4SpecificityPreview({
onZoomOut,
onResetView,
}: Step4SpecificityPreviewProps) {
+ const tracks = useMemo(
+ () => (Array.isArray(genome.tracks) ? genome.tracks : []),
+ [genome.tracks],
+ );
+ const baseTrackHeights = useMemo(
+ () => tracks.map((track) => track.height ?? 18),
+ [tracks],
+ );
+ const trackLayoutMemoRef = useRef
(null);
+
+ const getTrackLayoutMemo = useCallback(
+ (layoutScale: number, trackGap: number) => {
+ const cached = trackLayoutMemoRef.current;
+ if (
+ cached &&
+ cached.baseTrackHeightsRef === baseTrackHeights &&
+ cached.layoutScale === layoutScale &&
+ cached.trackGap === trackGap
+ ) {
+ return cached;
+ }
+
+ const trackHeights = new Array(baseTrackHeights.length);
+ const prefixSums = new Array(baseTrackHeights.length + 1);
+ prefixSums[0] = 0;
+ const lastTrackIndex = baseTrackHeights.length - 1;
+
+ for (let index = 0; index < baseTrackHeights.length; index += 1) {
+ const trackHeight = baseTrackHeights[index] * layoutScale;
+ trackHeights[index] = trackHeight;
+ const gapAfterTrack = index === lastTrackIndex ? 0 : trackGap;
+ prefixSums[index + 1] = prefixSums[index] + trackHeight + gapAfterTrack;
+ }
+
+ const memoized: TrackLayoutMemo = {
+ baseTrackHeightsRef: baseTrackHeights,
+ layoutScale,
+ trackGap,
+ trackHeights,
+ prefixSums,
+ totalTracksHeight: prefixSums[prefixSums.length - 1] ?? 0,
+ };
+ trackLayoutMemoRef.current = memoized;
+
+ return memoized;
+ },
+ [baseTrackHeights],
+ );
+
+ const handleDraw = useCallback(
+ (
+ ctx: CanvasRenderingContext2D,
+ _canvas: HTMLCanvasElement,
+ renderState: GenomeCanvasRenderState,
+ ) => {
+ const { data, viewport, viewState: canvasViewState } = renderState;
+ if (!data) return;
+
+ const dpr = viewport.devicePixelRatio || 1;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+ ctx.fillStyle = "#0c1222";
+ ctx.fillRect(0, 0, viewport.width, viewport.height);
+
+ const paddingX = 20;
+ const layoutScale = Math.min(1.4, Math.max(1, viewport.height / 400));
+
+ const headerY = 28 * layoutScale;
+ const trackStartY = 64 * layoutScale;
+ const trackGap = 28 * layoutScale;
+
+ const bpScale = createBpScale(
+ data.length,
+ viewport.width - paddingX * 2,
+ 0,
+ );
+
+ const toScreenX = (bp: number) =>
+ paddingX +
+ canvasViewState.offsetX +
+ bpScale.bpToX(bp) * canvasViewState.scale;
+
+ const toScreenWidth = (start: number, end: number) => {
+ const rawWidth = bpScale.spanToWidth(start, end, 0) * canvasViewState.scale;
+ return Math.max(2, rawWidth);
+ };
+
+ ctx.fillStyle = "#e2e8f0";
+ ctx.font = `600 ${14 * layoutScale}px ui-sans-serif, system-ui`;
+ ctx.fillText(`Genome length`, paddingX, headerY - 6 * layoutScale);
+
+ ctx.fillStyle = "#9fb3d4";
+ ctx.font = `${12 * layoutScale}px ui-sans-serif, system-ui`;
+ ctx.fillText(
+ `${data.length.toLocaleString()} bp`,
+ paddingX,
+ headerY + 10 * layoutScale,
+ );
+
+ ctx.strokeStyle = "#1f2b3f";
+ ctx.lineWidth = 1;
+ for (let i = 0; i <= 10; i += 1) {
+ const x = paddingX + i * ((viewport.width - paddingX * 2) / 10);
+ ctx.beginPath();
+ ctx.moveTo(x, trackStartY - 16 * layoutScale);
+ ctx.lineTo(x, viewport.height - 20);
+ ctx.stroke();
+ }
+
+ if (tracks.length === 0) return;
+
+ const { trackHeights, prefixSums, totalTracksHeight } = getTrackLayoutMemo(
+ layoutScale,
+ trackGap,
+ );
+ const trackLayerTop = trackStartY + canvasViewState.offsetY;
+ const trackLayerBottom = trackLayerTop + totalTracksHeight;
+
+ if (trackLayerBottom <= 0 || trackLayerTop >= viewport.height) return;
+
+ const visibleViewportHeight = Math.max(
+ 0,
+ viewport.height - Math.max(trackLayerTop, 0),
+ );
+ const visibleScrollTop = Math.max(0, -trackLayerTop);
+ const { startIndex, endIndex } = getVisibleRange(
+ prefixSums,
+ visibleViewportHeight,
+ 1,
+ visibleScrollTop,
+ );
+
+ if (endIndex < startIndex) return;
+
+ for (let trackIndex = startIndex; trackIndex <= endIndex; trackIndex += 1) {
+ const track = tracks[trackIndex];
+ if (!track) continue;
+
+ const trackHeight = trackHeights[trackIndex] ?? 0;
+ const y = trackLayerTop + prefixSums[trackIndex];
+
+ ctx.fillStyle = "#a5b4d8";
+ ctx.font = `${12 * layoutScale}px ui-sans-serif, system-ui`;
+ const trackLabel = track.name ?? track.id ?? "Track";
+ ctx.fillText(trackLabel, paddingX, y - 10 * layoutScale);
+
+ ctx.strokeStyle = "#23324a";
+ ctx.beginPath();
+ ctx.moveTo(paddingX, y + trackHeight / 2);
+ ctx.lineTo(
+ viewport.width - paddingX,
+ y + trackHeight / 2,
+ );
+ ctx.stroke();
+
+ const features = Array.isArray(track.features) ? track.features : [];
+
+ features.forEach((feature) => {
+ const start = Number(feature.start ?? feature.start_bp ?? 0);
+ const end = Number(feature.end ?? feature.end_bp ?? start);
+ const x = toScreenX(start);
+ const width = toScreenWidth(start, end);
+ const radius = Math.min(6, trackHeight / 2);
+
+ ctx.fillStyle = feature.color ?? "#38bdf8";
+ drawRoundedRect(
+ ctx,
+ x,
+ y,
+ width,
+ trackHeight,
+ radius,
+ );
+ ctx.fill();
+
+ const label = feature.label || feature.id || feature.name || "";
+
+ if (label) {
+ const labelPaddingX = 6 * layoutScale;
+ const labelPaddingY = 3 * layoutScale;
+ ctx.font = `600 ${11 * layoutScale}px ui-sans-serif, system-ui`;
+ const metrics = ctx.measureText(label);
+ const labelWidth = metrics.width + labelPaddingX * 2;
+ const labelHeight = 16 * layoutScale + labelPaddingY;
+ const labelX = x + 6 * layoutScale;
+ const labelY = y + trackHeight + 6 * layoutScale;
+
+ ctx.fillStyle = "rgba(15,23,42,0.9)";
+ ctx.strokeStyle = "#1f2b3f";
+ drawRoundedRect(
+ ctx,
+ labelX,
+ labelY,
+ labelWidth,
+ labelHeight,
+ 6 * layoutScale,
+ );
+ ctx.fill();
+ ctx.stroke();
+
+ ctx.fillStyle = "#e2e8f0";
+ ctx.fillText(
+ label,
+ labelX + labelPaddingX,
+ labelY + 12 * layoutScale,
+ );
+ }
+ });
+ }
+ },
+ [getTrackLayoutMemo, tracks],
+ );
+
return (
@@ -164,183 +394,11 @@ export default function Step4SpecificityPreview({
onViewStateChange={onViewStateChange}
className="w-full"
style={{ height: "450px" }}
- onDraw={(ctx, _canvas, renderState) => {
- const { data, viewport, viewState } = renderState;
- if (!data) return;
-
- const dpr = viewport.devicePixelRatio || 1;
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
-
- ctx.fillStyle = "#0c1222";
- ctx.fillRect(0, 0, viewport.width, viewport.height);
-
- const paddingX = 20;
- const layoutScale = Math.min(
- 1.4,
- Math.max(1, viewport.height / 400),
- );
-
- const headerY = 28 * layoutScale;
- const trackStartY = 64 * layoutScale;
- const trackGap = 28 * layoutScale;
-
- const bpScale = createBpScale(
- data.length,
- viewport.width - paddingX * 2,
- 0,
- );
-
- const toScreenX = (bp: number) =>
- paddingX +
- viewState.offsetX +
- bpScale.bpToX(bp) * viewState.scale;
-
- const toScreenWidth = (start: number, end: number) => {
- const rawWidth =
- bpScale.spanToWidth(start, end, 0) *
- viewState.scale;
- return Math.max(2, rawWidth);
- };
-
- ctx.fillStyle = "#e2e8f0";
- ctx.font = `600 ${14 * layoutScale}px ui-sans-serif, system-ui`;
- ctx.fillText(`Genome length`, paddingX, headerY - 6 * layoutScale);
-
- ctx.fillStyle = "#9fb3d4";
- ctx.font = `${12 * layoutScale}px ui-sans-serif, system-ui`;
- ctx.fillText(
- `${data.length.toLocaleString()} bp`,
- paddingX,
- headerY + 10 * layoutScale,
- );
-
- ctx.strokeStyle = "#1f2b3f";
- ctx.lineWidth = 1;
- for (let i = 0; i <= 10; i += 1) {
- const x =
- paddingX +
- i * ((viewport.width - paddingX * 2) / 10);
- ctx.beginPath();
- ctx.moveTo(x, trackStartY - 16 * layoutScale);
- ctx.lineTo(x, viewport.height - 20);
- ctx.stroke();
- }
-
- let y = trackStartY + viewState.offsetY;
-
- data.tracks.forEach((track) => {
- const trackHeight = (track.height ?? 18) * layoutScale;
-
- ctx.fillStyle = "#a5b4d8";
- ctx.font = `${12 * layoutScale}px ui-sans-serif, system-ui`;
- ctx.fillText(
- track.name ?? track.id,
- paddingX,
- y - 10 * layoutScale,
- );
-
- ctx.strokeStyle = "#23324a";
- ctx.beginPath();
- ctx.moveTo(paddingX, y + trackHeight / 2);
- ctx.lineTo(
- viewport.width - paddingX,
- y + trackHeight / 2,
- );
- ctx.stroke();
-
- track.features.forEach((feature) => {
- const x = toScreenX(feature.start);
- const width = toScreenWidth(
- feature.start,
- feature.end,
- );
- const radius = Math.min(6, trackHeight / 2);
-
- ctx.fillStyle = feature.color ?? "#38bdf8";
- drawRoundedRect(
- ctx,
- x,
- y,
- width,
- trackHeight,
- radius,
- );
- ctx.fill();
-
- if (feature.label) {
- const labelPaddingX = 6 * layoutScale;
- const labelPaddingY = 3 * layoutScale;
- ctx.font = `600 ${11 * layoutScale}px ui-sans-serif, system-ui`;
- const metrics = ctx.measureText(feature.label);
- const labelWidth =
- metrics.width + labelPaddingX * 2;
- const labelHeight =
- 16 * layoutScale + labelPaddingY;
- const labelX = x + 6 * layoutScale;
- const labelY = y + trackHeight + 6 * layoutScale;
-
- ctx.fillStyle = "rgba(15,23,42,0.9)";
- ctx.strokeStyle = "#1f2b3f";
- drawRoundedRect(
- ctx,
- labelX,
- labelY,
- labelWidth,
- labelHeight,
- 6 * layoutScale,
- );
- ctx.fill();
- ctx.stroke();
-
- ctx.fillStyle = "#e2e8f0";
- ctx.fillText(
- feature.label,
- labelX + labelPaddingX,
- labelY + 12 * layoutScale,
- );
- }
- });
-
- y += trackHeight + trackGap;
- });
- }}
+ onDraw={handleDraw}
/>
-
-
-
-
-
- Quality notes
-
-
- Quick health check
-
-
-
- Stable
-
-
-
-
-
- Primer candidates are spaced to avoid overlap with target amplicon.
-
-
-
- Zoom 상태와 오프셋은 결과 단계에서만 노출됩니다.
-
-
-
- 단계 전환은 상단 스텝 원형 인디케이터와 하단 버튼으로 진행합니다.
-
-
-
);
}
diff --git a/components/ui/WizardHeader.tsx b/components/ui/WizardHeader.tsx
index 6965a05..15d3c5c 100644
--- a/components/ui/WizardHeader.tsx
+++ b/components/ui/WizardHeader.tsx
@@ -1,5 +1,7 @@
"use client";
+import Image from "next/image";
+
type WizardStep = {
id: number;
label: string;
@@ -24,24 +26,30 @@ export default function WizardHeader({
}: WizardHeaderProps) {
return (
-
+
-
- PF
+
+
-
- Primerflow Lab
-
-
- Primer Design Input
-
-
- demodesign 흐름을 따라 입력 -> 특성 -> 위치 -> 특이성/미리보기 순으로 진행합니다.
-
+
+
+ Primer Designer
+
+
+ by SeqLab
+
+
@@ -66,17 +74,17 @@ export default function WizardHeader({
const isUnlocked = item.id <= step;
const circle =
status === "active"
- ? "bg-blue-600 border-blue-400 text-white shadow-lg shadow-blue-900/40"
+ ? "bg-blue-600 text-white shadow-lg shadow-blue-900/40"
: status === "done"
- ? "bg-blue-500 border-blue-500 text-white"
- : "bg-slate-900 border-slate-800 text-slate-500";
+ ? "bg-blue-500 text-white"
+ : "bg-slate-900 text-slate-500";
return (
isUnlocked && onStepChange(item.id)}
- className={`relative z-10 flex h-11 w-11 items-center justify-center rounded-full border text-sm font-bold transition ${
+ className={`relative z-10 flex h-11 w-11 items-center justify-center rounded-full text-sm font-bold transition ${
isUnlocked
? circle
: `${circle} cursor-not-allowed opacity-60`
diff --git "a/docs/prompts/2\354\243\274\354\260\250/spec_prompt.md" "b/docs/prompts/2\354\243\274\354\260\250/spec_prompt.md"
index 1df1e29..852c24f 100644
--- "a/docs/prompts/2\354\243\274\354\260\250/spec_prompt.md"
+++ "b/docs/prompts/2\354\243\274\354\260\250/spec_prompt.md"
@@ -79,7 +79,7 @@ npm 또는 yarn
# 1. 저장소 클론
git clone [https://github.com/Seq-Lab/PrimerFlow-FE.git](https://github.com/Seq-Lab/PrimerFlow-FE.git)# 2. 프로젝트 폴더로 이동cd PrimerFlow-FE# 3. 패키지 설치
-npm install# 4. 환경 변수 설정 (.env.local 생성)# (백엔드 API 주소 설정 예시)# echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local# 5. 개발 서버 실행
+npm install# 4. 환경 변수 설정 (.env.local 생성 - 현재는 비워둠, API는 /api/v1 상대 경로 사용)# 5. 개발 서버 실행
npm run dev
프로젝트 구조
@@ -192,4 +192,4 @@ ReDoc 문서: http://localhost:8000/redoc
## 4. 결과 및 적용 (Result)
- GPT와 Gemini에 각각 동일한 프롬프트를 입력하여, 둘의 내용들을 정독한 후 통합한 버전으로 작성하였다.
-- 시나리오와 제한사항들을 설계하여, 이후 개발과정과 테스트 케이스 설계의 기준으로 설정할 수 있다.
\ No newline at end of file
+- 시나리오와 제한사항들을 설계하여, 이후 개발과정과 테스트 케이스 설계의 기준으로 설정할 수 있다.
diff --git "a/docs/prompts/5\354\243\274\354\260\250/adapter_pattern_and_mock_response_flow.md" "b/docs/prompts/5\354\243\274\354\260\250/adapter_pattern_and_mock_response_flow.md"
new file mode 100644
index 0000000..3bd40fc
--- /dev/null
+++ "b/docs/prompts/5\354\243\274\354\260\250/adapter_pattern_and_mock_response_flow.md"
@@ -0,0 +1,36 @@
+### 1. 배경 및 목적
+ - 프론트엔드의 폼 입력 상태(Flat Object)와 백엔드 API가 요구하는 복잡한 중첩 구조(Nested Object) 간의 규격 차이를 해결하기 위함.
+ - Mock 데이터를 활용하여 분석 결과를 시각화하는 모달(Modal) 뷰에 실제 데이터 바인딩 파이프라인을 구축.
+
+### 2. 프롬프트 (User Input)
+```aiignore
+Phase 1: 프론트-백엔드 데이터 규격 변환 (Adapter 로직)
+[요구사항 정의]
+프론트엔드의 Zustand 스토어에 저장된 평면적인(Flat) 입력 데이터들을 백엔드 FastAPI 규격에 맞는 중첩(Nested) JSON 형태로 변환하는 Adapter 함수를 작성해 줘.
+요구사항:
+1. 입력 데이터 중 `Range`, `PrimerDesignRequest` 타입을 백엔드 스펙에 맞게 매핑할 것.
+2. 타입 안정성을 보장하고, 누락된 필드가 없도록 TypeScript 인터페이스를 기반으로 작성할 것.
+
+Phase 2: Mock API 클라이언트 구축 및 Result Modal 연동
+[데이터 바인딩 요청]
+만들어진 Adapter를 거쳐 생성된 Request 객체를 서버로 전송하는 API 호출 함수(`lib/api/primer.ts`)를 만들어 줘.
+아직 실제 서버가 없으니, 지연 시간(Delay)을 시뮬레이션하고 Mock 데이터를 반환하도록 구현해 줘.
+반환된 결과를 바탕으로, Result Modal 내의 캔버스와 결과 리스트 UI에 데이터가 렌더링되도록 연결해 줘.
+```
+
+### 3. AI 응답 요약 (AI Output)
+ - src/lib/api/adapters.ts 생성:
+ - UI 상태를 백엔드 DTO로 변환하는 mapStoreToPrimerRequest 함수 구현.
+
+ - src/lib/api/primer.ts 모킹:
+ - axios 혹은 fetch 기반의 API 통신 뼈대 작성.
+ - setTimeout을 활용한 네트워크 지연 시뮬레이션 및 PrimerDesignResponse 타입에 맞춘 더미(Mock) 데이터 반환 로직 추가.
+
+ - ResultModal 컴포넌트 수정:
+
+반환된 Mock 데이터를 React 상태로 받아와 내부 캔버스와 리스트 컴포넌트로 Prop 전달.
+
+4. 결과 및 적용 (Result)
+ 적용 파일: src/lib/api/adapters.ts, src/lib/api/primer.ts, components/PrimerResultModal.tsx
+
+주요 결과: 프라이머 설계 요청(Request) 프로세스(입력값 → 어댑터 → API 호출)의 전체 흐름 완성. Mock 데이터를 활용해 결과 화면 시각화 성공.
\ No newline at end of file
diff --git "a/docs/prompts/6\354\243\274\354\260\250/canvas_performance_and_jitter_fix.md" "b/docs/prompts/6\354\243\274\354\260\250/canvas_performance_and_jitter_fix.md"
new file mode 100644
index 0000000..d4139eb
--- /dev/null
+++ "b/docs/prompts/6\354\243\274\354\260\250/canvas_performance_and_jitter_fix.md"
@@ -0,0 +1,42 @@
+### 1. 배경 및 목적
+ - 10,000bp 이상의 대용량 DNA 서열 데이터를 캔버스에 렌더링할 때 발생하던 프레임 드랍(렉) 이슈를 완화.
+ - 캔버스 줌/패닝 시 배경 레이어가 함께 밀려 보이는 Jittering 현상을 줄여 UX 안정화.
+
+### 2. 프롬프트 (User Input)
+```
+Phase 1: 대용량 데이터 렌더링 성능 최적화 (O(log N))
+[알고리즘 구현 요청]
+전체 트랙을 매번 순회(O(N))하지 않고, 뷰포트에 보이는 구간의 시작/끝 인덱스를
+이분 탐색으로 빠르게 계산해 렌더링 범위를 줄여줘.
+
+Phase 2: Jittering 버그(배경 밀림 현상) 수정
+[이슈 트러블슈팅]
+캔버스 렌더링 시 배경이 스크롤/패닝 영향으로 같이 움직여 보이지 않도록,
+좌표계 변환과 배경 렌더링 순서를 분리해 화면 기준으로 안정적으로 그려줘.
+```
+
+### 3. AI 응답 요약 (AI Output)
+ - `src/lib/algorithms/visibleRange.ts`:
+ - `createPrefixSums`, `findItemIndexByPosition`(Binary Search), `getVisibleRange` 구현.
+ - 누적 높이(prefix sums) 기반으로 보이는 트랙 인덱스 범위(`startIndex ~ endIndex`) 계산.
+
+ - `hooks/useVisibleRange.ts`:
+ - 스크롤 위치/뷰포트 높이를 받아 가시 범위를 계산하는 커스텀 훅 제공.
+ - `onScroll`, `setScrollTop`, `findIndexByPosition` 유틸 포함.
+
+ - 캔버스 렌더링 로직 정리:
+ - `components/canvas/GenomeCanvas.tsx`에서 `setTransform` + `save()/restore()`로 변환 경계 분리.
+ - `components/steps/Step4SpecificityPreview.tsx`에서 배경/그리드와 트랙 렌더링 좌표 처리를 분리하고,
+ `getVisibleRange`를 사용해 보이는 트랙만 그리도록 적용.
+
+### 4. 결과 및 적용 (Result)
+ - 적용 파일:
+ - `src/lib/algorithms/visibleRange.ts`
+ - `hooks/useVisibleRange.ts`
+ - `components/canvas/GenomeCanvas.tsx`
+ - `components/steps/Step4SpecificityPreview.tsx`
+ - `tests/visibleRange.test.ts`
+
+ - 주요 결과:
+ - 가시 범위 계산을 이분 탐색 기반으로 개선하여 대용량 데이터에서 렌더링 부담 완화.
+ - 배경/레이어 좌표 처리 분리로 줌/패닝 시 시각적 떨림(Jittering) 현상 완화.
diff --git "a/docs/prompts/7\354\243\274\354\260\250/sequence_input_normalization.md" "b/docs/prompts/7\354\243\274\354\260\250/sequence_input_normalization.md"
new file mode 100644
index 0000000..bf2887c
--- /dev/null
+++ "b/docs/prompts/7\354\243\274\354\260\250/sequence_input_normalization.md"
@@ -0,0 +1,90 @@
+# Step1 시퀀스 입력 정규화 및 검증 UX 개선
+
+## 1. 배경 및 목적
+
+- Step1 입력에서 `atgc` 대소문자 처리, 비정상 문자(`N`, 숫자, 특수문자) 정리, 붙여넣기/업로드 동의 UX를 일관되게 만들기 위해 파서/입력 이벤트 로직을 개선.
+
+## 2. 프롬프트 (User Input)
+
+```text
+Phase 1: 염기서열(ATGC) 입력 파서 및 기본 UX 구현
+[요구사항 정의]
+src/lib/parsers 디렉토리 내에 Step 1에서 사용할 DNA 염기서열 입력 파싱 및 검증 로직을 구현해 주세요.
+
+자동 대문자 변환: 입력된 'atgc' 문자열을 대소문자 구분 없이 자동으로 대문자로 변환하여 상태를 업데이트해야 합니다.
+
+실시간 Sanitize: 입력 시점에서 ATGC 이외의 유효하지 않은 문자가 감지되면 즉시 필터링하여 제거하는 로직을 포함해 주세요.
+
+사용자 안내(UX): Caps Lock이 꺼져 있어도 대문자로 강제 변환되거나 예외 문자가 사라지는 동작에 사용자가 당황하지 않도록, 입력창 하단에 해당 동작을 설명하는 작은 안내 캡션(Caption) 텍스트를 추가해 주세요.
+
+Phase 2: 빌드 에러 트러블슈팅 및 로직 수정
+[이슈 해결 요청]
+Next.js 환경에서 Turbopack과 관련된 빌드 에러가 발생하여 Job이 실패했습니다.
+
+제공된 에러 로그를 기반으로 ./app/page.tsx (약 12번 라인) 및 ./components/steps/Step1TemplateEssential.tsx (약 12번 라인)의 모듈 Import 경로를 검토해 주세요.
+
+지정된 경로에 실제 모듈이나 파일이 존재하는지 확인하고, 누락되거나 잘못된 참조가 있다면 수정해 주세요.
+
+
+[로직 결함 수정 및 대안 제시]
+현재 구현된 파서 로직에서 긴 문자열을 한 번에 붙여넣기(Paste)할 때, 유효한 문자까지 과도하게 삭제되는 이슈가 확인되었습니다.
+
+해당 문자열 손실 문제를 해결할 수 있는 최적화된 Sanitize 접근 방식을 2~3가지 제안해 주세요.
+
+제안해 주신 솔루션 중 1번 방식을 채택하여 코드를 수정해 주시고, 클라이언트에서 백엔드로 데이터를 전송하기 직전에 최종 문자열이 '대문자 ATGC'로만 구성되어 있는지 다시 한번 엄격하게 확인하는 최종 검증(Validation) 로직을 추가해 주세요.
+
+Phase 3: 예외 케이스 처리 및 대화상자(Dialog) 연동
+[UX 개선 및 예외 문자 처리 로직 보강]
+단순 텍스트 입력과 달리, .fasta 파일 업로드나 대량 텍스트 붙여넣기 시 미확인 염기(예: 'N')가 포함되어 있을 수 있습니다. 무조건적인 삭제보다는 사용자 확인을 거치는 방향으로 흐름을 개선하고자 합니다.
+
+유효하지 않은 문자가 감지되었을 때 즉시 삭제하지 않고, "이상 문자를 제거하시겠습니까?"를 묻는 사용자 동의 대화상자(Confirmation Dialog)를 띄우도록 로직을 수정해 주세요.
+
+이 대화상자 호출 로직은 다음 세 가지 이벤트에 모두 동일하게 적용되어야 합니다:
+
+UI 상의 'Paste' 버튼 클릭 시
+
+'Upload FASTA' 기능을 통한 파일 로드 시
+
+입력 텍스트 영역(Textarea) 내에서의 Ctrl + V 키보드 이벤트 발생 시
+
+Phase 4: 이벤트 핸들러 리팩토링 및 로직 최적화
+[코드 최적화]
+이전 단계들에서 추가된 검증 로직들로 인해 중복된 코드가 발생하여, 컴포넌트의 상태 관리 로직을 다음과 같이 정리하고자 합니다.
+
+handleTextareaChange 이벤트 핸들러 내부에서 입력과 동시에 필터링이 이루어질 수 있도록 코드를 수정해 주세요.
+(예: updateSequence(sanitizeStep1TemplateSequenceInput(event.currentTarget.value)) 구조 활용)
+
+입력 단계에서 Sanitize가 보장됨에 따라, 폼 제출 시 작동하는 handleGenerate 함수 내의 불필요한 중복 검증 로직을 제거하고 전체 흐름을 단순화해 주세요.
+```
+
+## 3. AI 응답 요약 (AI Output)
+
+- `src/lib/parsers/step1TemplateSequence.ts` 생성/확장:
+ - `toUpperCaseAtgcOnly`, `sanitizeStep1TemplateSequenceInput`, `normalizeStep1TemplateSequence`
+ - `getInvalidStep1TemplateSequenceChar`, `getInvalidStep1TemplateSequenceChars`
+ - `isUppercaseAtgcOnlySequence`
+- `Step1TemplateEssential` 입력 처리 개선:
+ - 전체 문자열 sanitize 방식 → 입력 조각(chunk) sanitize 방식으로 전환.
+ - `Paste 버튼`, `Upload FASTA`, `Ctrl+V` 경로에서 비-ATGC 문자 발견 시 `window.confirm`으로 제거 동의 요청.
+ - `onBeforeInput` 기반 실시간 정리 + `onChange` sanitize 폴백 추가.
+ - 안내 문구 추가 및 2줄 줄바꿈 반영.
+- `app/page.tsx` 반영:
+ - Generate 직전 정규화 적용.
+ - “입력은 있었지만 정규화 후 빈 시퀀스” 차단 로직 추가.
+ - 중복 `ATGC-only` 재검증 블록 제거로 검증 단순화.
+ - Turbopack module-not-found 대응으로 parser import 경로 조정.
+- 테스트 추가/보강:
+ - `tests/step1TemplateSequence.test.ts`에 대문자화/정규화/invalid 문자 수집/검증 케이스 추가.
+
+## 4. 결과 및 적용 (Result)
+
+- 적용 파일:
+ - `src/lib/parsers/step1TemplateSequence.ts`
+ - `components/steps/Step1TemplateEssential.tsx`
+ - `app/page.tsx`
+ - `tests/step1TemplateSequence.test.ts`
+- 주요 결과:
+ - Step1 입력은 `ATGC` 중심으로 자동 정리되고, 가져오기(Paste/Ctrl+V/Upload) 시 비정상 문자 제거 전에 사용자 동의를 받음.
+ - paste 시 과도 삭제 문제를 조각 단위 처리로 완화.
+ - Generate 직전 유효 염기열 존재 여부를 보장.
+ - Turbopack import 에러 재발 가능성을 낮춤.
\ No newline at end of file
diff --git "a/docs/prompts/8\354\243\274\354\260\250/mock_data_removal_and_backend_integration.md" "b/docs/prompts/8\354\243\274\354\260\250/mock_data_removal_and_backend_integration.md"
new file mode 100644
index 0000000..9b54b00
--- /dev/null
+++ "b/docs/prompts/8\354\243\274\354\260\250/mock_data_removal_and_backend_integration.md"
@@ -0,0 +1,61 @@
+# Mock Data Removal and Deployed Backend Integration
+
+## 1. 배경 및 목적
+
+- Week 5~7까지 사용하던 Mock 기반 결과 흐름을 제거하고, 실제 배포된 백엔드 API 응답을 기준으로 프론트엔드 동작을 전환.
+- 프라이머 설계 요청(Request)부터 결과 모달(Canvas) 렌더링까지 실데이터 파이프라인을 안정화.
+- API 실패 상황(네트워크/서버 오류)에서도 사용자에게 명확한 에러 메시지를 노출하도록 예외 처리를 보강.
+
+## 2. 프롬프트 (User Input)
+
+```text
+Phase 1: Mock 제거 및 실서버 API 연동
+[요구사항]
+기존 Mock 응답(setTimeout 기반) 의존 로직을 제거하고, 배포된 백엔드 API를 호출하도록 프론트엔드 요청 흐름을 전환해 주세요.
+
+- API Base URL은 환경변수(NEXT_PUBLIC_API_BASE_URL)로 주입하고, 미지정 시 /api를 기본값으로 사용해 주세요.
+- 요청 엔드포인트는 /design으로 통일해 주세요.
+- 요청 payload는 UI의 flat 입력을 백엔드 스펙의 nested 구조로 변환해 주세요.
+
+Phase 2: 응답 매핑 및 UI 바인딩
+[데이터 변환]
+백엔드 응답(Genome/Candidate)을 Result Modal과 Canvas에서 바로 사용할 수 있는 UI 구조로 변환해 주세요.
+
+- length, tracks, candidate 좌표(start_bp/end_bp)를 UI 친화 형태로 매핑해 주세요.
+- 일부 필드가 누락된 경우에도 기본값으로 안전하게 동작하게 처리해 주세요.
+
+Phase 3: 오류 처리 및 사용자 피드백
+[UX 보강]
+요청 실패 시 에러를 콘솔에만 남기지 말고 화면에서도 확인 가능하도록 처리해 주세요.
+
+- 로딩/성공/실패 상태 전이를 명확히 관리해 주세요.
+- 빈 입력이나 무효 입력은 API 호출 전에 차단해 주세요.
+```
+
+## 3. AI 응답 요약 (AI Output)
+
+- `src/lib/api/client.ts`
+ - `NEXT_PUBLIC_API_BASE_URL` 기반 axios 클라이언트 구성.
+ - 기본값 `/api` 사용으로 로컬/배포 환경 모두 대응.
+- `src/services/analysisService.ts`
+ - `AnalyzeRequestInput`(flat) -> `PrimerDesignRequest`(nested) 변환 어댑터(`toPrimerDesignRequest`) 정리.
+ - 백엔드 응답을 UI 트랙/프라이머 후보 구조로 변환하는 `toUiResponse` 로직 적용.
+ - `analyzeGenome`에서 실제 `POST /design` 호출 후 UI 전용 응답 반환.
+- `app/page.tsx`
+ - Step1 검증 이후 `analyzeGenome` 호출하도록 Generate 흐름 연결.
+ - 로딩/에러/성공 상태를 모달 오픈과 함께 관리.
+- `components/PrimerResultModal.tsx`
+ - API 결과를 기반으로 캔버스 데이터 표시 및 결과 메타 정보 렌더링.
+
+## 4. 결과 및 적용 (Result)
+
+- 적용 파일:
+ - `src/lib/api/client.ts`
+ - `src/lib/api/primer.ts`
+ - `src/services/analysisService.ts`
+ - `app/page.tsx`
+ - `components/PrimerResultModal.tsx`
+- 주요 결과:
+ - Mock 의존 흐름을 제거하고, 배포된 백엔드 API 기반의 실데이터 파이프라인으로 전환.
+ - 프론트 입력 스키마와 백엔드 요청 스키마 간 매핑을 정리해 API 연동 안정성 개선.
+ - 실패 케이스에서 사용자 가시 에러 메시지를 제공해 디버깅 및 운영 대응성 향상.
diff --git a/docs/spec_QA&Op&Risk.md b/docs/spec_QA&Op&Risk.md
index 10d4cd7..f21dc05 100644
--- a/docs/spec_QA&Op&Risk.md
+++ b/docs/spec_QA&Op&Risk.md
@@ -17,7 +17,7 @@
# 8. 릴리즈 및 운영
* **배포:** Frontend는 **Vercel**, Backend는 FastAPI.
-* **환경 변수:** `NEXT_PUBLIC_API_URL` 필수.
+* **환경 변수:** 현재 프론트는 `/api/v1` 상대 경로만 사용하므로 필수 항목 없음.
* **모니터링:** API 실패율, 주요 UI 액션 실패율.
* **롤백:** Vercel 배포 히스토리 기반 즉시 롤백.
@@ -32,4 +32,4 @@
## 9.2 기술 스택 (Dependencies)
* **Frontend:** Next.js 16, TypeScript, Tailwind, Zustand, SWR/TanStack Query.
-* **Backend:** FastAPI (Python).
\ No newline at end of file
+* **Backend:** FastAPI (Python).
diff --git a/hooks/usePrimerDesign.ts b/hooks/usePrimerDesign.ts
index 27c23c3..e2167a1 100644
--- a/hooks/usePrimerDesign.ts
+++ b/hooks/usePrimerDesign.ts
@@ -2,10 +2,7 @@
import { useMutation } from "@tanstack/react-query";
import type { AxiosError } from "axios";
-import type {
- PrimerDesignRequest,
- PrimerDesignResponse,
-} from "@/lib/api/primer";
+import type { PrimerDesignRequest, PrimerDesignResponse } from "@/types";
import { postDesignPrimers } from "@/lib/api/primer";
export const usePrimerDesign = () =>
diff --git a/hooks/useVisibleRange.ts b/hooks/useVisibleRange.ts
new file mode 100644
index 0000000..460fb5e
--- /dev/null
+++ b/hooks/useVisibleRange.ts
@@ -0,0 +1,81 @@
+"use client";
+
+import type { UIEvent } from "react";
+import { useCallback, useMemo, useState } from "react";
+import {
+ createPrefixSums,
+ findItemIndexByPosition,
+ getVisibleRange,
+ type VisibleRange,
+} from "@/lib/algorithms/visibleRange";
+
+export interface UseVisibleRangeOptions {
+ itemHeights: number[];
+ viewportHeight: number;
+ overscan?: number;
+ initialScrollTop?: number;
+}
+
+export interface UseVisibleRangeResult extends VisibleRange {
+ scrollTop: number;
+ totalHeight: number;
+ onScroll: (event: UIEvent) => VisibleRange;
+ setScrollTop: (nextScrollTop: number) => VisibleRange;
+ findIndexByPosition: (targetPos: number) => number;
+}
+
+export const useVisibleRange = ({
+ itemHeights,
+ viewportHeight,
+ overscan = 1,
+ initialScrollTop = 0,
+}: UseVisibleRangeOptions): UseVisibleRangeResult => {
+ const [scrollTop, setScrollTopState] = useState(initialScrollTop);
+
+ const prefixSums = useMemo(() => createPrefixSums(itemHeights), [itemHeights]);
+ const totalHeight = prefixSums[prefixSums.length - 1] ?? 0;
+
+ const findIndexByPosition = useCallback(
+ (targetPos: number) => findItemIndexByPosition(prefixSums, targetPos),
+ [prefixSums],
+ );
+
+ const computeVisibleRange = useCallback(
+ (targetScrollTop: number) =>
+ getVisibleRange(prefixSums, viewportHeight, overscan, targetScrollTop),
+ [overscan, prefixSums, viewportHeight],
+ );
+
+ const visibleRange = useMemo(
+ () => computeVisibleRange(scrollTop),
+ [computeVisibleRange, scrollTop],
+ );
+
+ const setScrollTop = useCallback(
+ (nextScrollTop: number) => {
+ setScrollTopState(nextScrollTop);
+ return computeVisibleRange(nextScrollTop);
+ },
+ [computeVisibleRange],
+ );
+
+ const onScroll = useCallback(
+ (event: UIEvent) => {
+ const nextScrollTop = event.currentTarget.scrollTop;
+ setScrollTopState(nextScrollTop);
+ return computeVisibleRange(nextScrollTop);
+ },
+ [computeVisibleRange],
+ );
+
+ return {
+ ...visibleRange,
+ scrollTop,
+ totalHeight,
+ onScroll,
+ setScrollTop,
+ findIndexByPosition,
+ };
+};
+
+export default useVisibleRange;
diff --git a/lib/algorithms/.gitkeep b/lib/algorithms/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/api/analysisService.ts b/lib/api/analysisService.ts
deleted file mode 100644
index 0110ccb..0000000
--- a/lib/api/analysisService.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// eslint-disable-next-line @typescript-eslint/no-unused-vars -- keep import for the future real request
-import { api } from "./client";
-
-export interface AnalyzeRequest {
- target_sequence: string;
- species: string;
- analysis_type?: string;
- reference_genome?: string;
- notes?: string;
-}
-
-export interface AnalyzeResponse {
- result: string;
- score: number;
- details: Record | string;
-}
-
-const ANALYSIS_ENDPOINT = "/api/v1/analysis";
-
-export const analyzeGenome = async (
- payload: AnalyzeRequest,
-): Promise => {
- // Uncomment the lines below when the backend is ready.
- // const response = await api.post(ANALYSIS_ENDPOINT, payload);
- // return response.data;
-
- const sequenceLength = payload.target_sequence.length || 5000;
-
- const mockGenome = {
- length: Math.max(sequenceLength, 6000),
- tracks: [
- {
- id: "primers",
- name: "Primer Candidates",
- height: 28,
- features: [
- { id: "p1", start: 500, end: 900, label: "P-Forward", color: "#2563eb" },
- { id: "p2", start: 1300, end: 1700, label: "P-Reverse", color: "#22c55e" },
- { id: "p3", start: 2400, end: 2800, label: "Alt-P", color: "#eab308" },
- ],
- },
- {
- id: "target",
- name: "Target Region",
- height: 18,
- features: [
- { id: "t1", start: 1100, end: 2300, label: "Amplicon", color: "#f97316" },
- ],
- },
- ],
- };
-
- const mockResponse: AnalyzeResponse = {
- result: "Success",
- score: 98.5,
- details: {
- summary: "Mock genome analysis completed.",
- analysis_endpoint: ANALYSIS_ENDPOINT,
- target_sequence_preview: payload.target_sequence.slice(0, 20),
- species: payload.species,
- detected_markers: ["BRCA1", "TP53"],
- gc_content: "51.2%",
- genome: mockGenome,
- },
- };
-
- return new Promise((resolve) => {
- setTimeout(() => resolve(mockResponse), 1500);
- });
-};
diff --git a/lib/api/primer.ts b/lib/api/primer.ts
deleted file mode 100644
index dd42e0b..0000000
--- a/lib/api/primer.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import type { AxiosResponse } from "axios";
-import { apiClient } from "./client";
-
-export interface Range {
- min: number;
- max: number;
-}
-
-export interface PrimerDesignRequest {
- basic: {
- templateSequence: string;
- targetOrganism: string;
- productSize: Range;
- primerTm: { min: number; opt: number; max: number };
- };
- properties: {
- gcContent: Range;
- maxTmDifference: number;
- gcClamp: boolean;
- maxPolyX: number;
- concentration: number;
- };
- specificity: {
- checkEnabled: boolean;
- spliceVariantHandling: boolean;
- snpExclusion: boolean;
- misprimingLibrary: boolean;
- endMismatchStrictness?: { regionSize: number; minMismatch: number };
- };
- position: {
- searchRange: { from: number; to: number };
- exonJunctionSpan: "none" | "flanking" | "spanning";
- intronInclusion: boolean;
- restrictionEnzymes: string[];
- };
-}
-
-export interface PrimerCandidate {
- id: string;
- sequence: string;
- start_bp: number;
- end_bp: number;
- strand: "forward" | "reverse";
- metrics: {
- tm_c?: number;
- gc_percent?: number;
- penalties?: any;
- };
-}
-
-export interface PrimerDesignResponse {
- genome: { id: string; name: string; sequence: string; length_bp: number };
- candidates: PrimerCandidate[];
- meta: any;
-}
-
-export const postDesignPrimers = async (
- payload: PrimerDesignRequest,
-): Promise => {
- const response: AxiosResponse = await apiClient.post(
- "/api/v1/primer/design",
- payload,
- );
-
- return response.data;
-};
diff --git a/lib/math/.gitkeep b/lib/math/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/mocks/demoGenome.ts b/lib/mocks/demoGenome.ts
deleted file mode 100644
index 352079d..0000000
--- a/lib/mocks/demoGenome.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { GenomeData } from "@/lib/types/Genome";
-
-export const demoGenome: GenomeData = {
- length: 12000,
- tracks: [
- {
- id: "track-1",
- name: "Primer 후보군",
- height: 28,
- features: [
- { id: "f1", start: 400, end: 1200, label: "P-01", color: "#2563eb" },
- { id: "f2", start: 1800, end: 2600, label: "P-02", color: "#0ea5e9" },
- { id: "f3", start: 3200, end: 4300, label: "P-03", color: "#22c55e" },
- ],
- },
- {
- id: "track-2",
- name: "Target 구간",
- height: 18,
- features: [
- { id: "t1", start: 1500, end: 5200, label: "Amplicon", color: "#f97316" },
- ],
- },
- ],
-};
diff --git a/lib/parsers/.gitkeep b/lib/parsers/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/lib/types/Genome.ts b/lib/types/Genome.ts
deleted file mode 100644
index 7e409e8..0000000
--- a/lib/types/Genome.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { CSSProperties } from "react";
-
-export type GenomeFeature = {
- id?: string;
- start: number;
- end: number;
- label?: string;
- color?: string;
-};
-
-export type GenomeTrack = {
- id: string;
- name?: string;
- height?: number;
- features: GenomeFeature[];
-};
-
-export type GenomeData = {
- length: number;
- tracks: GenomeTrack[];
-};
-
-export type GenomeCanvasViewState = {
- scale: number;
- offsetX: number;
- offsetY: number;
-};
-
-export type GenomeCanvasRenderState = {
- data?: GenomeData;
- viewState: GenomeCanvasViewState;
- viewport: {
- width: number;
- height: number;
- devicePixelRatio: number;
- };
-};
-
-export type GenomeCanvasProps = {
- className?: string;
- style?: CSSProperties;
- genome?: GenomeData;
- viewState?: GenomeCanvasViewState;
- initialViewState?: GenomeCanvasViewState;
- minScale?: number;
- maxScale?: number;
- onViewStateChange?: (nextViewState: GenomeCanvasViewState) => void;
- onDraw?: (
- ctx: CanvasRenderingContext2D,
- canvas: HTMLCanvasElement,
- renderState: GenomeCanvasRenderState,
- ) => void;
-};
diff --git a/next.config.js b/next.config.js
deleted file mode 100644
index e69de29..0000000
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..e484fbd 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,28 @@
import type { NextConfig } from "next";
+const DEFAULT_LOCAL_BACKEND_URL = "http://127.0.0.1:8000";
+const configuredBackendUrl = process.env.BACKEND_URL?.trim();
+
+if (!configuredBackendUrl && process.env.NODE_ENV === "production") {
+ throw new Error(
+ "BACKEND_URL must be set in production. Set BACKEND_URL to your backend origin.",
+ );
+}
+
+const backendUrl = (configuredBackendUrl || DEFAULT_LOCAL_BACKEND_URL).replace(
+ /\/+$/,
+ "",
+);
+
const nextConfig: NextConfig = {
- /* config options here */
+ async rewrites() {
+ return [
+ {
+ source: "/api/:path*",
+ destination: `${backendUrl}/:path*`,
+ },
+ ];
+ },
};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index d3f69da..a29295f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"@tanstack/react-query": "^5.64.0",
"axios": "^1.13.2",
"lucide-react": "^0.562.0",
- "next": "16.1.1",
+ "next": "^16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-textarea-autosize": "^8.5.9",
@@ -22,10 +22,11 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "16.1.1",
+ "eslint": "^9.39.2",
+ "eslint-config-next": "^16.1.6",
"tailwindcss": "^4",
- "typescript": "^5"
+ "typescript": "^5",
+ "vitest": "^3.2.4"
}
},
"node_modules/@alloc/quick-lru": {
@@ -42,9 +43,9 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
- "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -57,9 +58,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
- "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -67,22 +68,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
- "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/generator": "^7.28.6",
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.28.6",
+ "@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -99,14 +99,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
- "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -219,13 +219,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
- "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.6"
+ "@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -259,18 +259,18 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
- "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/generator": "^7.28.6",
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.6",
+ "@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6",
+ "@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
@@ -278,9 +278,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
- "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -324,515 +324,665 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
+ "optional": true,
+ "os": [
+ "aix"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=18"
}
},
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
- "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.1",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=18"
}
},
- "node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
+ "node": ">=18"
}
},
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.17.0",
- "levn": "^0.4.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": ">=18"
}
},
- "node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.4.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@img/colour": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
- "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
- "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
- "arm64"
+ "ppc64"
],
- "license": "Apache-2.0",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
- "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
- "x64"
+ "riscv64"
],
- "license": "Apache-2.0",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.2.4"
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
- "arm64"
+ "s390x"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "darwin"
+ "linux"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
- "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
- "arm"
+ "arm64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "netbsd"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
- "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
- "arm64"
+ "x64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "netbsd"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-ppc64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
- "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
- "ppc64"
+ "arm64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "openbsd"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-riscv64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
- "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
- "riscv64"
+ "x64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "openbsd"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
- "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
- "s390x"
+ "arm64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "openharmony"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
- "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "sunos"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
- "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
- "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
- "x64"
+ "ia32"
],
- "license": "LGPL-3.0-or-later",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
],
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
- "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
- "arm"
+ "x64"
],
- "license": "Apache-2.0",
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
- "linux"
+ "win32"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/libvips"
+ "url": "https://opencollective.com/eslint"
},
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.2.4"
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
- "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
"license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.2.4"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
- "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
"license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://opencollective.com/libvips"
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ "funding": {
+ "url": "https://eslint.org/donate"
}
},
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
- "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
- "cpu": [
- "riscv64"
- ],
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
"license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
},
"funding": {
- "url": "https://opencollective.com/libvips"
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
},
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@img/sharp-linux-s390x": {
+ "node_modules/@img/colour": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
+ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
- "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
- "s390x"
+ "arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
- "linux"
+ "darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -841,20 +991,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.2.4"
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
- "node_modules/@img/sharp-linux-x64": {
+ "node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
- "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
- "linux"
+ "darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -863,382 +1013,1025 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.2.4"
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
- "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
- "license": "Apache-2.0",
+ "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
- "linux"
+ "darwin"
],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
"funding": {
"url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
- "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
- "license": "Apache-2.0",
+ "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
- "linux"
+ "darwin"
],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
"funding": {
"url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
- "node_modules/@img/sharp-wasm32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
- "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
- "wasm32"
+ "arm"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "license": "LGPL-3.0-or-later",
"optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.7.0"
- },
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
+ "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
+ "license": "MIT"
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz",
+ "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "3.3.1"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
+ "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
+ "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
+ "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
+ "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
+ "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "node": ">= 10"
}
},
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
- "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
+ "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
"cpu": [
- "arm64"
+ "x64"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "license": "MIT",
"optional": true,
"os": [
- "win32"
+ "linux"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "node": ">= 10"
}
},
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
- "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
+ "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
"cpu": [
- "ia32"
+ "arm64"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "node": ">= 10"
}
},
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
- "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
+ "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
"cpu": [
"x64"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "node": ">= 10"
}
},
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
}
},
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
}
},
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6.0.0"
+ "node": ">=12.4.0"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
- "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
- "dependencies": {
- "@emnapi/core": "^1.4.3",
- "@emnapi/runtime": "^1.4.3",
- "@tybys/wasm-util": "^0.10.0"
- }
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/@next/env": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
- "integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
- "license": "MIT"
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/@next/eslint-plugin-next": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.1.tgz",
- "integrity": "sha512-Ovb/6TuLKbE1UiPcg0p39Ke3puyTCIKN9hGbNItmpQsp+WX3qrjO3WaMVSi6JHr9X1NrmthqIguVHodMJbh/dw==",
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "fast-glob": "3.3.1"
- }
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
},
- "node_modules/@next/swc-darwin-arm64": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
- "integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
"cpu": [
"arm64"
],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
+ "linux"
+ ]
},
- "node_modules/@next/swc-darwin-x64": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
- "integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
"cpu": [
- "x64"
+ "riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
+ "linux"
+ ]
},
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
- "integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
"cpu": [
- "arm64"
+ "s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
- "integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
"cpu": [
- "arm64"
+ "x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
- "integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
- "integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
+ "openbsd"
+ ]
},
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
- "integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
+ "openharmony"
+ ]
},
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
- "integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
"cpu": [
- "x64"
+ "arm64"
],
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
+ ]
},
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 8"
- }
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/@nolyfill/is-core-module": {
- "version": "1.0.39",
- "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
- "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=12.4.0"
- }
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
@@ -1564,6 +2357,24 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1601,7 +2412,6 @@
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1617,17 +2427,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz",
- "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
+ "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.53.0",
- "@typescript-eslint/type-utils": "8.53.0",
- "@typescript-eslint/utils": "8.53.0",
- "@typescript-eslint/visitor-keys": "8.53.0",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/type-utils": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -1640,8 +2450,8 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.53.0",
- "eslint": "^8.57.0 || ^9.0.0",
+ "@typescript-eslint/parser": "^8.56.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
@@ -1656,17 +2466,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz",
- "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
+ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "8.53.0",
- "@typescript-eslint/types": "8.53.0",
- "@typescript-eslint/typescript-estree": "8.53.0",
- "@typescript-eslint/visitor-keys": "8.53.0",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1677,19 +2486,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz",
- "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
+ "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.53.0",
- "@typescript-eslint/types": "^8.53.0",
+ "@typescript-eslint/tsconfig-utils": "^8.56.0",
+ "@typescript-eslint/types": "^8.56.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1704,14 +2513,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz",
- "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
+ "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.53.0",
- "@typescript-eslint/visitor-keys": "8.53.0"
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1722,9 +2531,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz",
- "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
+ "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1739,15 +2548,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz",
- "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
+ "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.53.0",
- "@typescript-eslint/typescript-estree": "8.53.0",
- "@typescript-eslint/utils": "8.53.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -1759,14 +2568,14 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz",
- "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
+ "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1778,16 +2587,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz",
- "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
+ "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.53.0",
- "@typescript-eslint/tsconfig-utils": "8.53.0",
- "@typescript-eslint/types": "8.53.0",
- "@typescript-eslint/visitor-keys": "8.53.0",
+ "@typescript-eslint/project-service": "8.56.0",
+ "@typescript-eslint/tsconfig-utils": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -1832,9 +2641,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -1845,16 +2654,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz",
- "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
+ "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.53.0",
- "@typescript-eslint/types": "8.53.0",
- "@typescript-eslint/typescript-estree": "8.53.0"
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1864,19 +2673,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz",
- "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
+ "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.53.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@typescript-eslint/types": "8.56.0",
+ "eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1886,6 +2695,19 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
+ "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
@@ -2155,13 +2977,127 @@
"win32"
]
},
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2389,6 +3325,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/ast-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -2439,13 +3385,13 @@
}
},
"node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
@@ -2519,7 +3465,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2534,6 +3479,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2613,6 +3568,23 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2630,6 +3602,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2783,6 +3765,16 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2873,9 +3865,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.267",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
- "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
"dev": true,
"license": "ISC"
},
@@ -3015,6 +4007,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -3067,10 +4066,52 @@
"is-symbol": "^1.0.4"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
}
},
"node_modules/escalade": {
@@ -3102,7 +4143,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3158,13 +4198,13 @@
}
},
"node_modules/eslint-config-next": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.1.tgz",
- "integrity": "sha512-55nTpVWm3qeuxoQKLOjQVciKZJUphKrNM0fCcQHAIOGl6VFXgaqeMfv0aKJhs7QtcnlAPhNVqsqRfRjeKBPIUA==",
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz",
+ "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@next/eslint-plugin-next": "16.1.1",
+ "@next/eslint-plugin-next": "16.1.6",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -3288,7 +4328,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3411,19 +4450,25 @@
}
},
"node_modules/eslint-plugin-react/node_modules/resolve": {
- "version": "2.0.0-next.5",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
- "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "version": "2.0.0-next.6",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
+ "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-core-module": "^2.13.0",
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "node-exports-info": "^1.6.0",
+ "object-keys": "^1.1.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -3512,6 +4557,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3522,6 +4577,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3699,6 +4764,21 @@
"node": ">= 6"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3815,9 +4895,9 @@
}
},
"node_modules/get-tsconfig": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
- "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
+ "version": "4.13.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
+ "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4131,9 +5211,9 @@
}
},
"node_modules/is-bun-module/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -4930,6 +6010,13 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -5085,12 +6172,12 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
- "integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
+ "version": "16.1.6",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
+ "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
"license": "MIT",
"dependencies": {
- "@next/env": "16.1.1",
+ "@next/env": "16.1.6",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
@@ -5104,14 +6191,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "16.1.1",
- "@next/swc-darwin-x64": "16.1.1",
- "@next/swc-linux-arm64-gnu": "16.1.1",
- "@next/swc-linux-arm64-musl": "16.1.1",
- "@next/swc-linux-x64-gnu": "16.1.1",
- "@next/swc-linux-x64-musl": "16.1.1",
- "@next/swc-win32-arm64-msvc": "16.1.1",
- "@next/swc-win32-x64-msvc": "16.1.1",
+ "@next/swc-darwin-arm64": "16.1.6",
+ "@next/swc-darwin-x64": "16.1.6",
+ "@next/swc-linux-arm64-gnu": "16.1.6",
+ "@next/swc-linux-arm64-musl": "16.1.6",
+ "@next/swc-linux-x64-gnu": "16.1.6",
+ "@next/swc-linux-x64-musl": "16.1.6",
+ "@next/swc-win32-arm64-msvc": "16.1.6",
+ "@next/swc-win32-x64-msvc": "16.1.6",
"sharp": "^0.34.4"
},
"peerDependencies": {
@@ -5165,6 +6252,25 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-exports-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
+ "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array.prototype.flatmap": "^1.3.3",
+ "es-errors": "^1.3.0",
+ "object.entries": "^1.1.9",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -5403,6 +6509,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5525,7 +6648,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5535,7 +6657,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -5663,6 +6784,51 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -5964,6 +7130,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5980,6 +7153,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -6130,6 +7317,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/styled-jsx": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -6200,6 +7407,20 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6241,7 +7462,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -6249,6 +7469,36 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
+ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -6404,7 +7654,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6414,16 +7663,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.0.tgz",
- "integrity": "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
+ "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.53.0",
- "@typescript-eslint/parser": "8.53.0",
- "@typescript-eslint/typescript-estree": "8.53.0",
- "@typescript-eslint/utils": "8.53.0"
+ "@typescript-eslint/eslint-plugin": "8.56.0",
+ "@typescript-eslint/parser": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6433,7 +7682,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
@@ -6584,6 +7833,221 @@
}
}
},
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6689,6 +8153,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -6720,12 +8201,11 @@
}
},
"node_modules/zod": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
- "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index 06cb930..546bc70 100644
--- a/package.json
+++ b/package.json
@@ -6,13 +6,14 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "eslint"
+ "lint": "eslint .",
+ "test": "vitest run"
},
"dependencies": {
"@tanstack/react-query": "^5.64.0",
"axios": "^1.13.2",
"lucide-react": "^0.562.0",
- "next": "16.1.1",
+ "next": "^16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-textarea-autosize": "^8.5.9",
@@ -23,9 +24,10 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "16.1.1",
+ "eslint": "^9.39.2",
+ "eslint-config-next": "^16.1.6",
"tailwindcss": "^4",
- "typescript": "^5"
+ "typescript": "^5",
+ "vitest": "^3.2.4"
}
}
diff --git a/public/primer.jpg b/public/primer.jpg
new file mode 100644
index 0000000..1f8fa2b
Binary files /dev/null and b/public/primer.jpg differ
diff --git a/src/lib/algorithms/focusViewState.ts b/src/lib/algorithms/focusViewState.ts
new file mode 100644
index 0000000..b407812
--- /dev/null
+++ b/src/lib/algorithms/focusViewState.ts
@@ -0,0 +1,131 @@
+import type { GenomeCanvasViewState, GenomeData, GenomeFeature } from "@/types";
+
+type FocusRange = {
+ start: number;
+ end: number;
+};
+
+type FocusedViewStateOptions = {
+ genome: GenomeData;
+ viewportWidth: number;
+ minScale: number;
+ maxScale: number;
+ paddingX?: number;
+ fillRatio?: number;
+ fallbackScale?: number;
+};
+
+const DEFAULT_PADDING_X = 20;
+const DEFAULT_FILL_RATIO = 0.78;
+
+const clamp = (value: number, min: number, max: number) =>
+ Math.min(max, Math.max(min, value));
+
+const toNumber = (value: unknown) =>
+ typeof value === "number" && Number.isFinite(value) ? value : null;
+
+const isPrimerRelatedTrack = (trackIdOrName: string) => {
+ const normalized = trackIdOrName.toLowerCase();
+ return (
+ normalized.includes("primer") ||
+ normalized.includes("amplicon") ||
+ normalized.includes("target")
+ );
+};
+
+const isPrimerRelatedFeature = (feature: GenomeFeature) => {
+ const label = String(feature.label ?? feature.name ?? feature.id ?? "").toLowerCase();
+ return (
+ label.includes("primer") ||
+ label.includes("amplicon") ||
+ label.includes("target")
+ );
+};
+
+const extractFocusRange = (features: GenomeFeature[], genomeLength: number): FocusRange | null => {
+ if (features.length === 0) return null;
+
+ let minStart = Number.POSITIVE_INFINITY;
+ let maxEnd = Number.NEGATIVE_INFINITY;
+
+ for (const feature of features) {
+ const rawStart = toNumber(feature.start ?? feature.start_bp);
+ const rawEnd = toNumber(feature.end ?? feature.end_bp ?? rawStart);
+ if (rawStart == null || rawEnd == null) continue;
+
+ const start = Math.min(rawStart, rawEnd);
+ const end = Math.max(rawStart, rawEnd);
+
+ minStart = Math.min(minStart, start);
+ maxEnd = Math.max(maxEnd, end);
+ }
+
+ if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) return null;
+
+ const safeLength = Math.max(1, genomeLength);
+ const span = Math.max(1, maxEnd - minStart);
+ const margin = Math.max(1, span * 0.25);
+
+ return {
+ start: clamp(minStart - margin, 0, safeLength),
+ end: clamp(maxEnd + margin, 0, safeLength),
+ };
+};
+
+const findPrimerFocusRange = (genome: GenomeData): FocusRange | null => {
+ const tracks = Array.isArray(genome.tracks) ? genome.tracks : [];
+
+ const focusTrackFeatures = tracks.flatMap((track) => {
+ const id = String(track.id ?? "");
+ const name = String(track.name ?? "");
+ const isFocusTrack = isPrimerRelatedTrack(id) || isPrimerRelatedTrack(name);
+ if (!isFocusTrack) return [];
+ return Array.isArray(track.features) ? track.features : [];
+ });
+
+ const explicitFeatureMatches = tracks.flatMap((track) => {
+ const features = Array.isArray(track.features) ? track.features : [];
+ return features.filter(isPrimerRelatedFeature);
+ });
+
+ const primaryFeatures =
+ focusTrackFeatures.length > 0 ? focusTrackFeatures : explicitFeatureMatches;
+
+ return extractFocusRange(primaryFeatures, genome.length);
+};
+
+export const createFocusedPrimerViewState = ({
+ genome,
+ viewportWidth,
+ minScale,
+ maxScale,
+ paddingX = DEFAULT_PADDING_X,
+ fillRatio = DEFAULT_FILL_RATIO,
+ fallbackScale = 1,
+}: FocusedViewStateOptions): GenomeCanvasViewState => {
+ const safeMinScale = Math.min(minScale, maxScale);
+ const safeMaxScale = Math.max(minScale, maxScale);
+ const focusRange = findPrimerFocusRange(genome);
+ if (!focusRange) {
+ return {
+ scale: clamp(fallbackScale, safeMinScale, safeMaxScale),
+ offsetX: 0,
+ offsetY: 0,
+ };
+ }
+
+ const safeLength = Math.max(1, genome.length);
+ const drawableWidth = Math.max(1, viewportWidth - paddingX * 2);
+ const safeFillRatio = clamp(fillRatio, 0.1, 0.98);
+ const span = Math.max(1, focusRange.end - focusRange.start);
+ const centerBp = (focusRange.start + focusRange.end) / 2;
+
+ const scale = clamp((safeLength / span) * safeFillRatio, safeMinScale, safeMaxScale);
+ const offsetX = drawableWidth / 2 - (centerBp / safeLength) * drawableWidth * scale;
+
+ return {
+ scale,
+ offsetX,
+ offsetY: 0,
+ };
+};
diff --git a/src/lib/algorithms/visibleRange.ts b/src/lib/algorithms/visibleRange.ts
new file mode 100644
index 0000000..3b419b3
--- /dev/null
+++ b/src/lib/algorithms/visibleRange.ts
@@ -0,0 +1,74 @@
+export interface VisibleRange {
+ startIndex: number;
+ endIndex: number;
+}
+
+const EMPTY_RANGE: VisibleRange = {
+ startIndex: 0,
+ endIndex: -1,
+};
+
+const clamp = (value: number, min: number, max: number) =>
+ Math.min(max, Math.max(min, value));
+
+const normalizeHeight = (height: number) =>
+ Number.isFinite(height) ? Math.max(0, height) : 0;
+
+export const createPrefixSums = (itemHeights: number[]) => {
+ const prefixSums = new Array(itemHeights.length + 1).fill(0);
+
+ for (let index = 0; index < itemHeights.length; index += 1) {
+ prefixSums[index + 1] = prefixSums[index] + normalizeHeight(itemHeights[index]);
+ }
+
+ return prefixSums;
+};
+
+export const findItemIndexByPosition = (prefixSums: number[], targetPos: number) => {
+ const itemCount = Math.max(prefixSums.length - 1, 0);
+ if (itemCount === 0) return -1;
+
+ const totalHeight = prefixSums[itemCount];
+ if (totalHeight <= 0) return 0;
+
+ const clampedPos = clamp(targetPos, 0, totalHeight - Number.EPSILON);
+
+ let left = 0;
+ let right = itemCount - 1;
+
+ while (left < right) {
+ const middle = Math.floor((left + right) / 2);
+ if (prefixSums[middle + 1] <= clampedPos) {
+ left = middle + 1;
+ } else {
+ right = middle;
+ }
+ }
+
+ return left;
+};
+
+export const getVisibleRange = (
+ prefixSums: number[],
+ viewportHeight: number,
+ overscan: number,
+ targetScrollTop: number,
+): VisibleRange => {
+ const itemCount = Math.max(prefixSums.length - 1, 0);
+ if (itemCount === 0) return EMPTY_RANGE;
+
+ const totalHeight = prefixSums[itemCount];
+ const safeViewportHeight = Math.max(0, viewportHeight);
+ const safeOverscan = Math.max(0, Math.floor(overscan));
+ const maxScrollTop = Math.max(0, totalHeight - safeViewportHeight);
+ const scrollTop = clamp(targetScrollTop, 0, maxScrollTop);
+
+ const start = findItemIndexByPosition(prefixSums, scrollTop);
+ const viewportBottom = scrollTop + Math.max(0, safeViewportHeight - Number.EPSILON);
+ const end = findItemIndexByPosition(prefixSums, viewportBottom);
+
+ return {
+ startIndex: Math.max(0, start - safeOverscan),
+ endIndex: Math.min(itemCount - 1, end + safeOverscan),
+ };
+};
diff --git a/lib/api/client.ts b/src/lib/api/client.ts
similarity index 55%
rename from lib/api/client.ts
rename to src/lib/api/client.ts
index 510546a..d0057c1 100644
--- a/lib/api/client.ts
+++ b/src/lib/api/client.ts
@@ -1,7 +1,11 @@
import axios from "axios";
+const DEFAULT_API_BASE_URL = "/api";
+const apiBaseUrl =
+ process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || DEFAULT_API_BASE_URL;
+
export const apiClient = axios.create({
- baseURL: process.env.NEXT_PUBLIC_API_URL,
+ baseURL: apiBaseUrl,
headers: {
"Content-Type": "application/json",
},
diff --git a/src/lib/api/primer.ts b/src/lib/api/primer.ts
new file mode 100644
index 0000000..c8e657f
--- /dev/null
+++ b/src/lib/api/primer.ts
@@ -0,0 +1,21 @@
+import type { AxiosResponse } from "axios";
+import type {
+ Range,
+ PrimerDesignRequest,
+ PrimerCandidate,
+ PrimerDesignResponse,
+} from "@/types";
+import { apiClient } from "./client";
+
+export type { Range, PrimerCandidate };
+
+export const postDesignPrimers = async (
+ payload: PrimerDesignRequest,
+): Promise => {
+ const response: AxiosResponse = await apiClient.post(
+ "/design",
+ payload,
+ );
+
+ return response.data;
+};
diff --git a/lib/math/coords.ts b/src/lib/math/coords.ts
similarity index 100%
rename from lib/math/coords.ts
rename to src/lib/math/coords.ts
diff --git a/src/lib/parsers/step1TemplateSequence.ts b/src/lib/parsers/step1TemplateSequence.ts
new file mode 100644
index 0000000..8637feb
--- /dev/null
+++ b/src/lib/parsers/step1TemplateSequence.ts
@@ -0,0 +1,32 @@
+const FASTA_HEADER_PREFIX = ">";
+const NON_ATGC_PATTERN = /[^ATGCatgc]/;
+const NON_ATGC_GLOBAL_PATTERN = /[^ATGCatgc]/g;
+
+const stripFastaHeadersAndWhitespace = (rawSequence: string) =>
+ rawSequence
+ .split(/\r?\n/)
+ .filter((line) => !line.trim().startsWith(FASTA_HEADER_PREFIX))
+ .join("")
+ .replace(/\s+/g, "");
+
+export const sanitizeStep1TemplateSequenceInput = (rawSequence: string) =>
+ stripFastaHeadersAndWhitespace(rawSequence)
+ .replace(NON_ATGC_GLOBAL_PATTERN, "")
+ .toUpperCase();
+
+export const normalizeStep1TemplateSequence = (rawSequence: string) =>
+ sanitizeStep1TemplateSequenceInput(rawSequence);
+
+export const getInvalidStep1TemplateSequenceChars = (rawSequence: string) => {
+ const matches = stripFastaHeadersAndWhitespace(rawSequence).match(NON_ATGC_GLOBAL_PATTERN);
+ if (!matches) return [];
+
+ const uniqueChars = new Set();
+ for (const char of matches) {
+ uniqueChars.add(char);
+ }
+ return [...uniqueChars];
+};
+
+export const getInvalidStep1TemplateSequenceChar = (rawSequence: string) =>
+ stripFastaHeadersAndWhitespace(rawSequence).match(NON_ATGC_PATTERN)?.[0] ?? null;
diff --git a/src/services/analysisService.ts b/src/services/analysisService.ts
new file mode 100644
index 0000000..11b2cbd
--- /dev/null
+++ b/src/services/analysisService.ts
@@ -0,0 +1,251 @@
+import { api } from "@/lib/api/client";
+import type {
+ PrimerDesignRequest,
+ PrimerDesignResponse,
+ PrimerDesignResponseUI,
+ UIPrimerCandidate,
+ UIGenome,
+} from "@/types";
+
+const isRecord = (value: unknown): value is Record =>
+ typeof value === "object" && value !== null;
+
+type AnalyzeSequenceInput =
+ | { target_sequence: string; templateSequence?: string }
+ | { target_sequence?: string; templateSequence: string };
+
+// Flat input from UI (kept flexible)
+export type AnalyzeRequestInput = AnalyzeSequenceInput & {
+ species?: string;
+ targetOrganism?: string;
+ analysis_type?: string;
+ notes?: string;
+
+ // Properties
+ product_size_min?: number;
+ product_size_max?: number;
+ tm_min?: number;
+ tm_opt?: number;
+ tm_max?: number;
+ gc_content_min?: number;
+ gc_content_max?: number;
+ max_tm_difference?: number;
+ gc_clamp?: boolean;
+ max_poly_x?: number;
+ concentration?: number;
+
+ // Specificity
+ check_enabled?: boolean;
+ splice_variant_handling?: boolean;
+ snp_handling?: boolean;
+ end_mismatch_region_size?: number;
+ end_mismatch_min_mismatch?: number;
+ mispriming_library?: boolean;
+
+ // Position
+ search_start?: number;
+ search_end?: number;
+ exon_junction_span?: string;
+ intron_inclusion?: boolean;
+ intron_size_min?: number;
+ intron_size_max?: number;
+ restriction_enzymes?: string[];
+};
+
+const resolveTemplateSequence = (input: AnalyzeRequestInput): string => {
+ const templateSequence = (input.target_sequence ?? input.templateSequence ?? "").trim();
+
+ if (!templateSequence) {
+ throw new Error(
+ "템플릿 시퀀스가 비어 있습니다. target_sequence 또는 templateSequence를 입력해 주세요.",
+ );
+ }
+
+ return templateSequence;
+};
+
+// Adapter: flat UI -> official request schema
+const toPrimerDesignRequest = (
+ input: AnalyzeRequestInput,
+ templateSequence: string,
+): PrimerDesignRequest => {
+ const seq = templateSequence;
+ const searchFrom = input.search_start ?? 1;
+ const searchTo =
+ input.search_end ??
+ searchFrom + Math.max(seq.length - 1, 0);
+
+ return {
+ basic: {
+ templateSequence: seq,
+ targetOrganism: input.species || input.targetOrganism || "Homo sapiens",
+ productSize: {
+ min: input.product_size_min ?? 100,
+ max: input.product_size_max ?? 300,
+ },
+ primerTm: {
+ min: input.tm_min ?? 57,
+ opt: input.tm_opt ?? 60,
+ max: input.tm_max ?? 63,
+ },
+ },
+ properties: {
+ gcContent: {
+ min: input.gc_content_min ?? 40,
+ max: input.gc_content_max ?? 60,
+ },
+ maxTmDifference: input.max_tm_difference ?? 1,
+ gcClamp: input.gc_clamp ?? false,
+ maxPolyX: input.max_poly_x ?? 5,
+ concentration: input.concentration ?? 50,
+ },
+ specificity: {
+ checkEnabled: input.check_enabled ?? true,
+ spliceVariantHandling: input.splice_variant_handling ?? false,
+ snpExclusion: input.snp_handling ?? false,
+ endMismatchStrictness:
+ input.end_mismatch_region_size != null || input.end_mismatch_min_mismatch != null
+ ? {
+ regionSize: input.end_mismatch_region_size ?? 5,
+ minMismatch: input.end_mismatch_min_mismatch ?? 1,
+ }
+ : undefined,
+ misprimingLibrary: input.mispriming_library ?? false,
+ },
+ position: {
+ searchRange: { from: searchFrom, to: searchTo },
+ exonJunctionSpan:
+ (input.exon_junction_span === "no_pref" || !input.exon_junction_span)
+ ? "none"
+ : (input.exon_junction_span as "none" | "flanking" | "spanning"),
+ intronInclusion: input.intron_inclusion ?? false,
+ intronSize:
+ input.intron_size_min != null || input.intron_size_max != null
+ ? { min: input.intron_size_min ?? 0, max: input.intron_size_max ?? 0 }
+ : undefined,
+ restrictionEnzymes: input.restriction_enzymes ?? [],
+ },
+ } as PrimerDesignRequest;
+};
+
+// Normalize genome and candidates for UI (tracks + start/end/color)
+const toUiResponse = (raw: PrimerDesignResponse): PrimerDesignResponseUI => {
+ const genome = raw.genome;
+ const length = genome.length_bp ?? genome.length ?? genome.sequence?.length ?? 0;
+
+ const baseTracks =
+ Array.isArray(genome.tracks) && genome.tracks.length
+ ? genome.tracks
+ .map((item, idx: number) => {
+ if (!isRecord(item)) return null;
+
+ const start = Number(item.start ?? item.start_bp ?? 0);
+ const end = Number(item.end ?? item.end_bp ?? start);
+ const label =
+ (typeof item.label === "string" && item.label) ||
+ (typeof item.name === "string" && item.name) ||
+ (typeof item.type === "string" && item.type) ||
+ `Track ${idx + 1}`;
+ const color =
+ (typeof item.color === "string" && item.color) ||
+ (item.type === "target_region" ? "#3b82f6" : "#94a3b8");
+
+ return {
+ id:
+ (typeof item.id === "string" && item.id) ||
+ (typeof item.type === "string" && item.type) ||
+ `track-${idx}`,
+ name: label,
+ features: [
+ {
+ id: (typeof item.id === "string" && item.id) || `feature-${idx}`,
+ start,
+ end,
+ label,
+ color,
+ },
+ ],
+ };
+ })
+ .filter((track): track is NonNullable => track != null)
+ : [];
+
+ const uiCandidates: UIPrimerCandidate[] = (raw.candidates || []).map((c, idx) => {
+ const isForward = c.strand === "forward";
+ return {
+ ...c,
+ start: Number(c.start_bp ?? 0),
+ end: Number(c.end_bp ?? 0),
+ label: c.id || `Primer ${idx + 1}`,
+ type: "primer",
+ color: isForward ? "#2196F3" : "#4CAF50",
+ metrics: c.metrics || { tm_c: 0, gc_percent: 0, penalties: { score: 0 } },
+ };
+ });
+
+ const primerTrack =
+ uiCandidates.length > 0
+ ? [
+ {
+ id: "primers",
+ name: "Primers",
+ features: uiCandidates.map((c, idx) => ({
+ id: c.id || `primer-${idx}`,
+ start: c.start ?? 0,
+ end: c.end ?? 0,
+ label: c.label,
+ color: c.color,
+ })),
+ },
+ ]
+ : [];
+
+ const starts = uiCandidates.map((c) => c.start ?? 0).filter((n) => Number.isFinite(n));
+ const ends = uiCandidates.map((c) => c.end ?? 0).filter((n) => Number.isFinite(n));
+ const ampTrack =
+ starts.length && ends.length
+ ? [
+ {
+ id: "amplicon",
+ name: "Amplicon",
+ features: [
+ {
+ id: "amplicon-1",
+ start: Math.min(...starts),
+ end: Math.max(...ends),
+ label: "Amplicon",
+ color: "#f97316",
+ },
+ ],
+ },
+ ]
+ : [];
+
+ const uiGenome: UIGenome = {
+ ...genome,
+ length_bp: genome.length_bp ?? length,
+ tracks: [...baseTracks, ...ampTrack, ...primerTrack],
+ };
+
+ return {
+ ...raw,
+ genome: uiGenome,
+ candidates: uiCandidates,
+ };
+};
+
+export const analyzeGenome = async (
+ input: AnalyzeRequestInput,
+): Promise => {
+ const templateSequence = resolveTemplateSequence(input);
+ const payload = toPrimerDesignRequest(input, templateSequence);
+
+ console.log("🚀 Sending Payload:", payload);
+
+ const response = await api.post("/design", payload);
+ const rawData = response.data;
+
+ const transformed = toUiResponse(rawData);
+ console.log("✅ Transformed Data for UI:", transformed);
+ return transformed;
+};
diff --git a/src/types/analysis.ts b/src/types/analysis.ts
new file mode 100644
index 0000000..a015292
--- /dev/null
+++ b/src/types/analysis.ts
@@ -0,0 +1,116 @@
+ // --- Official Spec Start ---
+export interface Range {
+ min: number;
+ max: number;
+}
+
+export interface PrimerDesignRequest {
+ // 1. Basic Input
+ basic: {
+ templateSequence: string;
+ targetOrganism: string;
+ productSize: Range;
+ primerTm: {
+ min: number;
+ opt: number;
+ max: number;
+ };
+ };
+
+ // 2. Primer Property
+ properties: {
+ gcContent: Range;
+ maxTmDifference: number;
+ gcClamp: boolean;
+ maxPolyX: number;
+ concentration: number;
+ };
+
+ // 3. Primer Specificity
+ specificity: {
+ checkEnabled: boolean;
+ spliceVariantHandling: boolean;
+ snpExclusion: boolean;
+ endMismatchStrictness?: {
+ regionSize: number;
+ minMismatch: number;
+ };
+ misprimingLibrary: boolean;
+ };
+
+ // 4. Primer Binding Position
+ position: {
+ searchRange: { from: number; to: number };
+ exonJunctionSpan: "none" | "flanking" | "spanning";
+ intronInclusion: boolean;
+ intronSize?: Range;
+ restrictionEnzymes: string[];
+ };
+}
+
+export interface GenomeSequence {
+ id: string;
+ name: string;
+ sequence: string;
+ length_bp: number;
+ // Some responses may provide total length as `length`; keep optional for compatibility.
+ length?: number;
+ // UI helper fields (optional)
+ tracks?: unknown[];
+}
+
+export interface PrimerCandidate {
+ id: string;
+ sequence: string;
+ start_bp: number;
+ end_bp: number;
+ strand: "forward" | "reverse";
+ metrics: {
+ tm_c?: number;
+ gc_percent?: number;
+ penalties?: unknown;
+ };
+}
+
+export interface PrimerDesignResponse {
+ // Optional analysis summary fields (used by UI in mock/preview modes).
+ result?: string;
+ score?: number;
+ details?: unknown;
+ genome: GenomeSequence;
+ candidates: PrimerCandidate[];
+ meta: {
+ params: PrimerDesignRequest;
+ timestamp: string;
+ execution_time_ms?: number;
+ };
+}
+// --- Official Spec End ---
+
+// UI helper types (not part of the spec but used client-side)
+export type UIPrimerCandidate = PrimerCandidate & {
+ start?: number;
+ end?: number;
+ label?: string;
+ color?: string;
+ type?: string;
+};
+
+export type UIGenome = GenomeSequence & {
+ tracks?: Array<{
+ id: string;
+ name?: string;
+ features: Array<{
+ id?: string;
+ start: number;
+ end: number;
+ label?: string;
+ color?: string;
+ }>;
+ }>;
+};
+
+export type PrimerDesignResponseUI = PrimerDesignResponse & {
+ genome: UIGenome;
+ candidates: UIPrimerCandidate[];
+};
diff --git a/src/lib/types/Genome.ts b/src/types/genome.ts
similarity index 94%
rename from src/lib/types/Genome.ts
rename to src/types/genome.ts
index 7e409e8..ca17b9e 100644
--- a/src/lib/types/Genome.ts
+++ b/src/types/genome.ts
@@ -4,7 +4,10 @@ export type GenomeFeature = {
id?: string;
start: number;
end: number;
+ start_bp?: number;
+ end_bp?: number;
label?: string;
+ name?: string;
color?: string;
};
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..b3deeb1
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,3 @@
+export * from "./genome";
+export * from "./analysis";
+export * from "./ui";
diff --git a/src/types/ui.ts b/src/types/ui.ts
new file mode 100644
index 0000000..60984dc
--- /dev/null
+++ b/src/types/ui.ts
@@ -0,0 +1,2 @@
+// Shared UI-related types can be added here.
+export {};
diff --git a/store/useViewStore.ts b/store/useViewStore.ts
index f4a7dbc..ec2d8e3 100644
--- a/store/useViewStore.ts
+++ b/store/useViewStore.ts
@@ -1,7 +1,7 @@
"use client";
import { create } from "zustand";
-import type { GenomeCanvasViewState } from "@/lib/types/Genome";
+import type { GenomeCanvasViewState } from "@/types";
type ViewStore = {
viewState: GenomeCanvasViewState;
diff --git a/tailwind.config.ts b/tailwind.config.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/visibleRange.test.ts b/tests/visibleRange.test.ts
new file mode 100644
index 0000000..eede6e9
--- /dev/null
+++ b/tests/visibleRange.test.ts
@@ -0,0 +1,29 @@
+import { describe, expect, it } from "vitest";
+
+import {
+ createPrefixSums,
+ findItemIndexByPosition,
+ getVisibleRange,
+} from "../src/lib/algorithms/visibleRange";
+
+describe("visibleRange smoke", () => {
+ it("calculates prefix sums and visible range", () => {
+ const heights = [10, 10, 10, 10, 10];
+ const prefix = createPrefixSums(heights);
+
+ expect(prefix).toEqual([0, 10, 20, 30, 40, 50]);
+ expect(findItemIndexByPosition(prefix, 0)).toBe(0);
+ expect(findItemIndexByPosition(prefix, 10)).toBe(1);
+ expect(findItemIndexByPosition(prefix, 100)).toBe(4);
+
+ expect(getVisibleRange(prefix, 15, 0, 0)).toEqual({
+ startIndex: 0,
+ endIndex: 1,
+ });
+
+ expect(getVisibleRange(prefix, 15, 1, 0)).toEqual({
+ startIndex: 0,
+ endIndex: 2,
+ });
+ });
+});
diff --git a/tsconfig.json b/tsconfig.json
index 7d5eedd..6452adb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "baseUrl": ".",
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..b2a8312
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ environment: "node",
+ include: ["tests/**/*.{test,spec}.{ts,tsx}"],
+ },
+});