import { Command, EditorState } from './types'; export type StackListener = () => void; /** 명령 적용 방향 — op emit 어댑터가 forward(do/redo) vs invert(undo) op를 고르는 축. */ export type CommandDirection = 'do' | 'undo' | 'redo'; /** * 명령 적용 알림 구독자(트랙 B). `onChange`(상태 변경 무인자 알림)와 직교 — * 이쪽은 *어떤 명령이 어느 방향으로* 적용됐는지(+coalesce 병합 여부)를 전달해 * op emit 어댑터가 emitOps로 op 배치를 파생하게 한다. */ export type CommandListener = (cmd: Command, dir: CommandDirection, coalesced: boolean) => void; /** * 명령 스택 — 레거시 ActionManager 대응. * execute/undo/redo와 savePoint 기반 dirty 추적을 제공한다. */ export declare class CommandStack { private readonly state; private undoStack; private redoStack; private savePoint; private listeners; private commandListeners; private coalesceSealed; constructor(state: EditorState); execute(cmd: Command): void; undo(): void; redo(): void; /** * 다음 execute의 coalesce 병합을 1회 차단한다(op 어댑터의 idle flush·강제 flush 시 호출). * 어댑터가 coalesce 그룹을 영속에 내보낸 경계 이후의 동일 키 명령이 같은 undo 단위로 * 빨려 들어가면 undo 시 로컬·서버 상태가 어긋나므로, 봉인으로 새 단위를 강제한다. */ sealCoalesce(): void; get canUndo(): boolean; get canRedo(): boolean; /** * 저장 시점 표시 — 이후 dirty 판정 기준. * * ★저장 경계는 곧 **undo 단위 경계**여야 한다. 봉인하지 않으면 직후의 동일 `coalesceKey` 명령이 * savePoint가 가리키는 스택 top을 **슬롯째 교체**해(execute의 coalesce 분기) savePoint 객체가 * 스택에서 사라진다 — 그러면 `isDirty`가 다시 false가 될 수 없어 **영구 dirty로 고착**된다. * 플래그 문제가 아니라 경계 문제다: 병합된 단위의 invert는 그룹 시작으로 되돌아가므로 저장 지점 * 상태 자체가 undo로 **도달 불가**해진다. 근거는 op 어댑터의 flush 봉인과 동일하다(영속 경계 * 이후 동일 키는 새 단위). op-mode에서는 `controller.markSaved`가 `adapter.flush()`로 이미 * 봉인하지만, 그건 전송 경계라는 다른 이유의 우연한 커버라 여기서 구조적으로 닫는다. */ markSavePoint(): void; /** 마지막 저장 이후 변경 여부 (undo로 저장 지점에 정확히 돌아오면 clean) */ get isDirty(): boolean; /** 변경 알림 구독 — 반환 함수로 해제 */ onChange(listener: StackListener): () => void; /** * 명령 적용 알림 구독(트랙 B op emit). apply/invert 적용 *직후* 발화하므로 * 구독자가 emitOps()를 호출하면 memento가 채워진 상태를 본다(계약 1). 반환 함수로 해제. */ onCommand(listener: CommandListener): () => void; private emit; private emitCommand; }