라이브러리
77f78de 기준입니다.
Jantt는 JSON으로 표현한 프로젝트 일정을 편집 가능한 간트 차트로 바꾸는 TypeScript 엔진입니다. 이름, 시작일, 종료일, 선행 작업을 입력하면 막대와 연결선을 계산하고, 사용자의 조작을 다시 일정 데이터로 돌려줍니다.
화면을 저장하는 대신 일정 데이터를 저장하면, 같은 계획을 여러 화면과 도구에서 다시 사용할 수 있습니다. 이 원리가 코어 라이브러리, React 래퍼, 일반 웹페이지용 번들, CLI를 하나로 묶습니다.
악보에 음표를 적는 사람은 사람이든 AI든 상관없습니다. 악기는 음표의 위치와 길이를 해석합니다. 마찬가지로 Jantt는 작업 데이터를 받아 날짜를 가로 위치로, 기간을 막대 길이로 바꿉니다.
여기서는 연주자가 막대를 움직이면 악보도 바뀝니다. 이때 바뀐 JSON을 파일에 저장할지, 서버에 보낼지는 Jantt를 사용하는 애플리케이션이 결정합니다.
다음은 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일은 유지됩니다.
README의 “AI-native”는 모델이 엔진 안에서 추론한다는 뜻이 아닙니다.
외부 AI가 JSON을 만들고, 엔진이 검증·계산·표시를 맡는 사용 방식을 강조합니다.
packages/core/src/validator.ts에는 오류 위치와 수정 제안이 있어 재생성 흐름에 활용할 수 있습니다.
처음부터 협업 기능까지 읽을 필요는 없습니다. Task → validate() →
resolveSchedule() → layout() 순서로 따라가면 데이터가 화면이 되는 핵심을 만납니다.
TrendShift Weekly #23의 선정 대상이며, 2026년 8월 28일 생성 후 10월 23일 기준 스타 3,571개입니다. 이 수치는 관심의 크기를 보여주지만, 서비스 채택 수나 알고리즘의 정확성을 뜻하지는 않습니다.
주목 요인으로 읽을 수 있는 것은 AI가 일정 내용에 집중할 수 있는 데이터 계약입니다. 모델이 SVG 좌표와 이벤트 처리 코드를 매번 생성하는 대신, 정해진 JSON 필드만 채우게 합니다. 이는 소스 구조와 README의 제품 방향에 근거한 해석이며, 실제 유입 경로를 측정한 결과는 아닙니다.
AI가 만든 계획을 믿는 근거는 그럴듯한 화면이 아니라, 입력 검증과 명시적인 계산 규칙이어야 합니다. Jantt는 이 경계를 코드로 살펴보기에 좋은 규모의 사례입니다.
| 사용 방식 | 진입점 | 배울 수 있는 것 |
|---|---|---|
| 웹 엔진 | packages/core/src/renderer.ts | DOM 요소에 엔진을 붙이고 인스턴스를 반환하는 API |
| React | packages/react/src/Jantt.tsx | 외부 엔진의 생성·갱신·정리를 React 수명주기에 연결 |
| 일반 웹페이지 | packages/standalone/src/standalone.ts | 전역 Jantt.mount()를 제공하는 어댑터 |
| 로컬 파일 | cli/bin/jantt.js | HTTP 요청을 JSON 파일 읽기·쓰기로 연결 |
아래 표는 입력 방식과 학습 목적의 비교입니다. 경쟁 제품의 기능·크기·가격을 일괄 평가한 표는 아닙니다. Jantt의 장점은 JSON 계약, 일정 계산, UI 연결을 같은 저장소에서 함께 읽을 수 있다는 점입니다.
| 도구 | 공식 문서의 입력 방식 | Jantt를 공부할 이유 |
|---|---|---|
| Mermaid Gantt | 텍스트 문법으로 작업·날짜·선후 관계를 기술 | 데이터가 화면 편집을 거쳐 다시 JSON으로 나오는 흐름을 읽기 좋음 |
| Frappe Gantt | 작업 배열을 new Gantt()에 전달 | 표시뿐 아니라 자체 검증기·CPM·병합 로직까지 함께 분석 가능 |
| DHTMLX Gantt | parse() 또는 load()로 JSON/XML 수신 | 작업 내부의 dependsOn과 순수 계산 함수를 중심으로 구조 추적 가능 |
| 직접 DOM/SVG 작성 | 날짜·좌표·이벤트 처리를 직접 설계 | Jantt의 모듈 분리가 어떤 반복 작업을 줄이는지 비교 가능 |
입력 방식의 근거: Mermaid 공식 문서, Frappe Gantt 공식 문서, DHTMLX 공식 문서. DHTMLX도 JSON을 받으므로 “기존 도구는 JSON을 못 쓴다”는 비교는 맞지 않습니다.
| README 주장 | 커밋 77f78de에서 확인되는 범위 |
|---|---|
| 런타임 의존성 0 | packages/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개 전체 준수 인증을 뜻하지 않음 |
홍보 표현을 걷어내도 학습 가치는 남습니다. 작은 입력 모델을 실제 제품의 여러 경계에 연결하고, 문서와 구현의 차이까지 점검하는 연습을 할 수 있기 때문입니다.
코어는 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.1 | Playground와 React 래퍼; 래퍼의 peer 범위는 각각 >=17.0.0 |
| fflate | ^0.8.3 → 0.8.3 | 코어의 URL 공유 데이터 압축·해제 |
| lucide-react | ^0.378.0 → 0.378.0 | Playground 아이콘 |
| Firebase SDK | ^12.18.0 → 12.18.0 | Playground의 인증·Realtime Database 연결 |
^5.4.5는 설치를 허용하는 버전 범위이고,
잠금 파일의 5.9.3은 그 시점에 선택된 정확한 버전입니다.
peerDependency는 “이 라이브러리를 쓰는 앱이 호환되는 React를 준비해야 한다”는 계약입니다.| 구성 | 코드 위치 | 역할과 경계 |
|---|---|---|
| CLI HTTP 서버 | cli/bin/jantt.js | Node 내장 http·fs 사용. 기본 포트 3456, JSON 파일 읽기와 저장 |
| Playground 인증 | apps/playground/src/firebase/firebaseConfig.ts | GitHub 인증 공급자와 Firebase 앱 초기화. VITE_FIREBASE_* 설정 사용 |
| Playground 협업 | apps/playground/src/firebase/roomService.ts | runTransaction()으로 갱신, onValue()로 구독 |
| 코어의 클라우드 연결 | packages/core/src/firebase-sync.ts | REST와 EventSource 사용. SDK 기반 Playground 경로와 별개 |
| 선택적 프록시 | worker/src/worker.js | Cloudflare Worker의 방 API·외부 파일 프록시·요청 합치기 |
데이터베이스를 직접 운영하는 전통적인 API 서버가 중심인 구조는 아닙니다. 코어 렌더링은 브라우저에서 수행하고, 파일 저장·실시간 협업이 필요할 때 각각의 저장 경로를 연결합니다.
| 기술 | 선언 범위 → 잠금 버전 | 역할 |
|---|---|---|
| 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.0 | apps/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가 맡습니다.
의존성 파일을 읽을 때는 실행용 코드, 개발 도구, 사용 앱이 공급할 패키지를 나눠 봐야 합니다. “코어가 작다”와 “전체 앱이 외부 패키지를 쓰지 않는다”는 서로 다른 주장입니다.
packages/core/vite.config.ts: ES module과 CommonJS를 생성하고 CSS 출력명을 theme.css로 지정합니다.packages/react/vite.config.ts: React와 코어를 external로 두어 래퍼 번들에 중복 포함하지 않습니다.packages/standalone/vite.config.ts: UMD·IIFE 형식으로 만들며 전역 이름은 Jantt입니다..github/workflows/ci.yml: Node 20.x·22.x에서 설치 → 빌드 → 타입 검사 → 테스트 순서로 구성됩니다..github/workflows/deploy-pages.yml: apps/playground/dist를 GitHub Pages에 배포합니다.Dockerfile: node:20-alpine 빌더와 nginx:alpine 정적 서버를 분리합니다.루트 lint 스크립트는 성공 문구를 출력하는 명령입니다. 실제 정적 분석을 수행하는 린터가 아닙니다.
보안 워크플로의 npm audit도 continue-on-error: true이므로 실패가 배포 차단을 보장하지 않습니다.
아래에서 검증은 입력을 받는 쪽이 명시적으로 호출하는 경계입니다.
renderJantt()가 내부에서 항상 validate()를 호출한다고 가정하면 안 됩니다.
이 함수는 엔진을 만들고 mount()를 호출합니다.
packages/core/src/rendering/jantt-engine.ts의 render()는 먼저 전체 작업의 CPM을 구합니다.
그 다음 표시할 작업을 필터링하고, 계산한 결과를 layout()에 넘깁니다.
검색 때문에 선행 작업이 숨겨져도, 분석의 기준 그래프는 전체 일정으로 유지하려는 설계입니다.
일정을 계산하는 코드와 화면에 보일 작업을 고르는 코드는 서로 다른 책임입니다. 화면에 보이는 데이터만으로 모든 분석을 다시 하면 필터가 계산 결과까지 바꿔 버릴 수 있습니다.
packages/core/src/pipeline/schedule-pipeline.ts의
SchedulePipeline.createDefault()는 작업 배열을 단계별 처리기에 통과시킵니다.
각 단계는 IPipelineStage.process(input, 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회 상한에 도달해도 별도 미수렴 진단을 추가하지 않으므로, 긴 연결망은 배열 순서와 제약 충족 여부를 별도로 검토할 대상입니다.
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은 위상 정렬에서 빠진 작업을 뒤에 덧붙여 계속 계산합니다. 이는 오류로 화면이 멈추는 것을 피하는 처리이며, 순환 그래프의 분석값이 올바르다는 뜻은 아닙니다.
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이 있으면 추가 예약을 건너뜁니다.
많은 포인터 이벤트를 화면 갱신 시점에 모으는 기법입니다.
다만 날짜 계산·작업 검색 비용까지 사라지는 것은 아닙니다.
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를 확인하는 습관이 복사한 예제의 실패를 줄입니다.
packages/core/src/reconciliation/plan-reconciler.ts의
reconcilePlans(base, local, remote)는 공통 기준과 두 변경본을 비교합니다.
작업 ID별 맵을 만들고, 필드 시각과 삭제 기록을 고려해 병합 데이터와 충돌 내역을 반환합니다.
packages/core/src/reconciliation/task-merger.ts의 PM_TASK_FIELDS가 병합할 필드 목록을 정합니다.
새 작업 속성을 추가할 때 타입 정의만 바꾸면 끝나지 않는 이유입니다.
저장·검증·병합·공유 경로가 같은 속성을 다루는지 함께 살펴야 합니다.
이 구현의 CRDT·LWW 설명을 “모든 변경을 손실 없이 보존한다”로 확대하면 안 됩니다. 같은 필드에서 한 값을 선택하는 정책은 다른 값을 덮어쓸 수 있으며, 필드별 시각·동률 처리·삭제 후 갱신까지 조건을 맞춰 검토해야 합니다.
examples/basic.json: 작업 ID와 dependsOn을 종이에 화살표로 그립니다.packages/core/src/types.ts: 필수·선택 속성과 데이터·뷰 옵션의 경계를 읽습니다.packages/core/src/validator.ts, packages/core/src/date-math.ts: 잘못된 데이터가 어떤 오류가 되는지 봅니다.packages/core/src/resolver.ts, packages/core/src/pipeline/stages/cascade-resolve-stage.ts: 날짜 이동 규칙을 추적합니다.packages/core/src/layout.ts, packages/core/src/renderers/task-bars.ts: 숫자 좌표가 DOM으로 바뀌는 경계를 찾습니다.packages/react/src/Jantt.tsx: 엔진의 생성·업데이트·해제 위치를 표시합니다.apps/playground/src/App.tsx부터 시작하면 화면 옵션과 협업 상태를 동시에 만나기 쉽습니다.
핵심 모델을 먼저 익힌 뒤 앱으로 올라오면 각 hook이 어떤 책임을 받았는지 이해하기 편합니다.
| 확장자·분류 | 파일 수 | 대략적인 물리 줄 수 |
|---|---|---|
| TypeScript (.ts) | 151개 | 24,305줄 |
| TSX (.tsx) | 42개 | 15,417줄 |
| JavaScript (.js) | 2개 | 654줄 |
| CSS | 2개 | 8,256줄 |
| JSON | 23개 | 8,249줄; 잠금 파일·스키마·예제 포함 |
| 테스트 코드 | 23개 | 6,028줄; 위 .ts 집계에 포함 |
커밋 77f78de의 텍스트를 확장자별로 센 값이며 빈 줄·주석·설정을 포함합니다.
Git 내부 파일과 설치된 패키지는 제외합니다. TS·TSX·JS 합계는 40,376줄이고,
테스트를 다시 더하면 중복 집계가 됩니다. 줄 수는 구현 범위를 가늠하는 지표이며 품질 점수가 아닙니다.
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 문법이 맞다”와 “일정 의미가 맞다”를 구분하는 훈련입니다.
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일 |
실습 아이디어: 같은 작업의 계산 기간과 표시 폭을 나란히 적습니다. 하루 차이를 무조건 오류로 판단하기 전에 각 함수가 사용하는 기간 정의를 확인합니다.
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를 추가했을 때 어떤 작업이 큐에 못 들어가는지 찾습니다.
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으로 바꿨을 때 날짜 데이터는 그대로인데 화면 폭만 두 배가 되는 이유를 설명합니다.
onChange는 변경 중 데이터를 전달할 수 있는 API이고,
onCommit은 확정 저장 지점으로 사용할 수 있습니다.
실제 발생 시점은 제스처 전략과 호출 경로를 확인해야 합니다. 콜백 이름만으로 자동 저장을 기대하면 안 됩니다.
제스처 코드는 일부 작업 객체를 직접 수정합니다. 앱이 변경 객체를 React 상태로 돌려줄 때는
참조가 그대로라 갱신이 생략되지 않는지 살펴야 합니다.
destroy()에서는 이벤트·예약된 프레임 같은 자원을 해제하는지도 읽습니다.
실습 아이디어: 콜백에 전달할 때 최신 함수 ref를 읽는 경우와, 첫 렌더의 함수만 캡처한 경우를 종이에 비교해 봅니다. 데이터 갱신과 엔진 재생성을 혼동하지 않는 것이 목표입니다.
packages/core/src/share-coder.ts의 compressPlanToUrlPayload()는 정상 압축 경로에서 공유용 정리 → JSON → UTF-8 →
DEFLATE → Base64URL 순서로 변환하며 z~ 접두어를 붙입니다.
압축 실패 시에는 접두어 없는 비압축 Base64URL로 대체할 수 있습니다.
압축은 내용을 숨기는 암호화가 아니므로 공유 링크의 접근 범위도 고려해야 합니다.
packages/core/src/evm.ts의 calculateEVM()는 예산·진행률로 계획 가치와 획득 가치를 계산합니다.
실제 비용이 없으면 예산과 진행률로 비용을 추정하고, 분모가 0인 SPI·CPI는 1로 둡니다.
따라서 보기 좋은 지표가 실제 비용 측정의 존재를 보장하지 않습니다.
입력값의 의미와 기본값을 설명할 수 있어야, 계산 결과도 올바르게 해석할 수 있습니다. 학습의 끝은 함수를 호출하는 데 있지 않고, 그 결과를 믿을 수 있는 조건을 말하는 데 있습니다.
| 목적 | 필요 환경 | 근거·해석 |
|---|---|---|
| 일정 표시·편집 | DOM·SVG·Pointer Events 등 웹 API를 지원하는 브라우저 | 코어 렌더러와 컨트롤러 사용 API 기준. 공식 브라우저 최저 버전표는 없음 |
| 개발·CLI | Node.js >=20.0.0, npm | 루트·CLI package.json; CI 대상은 20.x·22.x |
| 정적 앱 배포 | 정적 파일 서버 또는 GitHub Pages | Playground 빌드 산출물을 제공하는 구조 |
| 컨테이너 | Docker 환경; 기본 호스트 포트 5173 | Compose의 5173:80 기본 매핑. 환경변수로 변경 가능 |
| 실시간 협업 | Firebase 프로젝트·인증·데이터베이스 규칙·네트워크 | Playground의 SDK 기반 경로. 로컬 차트 계산과 별도 요구 |
| 선택적 프록시 | Cloudflare Workers 환경 | worker/wrangler.toml과 Worker 코드 기준 |
공식 최소 CPU·RAM·디스크 수치는 명시돼 있지 않습니다. 학습용으로는 일반적인 노트북과 RAM 8GB 이상을 출발점으로 제안할 수 있지만, 이는 프로젝트가 보장하는 최소 사양이나 성능 측정 결과가 아닙니다.
AI가 JSON을 생성하는 역할을 외부에 두므로 차트 엔진 자체에 GPU나 로컬 모델 가중치가 필요하지 않습니다. 사용하는 AI 도구의 비용·환경 요구사항은 별도로 판단합니다.
작업 수, 의존성 수, 표시 기간, 하루 픽셀 폭이 함께 영향을 줍니다.
packages/core/src/layout.ts는 전체 기간과 작업에 맞는 좌표를 만들고,
렌더러는 본문 영역을 다시 구성합니다. 대량 데이터에서 항상 일정한 반응 속도를 보장하는 구조로 단정하기 어렵습니다.
성능을 검토할 때는 작업 수와 연결 수, 날짜 범위, 한 번의 조작 비용을 함께 기록해야 합니다. 100개의 독립 작업과 100개의 촘촘한 의존성 그래프는 같은 입력 크기로 취급하기 어렵습니다.
도로 지도의 부담은 도시 수만으로 정해지지 않습니다. 도시 사이의 길이 얼마나 얽혔는지, 지도를 어느 배율로 펼쳤는지에 따라서도 읽고 그리는 비용이 달라집니다.
LocalStorageService는 저장 실패 시 메모리 맵으로 우회하도록 작성돼 있습니다.
메모리 저장은 페이지를 닫으면 사라질 수 있으므로, 저장 API 호출 성공과 영구 보존을 같은 의미로 보면 안 됩니다.
Worker는 JANTT_ROOMS 바인딩이 있으면 저장소를 우선 조회하고, 결과가 없으면 메모리 방을 찾습니다.
현재 wrangler.toml에는 그 바인딩 선언이 없습니다.
메모리 방이나 캐시는 서로 다른 인스턴스와 재시작을 넘어 영속성을 보장하지 않습니다.
아래 과제는 작은 학습용 데이터와 독립된 예제로 진행할 수 있습니다. 네트워크·인증이 필요한 기능은 뒤로 두고, 입력과 기대 결과를 명확히 정의하는 데 집중합니다.
정상 입력과 함께 경계 입력을 설명해 보면 시스템을 더 깊이 이해할 수 있습니다.
목표: packages/core/src/validator.ts의 검사 순서와 오류 구조를 이해합니다.
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.
목표: 그림 없이도 막대 위치와 폭을 계산할 수 있게 됩니다.
dayWidth = 20일 때 x와 오른쪽 경계를 계산합니다.완료 기준: 첫 설정에서 x는 40, 오른쪽 경계는 100, 폭은 60px이라고 설명합니다. 이 값은 일반 막대 분기의 식을 적용한 결과입니다.
읽을 파일: packages/core/src/layout.ts, packages/core/tests/layout.test.ts.
목표: 이름이 같은 기능도 검사 범위가 다를 수 있음을 확인합니다.
cli/bin/jantt.js의 validate 분기는 코어 검증기를 가져오지 않습니다.
날짜 정규식·ID·직접 자기 의존성 등을 별도로 검사합니다. 날짜의 실제 달력 유효성과
여러 작업을 도는 순환·없는 선행 ID 검사는 코어와 범위가 다릅니다.
| 입력 | 코어 코드에서 예상되는 결과 | CLI 코드에서 예상되는 결과 |
|---|---|---|
| 없는 선행 ID | 의존성 오류 | 다른 조건이 맞으면 이 문제를 놓침 |
| 2026-02-30 | 실제 달력 날짜 검사에서 오류 | 날짜 정규식 자체는 통과 |
| A ↔ B 순환 | DFS로 순환 오류 | 직접 자기 의존성 검사만으로는 놓침 |
완료 기준: 출력 추측으로 끝내지 않고, 각 차이를 만드는 조건문을 표시한 비교표를 만듭니다. 개선안에는 “단일 검증 계약 공유”와 “CLI 배포물에 코어를 어떻게 포함할지”를 함께 적습니다.
목표: 날짜 이동의 규칙과 예외를 구분합니다.
A는 1월 1~5일, B는 1~3일이고 A에 의존한다고 둡니다. 기본 간격 2일이면 B는 7~9일로 이동합니다.
C도 A에 의존하지만 locked: true라면 원래 날짜를 유지합니다.
완료 기준: packages/core/tests/schedule-pipeline.test.ts의 기대값과 비교하고,
“이동하지 않는다”와 “제약을 만족한다”를 별도 판정하는 설명을 작성합니다.
목표: LWW 규칙이 어떤 충돌을 해결하고 무엇을 버릴 수 있는지 이해합니다.
fieldTimestamps를 붙입니다.완료 기준: 다른 필드 변경, 같은 필드 충돌, 삭제 후 갱신의 세 경우를 각각 설명하고,
작업 데이터와 conflicts 보고서를 구분합니다.
시각이나 클라이언트 ID를 생략했을 때도 같은 성질을 기대할 수 있는지 검토합니다.
읽을 파일: packages/core/src/reconciliation/plan-reconciler.ts,
packages/core/src/reconciliation/timestamps.ts,
packages/core/tests/reconciler.test.ts.
주당 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·CPM | packages/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주차에서 순환 탐지와 자동 보정의 차이를 설명하기 어렵다면,
작업 수를 줄여 종이 위에서 의존성 화살표를 다시 그리는 것이 좋습니다.
공부 순서는 데이터 계약에서 시작해 계산, 화면, 저장의 경계를 하나씩 넓히는 편이 이해하기 쉽습니다. 같은 기능을 여러 도구로 구현하는 것보다, 한 기능이 경계를 넘을 때 생기는 문제를 기록해 보세요.
예를 들어 riskLevel을 추가한다고 가정해 보세요.
타입, 스키마, 검증, 편집 UI, 내보내기, 공유, 협업 병합 중 무엇을 바꿔야 하는지 목록을 만들 수 있다면
개별 함수뿐 아니라 시스템의 계약을 이해한 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 선언형 | 그리는 명령을 순서대로 쓰기보다, 원하는 작업·날짜 상태를 데이터로 적는 방식 |
| JSON Schema | JSON의 속성·자료형·필수 항목을 설명하는 규칙 문서. 참조한다고 검사까지 자동 실행되지는 않음 |
| 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처럼 관련된 여러 패키지를 한 저장소에서 관리하는 구조 |
| 항목 | 상태 |
|---|---|
| 저장소 | AhmadHassan-BTed/Jantt; TrendShift Weekly #23 선정 대상 |
| 스타 / 포크 | 3,571 / 11 — 2026-10-23 GitHub 기준 |
| 주 언어 | TypeScript |
| 생성일 | 2026-08-28 |
| 최근 push | 2026-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 통과 표시는 독립적인 통과 판정과 구분해야 함 |
| CI | Node 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 표현은 운영 보증으로 읽지 마세요. 실제 입력 규모와 배포 환경에 맞는 검증이 필요합니다.