reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Start
Playground
Core Concepts
Core Concepts
Calendar
Calendar 개요
이벤트 편집
반복 일정
타임존
예약 · 가용성
AI 스트리밍
원격 소스
App Composition
Build & Operate
스킬
Release Notes
Reference
Hook Reference
Type Reference
Oopt-calendar
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

Core Concepts
  1. Docs
  2. /
  3. Core Concepts
  4. /
  5. Core Concepts

핵심 개념

CalendarSpec 5-맵 모델, 단일 변경 경로(applyPatch), 엔진/뷰 레이어 아키텍처

reopt design · Updated Jul 2, 2026

시작하기핵심 개념개요이벤트 편집반복 일정타임존예약 · 가용성AI 스트리밍원격 소스앱 조합스킬릴리즈 노트Hook 레퍼런스타입 레퍼런스

1. CalendarSpec — 5개 flat 맵

캘린더의 모든 상태는 CalendarSpec 하나에 담깁니다. spec은 5개의 id-keyed 맵(events, calendars, availability, eventTypes, bookings)에 기본 timeZone과 낙관적 동시성용 version을 더한 평평한 문서입니다. 중첩 트리가 아니라 각 축이 독립된 맵이라, RFC 6902 패치로 부분 편집하기에 이상적입니다.

맵축 / 역할엔티티 타입
events시간 축 — 그리드에 렌더되는 모든 이벤트CalendarEvent
calendars사이드바 그룹 + 색상Calendar
availability이름 붙은 주간 예약 스케줄AvailabilitySchedule
eventTypes예약 가능한 미팅 타입 카탈로그EventType
bookings예약(reservation) 레코드 큐Booking
tsx
import type { CalendarSpec } from "@reopt-ai/opt-calendar";

const spec: CalendarSpec = {
  timeZone: "Asia/Seoul",
  version: 0,
  // 시간 축 — 그리드에 렌더되는 모든 이벤트
  events: {
    lunch: {
      id: "lunch",
      title: "점심",
      start: "2026-07-01T12:00:00+09:00",
      end: "2026-07-01T13:00:00+09:00",
      calendarId: "work",
    },
  },
  // 사이드바 그룹 + 색상
  calendars: {
    work: { id: "work", name: "업무", color: "#3b82f6" },
  },
  // 예약(booking) 축 — 필요할 때만 채움
  availability: {},
  eventTypes: {},
  bookings: {},
};

왜 5개 맵인가 — `kind`-맵이 아닌 이유

에디터 문서와 달리 캘린더에는 단일 순서(ordering)가 없습니다. 그래서 하나의 kind-맵으로 합치지 않고 축마다 별도 맵을 둡니다. 덕분에 패치 경로가 self-describing(/events/{id}/start)이고, 읽는 쪽에서 런타임 switch 없이 곧바로 해당 맵을 참조할 수 있습니다.

Additive-only 불변식 (HARD): 필드를 제거하거나 용도를 바꾸지 않고, 최상위 맵 이름도 바꾸지 않습니다. 필수는 오직 id(이벤트는 title·start·end 추가)뿐이고, 나머지는 모두 하위 호환 기본값을 갖는 optional입니다. 모든 엔티티가 meta를 실어 통합이 스키마 bump를 강제하지 않습니다. 오래된 spec은 새 코드에서 그대로 적용되고, 새 spec은 옛 렌더러에서 우아하게 degrade합니다.

2. 단일 변경 경로 — applyPatch

spec은 오직 store.applyPatch(ops) 한 경로로만 바뀝니다. 드래그·리사이즈, 팝오버 편집, AI 스트림, 직접 편집이 모두 같은 함수로 흘러 하나의 undo 히스토리를 공유합니다. ops는 RFC 6902 JSON Patch(JsonPatchOp[])이며, store 내부에서 applyCalendarPatch가 불변(immutable)으로 적용합니다.

편집 원천커밋 방식
드래그 / 리사이즈pointerup에 useEventDrag / useEventResize가 단일 패치
팝오버 편집EventEditorPopover가 store.applyPatch
AI 스트림beginExternalStream → 여러 패치 → endExternalStream (하나의 undo)
직접 편집코드가 store.applyPatch(ops) 직접 호출
tsx
import {
  buildReplaceFieldPatch,
  createCalendarStore,
  createEmptyCalendarSpec,
} from "@reopt-ai/opt-calendar";

const store = createCalendarStore(createEmptyCalendarSpec("Asia/Seoul"));

// 빌더로 만든 패치 — 드래그/AI/직접 편집이 모두 쓰는 그 경로
store.applyPatch(buildReplaceFieldPatch("lunch", "title", "팀 점심"), {
  source: "edit", // 텔레메트리/디버깅용 자유 형식 태그
});

// 직접 JsonPatchOp[]를 넘겨도 동일 — 경로가 self-describing
store.applyPatch([
  { op: "replace", path: "/events/lunch/start", value: "2026-07-01T12:30:00+09:00" },
  { op: "replace", path: "/events/lunch/end", value: "2026-07-01T13:30:00+09:00" },
]);

// 마지막 변경으로 바뀐 "container/id" 키 집합
store.getLastChangedIds(); // ReadonlySet<string>: { "events/lunch" }

AI처럼 여러 패치를 연속으로 흘려보낼 때는 beginExternalStream()과 endExternalStream()으로 감쌉니다. 스트림 중 개별 applyPatch는 히스토리 기록을 건너뛰고, 스트림 전체가 하나의 undo 엔트리로 collapse됩니다.

tsx
// AI 패치 스트림 — 전체를 한 번의 undo로 collapse
store.beginExternalStream();
for (const op of patchOps) {
  store.applyPatch([op]); // 스트림 중에는 개별 히스토리 기록을 건너뜀
}
store.endExternalStream(); // 스냅샷을 단일 undo 엔트리로 커밋

store.undo(); // 스트림 전체를 한 번에 되돌림

드래그 도중 AI 패치가 도착하는 경우처럼 뷰를 강제 remount해야 하면 applyPatch(ops, { forceRender: true })로 엔티티 version을 올리고, getEntityVersions()가 반환한 "container/id" → n 맵을 React key에 섞어 remount를 유도합니다.

3. 2-레이어 아키텍처

패키지는 Core(AI/spec)와 View/integration 두 레이어로 나뉩니다. Core는 런타임 의존성이 0이며(ai·zod는 optional peer), View는 오직 CalendarStore 계약(store.applyPatch)을 통해서만 Core를 소비합니다.

text
core (zero-runtime-dep)
  spec/      CalendarSpec · applyCalendarPatch · diff · validate · store
  stream/    StreamCompiler · normalizeCalendarPatchSource (NDJSON)
  catalog    defineCalendarCatalog (엔티티 종류 + AI 프롬프트 + jsonSchema)
  ai/        useCalendarSuggestion (shadow draft, single-undo commit)

view / integration (CalendarStore 계약만 소비)
  engine/       recurrence 전개 · timezone 해석 · occurrence · booking slot
  views/        MonthView · WeekView · DayView · AgendaView · BookingView
  components/   Calendar · CalendarToolbar · EventEditorPopover · EventBlock
  interactions/ useEventDrag · useEventResize  →  store.applyPatch
  remote/ · hooks/ · lib/ · i18n/

engine이 단일 소유자. RRULE 전개와 타임존 해석은 engine/에만 존재합니다. 뷰는 expandEvents 같은 엔진 함수의 결과를 렌더할 뿐, occurrence·tz 계산을 중복 구현하지 않습니다. 반복 엔진(minimalRecurrenceEngine)과 타임존 어댑터(intlTimeZoneAdapter)는 주입 가능한 seam이라, 필요하면 full RFC 5545 rrule이나 Temporal polyfill로 교체할 수 있습니다.

4. Catalog

CalendarCatalog는 엔티티 종류(kind) 레지스트리로, AI 레이어를 구동합니다. 각 kind는 자신의 인스턴스가 어느 CalendarSpec 맵(container)에 들어가는지와 attrsSchema를 선언합니다. 카탈로그는 이로부터 AI 시스템 프롬프트와 구조화 출력용 JSON Schema를 생성합니다.

tsx
import {
  defineCalendarCatalog,
  defaultCalendarCatalog,
} from "@reopt-ai/opt-calendar";

// 기본 카탈로그: event · all-day-event · task · calendar ·
// availability · event-type · booking 종류를 이미 등록
const catalog = defaultCalendarCatalog;

const systemPrompt = catalog.prompt();  // AI 시스템 프롬프트 (NDJSON 프로토콜 포함)
const schema = catalog.jsonSchema();    // 구조화 출력용 JSON Schema
const errors = catalog.validateAttrs(store.getSpec()); // 종류별 필드 검증

// 커스텀 종류가 필요하면 직접 정의
const custom = defineCalendarCatalog({
  event: {
    kind: "event",
    container: "events",
    prompt: "A timed event. start/end are ISO datetimes WITH offset.",
    attrsSchema: {
      title: "string",
      start: "iso-datetime",
      end: "iso-datetime",
      status: { type: "string", default: "confirmed" },
    },
  },
});

필드 정의는 bare 타입이거나 기본값을 가진 객체입니다. getFieldType(def)와 getFieldDefault(def)로 두 형태를 균일하게 읽을 수 있습니다. Calendar에 catalog prop을 넘기지 않으면 defaultCalendarCatalog가 쓰입니다.

5. 다음 단계

이벤트 편집

CRUD 패치 빌더, 팝오버, 드래그·리사이즈, 단일 undo

반복 일정

RRULE 전개, occurrence, this/following/all 편집

타입 레퍼런스

CalendarSpec, CalendarEvent, Booking, EventType 정의

PreviousStart패키지 설치, styles.css import, 첫 Calendar 렌더링까지의 빠른 시작 가이드Start
Go to Start
NextCalendar 개요이벤트, 예약, 반복(RRULE), 타임존, 드래그·리사이즈, AI 스트리밍 draft를 한 엔진으로 다루는 opt-calendar 실전 가이드Calendar