크라우니 스티커 — macOS 스티커(Stickies) 대용 프로그램
개요
사용자 요청: "맥용 스티커 프로그램 대용 프로그램 만들기, 색상 더 고를 수 있게, 꺼졌다 켜져도 멀티 스크린 위치에 다시 배치되도록."
macOS 기본 스티커를 대체하는 네이티브 데일리드라이버 앱을 제작. 3대 요구사항(떠다니는 메모 / 확장 색상 / 멀티스크린 위치 영속) 모두 구현·검증 완료.
분별 판정
- 신규 기능(티) + GUI 플랫폼락 → 재구축 독트린 #4(플랫폼락=더블 작성) 적용.
crownyc_window.m은 VM이 단일 프레임버퍼를 그리는 단일 캔버스 → 메모마다 독립NSWindow가 필요한 스티커는 네이티브 멀티윈도우 작업.- 결정: 네이티브 Swift/AppKit 데일리드라이버 + 한선씨 동반(위치해결/클램프 로직).
무엇을 했는지
1. 네이티브 앱 (CrownySticky.swift)
- 메모 1개 = borderless
NSWindow(StickyWindow,canBecomeKey/Mainoverride). 헤더 드래그 이동, 가장자리 리사이즈. - 16색 팔레트 메뉴 + "사용자 지정 색…"(시스템 색상패널). 배경 밝기(luminance>0.55)로 글자색 자동 대비.
- 메뉴: ⌘N 새 메모, ⌘W 닫기(내용 있으면 삭제 확인), 편집(undo/redo/cut/copy/paste/selectAll).
2. 멀티스크린 위치 영속 (핵심)
- 저장 모델(NoteData):
text · rgba · displayUUID · offset(x,y) · size(w,h)→~/Library/Application Support/CrownySticky/notes.json. - displayUUID =
CGDisplayCreateUUIDFromDisplayID(재연결해도 유지). 오프셋은 그 화면 원점 기준 상대좌표. - 복원(resolvePlacement): 디스플레이 연결됨→그 화면 복원 / 사라짐→메인 폴백. 두 경우 모두
visibleFrame클램프. - 사라진 모니터 메모는 원래 UUID 보존(무상호작용 재실행 시 미저장) → 모니터 복귀 시 제자리.
didChangeScreenParameters로 화면밖 메모 자동 재클램프.- 저장 트리거: 이동/리사이즈/입력(0.4s 디바운스)·종료·화면변경.
3. 한선씨 동반 (CrownySticky.한선)
클램프+위치해결순수 로직. crownyc 컴파일·실행 검증(4사례: 정상/넘침클램프/외부모니터복원/사라짐폴백) 통과.- 학습 등록: intent
스티커_멀티스크린_위치해결_클램프.
검증 결과 (라이브 실측)
| 항목 | 결과 |
|---|---|
| 색상 영속 | 노랑 r=1,g=0.96,b=0.62 정확 라운드트립 |
| 색 피드백 루프 | 해결(applyColor 폭증 0). NSColorWell 배타활성으로 차단 |
| 메모 A(실 UUID, off 300,300) | 실제 window.frame {{300,300},{260,200}} 정확 복원 |
| 메모 B(가짜 UUID DEAD, off 99999) | 메인 폴백 + 클램프 {{1620,809},{300,240}} (maxX=1920-300, maxY=1049-240) |
| 한선씨 동반 | 4/4 사례 기대값 일치 |
관련 파일
/Users/ef/crowny-sticky/CrownySticky.swift— 네이티브 앱/Users/ef/crowny-sticky/CrownySticky.한선— 동반 위치해결 로직/Users/ef/crowny-sticky/build.sh— 빌드/Users/ef/crowny-sticky/README.md- 산출 앱:
/Users/ef/crowny-sticky/크라우니스티커.app - 저장소:
~/Library/Application Support/CrownySticky/notes.json
함정 (재사용 지식)
- 색상 피드백 루프: borderless 창 + 전역
NSColorPanel액션 가로채기 시,textView.textColor설정이 패널에 동기화→액션 재발火→무한루프(배경이 텍스트색으로 덮임). 해결=NSColorWell.activate(true)배타 활성화. - AX 좌표 측정 아티팩트: borderless 창은 System Events
position이 실제window.frame과 다르게 보고됨 → 검증은NSStringFromRect(window.frame)직접 로깅으로. - borderless 창 텍스트 편집엔
canBecomeKey/Mainoverride 필수.
2차 추가 기능 (2026-06-25, 동일 세션)
사용자 요청: "커맨드 플러스 마이너스로 폰트와 상단바까지 크기 조절. 첫 줄 작성하고 엔터치면 제목. 스티커끼리 연계성 연결."
- 폰트 + 상단바 크기 조절(⌘+/⌘-) —
setFontSize로 9~48pt.headerHeight = max(22, fontSize+12)로 상단바·버튼·제목 글자 동반 스케일. 메뉴글자/상단바 크게(⌘+, ⌘= 대체) / 작게(⌘-). 포커스된 메모(keyNote)에 적용. - 첫 줄 = 제목 —
firstLine()이 본문 첫 줄을 헤더 제목 라벨에 굵게 표시,textDidChange로 실시간. 첫 줄은 본문에 그대로 남고 제목으로 미러(Apple Notes 모델). - 스티커 연결(연계성) —
links: Set<String>. 헤더 🔗 버튼 메뉴로 토글, 양방향(toggleLink이 상대 메모 links도 갱신). 제목 옆🔗N배지.revealLinks로 연결 메모 앞으로+파란 테두리flash(). 삭제 시 타 메모 links에서 자동 제거. NoteData에fontSize?·links?옵셔널 추가(구버전 json 호환).
검증 — --selftest (UI 무의존 셀프테스트, ALL PASS)
- 첫줄→제목 '제목A' / 폰트 14→18 / 상단바 26→30 / A→B·양방향 B→A / 인코드·디코드 왕복.
- 라이브 ⌘+ 자동화 검증은 System Events Accessibility 권한·borderless AX 좌표 한계로 미수행 → 동일 코드경로를 셀프테스트로 대체 검증.
잔여 이슈
- 코드 서명은 ad-hoc(로컬 전용). 배포 시 Developer ID 서명·공증 필요.
- 멀티 디스플레이 실기기 2화면 동시 테스트는 단일화면 환경에서 가짜 UUID 폴백으로 대체 검증(로직 동일).
- 추후: 항상 위(float) 토글, 메뉴바 아이콘. (폰트 크기 조절은 2차에서 구현 완료)