본문으로 건너뛰기
5분 AI.
TRENDSHIFT WEEKLY #23 딥다이브 · 2026-10-23 분석

Jantt 딥다이브
— JSON 일정표가 움직이는 화면이 되기까지

라이브러리

빠른 판단

무엇
선행 작업이 있는 JSON 일정을 편집 가능한 간트 차트로 계산하는 TypeScript 엔진
이럴 때
프로젝트 일정을 웹에서 표시·조작하고 드래그 결과를 다시 일정 데이터로 저장할 때
입력
작업명·시작일·종료일·선행 관계를 담은 JSON
결과
간트 막대·연결선과 편집으로 갱신된 일정 데이터
업무 이름과 날짜를 JSON으로 적으면 간트 차트가 됩니다. 막대를 끌면 날짜가 바뀌고, 선행 작업이 늦어지면 후속 작업도 이동합니다. Jantt를 따라가며 데이터 계약, 그래프 알고리즘, 좌표 계산, React 연결, 협업 충돌 처리를 한 흐름으로 배웁니다. AhmadHassan-BTed/Jantt · ⭐3,571 · 포크 11 · TypeScript · MIT · 2026-10-23 GitHub 기준. 코드 설명은 커밋 77f78de 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

화면을 하나씩 그리는 대신, 일정 데이터를 선언하는 엔진

Jantt는 JSON으로 표현한 프로젝트 일정을 편집 가능한 간트 차트로 바꾸는 TypeScript 엔진입니다. 이름, 시작일, 종료일, 선행 작업을 입력하면 막대와 연결선을 계산하고, 사용자의 조작을 다시 일정 데이터로 돌려줍니다.

화면을 저장하는 대신 일정 데이터를 저장하면, 같은 계획을 여러 화면과 도구에서 다시 사용할 수 있습니다. 이 원리가 코어 라이브러리, React 래퍼, 일반 웹페이지용 번들, CLI를 하나로 묶습니다.

용어
간트 차트와 의존성
간트 차트는 가로축에 시간, 세로축에 작업을 놓는 일정표입니다. 의존성은 “디자인이 끝나야 구현을 시작한다” 같은 순서 제약입니다. 두 작업의 날짜가 우연히 이어지는 것과, 의존성으로 연결된 것은 다릅니다.
한 문장 비유

JSON은 악보이고, Jantt는 악보를 화면에서 연주하는 악기입니다

악보에 음표를 적는 사람은 사람이든 AI든 상관없습니다. 악기는 음표의 위치와 길이를 해석합니다. 마찬가지로 Jantt는 작업 데이터를 받아 날짜를 가로 위치로, 기간을 막대 길이로 바꿉니다.

여기서는 연주자가 막대를 움직이면 악보도 바뀝니다. 이때 바뀐 JSON을 파일에 저장할지, 서버에 보낼지는 Jantt를 사용하는 애플리케이션이 결정합니다.

1-1. 읽을 수 있는 최소 일정

다음은 examples/basic.json의 구조를 줄여 만든 학습용 예시입니다. progress는 0~1 사이의 비율이고, dependsOn은 선행 작업의 ID입니다.

{
  "meta": { "title": "작은 웹사이트", "defaultGapDays": 2 },
  "categories": {
    "web": { "label": "웹 개발", "color": "#3B82F6" }
  },
  "tasks": [
    {
      "id": "design", "label": "화면 설계", "category": "web",
      "start": "2026-10-01", "end": "2026-10-03", "progress": 1
    },
    {
      "id": "build", "label": "화면 구현", "category": "web",
      "start": "2026-10-02", "end": "2026-10-06",
      "dependsOn": "design", "progress": 0.25
    }
  ]
}

이 데이터에는 “설계가 끝나기 전에 구현이 시작된다”는 충돌이 있습니다. resolveSchedule()의 기본 간격 2일을 적용하면 구현 시작일은 10월 5일, 종료일은 10월 9일이 됩니다. 시작일과 종료일의 차이인 4일은 유지됩니다.

1-2. AI가 들어가는 자리는 어디인가

README의 “AI-native”는 모델이 엔진 안에서 추론한다는 뜻이 아닙니다. 외부 AI가 JSON을 만들고, 엔진이 검증·계산·표시를 맡는 사용 방식을 강조합니다. packages/core/src/validator.ts에는 오류 위치와 수정 제안이 있어 재생성 흐름에 활용할 수 있습니다.

학습 목표
작업 두 개로 시작해서 시스템의 경계를 이해하기

처음부터 협업 기능까지 읽을 필요는 없습니다. Task → validate() → resolveSchedule() → layout() 순서로 따라가면 데이터가 화면이 되는 핵심을 만납니다.

2왜 주목받는가

트렌딩 배경을 해석하되, 홍보 문구와 구현 근거를 구분하기

2-1. AI가 만들기 쉬운 입력 형식

TrendShift Weekly #23의 선정 대상이며, 2026년 8월 28일 생성 후 10월 23일 기준 스타 3,571개입니다. 이 수치는 관심의 크기를 보여주지만, 서비스 채택 수나 알고리즘의 정확성을 뜻하지는 않습니다.

주목 요인으로 읽을 수 있는 것은 AI가 일정 내용에 집중할 수 있는 데이터 계약입니다. 모델이 SVG 좌표와 이벤트 처리 코드를 매번 생성하는 대신, 정해진 JSON 필드만 채우게 합니다. 이는 소스 구조와 README의 제품 방향에 근거한 해석이며, 실제 유입 경로를 측정한 결과는 아닙니다.

AI가 만든 계획을 믿는 근거는 그럴듯한 화면이 아니라, 입력 검증과 명시적인 계산 규칙이어야 합니다. Jantt는 이 경계를 코드로 살펴보기에 좋은 규모의 사례입니다.

2-2. 한 데이터에서 여러 사용 방식으로

사용 방식진입점배울 수 있는 것
웹 엔진packages/core/src/renderer.tsDOM 요소에 엔진을 붙이고 인스턴스를 반환하는 API
Reactpackages/react/src/Jantt.tsx외부 엔진의 생성·갱신·정리를 React 수명주기에 연결
일반 웹페이지packages/standalone/src/standalone.ts전역 Jantt.mount()를 제공하는 어댑터
로컬 파일cli/bin/jantt.jsHTTP 요청을 JSON 파일 읽기·쓰기로 연결

2-3. 경쟁 도구와 비교할 때 볼 기준

아래 표는 입력 방식과 학습 목적의 비교입니다. 경쟁 제품의 기능·크기·가격을 일괄 평가한 표는 아닙니다. Jantt의 장점은 JSON 계약, 일정 계산, UI 연결을 같은 저장소에서 함께 읽을 수 있다는 점입니다.

도구공식 문서의 입력 방식Jantt를 공부할 이유
Mermaid Gantt텍스트 문법으로 작업·날짜·선후 관계를 기술데이터가 화면 편집을 거쳐 다시 JSON으로 나오는 흐름을 읽기 좋음
Frappe Gantt작업 배열을 new Gantt()에 전달표시뿐 아니라 자체 검증기·CPM·병합 로직까지 함께 분석 가능
DHTMLX Ganttparse() 또는 load()로 JSON/XML 수신작업 내부의 dependsOn과 순수 계산 함수를 중심으로 구조 추적 가능
직접 DOM/SVG 작성날짜·좌표·이벤트 처리를 직접 설계Jantt의 모듈 분리가 어떤 반복 작업을 줄이는지 비교 가능

입력 방식의 근거: Mermaid 공식 문서, Frappe Gantt 공식 문서, DHTMLX 공식 문서. DHTMLX도 JSON을 받으므로 “기존 도구는 JSON을 못 쓴다”는 비교는 맞지 않습니다.

2-4. README의 강한 표현을 읽는 법

README 주장커밋 77f78de에서 확인되는 범위
런타임 의존성 0packages/core/package.json에 fflate: ^0.8.3이 있으며 packages/core/src/share-coder.ts에서 실제 사용
Node.js 18 이상루트와 CLI의 engines.node는 >=20.0.0
토큰 10배 절감README의 주장. 비교 입력·모델·측정 절차가 제시된 재현 가능한 수치로 취급하기 어려움
gzip 14KB 미만README 기준 수치. 재현에 필요한 측정 명령과 출력 기록은 제시돼 있지 않음
DCMA-14 감사packages/core/src/schedule-health.ts는 “14개 항목에서 착안”한 검사이며 14개 전체 준수 인증을 뜻하지 않음

홍보 표현을 걷어내도 학습 가치는 남습니다. 작은 입력 모델을 실제 제품의 여러 경계에 연결하고, 문서와 구현의 차이까지 점검하는 연습을 할 수 있기 때문입니다.

3기술 스택 전체 지도

브라우저 코어 · 선택적 백엔드 · 패키징과 배포

3-1. 프론트엔드: 코어와 데모 앱을 구분하기

코어는 React 컴포넌트 트리가 아니라 TypeScript로 작성한 DOM/SVG 엔진입니다. React는 이를 감싸는 별도 패키지이며, Playground는 그 위에 편집기·노트·클라우드 UI를 얹은 앱입니다.

기술선언 범위 → 잠금 버전실제 역할·근거 파일
TypeScript^5.4.5 → 5.9.3루트 package.json, package-lock.json; 타입 검사와 소스 작성
React / React DOM^18.3.1 → 18.3.1Playground와 React 래퍼; 래퍼의 peer 범위는 각각 >=17.0.0
fflate^0.8.3 → 0.8.3코어의 URL 공유 데이터 압축·해제
lucide-react^0.378.0 → 0.378.0Playground 아이콘
Firebase SDK^12.18.0 → 12.18.0Playground의 인증·Realtime Database 연결
용어
선언 범위, 잠금 버전, peerDependency
^5.4.5는 설치를 허용하는 버전 범위이고, 잠금 파일의 5.9.3은 그 시점에 선택된 정확한 버전입니다. peerDependency는 “이 라이브러리를 쓰는 앱이 호환되는 React를 준비해야 한다”는 계약입니다.

3-2. 백엔드: 기본 렌더링에는 별도 서버가 필요 없다

구성코드 위치역할과 경계
CLI HTTP 서버cli/bin/jantt.jsNode 내장 http·fs 사용. 기본 포트 3456, JSON 파일 읽기와 저장
Playground 인증apps/playground/src/firebase/firebaseConfig.tsGitHub 인증 공급자와 Firebase 앱 초기화. VITE_FIREBASE_* 설정 사용
Playground 협업apps/playground/src/firebase/roomService.tsrunTransaction()으로 갱신, onValue()로 구독
코어의 클라우드 연결packages/core/src/firebase-sync.tsREST와 EventSource 사용. SDK 기반 Playground 경로와 별개
선택적 프록시worker/src/worker.jsCloudflare Worker의 방 API·외부 파일 프록시·요청 합치기

데이터베이스를 직접 운영하는 전통적인 API 서버가 중심인 구조는 아닙니다. 코어 렌더링은 브라우저에서 수행하고, 파일 저장·실시간 협업이 필요할 때 각각의 저장 경로를 연결합니다.

3-3. 인프라·빌드·검증 도구

기술선언 범위 → 잠금 버전역할
Vite^5.2.11 → 5.4.21패키지 번들 생성과 Playground 개발 서버
vite-plugin-dts^3.9.1 → 3.9.1코어·React 라이브러리의 타입 선언 생성
React Vite 플러그인^4.2.1 → 4.7.0apps/playground/vite.config.ts의 React 변환
Vitest^1.6.0 → 1.6.1코어의 vitest run 테스트
jsdom루트 ^24.1.0 → 24.1.0
코어 ^29.1.1 → 29.1.1
Node에서 DOM 테스트 환경 제공. 워크스페이스마다 선언이 다름

packages/core/tsconfig.json은 target: ES2020, strict: true, moduleResolution: bundler, noEmit: true입니다. 타입 검사와 실제 파일 출력의 책임을 나눠, 번들 출력은 Vite가 맡습니다.

의존성 파일을 읽을 때는 실행용 코드, 개발 도구, 사용 앱이 공급할 패키지를 나눠 봐야 합니다. “코어가 작다”와 “전체 앱이 외부 패키지를 쓰지 않는다”는 서로 다른 주장입니다.

3-4. 배포 설정이 설명하는 제품 형태

루트 lint 스크립트는 성공 문구를 출력하는 명령입니다. 실제 정적 분석을 수행하는 린터가 아닙니다. 보안 워크플로의 npm audit도 continue-on-error: true이므로 실패가 배포 차단을 보장하지 않습니다.

4아키텍처 심화 분석

입력 계약 → 일정 계산 → 좌표 → 사용자 조작 → 저장

4-1. 전체 시스템의 데이터 흐름

아래에서 검증은 입력을 받는 쪽이 명시적으로 호출하는 경계입니다. renderJantt()가 내부에서 항상 validate()를 호출한다고 가정하면 안 됩니다. 이 함수는 엔진을 만들고 mount()를 호출합니다.

[사람 / 외부 AI / JSON 파일] | v [JanttData: tasks, meta, categories] | [validate(): 호출자가 선택하는 입력 검증] | +---------+---------------------+ | | v v [React 래퍼 / standalone] [CLI: Node HTTP 서버] | | HTML·번들 제공 | v | [브라우저 standalone] | | +-------------+-----------------+ v [renderJantt / JanttRendererEngine] | [resolveSchedule: 일정 이동] | [calculateCriticalPath: 전체 그래프 분석] | [표시 작업 필터 → layout: 좌표 계산] | [DOM 작업 막대 + SVG 의존성 선] | [InteractionController + 제스처 전략] | [onChange / onCommit] | +-------------+------------------+ | | | [앱의 상태] [CLI 파일 저장] [Playground 협업] | [Firebase 트랜잭션 + 구독 + 병합] [선택적 Worker 프록시] ← 외부 파일 조회·별도 방 API * 모든 저장 경로가 Worker를 통과하는 구조는 아님

packages/core/src/rendering/jantt-engine.ts의 render()는 먼저 전체 작업의 CPM을 구합니다. 그 다음 표시할 작업을 필터링하고, 계산한 결과를 layout()에 넘깁니다. 검색 때문에 선행 작업이 숨겨져도, 분석의 기준 그래프는 전체 일정으로 유지하려는 설계입니다.

일정을 계산하는 코드와 화면에 보일 작업을 고르는 코드는 서로 다른 책임입니다. 화면에 보이는 데이터만으로 모든 분석을 다시 하면 필터가 계산 결과까지 바꿔 버릴 수 있습니다.

4-2. 설계 패턴 ① 파이프와 필터

packages/core/src/pipeline/schedule-pipeline.ts의 SchedulePipeline.createDefault()는 작업 배열을 단계별 처리기에 통과시킵니다. 각 단계는 IPipelineStage.process(input, context) 계약을 구현합니다.

작업 배열 | SanitizeStage 날짜 역전 보정, 자기 자신·없는 ID 의존성 제거 v | CycleGuardStage 순환 진단, 옵션에 따라 의존성 제거 v | CascadeResolveStage 선행 작업 종료일에 맞춰 후속 작업 이동 v | CriticalPathStage 선택 사항: CPM 결과를 문맥에 저장하고 표시 추가 v 처리된 작업 + diagnostics + context

검증과 보정의 목적은 다릅니다. packages/core/src/validator.ts의 validate()는 문제를 보고하지만, SanitizeStage는 종료일이 시작일보다 빠르면 종료일을 시작일로 바꿉니다. 잘못된 원본을 사용자에게 알릴지, 자동으로 보정할지는 앱의 정책입니다.

// 사용 예시: CPM 단계는 파이프라인 생성 시 포함한다.
const pipeline = SchedulePipeline.createDefault({ includeCriticalPath: true });
const result = pipeline.execute(plan.tasks, {
  defaultGapDays: 2,
  breakCycles: false
});
// result.tasks, result.diagnostics, result.context를 각각 확인한다.

CycleGuardStage.process()는 Kahn 방식으로 진입 차수가 0인 작업부터 방문합니다. 삭제되지 않은 작업만 대상으로 하며, 방문 수가 대상 작업 수보다 적으면 순환 진단을 남깁니다. 기본값 breakCycles: false는 진단만 남기며, 뒤 단계를 중단하지 않습니다. true일 때는 남은 진입 차수가 양수인 작업의 의존성을 비웁니다.

비유

검증기는 접수 서류의 오류 목록을 작성하는 직원이고, 보정 단계는 잘못 적힌 칸을 실제로 고치는 직원입니다. 고친 서류만 보면 원래 어떤 문제가 있었는지 모를 수 있으므로, 진단 기록도 함께 다뤄야 합니다.

연쇄 이동과 위상 정렬은 같은 알고리즘이 아니다

packages/core/src/resolver.ts의 resolveSchedule()는 CascadeResolveStage.process()에 위임합니다. 이 단계는 작업을 ID 맵으로 만들고 최대 MAX_PASSES = 24회 훑으며, 바뀐 작업이 없는 회차에서 멈춥니다.

// CascadeResolveStage의 규칙을 요약한 의사 코드
minStart = max(선행작업들의 end + 현재작업의 gapDays)
if (현재작업이 잠겨 있지 않고 start < minStart) {
  duration = end - start
  start = minStart
  end = start + duration
}

작업은 필요할 때 뒤로 밀리며, 선행 작업을 앞당겼다고 자동으로 앞으로 당겨지지는 않습니다. locked: true인 작업도 이동하지 않습니다. 잠금은 날짜 고정 정책이지, 모든 의존성 제약이 충족된다는 보장이 아닙니다.

문서 docs/algorithms.md에는 같은 카테고리 작업의 암묵적 순차 배치가 설명돼 있지만, 현재 연쇄 이동 단계는 명시적 dependsOn을 기준으로 움직입니다. 카테고리가 같다는 이유만으로 자동 순서를 기대하면 안 됩니다.

작업 배열이 의존성의 역순이고 연결이 매우 길면 여러 회차가 필요합니다. 24회 상한에 도달해도 별도 미수렴 진단을 추가하지 않으므로, 긴 연결망은 배열 순서와 제약 충족 여부를 별도로 검토할 대상입니다.

CPM: 앞으로 한 번, 뒤로 한 번

packages/core/src/cpm.ts의 calculateCriticalPath()는 선행·후속 작업 맵과 진입 차수를 만들고 위상 순서를 계산합니다. 앞에서 뒤로 가장 빠른 시작·종료일을, 뒤에서 앞으로 가장 늦게 허용되는 날짜를 구합니다.

// 코드의 계산 의미를 단순화한 식
ES = max(입력 start, 선행 작업의 EF + gap)
EF = ES + duration
LF = min(후속 작업의 LS - 후속 작업의 gap)
LS = LF - duration
TF = LS - ES

TF는 전체 완료 시점을 지키면서 미룰 수 있는 여유입니다. 구현은 tf <= 0 || tf === minTotalFloat를 중요 작업 판정에 사용합니다. 목표일 targetDate를 지정하면 음수 여유도 표현할 수 있습니다. CPM의 프로젝트 기본 간격은 0일로, 연쇄 이동의 기본 2일과 다릅니다. 렌더러도 CPM을 별도 간격 옵션 없이 호출합니다.

순환이 있으면 CPM은 위상 정렬에서 빠진 작업을 뒤에 덧붙여 계속 계산합니다. 이는 오류로 화면이 멈추는 것을 피하는 처리이며, 순환 그래프의 분석값이 올바르다는 뜻은 아닙니다.

4-3. 설계 패턴 ② 제스처 전략과 상태

packages/core/src/controller.ts의 InteractionController는 현재 드래그 상태와 선택된 작업을 관리합니다. 작업 이동·크기 변경·진행률 조절·의존성 연결·분할선 조절·영역 선택·화면 이동을 Map<DragMode, IGestureStrategy>에 등록한 일곱 전략으로 나눕니다.

packages/core/src/controller/contracts.ts는 전략에 onMove()와 onUp()을 요구합니다. MoveGestureStrategy.onMove()는 픽셀 차이를 날짜 차이로 바꾸고, onUp()은 실제 이동이 있었을 때 commitData()를 호출합니다.

// packages/core/src/controller/strategies/move-gesture.ts의 핵심 식
const deltaX = e.clientX - state.startX;
const deltaDays = Math.round(deltaX / ctx.dayWidth);

scheduleRender()는 이미 예약된 requestAnimationFrame이 있으면 추가 예약을 건너뜁니다. 많은 포인터 이벤트를 화면 갱신 시점에 모으는 기법입니다. 다만 날짜 계산·작업 검색 비용까지 사라지는 것은 아닙니다.

4-4. 설계 패턴 ③ React·일반 웹페이지 어댑터

packages/react/src/Jantt.tsx의 React 컴포넌트는 컨테이너와 엔진 인스턴스를 ref에 보관합니다. 첫 effect에서 renderJantt(), 갱신 effect에서 instance.update(), 정리 함수에서 destroy()를 호출합니다.

이벤트 콜백은 callbacksRef.current를 통해 전달합니다. 엔진을 처음 만들 때의 낡은 콜백이 이후에도 호출되는 오래된 클로저 문제를 피하려는 장치입니다. standalone은 같은 엔진을 Jantt.mount()로 노출합니다.

// React 연결 예시: 확정된 변경을 savePlan으로 전달한다.
import { Jantt } from "@jantt/react";
import type { JanttData } from "@jantt/core";
import "@jantt/core/theme.css";

type TimelineProps = {
  plan: JanttData;
  savePlan: (data: JanttData) => void;
};

function Timeline({ plan, savePlan }: TimelineProps) {
  return <Jantt data={plan} onCommit={savePlan} />;
}

코어의 export와 빌드 설정은 위 예시의 CSS 경로를 명시합니다. README 예제의 @jantt/react/dist/index.css는 React 패키지의 명시적 export에 없습니다. 실제 공개 API를 확인하는 습관이 복사한 예제의 실패를 줄입니다.

4-5. 설계 패턴 ④ 기준·로컬·원격의 병합

packages/core/src/reconciliation/plan-reconciler.ts의 reconcilePlans(base, local, remote)는 공통 기준과 두 변경본을 비교합니다. 작업 ID별 맵을 만들고, 필드 시각과 삭제 기록을 고려해 병합 데이터와 충돌 내역을 반환합니다.

필드 시각·작업 updatedAt이 없는 3-way 병합 예시 공통 기준: { label: "설계", progress: 0 } | | 로컬: label = "화면 설계" 원격: progress = 0.5 | | +------ 병합 후보 --------+ { label: "화면 설계", progress: 0.5 } 같은 필드가 동시에 바뀌면? 양쪽 시각이 있으면 시각·클라이언트 식별자로 LWW 비교 복합 시각까지 같거나 시각 없는 충돌은 기본적으로 원격 값 선택 삭제는? _deleted / deletedAt / meta.tombstones로 삭제 시각도 비교

packages/core/src/reconciliation/task-merger.ts의 PM_TASK_FIELDS가 병합할 필드 목록을 정합니다. 새 작업 속성을 추가할 때 타입 정의만 바꾸면 끝나지 않는 이유입니다. 저장·검증·병합·공유 경로가 같은 속성을 다루는지 함께 살펴야 합니다.

이 구현의 CRDT·LWW 설명을 “모든 변경을 손실 없이 보존한다”로 확대하면 안 됩니다. 같은 필드에서 한 값을 선택하는 정책은 다른 값을 덮어쓸 수 있으며, 필드별 시각·동률 처리·삭제 후 갱신까지 조건을 맞춰 검토해야 합니다.

5디렉토리 구조 해부

폴더 이름을 외우기보다, 책임이 넘어가는 경계를 따라가기
Jantt/ ├── package.json / package-lock.json 워크스페이스·버전 고정 ├── packages/ │ ├── core/ │ │ ├── src/ │ │ │ ├── types.ts / validator.ts 데이터 계약·검증 │ │ │ ├── date-math.ts / cpm.ts 날짜·그래프 분석 │ │ │ ├── resolver.ts / layout.ts 일정 이동·좌표 │ │ │ ├── pipeline/ 일정 처리 단계 │ │ │ ├── controller/strategies/ 제스처별 동작 │ │ │ ├── rendering/ / renderers/ 엔진 조율·DOM/SVG 출력 │ │ │ ├── reconciliation/ 시각·해시·병합 │ │ │ └── themes/ 테마 정의·관리 │ │ └── tests/ 알고리즘·DOM·협업 테스트 │ ├── react/src/ React 수명주기 어댑터 │ └── standalone/src/ 전역 API 진입점 ├── apps/playground/ │ ├── src/components/ 편집·보기·모달 UI │ ├── src/hooks/ 프로젝트 상태·저장·동기화 │ ├── src/services/ 질의·저장소·명령 버스 │ └── src/firebase/ 인증·방·접속 상태 ├── cli/bin/jantt.js 로컬 파일 HTTP 연결 ├── worker/ Cloudflare Worker ├── schema/jantt.schema.json JSON Schema ├── examples/ 정상·오류 입력 예제 ├── docs/ API·구조·알고리즘 문서 ├── .github/workflows/ 검증·보안·배포·공개 └── Dockerfile / docker-compose.yml 정적 앱 컨테이너

5-1. 초보자에게 권하는 읽기 순서

  1. examples/basic.json: 작업 ID와 dependsOn을 종이에 화살표로 그립니다.
  2. packages/core/src/types.ts: 필수·선택 속성과 데이터·뷰 옵션의 경계를 읽습니다.
  3. packages/core/src/validator.ts, packages/core/src/date-math.ts: 잘못된 데이터가 어떤 오류가 되는지 봅니다.
  4. packages/core/src/resolver.ts, packages/core/src/pipeline/stages/cascade-resolve-stage.ts: 날짜 이동 규칙을 추적합니다.
  5. packages/core/src/layout.ts, packages/core/src/renderers/task-bars.ts: 숫자 좌표가 DOM으로 바뀌는 경계를 찾습니다.
  6. packages/react/src/Jantt.tsx: 엔진의 생성·업데이트·해제 위치를 표시합니다.

apps/playground/src/App.tsx부터 시작하면 화면 옵션과 협업 상태를 동시에 만나기 쉽습니다. 핵심 모델을 먼저 익힌 뒤 앱으로 올라오면 각 hook이 어떤 책임을 받았는지 이해하기 편합니다.

5-2. 코드 규모를 읽는 기준

확장자·분류파일 수대략적인 물리 줄 수
TypeScript (.ts)151개24,305줄
TSX (.tsx)42개15,417줄
JavaScript (.js)2개654줄
CSS2개8,256줄
JSON23개8,249줄; 잠금 파일·스키마·예제 포함
테스트 코드23개6,028줄; 위 .ts 집계에 포함

커밋 77f78de의 텍스트를 확장자별로 센 값이며 빈 줄·주석·설정을 포함합니다. Git 내부 파일과 설치된 패키지는 제외합니다. TS·TSX·JS 합계는 40,376줄이고, 테스트를 다시 더하면 중복 집계가 됩니다. 줄 수는 구현 범위를 가늠하는 지표이며 품질 점수가 아닙니다.

6학습 포인트 (기술별)

기술 이름보다, 그 기술로 해결하는 문제를 익히기

6-1. TypeScript와 JSON Schema: 타입은 입력 검증을 대신하지 않는다

packages/core/src/types.ts의 Task는 컴파일할 때 사용하는 약속입니다. 반면 파일이나 네트워크에서 받은 JSON은 실행 중에 들어옵니다. validate(json: unknown)가 unknown을 받는 이유가 여기에 있습니다.

schema/jantt.schema.json은 Draft-07 형식의 별도 명세입니다. 코어 검증기는 이 파일을 범용 검증기로 실행하지 않고, 직접 작성한 조건문으로 데이터를 검사합니다. 따라서 타입·스키마·검증기는 관련돼 있지만 자동으로 완전히 일치하지는 않습니다.

// 외부 입력을 검증하는 사용 예시
const raw: unknown = JSON.parse(text);
const result = validate(raw);
if (!result.valid) {
  for (const error of result.errors) {
    console.log(error.path, error.code, error.suggestion);
  }
}

실습 아이디어: 필수 속성, 잘못된 날짜, 없는 선행 ID를 한 가지씩 넣고 어떤 오류 코드가 나와야 하는지 먼저 표로 작성합니다. “JSON 문법이 맞다”와 “일정 의미가 맞다”를 구분하는 훈련입니다.

6-2. 날짜 연산: 하루 차이와 달력 칸 수

packages/core/src/date-math.ts의 isValidISODate()는 형식 검사 후 Date.UTC()로 만든 연·월·일을 원래 값과 대조합니다. 이 왕복 비교가 2026-02-30처럼 모양만 날짜인 값을 걸러냅니다.

diffDays()와 addDays()는 UTC 기준으로 계산합니다. 다만 “오늘” 표시에 쓰는 getTodayISODate()는 로컬 날짜를 사용하므로, 달력 연산과 사용자에게 보여 줄 현재 날짜를 구분해서 읽어야 합니다.

같은 입력함수·용도값
10월 1일 → 3일diffDays(): 날짜 간 차이2일
10월 1일 → 3일taskDurationDays(): 양 끝 포함 달력 길이일반 작업이면 3일
같은 날짜layout(): 차이가 0인 작업이정표로 처리
10월 1일 → 3일CPM·CSV의 기간 계산diffDays 기준 2일

실습 아이디어: 같은 작업의 계산 기간과 표시 폭을 나란히 적습니다. 하루 차이를 무조건 오류로 판단하기 전에 각 함수가 사용하는 기간 정의를 확인합니다.

6-3. 그래프: 선행 작업을 빠르게 찾는 자료구조

packages/core/src/cpm.ts는 Map으로 작업·선행·후속 관계를 저장하고, Set으로 중요 작업 ID를 관리합니다. 배열을 매번 처음부터 찾는 방식과 ID 기반 조회 방식의 차이를 배울 수 있습니다.

Kahn 알고리즘 자체는 작업 수 V와 연결 수 E에 대해 선형 탐색을 목표로 합니다. 다만 여기서는 큐에 배열의 shift()를 쓰고, 중요 경로를 여러 갈래로 열거하는 코드도 있으므로 전체 함수의 모든 상황을 단순히 O(V + E)라고 단정하면 안 됩니다.

실습 아이디어: A·B가 끝나야 C를 시작하는 그래프에서 C의 진입 차수를 2로 적고, A와 B를 처리할 때 각각 1씩 줄여 봅니다. 이후 C → A를 추가했을 때 어떤 작업이 큐에 못 들어가는지 찾습니다.

6-4. DOM/SVG: 날짜가 픽셀이 되는 수학

packages/core/src/layout.ts의 일반 막대는 시작일까지의 날짜 차이에 dayWidth를 곱해 x를 구합니다. 종료일 칸까지 표시하려고 오른쪽 경계에는 하루를 더합니다. DOM 막대와 SVG 연결선은 계산된 TaskLayout과 DependencyLine을 소비합니다.

// 시작일 ≤ 종료일인 일반 막대의 좌표 관계를 단순화한 식
x = diffDays(chartStart, task.start) * dayWidth
right = (diffDays(chartStart, task.end) + 1) * dayWidth
width = max(right - x, 14)
centerY = rowIndex * rowHeight + rowHeight / 2

실습 아이디어: dayWidth = 10일 때 3칸짜리 막대 폭을 계산하고, 값을 20으로 바꿨을 때 날짜 데이터는 그대로인데 화면 폭만 두 배가 되는 이유를 설명합니다.

6-5. React와 상태: 변경 중·확정·정리의 경계

onChange는 변경 중 데이터를 전달할 수 있는 API이고, onCommit은 확정 저장 지점으로 사용할 수 있습니다. 실제 발생 시점은 제스처 전략과 호출 경로를 확인해야 합니다. 콜백 이름만으로 자동 저장을 기대하면 안 됩니다.

제스처 코드는 일부 작업 객체를 직접 수정합니다. 앱이 변경 객체를 React 상태로 돌려줄 때는 참조가 그대로라 갱신이 생략되지 않는지 살펴야 합니다. destroy()에서는 이벤트·예약된 프레임 같은 자원을 해제하는지도 읽습니다.

실습 아이디어: 콜백에 전달할 때 최신 함수 ref를 읽는 경우와, 첫 렌더의 함수만 캡처한 경우를 종이에 비교해 봅니다. 데이터 갱신과 엔진 재생성을 혼동하지 않는 것이 목표입니다.

6-6. 직렬화와 비용 지표: 숫자에도 전제가 있다

packages/core/src/share-coder.ts의 compressPlanToUrlPayload()는 정상 압축 경로에서 공유용 정리 → JSON → UTF-8 → DEFLATE → Base64URL 순서로 변환하며 z~ 접두어를 붙입니다. 압축 실패 시에는 접두어 없는 비압축 Base64URL로 대체할 수 있습니다. 압축은 내용을 숨기는 암호화가 아니므로 공유 링크의 접근 범위도 고려해야 합니다.

packages/core/src/evm.ts의 calculateEVM()는 예산·진행률로 계획 가치와 획득 가치를 계산합니다. 실제 비용이 없으면 예산과 진행률로 비용을 추정하고, 분모가 0인 SPI·CPI는 1로 둡니다. 따라서 보기 좋은 지표가 실제 비용 측정의 존재를 보장하지 않습니다.

입력값의 의미와 기본값을 설명할 수 있어야, 계산 결과도 올바르게 해석할 수 있습니다. 학습의 끝은 함수를 호출하는 데 있지 않고, 그 결과를 믿을 수 있는 조건을 말하는 데 있습니다.

7하드웨어 / 시스템 요구사항

실행 환경의 선언과 학습용 권장 조건을 나누어 보기
목적필요 환경근거·해석
일정 표시·편집DOM·SVG·Pointer Events 등 웹 API를 지원하는 브라우저코어 렌더러와 컨트롤러 사용 API 기준. 공식 브라우저 최저 버전표는 없음
개발·CLINode.js >=20.0.0, npm루트·CLI package.json; CI 대상은 20.x·22.x
정적 앱 배포정적 파일 서버 또는 GitHub PagesPlayground 빌드 산출물을 제공하는 구조
컨테이너Docker 환경; 기본 호스트 포트 5173Compose의 5173:80 기본 매핑. 환경변수로 변경 가능
실시간 협업Firebase 프로젝트·인증·데이터베이스 규칙·네트워크Playground의 SDK 기반 경로. 로컬 차트 계산과 별도 요구
선택적 프록시Cloudflare Workers 환경worker/wrangler.toml과 Worker 코드 기준

공식 최소 CPU·RAM·디스크 수치는 명시돼 있지 않습니다. 학습용으로는 일반적인 노트북과 RAM 8GB 이상을 출발점으로 제안할 수 있지만, 이는 프로젝트가 보장하는 최소 사양이나 성능 측정 결과가 아닙니다.

AI가 JSON을 생성하는 역할을 외부에 두므로 차트 엔진 자체에 GPU나 로컬 모델 가중치가 필요하지 않습니다. 사용하는 AI 도구의 비용·환경 요구사항은 별도로 판단합니다.

7-1. 데이터 크기를 결정하는 것은 작업 수만이 아니다

작업 수, 의존성 수, 표시 기간, 하루 픽셀 폭이 함께 영향을 줍니다. packages/core/src/layout.ts는 전체 기간과 작업에 맞는 좌표를 만들고, 렌더러는 본문 영역을 다시 구성합니다. 대량 데이터에서 항상 일정한 반응 속도를 보장하는 구조로 단정하기 어렵습니다.

성능을 검토할 때는 작업 수와 연결 수, 날짜 범위, 한 번의 조작 비용을 함께 기록해야 합니다. 100개의 독립 작업과 100개의 촘촘한 의존성 그래프는 같은 입력 크기로 취급하기 어렵습니다.

비유

도로 지도의 부담은 도시 수만으로 정해지지 않습니다. 도시 사이의 길이 얼마나 얽혔는지, 지도를 어느 배율로 펼쳤는지에 따라서도 읽고 그리는 비용이 달라집니다.

7-2. 저장 기능의 환경 한계

LocalStorageService는 저장 실패 시 메모리 맵으로 우회하도록 작성돼 있습니다. 메모리 저장은 페이지를 닫으면 사라질 수 있으므로, 저장 API 호출 성공과 영구 보존을 같은 의미로 보면 안 됩니다.

Worker는 JANTT_ROOMS 바인딩이 있으면 저장소를 우선 조회하고, 결과가 없으면 메모리 방을 찾습니다. 현재 wrangler.toml에는 그 바인딩 선언이 없습니다. 메모리 방이나 캐시는 서로 다른 인스턴스와 재시작을 넘어 영속성을 보장하지 않습니다.

8직접 해볼 수 있는 실습 과제

예상 결과를 먼저 적고, 함수의 계약과 비교하는 다섯 단계

아래 과제는 작은 학습용 데이터와 독립된 예제로 진행할 수 있습니다. 네트워크·인증이 필요한 기능은 뒤로 두고, 입력과 기대 결과를 명확히 정의하는 데 집중합니다.

정상 입력과 함께 경계 입력을 설명해 보면 시스템을 더 깊이 이해할 수 있습니다.

과제 1 · 입문

검증 오류를 사람이 읽는 진단표로 바꾸기 예상 30~45분

목표: packages/core/src/validator.ts의 검사 순서와 오류 구조를 이해합니다.

  1. 정상 작업 두 개를 만들고 ID·카테고리·시작일·종료일을 채웁니다.
  2. 두 번째 작업의 ID를 첫 번째와 같게 바꿉니다.
  3. 종료일을 시작일보다 앞당긴 경우와 없는 선행 ID를 참조한 경우를 각각 분리합니다.
  4. 각 입력의 path, code, 수정 방법을 한국어 표로 작성합니다.

완료 기준: DUPLICATE_TASK_ID, INVALID_DATE_RANGE, DANGLING_DEPENDENCY가 무엇을 가리키는지 입력 위치와 함께 설명합니다.

읽을 파일: packages/core/tests/validator.test.ts, examples/broken-missing-id.json, examples/broken-dangling-dependency.json.

과제 2 · 입문

날짜를 손으로 좌표로 바꾸기 예상 30분

목표: 그림 없이도 막대 위치와 폭을 계산할 수 있게 됩니다.

  1. 차트 시작일을 10월 1일, 작업을 10월 3~5일로 둡니다.
  2. dayWidth = 20일 때 x와 오른쪽 경계를 계산합니다.
  3. 폭을 40으로 바꾸고 위치·길이의 변화만 비교합니다.
  4. 시작일과 종료일이 같은 작업이 일반 막대가 되지 않는 이유를 찾습니다.

완료 기준: 첫 설정에서 x는 40, 오른쪽 경계는 100, 폭은 60px이라고 설명합니다. 이 값은 일반 막대 분기의 식을 적용한 결과입니다.

읽을 파일: packages/core/src/layout.ts, packages/core/tests/layout.test.ts.

과제 3 · 중급

두 검증기의 차이를 찾아 계약으로 만들기 예상 1~2시간

목표: 이름이 같은 기능도 검사 범위가 다를 수 있음을 확인합니다.

cli/bin/jantt.js의 validate 분기는 코어 검증기를 가져오지 않습니다. 날짜 정규식·ID·직접 자기 의존성 등을 별도로 검사합니다. 날짜의 실제 달력 유효성과 여러 작업을 도는 순환·없는 선행 ID 검사는 코어와 범위가 다릅니다.

입력코어 코드에서 예상되는 결과CLI 코드에서 예상되는 결과
없는 선행 ID의존성 오류다른 조건이 맞으면 이 문제를 놓침
2026-02-30실제 달력 날짜 검사에서 오류날짜 정규식 자체는 통과
A ↔ B 순환DFS로 순환 오류직접 자기 의존성 검사만으로는 놓침

완료 기준: 출력 추측으로 끝내지 않고, 각 차이를 만드는 조건문을 표시한 비교표를 만듭니다. 개선안에는 “단일 검증 계약 공유”와 “CLI 배포물에 코어를 어떻게 포함할지”를 함께 적습니다.

과제 4 · 중급

연쇄 이동과 잠금의 충돌 설명하기 예상 1~2시간

목표: 날짜 이동의 규칙과 예외를 구분합니다.

A는 1월 1~5일, B는 1~3일이고 A에 의존한다고 둡니다. 기본 간격 2일이면 B는 7~9일로 이동합니다. C도 A에 의존하지만 locked: true라면 원래 날짜를 유지합니다.

  1. B의 시작·종료일이 각각 며칠 바뀌었는지 적습니다.
  2. A의 종료일을 앞당겨도 B를 자동으로 당기지 않는 조건문을 찾습니다.
  3. 같은 카테고리지만 의존성이 없는 D를 추가하고 자동 이동 여부를 구분합니다.
  4. 역순 작업 배열과 긴 의존성 연결에서 24회 상한의 영향을 검토합니다.

완료 기준: packages/core/tests/schedule-pipeline.test.ts의 기대값과 비교하고, “이동하지 않는다”와 “제약을 만족한다”를 별도 판정하는 설명을 작성합니다.

과제 5 · 고급

네트워크 없이 협업 병합의 성질 검토하기 예상 반나절

목표: LWW 규칙이 어떤 충돌을 해결하고 무엇을 버릴 수 있는지 이해합니다.

  1. 기준 계획에서 로컬은 제목만, 원격은 진행률만 바꿉니다.
  2. 양쪽이 같은 필드를 바꾸고 서로 다른 fieldTimestamps를 붙입니다.
  3. 한쪽은 삭제하고 다른 쪽은 삭제 전·후 시각으로 수정하는 경우를 나눕니다.
  4. 로컬·원격 인자의 순서를 바꿔도 핵심 작업 값이 같은지 비교할 조건을 적습니다.

완료 기준: 다른 필드 변경, 같은 필드 충돌, 삭제 후 갱신의 세 경우를 각각 설명하고, 작업 데이터와 conflicts 보고서를 구분합니다. 시각이나 클라이언트 ID를 생략했을 때도 같은 성질을 기대할 수 있는지 검토합니다.

읽을 파일: packages/core/src/reconciliation/plan-reconciler.ts, packages/core/src/reconciliation/timestamps.ts, packages/core/tests/reconciler.test.ts.

9관련 기술 심화 학습 로드맵

6주 동안 데이터 모델에서 제품 경계까지 올라가는 순서

주당 3~5시간 정도의 학습 계획입니다. 시간은 학습용 제안이며, 매주 “어떤 입력을 받으며 어떤 출력을 보장하는가”를 자기 말로 설명하는 결과물을 남깁니다.

주차핵심 주제읽을 코드남길 결과물
1주JSON·TypeScript·실행 중 검증packages/core/src/types.ts, packages/core/src/validator.ts, 스키마정상·오류 입력 5개와 진단표
2주UTC·윤년·기간 정의packages/core/src/date-math.ts, 날짜 테스트월말·윤년·같은 날짜의 기대값 표
3주DAG·Kahn·DFS·CPMpackages/core/src/cpm.ts, packages/core/src/resolver.ts, 단계 코드작업 5개의 방문 순서·여유 시간 계산
4주좌표 변환·DOM·SVG·포인터packages/core/src/layout.ts, packages/core/src/renderers/, 이동 전략확대·이동 전후 좌표와 이벤트 흐름도
5주React effect·ref·어댑터packages/react/src/Jantt.tsx생성·갱신·해제 수명주기 표
6주LWW·삭제 기록·저장 경계packages/core/src/reconciliation/, packages/core/src/firebase-sync.ts, CLI충돌 시나리오와 배포 경계 검토서

표의 코어 계산 모듈은 packages/core/src/ 아래에 있습니다. 3주차에서 순환 탐지와 자동 보정의 차이를 설명하기 어렵다면, 작업 수를 줄여 종이 위에서 의존성 화살표를 다시 그리는 것이 좋습니다.

공부 순서는 데이터 계약에서 시작해 계산, 화면, 저장의 경계를 하나씩 넓히는 편이 이해하기 쉽습니다. 같은 기능을 여러 도구로 구현하는 것보다, 한 기능이 경계를 넘을 때 생기는 문제를 기록해 보세요.

9-1. 관심 분야에 따른 다음 갈림길

학습 확인
새 속성 하나를 끝까지 통과시킬 수 있는가

예를 들어 riskLevel을 추가한다고 가정해 보세요. 타입, 스키마, 검증, 편집 UI, 내보내기, 공유, 협업 병합 중 무엇을 바꿔야 하는지 목록을 만들 수 있다면 개별 함수뿐 아니라 시스템의 계약을 이해한 것입니다.

10핵심 키워드 사전

소스에서 다시 만났을 때 떠올릴 쉬운 설명
용어쉬운 설명
선언형그리는 명령을 순서대로 쓰기보다, 원하는 작업·날짜 상태를 데이터로 적는 방식
JSON SchemaJSON의 속성·자료형·필수 항목을 설명하는 규칙 문서. 참조한다고 검사까지 자동 실행되지는 않음
unknown아직 어떤 값인지 확인되지 않은 입력. 검사 없이 속성을 믿고 쓰지 않도록 하는 TypeScript 타입
DAG화살표 방향을 따라가도 출발점으로 되돌아오지 않는 그래프. 올바른 작업 선후 관계의 기본 모델
진입 차수작업으로 들어오는 선행 연결의 수. 0이면 앞서 처리할 연결이 남지 않았다는 뜻
위상 정렬선행 작업이 후속 작업보다 앞에 오도록 순서를 만드는 것. 순환이 있으면 전체 순서를 만들 수 없음
Kahn 알고리즘진입 차수가 0인 작업을 꺼내고, 연결된 후속 작업의 차수를 줄여 가는 위상 정렬 방식
DFS한 갈래를 깊게 따라간 뒤 돌아오는 탐색. 방문 중인 경로에 다시 들어오는지 확인해 순환을 찾을 수 있음
연쇄 이동선행 작업의 날짜 때문에 후속 작업도 함께 미루는 처리. Jantt에서는 반복 회차에 상한이 있음
CPM빠른 날짜와 늦게 허용되는 날짜를 계산해 전체 완료 시점을 좌우하는 작업을 찾는 방법
ES / EF제약을 고려한 가장 빠른 시작일·종료일
LS / LF설정된 완료 시점을 지키면서 허용되는 가장 늦은 시작일·종료일
Total Float작업을 미룰 수 있는 전체 여유. 음수면 기준 완료일에 대한 지연 신호
이정표일정의 중요한 시점을 나타내는 표시. 이 엔진은 날짜 차이가 0인 작업도 이정표로 그림
기준 일정현재 날짜와 비교하기 위해 남겨 둔 원래 계획. baseline으로 표현
전략 패턴공통 인터페이스 뒤에 이동·크기 변경 같은 서로 다른 동작을 바꿔 끼우는 설계
어댑터같은 엔진을 React나 전역 API처럼 서로 다른 사용 방식에 맞춰 연결하는 얇은 층
오래된 클로저이벤트 함수가 과거 렌더의 값만 계속 기억하는 문제. 최신 콜백 ref로 완화할 수 있음
CRDT / LWW복제 데이터의 수렴을 다루는 설계 / 충돌한 값 중 시각 등 순서 규칙으로 한 값을 선택하는 정책
tombstone항목이 삭제됐다는 기록. 오래된 복사본이 삭제된 항목을 무심코 되살리지 않게 도움
ETag / revision내용 버전 표식 / 변경 차수. 읽은 뒤 다른 변경이 있었는지 확인하는 재료
OCC저장 직전에 버전 충돌을 확인하는 낙관적 동시성 제어. 비교와 쓰기의 원자성도 따져야 함
SSE서버가 브라우저로 변경 이벤트를 이어 보내는 통신 방식. 코어의 EventSource 경로에 사용
EVM예산·계획 진행률·실제 진행률·비용을 비교해 일정과 비용 상태를 해석하는 방법
SPI / CPI획득 가치 ÷ 계획 가치 / 획득 가치 ÷ 실제 비용. 입력과 0 분모 처리 정책을 함께 봐야 하는 비율
Base64URL바이트를 URL에 넣기 좋은 문자로 바꾸는 인코딩. 압축이나 암호화 자체를 뜻하지 않음
UMD / IIFE브라우저 전역 사용 등을 위한 JavaScript 배포 형식. standalone 번들이 제공하는 선택지
모노레포코어·래퍼·앱·CLI처럼 관련된 여러 패키지를 한 저장소에서 관리하는 구조

11참고 링크

공식 설명을 읽고, 중요한 규칙은 같은 커밋의 구현과 대조하기

11-1. 프로젝트와 코드 근거

11-2. 관련 기술의 공식 학습 자료

11-3. 현재 상태 (GitHub 정보는 2026-10-23 기준)

항목상태
저장소AhmadHassan-BTed/Jantt; TrendShift Weekly #23 선정 대상
스타 / 포크3,571 / 11 — 2026-10-23 GitHub 기준
주 언어TypeScript
생성일2026-08-28
최근 push2026-10-22
최근 커밋77f78de · 2026-10-22 · Add Trendshift badges to README
최신 릴리스2026-10-23 GitHub 기준 없음. npm 공개 여부·태그 존재 여부까지 뜻하지는 않음
패키지 버전각 package.json은 1.0.0. 별도 표시 문자열은 코어 1.3.0, CLI 1.1.0, standalone 1.0.0으로 서로 다름
코드 규모.ts 24,305줄 + .tsx 15,417줄 + .js 654줄 = 40,376줄; 설정·테스트·주석·빈 줄 포함
테스트23개 파일, 6,028줄. README의 339/339 통과 표시는 독립적인 통과 판정과 구분해야 함
CINode 20.x·22.x의 빌드·타입 검사·테스트 구성. 워크플로 존재 자체가 현재 성공 상태를 증명하지는 않음
라이선스MIT. LICENSE에는 Ahmad Hassan과 Jantt 기여자의 2026년 저작권 표기
쓰기 전에 알아둘 한계
학습할 설계와 제품 도입 때 확인할 보장은 다릅니다

일정 정확성: 검증은 자동 호출을 가정하지 말고 입력 경계에서 적용해야 합니다. 순환 복구와 24회 연쇄 이동 상한은 올바른 일정의 수학적 보장을 대신하지 않습니다. 현재 날짜 계산은 달력일 기반이므로 영업일·휴일 규칙이 필요한 업무에 그대로 대입하지 마세요.

도구 간 차이: CLI 검증은 코어 검증보다 좁습니다. CLI의 정적 자산 경로도 저장소 루트 기준 packages/standalone/dist/를 가리키므로 독립 배포물에서 자산 포함 여부를 확인해야 합니다. “양방향” 저장은 화면 변경의 파일 쓰기이며, 외부 파일 변경을 감시하는 구현은 이 CLI에 없습니다.

저장·접근 경계: CLI 서버는 server.listen(PORT)에 호스트를 지정하지 않고, CORS도 모든 출처를 허용하며 저장 API에 별도 인증 검사가 없습니다. 기본 설정을 신뢰할 수 없는 네트워크에 그대로 노출하지 마세요. 클라우드 기능에는 기본 Firebase 프로젝트 설정도 있으므로 자신의 환경 구성을 확인해야 합니다.

협업·성능: 필드별 병합, 삭제 후 갱신, 저장소의 영속성은 각각 검토할 문제입니다. 토큰 절감률·번들 크기·대규모 동시 접속·보안 등급에 관한 README 표현은 운영 보증으로 읽지 마세요. 실제 입력 규모와 배포 환경에 맞는 검증이 필요합니다.