Lightweight Electron desktop wallpaper calendar with dynamic click-through.
- 개발자 홈페이지(매뉴얼 및 개발 이력): https://note4all.tistory.com
Windows
macOS
origin은 GitLab입니다. git push하면 GitLab과 GitHub에 함께 올라갑니다.
Copyright (C) 2026 Neo Desktop Calendar
GNU Affero General Public License v3.0 (AGPL-3.0-only) — see LICENSE.
소스·바이너리 재배포·개작 시 AGPL-3.0 의무(소스 제공 등)가 적용됩니다.
네트워크(내장 웹 서버)로 수정본을 제공할 때도 AGPL §13 소스 제공 의무가 적용될 수 있습니다.
제3자 구성 요소 고지: legal/THIRD_PARTY_NOTICES.md
- Electron (main)
- React + TypeScript (renderer)
- Tailwind CSS v4
- IPC click-through bridge (
set-ignore-mouse)
- Frameless window; Windows can embed under desktop icons via WorkerW
SetParent(바탕화면 모드) - macOS runs as a normal app window; data stays in Application Support when you replace the
.app - Empty space clicks pass through to the OS desktop (Windows click-through)
- Interactive controls (nav, events, add) capture mouse on hover (
.interaction-ui) - Narrow / tablet settings: top chips you can drag sideways; 내·고정 캘린더는 본문 목록에서 다룸
- 세로보기 상단 기간 줄은 창이 좁으면 옆으로 밀어 쓸 수 있음
- Settings → 회원 관리 (super_admin): members, optional 3-fail/5-min lockout, login audit (CSV)
- Settings → 서버 관리 (super_admin): HTTP(S) port, Local/Web start·stop, HTTPS/TLS (
data/tls), firewall inbound - Month toolbar
[-]/[+]event density scales main bars and quick-edit list titles together - Calendar / backup ZIP import·export via bundled
7za(UTF-8 filenames,-mcu=on)
npm install
cp .env.example .env # Windows: copy .env.example .env
npm run dev
npm run dev:restart
npm run stopElectron 앱(npm run dev)이 실행 중일 때 HTTP API(기본 :3010)와 Vite UI(:5173)가 함께 뜹니다.
- 터미널 1:
npm run dev(또는npm run dev:restart) - 터미널 2:
npm run browser:dev— 준비되면 브라우저를 엽니다
또는 직접 http://127.0.0.1:5173/ 접속
- UI는 Vite(5173) 에서 제공하고,
/api·/ws는 Vite가 CalendarWebServer(3010) 로 프록시합니다. :3010만 열면 HTTP일 때 Vite(5173)로 리다이렉트됩니다. HTTPS를 켠 뒤에는:3010이 TLS로 직접 응답합니다..env에PORT=3010이 있어야 합니다 (.env.example참고).
Admin login credentials come from .env (MYCALENDAR_ADMIN_ID / MYCALENDAR_ADMIN_PW), with NEOCALENDAR_* / ADMIN_* aliases and built-in defaults as fallback.
| Role | Can do |
|---|---|
super_admin (총괄관리자) |
Full calendar store, members (optional 3-fail/5-min lockout, login audit), security/IP, 서버 관리, holiday API sync, ZIP backup/import |
member (일반사용자) |
Own calendars & events, tags, account password change, Excel/PDF/HTML export of own data, view holidays |
| Guest (logged out) | Empty store; must log in to edit |
- Session role is loaded from
data/members.jsonon login/restore. - Settings → 내 계정 is available to every logged-in user for password change.
- Admin-only Settings tabs (보안·서버 관리·회원·공휴일 동기화·전체 가져오기/내보내기) and matching IPC/HTTP APIs reject non-admins with “권한이 없습니다.”
- Agent rule:
.cursor/rules/neo-permissions.mdc
npm run build
npm run distnpm run build:releaseElectron을 한 번만 빌드한 뒤 MSI와 portable zip을 같은 YYMMDD_HHMMSS로 만듭니다
(업데이트 확인 — 같은 버전에서도 Windows 설치본끼리 빌드 스탬프를 비교할 때 사용).
개별 빌드:
npm run build:msi
npm run build:portable사전 요구: WiX CLI 7+ (winget install WiXToolset.WiXCLI) 후 wix eula accept wix7
포터블 zip은 7-Zip 필요.
대한민국 공휴일은 커밋된 src/shared/seed/holidays-kr.json(현재 3년치)을 그대로 번들합니다.
빌드는 이 파일을 갱신하지 않습니다. 갱신이 필요할 때만 npm run seed:holidays를 직접 실행하면
실행 연도부터 3년치를 API에서 받아 다시 굽습니다. 이때만 .env(또는 환경 변수)의
DATA_GO_KR_SERVICE_KEY를 사용하며, API 키는 설치본과 앱 설정에 저장되지 않습니다 —
사용자는 휴일 데이터를 키 없이 그대로 받고, 최신화가 필요하면 설정에서 본인 키로 동기화합니다
(「저장」을 직접 체크한 경우에만 그 키가 설정에 남습니다).
→ msi/Neo Desktop Calendar v{버전}_YYMMDD_HHMMSS.msi (현재 사용자 설치, 관리자 권한 불필요)
→ msi/Neo Desktop Calendar v{버전}_YYMMDD_HHMMSS_portable.zip
설치 마법사에서 설치 폴더 선택 가능 (WixUI_InstallDir).
캘린더 데이터는 설치/포터블 폴더의 data/, Electron 캐시는 .neo-desktop-calendar/electron-profile에 둡니다 (%APPDATA%에는 쓰지 않습니다).
MSI에는 Electron 런타임이 포함됩니다 (Neo Desktop Calendar.exe + resources/app.asar + DLL). 별도 Electron 설치 불필요.
앱 시작 시·도움말의 업데이트 확인은 GitHub latest 릴리스와 비교합니다.
- 버전이 더 높으면 새 버전으로 안내합니다.
- 버전이 같으면 파일명에 들어간 빌드 스탬프(
YYMMDD_HHMMSS)만 비교합니다. - OS별로 나눕니다. Windows는
.msi/_portable.zip만, macOS는_macOS.dmg/_macOS.zip만 봅니다.
다른 OS 자산을 나중에 올려도 상대 OS에는 “같은 버전의 더 최신 빌드”로 뜨지 않습니다. - 시작 알림의 「이 버전은 알리지 않기」도 OS·버전·스탬프 단위로 기억합니다.
npm run build:dist:macmacOS에서만 실행됩니다 (electron-builder가 Windows에서 .app/.dmg를 만들지 않습니다).
바탕화면 WorkerW 임베드는 포함되지 않고, 일반 앱 창으로 동작합니다.
기본 HTTP 접속 포트는 3012입니다 (Windows / npm run dev는 3010).
npm run build:update_all:macupdate:all 후 build:dist:mac과 같습니다.
→ msi/Neo Desktop Calendar v{버전}_YYMMDD_HHMMSS_macOS.dmg
→ msi/Neo Desktop Calendar v{버전}_YYMMDD_HHMMSS_macOS.zip
업데이트 확인 시 macOS는 위 _macOS.* 자산의 스탬프만 보며, Windows MSI·포터블과는 섞지 않습니다.
캘린더 데이터는 ~/Library/Application Support/Neo Desktop Calendar/data/에 두고,
Electron 캐시는 그 아래 .neo-desktop-calendar/electron-profile에 둡니다.
.app을 교체해도 설정·일정·회원 데이터가 유지됩니다.
.app과 같은 폴더에 data/settings.json 또는 .neo-desktop-calendar-portable이 있으면
그 폴더를 포터블 저장 위치로 씁니다.
| 스크립트 | 설명 |
|---|---|
npm run dist |
NSIS 설치 파일 (release/) |
npm run build:release |
MSI + portable (동일 스탬프, Electron 1회 빌드) |
npm run build:dist:mac |
macOS DMG + zip (msi/*_macOS.dmg / _macOS.zip, Mac에서만) |
npm run build:msi |
WiX MSI 설치판만 (msi/*.msi) |
npm run build:portable |
포터블 zip만 (msi/*_portable.zip, 7-Zip 필요) |
npm run sync-version |
constants.ts 버전 → package.json / License.rtf / 고지 동기화 |
npm run update:all |
npm 직접 의존성 @latest(메이저 포함) 후 typecheck·export 검증 + desktop-hit 헬퍼 재빌드 |
npm run build:update_all |
update:all 후 build:release (MSI + portable, 동일 스탬프) |
npm run build:update_all:mac |
update:all 후 build:dist:mac (DMG + zip, 동일 스탬프, Mac에서만) |
공개 배포 시 라이선스: AGPL-3.0 (LICENSE)
제3자 고지: legal/THIRD_PARTY_NOTICES.md
(설치본: resources/notices/LICENSE, resources/notices/THIRD_PARTY_NOTICES.md)
NAS4USB와 같은 흐름입니다.
npm run update:all옵션: --skip-git --skip-npm --skip-majors --skip-verify --skip-hit --build --msi --release --release-mac
직접 의존성(dependencies·devDependencies)은 pkg@latest로 올리고 메이저도 포함합니다.
Vite 스택은 electron-vite peer에 맞춥니다(현재 Vite 8·plugin-react 6은 electron-vite 5와 같이 쓸 수 없음).
--skip-majors면 package.json 범위 안의 npm update만 하고, Electron만 latest로 올립니다.
의존성 업데이트 후 typecheck와 verify:export가 실패하면 릴리스를 만들지 않습니다.
예: npm run update:all -- --build
npm run build:update_all 은 --release 와 같습니다.
npm run build:update_all:mac 은 --release-mac 과 같습니다.
Windows: update_all.bat (로그: .cache/logs/update-all.log)
- Main process starts with
setIgnoreMouseEvents(true, { forward: true }) - Renderer
WallpaperContainerkeeps ignore enabled for empty space InteractionUIcallssetIgnoreMouseEvents(false)onmouseenter- Leaving interactive UI / the window re-enables click-through


