Bluehair 시간표 운영·유지보수·디버깅·확장 인덱스#
이 문서는 timetable-cloudflare 저장소의 현재 구현을 기준으로 작성한 운영자·개발자용 색인입니다. 기능을 수정하거나 장애를 조사할 때는 먼저 이 문서를 보고, 이어서 연결된 파일을 여십시오. 배포·시크릿 변경·D1 데이터 변경은 되돌리기 어렵거나 서비스에 영향을 줄 수 있으므로, 로컬 검증과 백업 확인 뒤에만 수행합니다.
기준: 현재 단일 워크스페이스(
default), 시간대Asia/Seoul, 주 서비스 이름timetable.
0. 가장 빠른 길#
| 목적 | 먼저 볼 위치 | 다음 행동 |
|---|---|---|
| 시간표가 갱신되지 않음 | src/index.ts의 revision/SyncHub, public/app.js |
/api/changes와 /ws를 각각 확인한다. |
| 웹 화면/디자인 수정 | public/index.html, public/styles.css, public/app.js |
pnpm test 후 브라우저에서 수동 확인한다. |
| API·검증 규칙 수정 | src/index.ts, test/worker.test.ts |
입력 검증, D1 변경, revision 증가, 알림을 함께 검토한다. |
| 데이터 구조 변경 | migrations/, src/index.ts, seed.sql |
새 migration을 추가하고 기존 migration은 수정하지 않는다. |
| ChatGPT 도구 문제 | integrations/chatgpt-mcp/ |
Service Binding과 양쪽 APP_TOKEN을 확인한다. |
| Windows 위젯 문제 | windows/README.md, windows/src/TodayWidget/, windows/scripts/ |
로그와 Smoke Test를 먼저 확인한다. |
| 배포/도메인 문제 | wrangler.jsonc, MCP의 wrangler.jsonc |
dry-run, 도메인·바인딩·시크릿 순서로 확인한다. |
| CI 실패 | .github/workflows/ci.yml |
실패한 job과 같은 명령을 로컬에서 재현한다. |
1. 시스템 구성요소 지도#
브라우저 PWA ───────────────┐
Windows TodayWidget(WebView2) ─┼─▶ 주 Timetable Worker ─▶ D1(권위 데이터)
│ ├─▶ public/(정적 화면)
ChatGPT Web ─▶ OAuth MCP Worker ┘ └─▶ SyncHub ─▶ WebSocket 무효화 신호
└─▶ OAUTH_KV
MCP Worker ── TIMETABLE_API Service Binding + 서버 측 Bearer ──▶ 주 Worker
권한과 데이터의 단일 원천#
- D1이 일정·일회성 할 일·체크리스트·완료 기록·현재 revision의 권위 있는 저장소다.
- 주 Worker만 D1에 연결되어 입력 검증, 인증, 변경 commit, revision 증가를 수행한다.
- **Durable Object
SyncHub**은 상태를 저장하지 않는다. 연결된 클라이언트에{ type: "invalidate", revision }를 보내는 fan-out 용도다. - MCP Worker는 D1을 직접 읽거나 쓰지 않는다.
TIMETABLE_APIService Binding을 통해 주 Worker의 같은 API를 호출한다. - SSE는 구현되어 있지 않다. 실시간 전달 방식은 인증된 WebSocket 하나다.
2. 디렉터리와 핵심 파일 인덱스#
| 경로 | 책임 | 변경 시 함께 확인할 곳 |
|---|---|---|
src/index.ts |
주 Worker: 라우팅, 인증, API, D1 CRUD, revision, SyncHub, 보안 헤더 |
test/worker.test.ts, worker-configuration.d.ts, wrangler.jsonc |
migrations/0001_initial.sql |
초기 D1 스키마·인덱스·sync_meta |
새 스키마 변경은 새 번호 migration으로 추가 |
seed.sql |
초기 반복 일정과 체크리스트 | 운영 데이터를 덮어쓰지 않는지 검토 (INSERT OR IGNORE) |
public/index.html |
PWA 마크업 | public/styles.css, public/app.js |
public/styles.css |
웹·위젯 화면 스타일 | 브라우저와 Windows WebView2에서 확인 |
public/app.js |
인증 세션, API 호출, revision 동기화, WebSocket 재접속, CRUD UI | public/timetable-core.js, Worker API |
public/timetable-core.js |
서울 시간 기준 현재/다음 일정 및 타임라인 계산 | test/web-core.test.js |
public/sw.js |
PWA 앱 셸 캐시 | API와 /ws는 캐시하지 않는 정책을 유지 |
docs/USER_GUIDE_KO.md |
비개발자용 순차 사용 가이드의 원본 | pnpm docs:build 후 public/guide.html 갱신 |
docs/MAINTENANCE_INDEX_KO.md |
운영·디버깅·확장 인덱스의 원본 | pnpm docs:build 후 public/maintenance.html 갱신 |
scripts/build-docs.mjs |
두 Markdown 원본을 배포용 HTML로 결정적으로 변환 | public/docs.css, pnpm docs:check |
public/docs.css |
가이드 페이지의 반응형·인쇄 스타일 | 데스크톱과 좁은 위젯 폭에서 수동 확인 |
wrangler.jsonc |
주 Worker, custom domain, D1, DO, 정적 assets, 관측 설정 | 실제 Cloudflare 계정 바인딩·도메인 |
test/worker.test.ts |
인증, WebSocket, post-commit 알림, revision API 테스트 | src/index.ts |
test/web-core.test.js |
서울 시간/정렬/동기화 burst 제어 테스트 | public/timetable-core.js |
integrations/chatgpt-mcp/src/index.ts |
OAuth 2.1, MCP 도구, 주 Worker 프록시 | MCP 테스트와 별도 Wrangler 설정 |
integrations/chatgpt-mcp/wrangler.jsonc |
MCP custom domain, KV, cron, Service Binding, secret | 주 Worker 서비스명과 시크릿 |
integrations/chatgpt-mcp/test/worker.test.ts |
서비스 바인딩, 오류 은닉, OAuth 보안 테스트 | MCP Worker |
windows/src/TodayWidget/ |
WinUI 3 + WebView2 위젯, 설정, 시작 프로그램, 로그 | windows/scripts/, windows/README.md |
windows/scripts/ |
빌드, publish, 설치, 시작 프로그램, 제거, Smoke Test | Windows에서만 실행 |
.github/workflows/ci.yml |
PR/push CI; 검사만 수행하며 배포하지 않음 | root/MCP/Windows 명령 |
docs/ARCHITECTURE.md |
설계 경계 | 데이터 흐름을 크게 바꿀 때 갱신 |
docs/HANDOFF.md |
인계와 검증 명령 | 운영 상태가 달라질 때 갱신 |
3. 데이터·API·실시간 흐름#
D1 스키마#
migrations/0001_initial.sql에는 다음 테이블이 있다.
| 테이블 | 용도 | 핵심 규칙 |
|---|---|---|
sync_meta |
workspace별 최신 revision | 현재는 workspace_id = 'default' 한 행 |
schedule_items |
반복 일정 | weekday 0~6, HH:MM, enabled, sort_order |
one_off_tasks |
날짜별 추가 할 일 | task_date, 선택 시간, completed_at |
checklist_items |
재사용 체크리스트 정의 | is_active, sort_order |
checklist_completions |
날짜별 완료 상태 | (item_id, completion_date) 복합 PK |
모든 정상적인 변경은 D1 batch에서 데이터 조작 뒤 sync_meta.revision을 1 증가시킨다. commit이 끝난 후에만 SyncHub.invalidate(revision)을 호출한다. 알림 전송 실패는 이미 성공한 데이터 commit을 되돌리지 않는다. 따라서 클라이언트는 WebSocket 메시지를 정답 데이터로 취급하지 않고 스냅샷을 다시 읽어야 한다.
API 요약#
모든 /api/*와 /ws는 아래 둘 중 하나로 인증해야 한다.
- 브라우저:
POST /api/auth/session에서 토큰을 HttpOnly/SameSite=Strict 세션 쿠키로 교환 - 자동화·네이티브·Service Binding:
Authorization: Bearer <APP_TOKEN>
쿠키를 사용한 쓰기 요청은 현재 Origin과 같은 Origin 헤더가 반드시 필요하다. API JSON 본문은 Content-Type: application/json이며 16 KiB를 넘을 수 없다.
| 목적 | 경로와 메서드 | 동기화 결과 |
|---|---|---|
| 세션 생성/삭제/확인 | POST, DELETE /api/auth/session; GET /api/auth/status |
세션 쿠키 설정/삭제 |
| 범위 스냅샷 | GET /api/snapshot?from=YYYY-MM-DD&to=YYYY-MM-DD |
revision 포함 전체 범위 반환 |
| 변경 확인 | GET /api/changes?since=N&from=...&to=... |
동일 revision이면 { changed: false }, 다르면 스냅샷 |
| 반복 일정 | POST /api/schedule; PATCH, DELETE /api/schedule/:id |
revision 증가, WebSocket invalidation |
| 일회성 할 일 | POST /api/tasks; PATCH, DELETE /api/tasks/:id |
revision 증가, WebSocket invalidation |
| 체크리스트 정의 | POST /api/checklist; PATCH /api/checklist/:id |
revision 증가, WebSocket invalidation |
| 체크 완료 | PUT /api/checklist/:id/completions/:YYYY-MM-DD |
{ completed: boolean }, revision 증가 |
| 실시간 무효화 | GET /ws + WebSocket Upgrade |
{ type: "invalidate", revision } push |
범위는 서울 날짜 기준이고 기본값은 오늘이며, 최장 31일이다. Worker가 허용하는 category는 english, exam, content, review, rest다.
클라이언트 동기화 순서#
public/app.js가 인증 상태를 확인한다.- 지정한 주간 범위의
/api/snapshot을 받아localStorage의timetable.snapshot.v2에 마지막 정상 데이터를 보관한다. - 인증된 온라인 클라이언트가
/ws에 연결한다. - 어느 클라이언트/ChatGPT가 변경을 commit하면 revision이 증가하고
SyncHub가 invalidate를 push한다. - 메시지의 revision이 로컬 값보다 새로우면 클라이언트가
/api/changes또는 snapshot으로 다시 읽고 렌더링한다. - WebSocket이 끊기면 재접속한다. 오프라인일 때는 마지막 정상 스냅샷을 표시한다.
public/sw.js는 앱 셸의 GET 요청만 캐시하며 /api/*와 /ws는 명시적으로 제외한다. API 응답도 Cache-Control: no-store다.
4. 설정·시크릿·Cloudflare·도메인#
주 Worker 설정 (wrangler.jsonc)#
| 항목 | 현재 선언 | 운영 주의점 |
|---|---|---|
| Worker 서비스 | timetable |
MCP Service Binding의 대상 이름과 일치해야 한다. |
| 주 custom domain | today.bluehair.blue |
도메인/DNS 변경은 Cloudflare 계정에서 별도 확인이 필요하다. |
| 정적 자산 | public/ → ASSETS, run_worker_first: true |
Worker가 API와 정적 앱을 함께 제공한다. |
| D1 | binding DB, 이름 timetable |
database_id는 실제 운영 DB를 가리킨다. 함부로 바꾸지 않는다. |
| Durable Object | SYNC_HUB → SyncHub |
DO migration tag는 v1이다. |
| 관측 | observability.enabled: true, sampling 1 |
로그 보관·알림·외부 전송은 별도 설정이 없다. |
| 필수 secret | APP_TOKEN |
설정 파일·소스·클라이언트에 넣지 않는다. |
MCP Worker 설정 (integrations/chatgpt-mcp/wrangler.jsonc)#
| 항목 | 현재 선언 | 운영 주의점 |
|---|---|---|
| Worker 서비스 | timetable-chatgpt-mcp |
주 Worker와 별도 배포 단위다. |
| custom domain | mcp.bluehair.blue |
/mcp, OAuth discovery와 authorize/token 경로를 같은 origin으로 제공한다. |
| Service Binding | TIMETABLE_API → timetable |
D1 직접 바인딩을 추가하지 않는다. |
| KV | OAUTH_KV |
OAuth grant/token 자료용이다. |
| cron | 매일 17 3 * * * |
만료 OAuth KV 레코드 정리만 수행하며 시간표 데이터는 바꾸지 않는다. |
| 필수 secret | APP_TOKEN |
주 Worker와 동일한 운영 값이어야 한다. |
시크릿 규칙#
.dev.vars와integrations/chatgpt-mcp/.dev.vars는 Git ignore 대상이다. 예제 파일만 커밋한다.- 운영
APP_TOKEN은 두 Worker의 Cloudflare secret에 함께 설정한다. 교체 시 새 값이 양쪽에 적용되기 전까지 MCP 호출이 실패할 수 있다. - 입력 토큰과 HMAC 세션 서명의 원천이 같은
APP_TOKEN이므로, 유출 또는 분실이 의심되면 즉시 두 Worker에서 교체하고 기존 브라우저 세션이 무효화됨을 전제한다. - CI의
local-development-token-not-for-production은 테스트용이며 운영 시크릿이 아니다.
5. 로컬 개발과 검증#
필요한 도구#
- root 및 MCP: Node.js 22, pnpm 10, 프로젝트의 lockfile에 맞는 의존성
- Windows: .NET SDK 8 (
windows/global.json은 8.0.423), Windows SDK, WebView2 Runtime - Cloudflare 실제 배포/원격 D1 조작: 해당 계정에 인증된 Wrangler
다음으로 셸의 도구 상태를 먼저 확인한다.
node --version
pnpm --version
pnpm exec wrangler --version
node가 인식되지 않거나 22 계열이 아니면, 먼저 Node.js 22를 설치/활성화한 뒤 새 PowerShell 창에서 재시도한다.
주 Worker/PWA 안전 검증#
저장소 root에서 실행한다.
pnpm install --frozen-lockfile
pnpm docs:check
pnpm types
pnpm types:check
pnpm typecheck
pnpm test
pnpm check
pnpm docs:check는 Markdown 원본과 배포용 HTML이 같은지 확인한다. 실패하면 pnpm docs:build로 HTML을 다시 생성한다. pnpm check과 pnpm deploy:dry-run은 wrangler deploy --dry-run이며 실제 배포를 하지 않는다. 생성된 타입 파일의 변경이 의도된 것인지 확인한 뒤 커밋한다.
로컬 데이터로 UI를 실행하는 순서는 다음과 같다.
Copy-Item .dev.vars.example .dev.vars
# .dev.vars의 APP_TOKEN을 개발용 긴 난수로 교체한다.
pnpm db:migrate:local
pnpm db:seed:local
pnpm dev
브라우저에서 http://localhost:8787을 열고, 개발용 토큰으로 로그인한다. .wrangler/의 로컬 D1 상태는 운영 DB가 아니다.
ChatGPT MCP 패키지 검증#
다음 명령은 MCP 하위 디렉터리에서 실행한다.
Set-Location integrations/chatgpt-mcp
pnpm install --frozen-lockfile --ignore-workspace
pnpm types
pnpm typecheck
pnpm test
pnpm check
로컬 OAuth/MCP 실험은 Copy-Item .dev.vars.example .dev.vars 뒤에 실행한다. Service Binding을 포함한 end-to-end 로컬 검증은 두 Worker를 함께 Wrangler 개발 런타임으로 실행해야 한다. 이 저장소에는 이를 한 번에 띄우는 compose/script는 없다.
Windows 위젯 검증#
Set-Location windows
.\scripts\Build.ps1 -Configuration Release
.\scripts\Publish.ps1
.\scripts\Smoke-Test.ps1
Build.ps1은 CI용 빌드이고, Publish.ps1은 self-contained x64 산출물과 ZIP을 만든다. 설치 전 Smoke Test를 통과시킨다.
CI가 보장하는 범위#
.github/workflows/ci.yml은 pull request, push, 수동 실행에서 다음 세 job을 수행한다.
| Job | 보장하는 것 | 보장하지 않는 것 |
|---|---|---|
| Worker and web checks | root install, 문서 생성물 일치, 타입 생성/검사, Worker·웹 테스트, dry-run | 실제 배포, 운영 D1, 실 브라우저 UI |
| ChatGPT MCP checks | MCP install, 타입/테스트, dry-run | 실제 OAuth 승인, 실제 ChatGPT 연결, 운영 KV |
| Windows x64 build | Release/x64 restore/build | publish, 설치, WebView2 실기기 smoke test |
CI에는 배포 권한이나 운영 secret을 주지 않는다.
6. 배포·롤백·백업·복구#
주 Worker 배포 전 점검#
pull request CI가 모두 녹색인지 확인한다.
wrangler.jsonc의 서비스명,today.bluehair.blue, D1 binding/ID, DO binding을 확인한다.운영
APP_TOKEN이 이미 주 Worker에 설정됐는지 확인한다. 새 환경이면 대화형으로 설정한다.pnpm exec wrangler secret put APP_TOKEN새 migration이 있으면 배포 전에 원격 D1에 적용할 대상과 백업 상태를 재확인한다.
pnpm exec wrangler d1 migrations apply timetable --remote사람이 dry-run 결과를 검토한 뒤 배포한다.
pnpm deploy:dry-run pnpm exec wrangler deploy
seed.sql은 INSERT OR IGNORE를 사용하지만, 운영 seed 실행은 새 기본 행을 추가할 수 있다. 운영 데이터에 필요한지 확인하지 않은 상태에서 재실행하지 않는다.
MCP Worker 배포 순서#
TIMETABLE_API의 서비스 대상이timetable인지 확인한다.MCP Worker에도 같은 운영
APP_TOKEN을 설정한다.Set-Location integrations/chatgpt-mcp pnpm exec wrangler secret put APP_TOKEN pnpm check pnpm exec wrangler deploymcp.bluehair.blue/mcp의 OAuth discovery와 인증 전401을 확인한다. 실제 시간표를 변경하는 smoke test는 피한다.
롤백 원칙#
이 저장소에는 자동 배포나 원클릭 롤백 스크립트가 없다.
코드 장애는 우선 현재 배포 이력을 읽기 전용으로 확인한다.
pnpm exec wrangler deployments list pnpm exec wrangler deployments status즉시 이전 Worker 버전으로 되돌려야 한다면 정상 version ID를 사람이 확인한 뒤 다음 명령을 실행할 수 있다. 이 명령은 운영 배포를 바꾸므로 변경 사유와 version ID를 기록한다.
pnpm exec wrangler rollback <NORMAL_VERSION_ID> --message "장애 대응: <REASON>"또는 마지막 정상 Git commit을 새 브랜치에서 복원한 뒤 같은 검증과
wrangler deploy를 다시 실행하면 소스와 배포 상태를 일치시키기 쉽다.D1 migration은 일반적으로 자동 역방향 실행되지 않는다. 잘못된 migration을 이미 원격 적용했다면, 먼저 영향 범위와 최신 백업을 확인하고 별도 forward-fix migration 또는 복구 계획을 작성한다. 기존 migration 파일을 수정해 과거 상태를 바꾸지 않는다.
custom domain/DNS 변경 롤백은 Worker 코드 롤백과 별개다. Cloudflare DNS/route 상태를 따로 확인한다.
백업·복구 현황과 필수 운영 절차#
- 저장소에는 D1 자동 백업, export 스케줄러, 복구 스크립트가 구현되어 있지 않다.
- 현재 고정된 Wrangler에서 원격 SQL export 문법은
pnpm exec wrangler d1 export timetable --remote --output <BACKUP.sql>이다. 권한·보존 위치를 사전 검증하고, 실행 직전pnpm exec wrangler d1 export --help로 설치된 CLI 문법을 다시 확인한다. - 최소 절차는 “변경 전 export를 암호화된 접근 제한 저장소에 보관 → 파일 해시·DB명·시각 기록 → 복구는 격리된 대상에서 먼저 검증 → 원격 복구 승인”이다. Git 저장소나 공개 artifact에 SQL export/토큰을 넣지 않는다.
- 시점 복구가 필요하면 먼저
pnpm exec wrangler d1 time-travel info timetable --timestamp <RFC3339>로 bookmark를 조회한다.time-travel restore는 운영 DB를 덮어쓰므로 최신 export와 별도 승인을 확보한 뒤에만 사용한다. - 복구 시 revision과 WebSocket 상태도 고려한다. D1을 이전 시점으로 복구하면 연결된 클라이언트의 로컬 snapshot보다 revision이 낮아질 수 있으므로, 사용자 세션을 새로고침/재인증하고 동기화 동작을 수동 점검한다.
7. 로그·관측·점검 순서#
현재 확인 가능한 신호#
| 신호 | 위치 | 사용법 |
|---|---|---|
| Worker 관측 | 주/MCP wrangler.jsonc의 observability.enabled |
Cloudflare 대시보드에서 해당 Worker의 요청·오류를 본다. |
| 요청 상관관계 | 주 Worker 응답 X-Request-Id |
사용자에게 받은 응답 헤더 값을 오류 로그의 requestId와 대조한다. |
| 예외 로그 | 주 Worker console.error |
예상 밖 오류는 { event: "request_error", requestId, path } 형식이다. |
| 실시간 상태 | 웹 화면 연결 상태, public/app.js |
연결/재연결/오프라인 표기를 확인한다. |
| Windows 충돌 로그 | %LOCALAPPDATA%\Bluehair\TodayWidget\Data\Logs\today-widget-YYYYMMDD.log |
WebView2 초기화, 설정 저장, startup 오류를 확인한다. |
| Windows 설정/쿠키 profile | %LOCALAPPDATA%\Bluehair\TodayWidget\Data\settings.json, WebView2 |
설치 업데이트 시 보존된다. 민감 세션 정보가 있을 수 있어 공유하지 않는다. |
| CI | GitHub Actions 실행 로그 | 실패 job의 명령과 에러를 확인한다. |
현재 알림 규칙, Logpush, Analytics Engine, 외부 APM, 가동률 모니터링은 저장소 설정에 없다. 장기 운영 시 별도 설계·권한 승인 후 추가한다.
8. 증상 → 확인 → 조치#
| 증상 | 먼저 확인 | 조치 |
|---|---|---|
로그인 후에도 401 |
APP_TOKEN 값, /api/auth/status, 브라우저 쿠키 |
토큰 오타를 확인하고, HTTPS origin에서 다시 로그인한다. 토큰 교체 뒤에는 기존 세션이 무효가 된다. |
쓰기 요청이 403 CSRF_REJECTED |
요청의 Origin, cookie 인증 여부 |
브라우저는 같은 origin에서 실행한다. 외부 origin의 cookie 쓰기는 지원하지 않는다. 자동화는 bearer 방식을 쓴다. |
415 또는 400/422 |
Content-Type, JSON, 날짜/시간/category |
application/json 객체인지, 실제 YYYY-MM-DD, HH:MM, 허용 category인지 확인한다. |
| 수정은 성공했는데 다른 화면이 늦게 바뀜 | 응답 revision, /ws 연결 상태, /api/changes |
WebSocket은 무효화 신호다. 끊겼다면 snapshot/changes 재조회가 정답이며, commit 자체는 이미 성공했을 수 있다. |
| 데이터가 오래된 것처럼 보임 | revision label, localStorage cache, 네트워크 |
온라인에서 snapshot을 재조회한다. 앱 셸만 캐시되며 API는 캐시되지 않는다. 필요 시 해당 사이트 저장소의 timetable.snapshot.v2를 지운 뒤 재로그인한다. |
/ws가 426 |
WebSocket Upgrade 헤더 | HTTP fetch가 아닌 WebSocket 클라이언트로 연결한다. |
| 새 schema가 없거나 D1 오류 | wrangler.jsonc의 DB binding/ID, migration 적용 여부 |
새 migration을 만들고 원격 적용 여부를 확인한다. 운영 DB에 seed로 임시 해결하지 않는다. |
| 주 도메인이 열리지 않음 | today.bluehair.blue route/DNS, Workers.dev fallback |
Cloudflare DNS/route/배포를 분리해 확인한다. Windows 위젯은 주 도메인 실패 시 Workers.dev fallback을 한 번 시도한다. |
| MCP가 시간표를 못 읽거나 쓰지 못함 | MCP TIMETABLE_API, 두 APP_TOKEN, OAuth scope |
Service Binding target이 timetable인지, 양쪽 secret이 같은지, timetable.read/write 권한이 있는지 확인한다. MCP에 D1 binding을 추가해 우회하지 않는다. |
| ChatGPT 앱에서 MCP가 안 보임 | ChatGPT workspace/Developer Mode/웹 지원 여부 | MCP README의 등록 요건을 확인한다. 저장소 기능만으로 ChatGPT 모바일 custom MCP 사용을 보장하지 않는다. |
| Windows 위젯이 빈 화면/초기화 실패 | Data\Logs, WebView2 Runtime, Smoke-Test.ps1 |
WebView2 Runtime과 Windows/.NET 요구사항을 확인한 뒤 smoke test를 재실행한다. |
| 위젯이 부팅 시 안 열림 | HKCU\Software\Microsoft\Windows\CurrentVersion\Run의 BluehairTodayWidget |
windows\scripts\Set-Startup.ps1 -Mode Enable 또는 위젯의 tray 메뉴로 다시 등록한다. |
| Windows 설정/로그인이 이상함 | %LOCALAPPDATA%\Bluehair\TodayWidget\Data |
먼저 백업한 뒤 settings.json 또는 WebView2 profile을 격리/재생성한다. Uninstall.ps1 -RemoveUserData는 사용자 데이터도 삭제한다. |
CI에서 node/의존성 실패 |
Node 22, pnpm 10, lockfile | pnpm install --frozen-lockfile을 새 셸에서 재실행한다. lockfile을 임의로 재생성하지 않는다. |
9. 보안 운영 체크리스트#
배포 전 또는 시크릿/권한 변경 전에 확인한다.
- 실제
APP_TOKEN이 소스,wrangler.jsonc,.dev.vars.example, Git diff, 이슈/PR, 화면 캡처에 없는가? - 주 Worker와 MCP Worker의 운영
APP_TOKEN이 의도적으로 같은 최신 값인가? -
.dev.vars와 Windows의Data\WebView2를 공유/커밋하지 않는가? - API 쓰기 요청은 cookie일 경우 same-origin CSRF 검사를, 자동화일 경우 bearer 인증을 유지하는가?
- 새 API가
readJson, 길이·날짜·시간·category 검증, 에러 코드 정책을 따르는가? - 모든 성공 mutation이 D1 transaction + revision 증가 +
notify경로를 거치는가? - MCP의 새 도구가 OAuth scope를 확인하고, Service Binding만 사용하며, upstream 오류 본문/시크릿을 노출하지 않는가?
- 보안 헤더(CSP, HSTS, frame 차단 등)를 바꿀 경우 PWA/WebView2/MCP 동작을 함께 시험했는가?
- Windows 배포 파일을 외부에 배포한다면 Authenticode 서명과 SHA-256 검증 절차가 준비됐는가? 현재 저장소는 서명 자동화를 제공하지 않는다.
10. 기능 확장 지점과 경계#
안전한 변경 순서#
- 요구사항과 데이터 모델을 먼저 정한다.
- 저장 구조가 바뀌면 새 D1 migration을 추가한다.
src/index.ts에 입력 검증·권한·CRUD를 추가한다.- 쓰기라면
mutate와commitWithBestEffortNotification경로로 revision/invalidation을 보장한다. - API 테스트를 추가한다.
public/app.js와 필요 시timetable-core.js/웹 테스트를 갱신한다.- ChatGPT에서도 편집해야 한다면 MCP Zod schema, 도구, scope 검사, MCP 테스트를 추가한다.
- Windows는 WebView2로 웹 UI를 호스팅하므로, 기본적으로 웹 UI 변경이 반영된다. 네이티브 tray/창 동작 변경만
windows/src/TodayWidget를 수정한다. - root/MCP/Windows 검증과 CI를 모두 통과한 뒤 배포한다.
구현되어 있는 확장 지점#
| 목표 | 주 변경 위치 | 주의점 |
|---|---|---|
| 일정/할 일 필드 추가 | migration, Worker parser/serializer, PWA, MCP schema | snapshot 계약과 기존 행의 기본값·migration을 함께 설계 |
| 새 category | Worker CATEGORIES, DB CHECK, PWA UI/스타일, MCP Zod schema |
한 곳만 바꾸면 API/DB/UI가 불일치한다. |
| 새 PWA 화면 | public/index.html, styles.css, app.js |
widget mode와 mobile 반응형·offline shell을 확인 |
| 실시간 효율 개선 | SyncHub, public/app.js |
WebSocket은 push-only invalidation이라는 복구 가능 모델을 유지 |
| 새 ChatGPT 도구 | MCP createServer, schema, tool tests |
destructive action annotation은 UX 힌트일 뿐, scope 검사가 실제 경계 |
| Windows 네이티브 기능 | windows/src/TodayWidget/Infrastructure |
x64 build, startup, 로그, WebView2 허용 host 정책을 함께 검토 |
명시적으로 미구현/별도 설계가 필요한 항목#
- Android 네이티브 앱/APK 및 Android 위젯
- 다중 사용자·다중 workspace 권한 모델 (현재
default한 workspace) - ChatGPT native Scheduled Tasks 저장소와 D1의 자동 양방향 동기화
- SSE 전송
- 푸시 알림/캘린더 동기화/외부 캘린더 API
- D1 자동 백업·자동 복구·정기 export
- 운영 알림, 외부 로그 수집, 가동률 모니터링
- Windows 코드 서명/공개 배포 파이프라인
- CI 기반 자동 배포와 자동 롤백
이 항목들은 설정만 추가해서 안전하게 완성되지 않는다. 데이터 소유권, 인증, 비용, 장애 복구, 개인정보 보관 정책을 정한 뒤 별도 작업으로 설계한다.
11. 변경 후 최소 인계 기록#
운영 변경 또는 기능 확장 PR에는 아래 정보를 남긴다.
- 변경한 데이터/라우트/클라이언트/Cloudflare 바인딩
- migration 이름과 원격 적용 여부
- 시크릿 교체 여부(값 자체는 기록하지 않음)
- 실행한 root, MCP, Windows 검증 명령과 결과
- 배포 Worker 버전/시각, 도메인 확인 결과
- 백업 위치의 식별자와 복구 책임자(비밀/경로 공개 금지)
- 롤백 기준 commit과 D1 영향 여부
이 기록이 있어야 다음 운영자가 “코드는 정상인데 데이터/도메인/시크릿이 다르다”는 상황을 빠르게 분리할 수 있다.