@nolit/dsh-notify 0.0.0-stage → 0.0.1-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +388 -0
  2. package/LICENSE +21 -0
  3. package/PROVENANCE.md +96 -0
  4. package/README.md +298 -2
  5. package/cordis.patch.yml +19 -0
  6. package/docs/2026-10-20-dsh-notify-design.md +335 -0
  7. package/docs/2026-10-20-final-status.md +125 -0
  8. package/docs/2026-10-23-asset-policy.md +170 -0
  9. package/docs/2026-10-23-deployment-strategy-review.md +530 -0
  10. package/docs/2026-10-23-headless-nonintrusive-ui-architecture.md +579 -0
  11. package/docs/2026-10-23-hybrid-dual-mode-design.md +955 -0
  12. package/docs/2026-10-23-timezone-global-report.md +204 -0
  13. package/docs/2026-10-23-web-notify-plugin-spec.md +454 -0
  14. package/docs/2026-10-23-web-plugin-mount.md +111 -0
  15. package/docs/2026-10-23-windows-widget-poc-report.md +312 -0
  16. package/docs/2026-10-24-linux-desktop-verification.md +134 -0
  17. package/docs/BOOT-CARD.md +176 -0
  18. package/docs/RECOVERY-NOTES.md +111 -0
  19. package/docs/WORK-PROTOCOL.md +155 -0
  20. package/docs/deepseek-peak-spec.md +370 -0
  21. package/docs/dsh-harness-notify-spec.md +149 -0
  22. package/icon.svg +6 -0
  23. package/lib/client.js +2172 -0
  24. package/lib/client.src.js +1963 -0
  25. package/lib/index.js +13 -0
  26. package/lib/locale-table.js +215 -0
  27. package/locale/en.json +104 -0
  28. package/locale/ko.json +104 -0
  29. package/market/icons/dsh-notify.svg +6 -0
  30. package/market/index.json +77 -0
  31. package/package.json +80 -4
  32. package/scripts/build-client.mjs +41 -0
  33. package/scripts/gen-locale.mjs +36 -0
  34. package/test/client.test.mjs +601 -0
  35. package/test/locale.test.mjs +114 -0
  36. package/test/manifest.test.mjs +115 -0
  37. package/test/peak.test.mjs +333 -0
  38. package/test/typography.test.mjs +85 -0
@@ -0,0 +1,530 @@
1
+ # DSH Notify 2-패키지 배포·운영 전략 — 기술 검증 및 실행 계획
2
+
3
+ <!-- skeleton:
4
+ version: 1.0
5
+ kind: report
6
+ status: historical
7
+ updated: 2026-10-23
8
+ summary: Technical review of the two-package deployment and operations strategy for DSH Notify, with the resulting execution plan.
9
+ -->
10
+
11
+
12
+ > 상태: proposed · 최종 갱신: 2026-10-23 · git: main @ 8129b54 · 상위: [README.md](../README.md)
13
+
14
+ - 대상: ① DSH 웹 알림 플러그인(공개 배포 지향), ② 윈도우 오프피크 위젯(포터블 스크립트 배포)
15
+ - 전제: 두 패키지는 서로 통신하지 않는다. 앱-플러그인 IPC/HTTP 브리지는 폐기한다.
16
+ - 이번 단계에서 **코드와 기존 파일은 수정하지 않았다.** 이 보고서만 추가했다.
17
+ - 실측 시점: 2026-10-23 03:40 KST (Asia/Seoul).
18
+ - **정책 변경(2026-10-23):** 모듈 A는 **PowerShell 7+ 필수**로 확정. 5.1 다운그레이드는 인코딩/IANA 시간대/스레드 호환 문제로 기각. [모듈 A PoC 보고서](2026-10-23-windows-widget-poc-report.md) 참조.
19
+
20
+ ## 인덱스
21
+
22
+ | § | 섹션 | 한 줄 요약 |
23
+ |---|---|---|
24
+ | 1 | 검증 결론 | 웹은 "사용 중 알림"으로 범위를 명시하면 Go, 위젯은 5.1 포팅·라이선스 정리 후 Go |
25
+ | 2 | 실측 근거 | SAC 켜짐, RemoteSigned, MOTW 차단/Bypass 통과, 5.1 WPF·Start-Job 동작 |
26
+ | 3 | 웹 플러그인 검증 | 탭 절전·알림/소리 억제·재연결·다중 탭 엣지 케이스 |
27
+ | 4 | 윈도우 위젯 검증 | ExecutionPolicy·SAC·MOTW·자동 시작·5.1 전환의 실제 변수 |
28
+ | 5 | 배포 아키텍처 | 모노레포 독립 패키지, npm + GitHub Releases |
29
+ | 6 | 최소 품 패키징 | 웹 패키지 체크리스트, 위젯 zip/실행/설치/제거 UX |
30
+ | 7 | 실행 로드맵 | Phase 0–4, 병목과 검증 절차 |
31
+ | 8 | 리스크·법무 | 폰트/로고/사운드 라이선스가 공개 배포의 최대 블로커 |
32
+ | 9 | 결정 필요 항목 | 5.1 vs 7, 배포 형태, 공개 범위 |
33
+ | 10 | 부록 | 실측 명령·결과, 릴리스 체크리스트 |
34
+
35
+ ---
36
+
37
+ ## 1. 검증 결론
38
+
39
+ | 항목 | 판정 | 근거/조건 |
40
+ |---|---|---|
41
+ | 웹 플러그인을 순수 브라우저 API로 격리 | **조건부 Go** | DSH 탭이 살아 있는 동안은 완결 가능. 탭이 닫히거나 discard되면 알림 없음. "always-on" 약속은 불가 |
42
+ | DSH 공식 Cordis 클라이언트 규격 준수 | **Go** | `dsh.client` + `lib/client.js` + no-op host + `cordis.patch.yml`로 구성 가능. 공식 마켓 등재는 별도 심사 |
43
+ | 윈도우 위젯을 비인증 포터블 스크립트로 배포 | **조건부 Go** | 기본 Windows 11에서 `powershell.exe -ExecutionPolicy Bypass`로 실행 가능. 단, 5.1 포팅과 라이선스 정리 필요 |
44
+ | Developer Mode 없이 동작 | **Go** | 스크립트 실행에 Developer Mode는 필요 없다. 필요한 것은 ExecutionPolicy 완화와 서명된 `powershell.exe`뿐 |
45
+ | 레지스트리/시스템 강제 설치 없이 운영 | **Go** | 사용자 Startup 폴더 `.lnk` + 앱 폴더 데이터. 관리자 권한/레지스트리 불필요 |
46
+ | 공개 오픈소스 배포 | **조건부 Go** | MIT LICENSE + BYO 정책 적용. Digital-7/DeepSeek 로고/`ding.wav`는 배포물에서 제외하고 사용자 주입만 허용 |
47
+ | 두 패키지의 완전 독립 배포 | **Go** | 공유 런타임 없음. 모노레포 독립 패키지 또는 별도 저장소 모두 가능 |
48
+
49
+ **핵심 판단:** 기술적 차단 요소는 크지 않다. 가장 큰 리스크는 ① 웹 플러그인의 "탭이 닫혀도 알림"을 기대하게 만드는 제품 약속, ② 윈도우 위젯의 PowerShell 5.1 호환성, ③ 폰트/로고/사운드 라이선스다. ③은 **BYO 정책 + MIT LICENSE**로 해소한다. 기본 패키지는 에셋을 배포하지 않고, 사용자 주입만 허용한다. 상세: [에셋·상표 라이선스 정책](2026-10-23-asset-policy.md).
50
+
51
+ ---
52
+
53
+ ## 2. 실측 근거
54
+
55
+ | 항목 | 실측값 | 의미 |
56
+ |---|---|---|
57
+ | OS | Windows 11 Home 10.0.26300, AMD64 | 대상 OS |
58
+ | PowerShell 7 | 7.6.7 | 현재 앱 실행 호스트 |
59
+ | Windows PowerShell | 5.1.26100.9711 | 기본 내장. 5.1 포팅의 베이스 |
60
+ | ExecutionPolicy | `LocalMachine: RemoteSigned`, 나머지 `Undefined` | MOTW 스크립트는 차단, Bypass는 통과 |
61
+ | Smart App Control | `VerifiedAndReputablePolicyState = 1` (켜짐) | 서명 없는 exe 차단. 스크립트는 별도 |
62
+ | Defender | RealTimeProtection `True`, TamperProtection `True` | 실행 시 실시간 검사 |
63
+ | MOTW + 5.1 기본 | `cannot be loaded ... is not digitally signed`, exit 1 | 다운로드 zip을 그대로 풀면 실행 차단 |
64
+ | MOTW + 5.1 Bypass | `PROBE_OK`, exit 0 | `-ExecutionPolicy Bypass`가 프로세스 범위에서 유효 |
65
+ | MOTW + `Unblock-File` | 차단 해제 후 기본 정책에서 `UNBLOCK_OK`, exit 0 | 대안: 1회 Unblock |
66
+ | 5.1 WPF/STA | `WPF_OK System.Windows.Window`, `FORMS_OK 2880` | WPF/Windows Forms가 5.1에서 로드됨 |
67
+ | 5.1 Start-Job | `JOB_OK` | `Start-ThreadJob` 대체 가능 |
68
+ | 5.1 미지원 | `Start-ThreadJob` 없음, `ConvertFrom-Json -AsHashtable` 없음 | 현재 코드의 PS7 의존 3곳 |
69
+ | WScript.Shell COM | 5.1/7 모두 `COM_OK` | `.lnk` 생성은 양쪽에서 가능 |
70
+ | 기존 바로가기 | Target이 Store `pwsh.exe` 경로 | 머신/버전 종속. 배포용으로 부적합 |
71
+
72
+ **PS7 의존 스캔 결과:** `core.ps1`의 `Start-ThreadJob`, `ConvertFrom-Json -AsHashtable`, `providers/deepseek-peak.ps1`의 `Start-ThreadJob` — 총 3곳. `??`, `?.`, `-Parallel` 등 다른 PS7 문법은 발견되지 않았다. **5.1 포팅 범위는 작다.**
73
+
74
+ ---
75
+
76
+ ## 3. 웹 플러그인 타당성 검증
77
+
78
+ ### 3.1 순수 브라우저 API로 가능한 범위
79
+
80
+ 가능:
81
+ - DSH 탭이 열려 있을 때 턴 완료/승인/질문/오류 알림.
82
+ - Web Notification, Web Audio, 인페이지 토스트, 상태 배지.
83
+ - 다중 탭 리더 선출, 세션 쿨다운, 권한/소리 상태 표시.
84
+
85
+ 불가능:
86
+ - DSH 탭이 닫힌 뒤 알림.
87
+ - 브라우저가 탭을 discard/freeze한 동안 이벤트 수신.
88
+ - OS 집중 지원/방해 금지에서 알림이 실제 표시되는지 보장.
89
+ - 사용자가 브라우저/OS에서 알림을 끈 경우 이를 우회.
90
+
91
+ **따라서 제품 문구는 “DSH를 사용하는 동안의 알림”으로 한정해야 한다.** “항상 알림”이 필요하면 네이티브 앱/서비스 워커 푸시가 필요하며, 이는 현재의 2-패키지 독립 구조와 별개다.
92
+
93
+ ### 3.2 엣지 케이스와 현실적 대응
94
+
95
+ | 엣지 케이스 | 발생 원인 | 영향 | 대응 |
96
+ |---|---|---|---|
97
+ | 탭 discard/freeze | Chrome Memory Saver, 절전 | 이벤트 수신 중단, 알림 누락 | `freeze`/`resume`, `visibilitychange`, `pageshow`에서 세션 목록 재동기화. 닫힌 탭은 수용 |
98
+ | 백그라운드 타이머 스로틀 | Chrome intensive throttling | 쿨다운/배지 갱신 지연 | 핵심 로직은 `setInterval`이 아니라 이벤트+타임스탬프 기반. 주기 작업 최소화 |
99
+ | 알림 권한 출처 종속 | 포트/도메인 변경 | 권한 재요청 필요 | 로드 시 `Notification.permission` 확인, 배지/패널에서 명확히 안내. 자동 재프롬프트 금지 |
100
+ | `127.0.0.1` vs `localhost` | 다른 origin | 권한/오디오 상태 불일치 | DSH 접속 URL을 하나로 고정하도록 문서화. 상태 배지에 현재 origin 표시 |
101
+ | OS 집중 지원/방해 금지 | Windows 설정 | 알림 배너 숨김, 앱은 인지 불가 | 인페이지 토스트 + 소리 병행. “표시 보장 없음” 문서화 |
102
+ | 브라우저 사이트 알림 차단 | 사용자 설정 | `Notification` 무시 | `permission === 'denied'` 감지 → 토스트 폴백 |
103
+ | 오디오 자동재생 차단 | 사용자 제스처 없음 | 첫 알림 무음 | 패널의 “소리 켜기”/“테스트 알림” 클릭에서 `AudioContext.resume()` |
104
+ | 탭/시스템 음소거 | 사용자 설정 | 소리 없음 | 감지 불가. 배지에 “소리 잠김” 안내, 테스트 버튼 제공 |
105
+ | 백그라운드 오디오 정책 | 브라우저/OS | 소리 지연/무음 | 알림 자체는 유지. 소리는 best-effort로 명시 |
106
+ | 다중 탭 | 같은 페이지 2개 이상 | 중복 알림 | Web Locks 리더 + localStorage 폴백 + Notification `tag` |
107
+ | BFCache 복원 | 뒤로/앞으로 | 구독/연결 stale | `pageshow.persisted`에서 재초기화/세션 목록 refresh |
108
+ | DSH 재연결 | 서버 재시작/네트워크 | 일반 완료 이벤트 유실 | 재연결 시 세션 목록으로 running→idle 전이 재구성. 승인/질문은 pending waterfall 재생 + `callId` dedupe |
109
+ | 승인 waterfall 중복 | 재연결 replay | 같은 승인 알림 2회 | `request.callId`/id 기반 5초 dedupe |
110
+ | 서브에이전트 이벤트 | child 세션 | 원치 않는 완료 알림 | `parentId`/`origin === 'subagent'` 필터. 승인/질문은 기본 허용 |
111
+ | DSH 버전 변경 | 0.2.x → 0.3 | 이벤트/슬롯 API 변경 | `engines.dsh` 고정, 이벤트 4종 단위 테스트, 로드 실패를 플러그인 내부로 격리 |
112
+ | 클라이언트 로드 실패 | `lib/client.js` 누락/문법 오류 | 플러그인만 비활성 | 패키지 `files` 검증, `npm pack` 내용 테스트, DSH Plugins 페이지에서 오류 확인 |
113
+ | CSP/번들 제약 | DSH 페이지 정책 | 인라인 스크립트/스타일 차단 가능 | module-loader 번들 + 슬롯 컴포넌트 사용. `eval`/인라인 `<script>` 금지 |
114
+
115
+ ### 3.3 Web Notification/Web Audio의 구조적 한계
116
+
117
+ - **서비스 워커 푸시 없이는 탭 종료 후 알림이 불가능하다.** DSH는 로컬 서버이고 푸시 백엔드가 없으므로, Web Push(VAPID) 도입은 별도 서버/키 관리가 필요하다. 현재 범위에서 제외한다.
118
+ - **알림 표시 여부는 브라우저/OS가 최종 결정한다.** `new Notification()`이 예외 없이 반환되어도 배너가 안 뜰 수 있다. 앱 쪽에서 “표시됨”을 확정할 수 없다.
119
+ - **소리는 “들린다”를 보장할 수 없다.** AudioContext 상태, 탭 음소거, 시스템 볼륨, 출력 장치 변경에 종속된다. 테스트 버튼과 상태 표시가 필요하다.
120
+ - **탭을 살리기 위해 무음 오디오를 계속 재생하는 것은 금지한다.** 배터리/UX/정책 위반 소지가 크다.
121
+
122
+ ---
123
+
124
+ ## 4. 윈도우 위젯 타당성 검증
125
+
126
+ ### 4.1 “비인증 포터블 스크립트”는 기술적으로 가능한가
127
+
128
+ 가능하다. 단, 다음 조건이 맞아야 한다.
129
+
130
+ 1. **호스트는 서명된 `powershell.exe`** (`%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe`)를 쓴다. 서명 없는 것은 `.ps1`(데이터)이며, SAC는 PE 실행 파일을 주로 막는다. 이 PC(SAC 켜짐, Defender 실시간)에서 MOTW 스크립트도 `-ExecutionPolicy Bypass`로 실행됐다.
131
+ 2. **ExecutionPolicy는 프로세스 범위에서만 완화**한다. `-ExecutionPolicy Bypass`는 레지스트리를 바꾸지 않는다. 단, MachinePolicy/UserPolicy가 `AllSigned`/`Restricted`이면 우회되지 않는다.
132
+ 3. **MOTW를 처리**한다. 다운로드 zip을 풀면 `.ps1`에 Zone.Identifier가 붙는다. 기본 RemoteSigned에서 차단되므로 `-ExecutionPolicy Bypass`를 모든 진입점에 넣거나, 1회 `Unblock-File`을 제공한다.
133
+ 4. **Developer Mode는 필요 없다.** Developer Mode는 앱 사이드로딩/개발자 기능이며, PowerShell 스크립트 실행과 무관하다. 사용자에게 끄라고 요구하지 않는다.
134
+
135
+ ### 4.2 실행 정책·보안 변수
136
+
137
+ | 변수 | 실제 동작 | 대응 |
138
+ |---|---|---|
139
+ | 기본 RemoteSigned + MOTW | 서명 없는 `.ps1` 차단 (실측 exit 1) | `-ExecutionPolicy Bypass` 또는 `Unblock-File` |
140
+ | Bypass | 프로세스 범위에서 통과 (실측 exit 0) | 바로가기/`start.cmd` 인자에 포함 |
141
+ | `Unblock-File` | MOTW 제거 후 기본 정책 통과 (실측) | 설치 스크립트에서 선택 제공 |
142
+ | MachinePolicy/UserPolicy `AllSigned` | Bypass보다 우선 | `Get-ExecutionPolicy -List` 확인 후 명확한 오류/문서. 엔터프라이즈는 지원 범위 밖 |
143
+ | AppLocker/WDAC | 경로/게시자 기반 차단 가능 | 사용자 폴더 실행이 막힌 환경은 지원 불가로 명시 |
144
+ | SAC | 서명 없는 exe 차단. 스크립트는 이 PC에서 통과 | exe 배포 금지, 스크립트 유지. 추후 정책 변화 모니터 |
145
+ | Defender | 실시간 검사. 숨김/자동시작 패턴에 민감 | 난독화/EncodedCommand/다운로드 금지, 소스 공개, 오탐 신고 |
146
+ | SmartScreen | zip/`.cmd` 다운로드 시 경고 가능 | “추가 정보 → 실행” 안내, 체크섬 제공, 가능하면 서명 |
147
+ | VBScript 엔진 | 이 PC에서 꺼짐 | `start.vbs` 사용 금지. `.cmd`/`.lnk` 사용 |
148
+ | Store `pwsh` 경로 | 사용자/버전별 경로, 업데이트 시 변동 | 5.1 `powershell.exe` 고정 경로 사용 권장 |
149
+ | 자식 프로세스 | 부모와 별개로 정책 적용 | 모든 spawn에 Bypass를 명시하거나, spawn 자체를 제거 |
150
+
151
+ ### 4.3 PowerShell 5.1 vs 7 — 권장안
152
+
153
+ **권장: 위젯은 Windows PowerShell 5.1을 기준으로 하고, 7은 선택적 가속/호환으로 둔다.**
154
+
155
+ 근거:
156
+ - 5.1은 Windows 10/11에 기본 내장. 일반 사용자에게 추가 설치를 요구하지 않는다.
157
+ - WPF/STA, Windows Forms, `Start-Job`, `WScript.Shell` COM이 5.1에서 동작함을 실측했다.
158
+ - 현재 코드의 PS7 의존은 3곳뿐이다.
159
+ - `Start-ThreadJob` → `Start-Job` 또는 runspace로 대체.
160
+ - `ConvertFrom-Json -AsHashtable` → PSCustomObject→hashtable 변환 또는 `JavaScriptSerializer`.
161
+ - `Start-Process pwsh` → `$PSHOME\powershell.exe` 또는 인프로세스 팝업으로 대체.
162
+ - `pwsh` 7을 요구하면 Store 설치/경로/업데이트 변수가 추가되고, 일반 사용자 마찰이 커진다.
163
+
164
+ 비용:
165
+ - `Start-Job`은 프로세스 기반이라 `Start-ThreadJob`보다 느리다. 60초 poll/5초 초기 sync에는 충분하지만, 시작 지연을 측정해야 한다.
166
+ - 5.1의 `Set-Content -Encoding UTF8`은 BOM을 쓴다. JSON/로그를 다른 프로그램이 읽을 경우 BOM 없는 UTF-8로 쓰는 helper가 필요하다.
167
+ - `ConvertTo-Json`/`ConvertFrom-Json`의 깊이/이스케이프 차이를 테스트해야 한다.
168
+ - 5.1에서는 `Get-Content -Encoding UTF8`이 BOM을 요구하는 경로가 있으므로, 스크립트 자체는 UTF-8 BOM으로 유지한다.
169
+
170
+ **대안:** v1은 `pwsh` 7 필수로 두고, v1.1에서 5.1 지원. 이 경우 “PowerShell 7 설치”가 최소 품의 일부가 되어 일반 사용자 이탈 가능성이 높다. 권장하지 않는다.
171
+
172
+ ### 4.4 포터블 실행·상주 변수
173
+
174
+ | 변수 | 문제 | 대응 |
175
+ |---|---|---|
176
+ | 콘솔 창 깜빡임 | `powershell.exe`는 콘솔 앱 | 일상 실행은 `.lnk`(`WindowStyle=7`, `-WindowStyle Hidden`). 최초 1회는 `start.cmd` 허용 |
177
+ | 바로가기 경로 | 앱 폴더 이동 시 깨짐 | `install-startup.cmd`가 현재 경로로 `.lnk` 재생성. 이동 후 재실행 안내 |
178
+ | 시작 프로그램 | 사용자별 Startup 폴더 | `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`에 `.lnk`. 레지스트리/작업 스케줄러 불필요 |
179
+ | 관리자 권한 | 설치/레지스트리 없음 | 요구하지 않음. 실패 시 사용자 폴더에서 재실행 |
180
+ | 단일 인스턴스 | 중복 실행 | 기존 `Local\DSHNotify` 뮤텍스 유지 |
181
+ | 앱 폴더 쓰기 권한 | Program Files/읽기 전용 | 기본은 사용자 폴더. 쓰기 불가 시 `%LOCALAPPDATA%\DSH Notify`로 데이터 폴백 |
182
+ | 데이터/설정 | 레지스트리 오염 | 앱 폴더 `data\` JSON. 제거 시 폴더 삭제로 끝 |
183
+ | 업그레이드 | 설정 유실 | `data\` 보존, 앱 코드만 교체. 버전 파일 기록 |
184
+ | 종료 | 백그라운드 잔존 | 트레이 종료 메뉴. 제거 스크립트가 프로세스 종료 후 파일 삭제 |
185
+ | 절전/재개 | 타이머 지연 | `DispatcherTimer`는 재개 후 계속. provider poll이 따라잡음. 필요 시 `SystemEvents.PowerModeChanged`에서 즉시 poll |
186
+ | Explorer 재시작 | 트레이 아이콘 소실 | `TaskbarCreated` 메시지 재등록(후속) |
187
+ | 다중 모니터/DPI | 카드 위치 이탈 | 기존 `Get-ClampedPosition` 유지. 100%/125%/150% 테스트 |
188
+ | 한글/공백 경로 | 인용 누락 | 모든 경로 `-LiteralPath`, `"..."`, `WorkingDirectory` 명시 |
189
+ | Defender 오탐 | 숨김+자동시작 | 소스 공개, 난독화 금지, README에 동작 설명, 오탐 신고, 선택적 코드 서명 |
190
+ | 코드 서명 | 비용/인증서 | v1 필수 아님. 서명 시 `.ps1` Authenticode + zip 배포. SAC는 스크립트에 영향 적음 |
191
+
192
+ ### 4.5 “최소 품” 사용자 여정(권장)
193
+
194
+ ```
195
+ 1) GitHub Releases에서 dsh-notify-widget-v0.1.0-win-x64.zip 다운로드
196
+ 2) 사용자 폴더에 압축 해제 (예: %LOCALAPPDATA%\Programs\DSH Notify)
197
+ 3) install-startup.cmd 더블클릭
198
+ - 필요 시 ExecutionPolicy Bypass로 앱 시작
199
+ - Startup 폴더에 바로가기 생성
200
+ - 트레이에 위젯 표시
201
+ 4) 이후 로그인 시 자동 시작
202
+ 5) 제거: uninstall-startup.cmd → 바로가기/프로세스 정리 → 폴더 삭제
203
+ ```
204
+
205
+ - `start.cmd`: 앱만 실행(설치 없이 체험).
206
+ - `install-startup.cmd`: 바로가기 생성 + 앱 시작.
207
+ - `uninstall-startup.cmd`: 바로가기 제거 + 프로세스 종료.
208
+ - `README.txt`: “Developer Mode 불필요”, “관리자 권한 불필요”, “데이터는 폴더 안”, “문제 시 로그 위치”.
209
+ - `LICENSE`, `THIRD-PARTY-NOTICES.md`, `SHA256SUMS.txt` 포함.
210
+
211
+ ---
212
+
213
+ ## 5. 배포 아키텍처
214
+
215
+ ### 5.1 저장소 전략
216
+
217
+ **권장: 모노레포 + 독립 패키지/독립 릴리스.**
218
+
219
+ ```
220
+ dsh-notify/
221
+ packages/
222
+ web-plugin/ # npm 배포
223
+ package.json
224
+ cordis.patch.yml
225
+ lib/index.js
226
+ lib/client.js
227
+ locale/{en,ko}.json
228
+ market/index.json
229
+ icon.svg
230
+ README.md
231
+ LICENSE
232
+ windows-widget/ # GitHub Releases zip
233
+ app/
234
+ start.cmd
235
+ install-startup.cmd
236
+ uninstall-startup.cmd
237
+ README.txt
238
+ LICENSE
239
+ THIRD-PARTY-NOTICES.md
240
+ docs/
241
+ .github/workflows/
242
+ ```
243
+
244
+ - 공유 런타임 코드는 없다. 공유하는 것은 문서/CI/릴리스 규칙뿐이다.
245
+ - 태그는 독립: `web-v0.1.0`, `widget-v0.1.0`.
246
+ - 나중에 커뮤니티가 커지면 `web-plugin`만 별도 저장소로 분리 가능. 처음부터 2개 저장소를 두면 CI/이슈/문서 중복이 크다.
247
+ - npm 패키지 이름은 `@local`을 쓰지 않는다. 예: `@nolit/dsh-notify` 또는 unscoped `dsh-notify-web`. `private: true`를 제거한다.
248
+
249
+ ### 5.2 웹 플러그인 배포 채널
250
+
251
+ | 채널 | 용도 | 요건 |
252
+ |---|---|---|
253
+ | npm | 주 배포 | `npm publish`, `files` 화이트리스트, `engines.dsh`, `README`, `LICENSE` |
254
+ | DSH Plugin Market | 발견/설치 | `market/index.json`(format 1), `icon.svg`, `locale`, 버전 동기화 |
255
+ | GitHub Release | 오프라인/검증 | npm tarball 또는 zip, SHA256, changelog |
256
+
257
+ **공개 패키지 체크리스트:**
258
+
259
+ - [ ] `private` 제거, `license` 명시(MIT/Apache-2.0)
260
+ - [ ] `dsh.manifestVersion: 1`, `dsh.bundle.patch`, `dsh.client.platform: 'web'`
261
+ - [ ] `main`, `exports['./client']`, `files`에 `lib/client.js` 포함
262
+ - [ ] `lib/index.js`는 no-op host (파일/포트/네트워크 없음)
263
+ - [ ] `engines.dsh` 범위 고정, `peerDependencies` cordis
264
+ - [ ] `market/index.json`의 `versions`/`engines` 동기화
265
+ - [ ] `locale/en.json`, `locale/ko.json`, `icon.svg`
266
+ - [ ] 절대경로/사용자명/포트 상수 없음
267
+ - [ ] `npm pack` 내용 검사(불필요한 파일/비밀 없음)
268
+ - [ ] 깨끗한 DSH 프로필에서 설치/활성/비활성/제거 테스트
269
+ - [ ] DSH 0.2.1-rc.2 및 최신 호환 버전에서 로드 테스트
270
+
271
+ **“공식 규격” 주의:** `dsh.client`/Cordis 규격 준수는 가능하지만, DSH 공식 마켓 등재는 별도 심사·정책에 달려 있다. “공식”이라는 표현은 마켓 등재 이후에 쓰는 것이 안전하다.
272
+
273
+ ### 5.3 윈도우 위젯 배포 채널
274
+
275
+ | 채널 | 용도 | 요건 |
276
+ |---|---|---|
277
+ | GitHub Releases zip | 주 배포 | `dsh-notify-widget-vX.Y.Z-win-x64.zip`, SHA256, README |
278
+ | winget/scoop | 후속 | 초기에는 수동 zip이 마찰이 적다. 패키지 정의 유지비용 존재 |
279
+ | 코드 서명 | 선택 | 인증서 구매/갱신 필요. v1 필수 아님 |
280
+
281
+ **zip 내부:**
282
+
283
+ ```
284
+ DSH Notify Widget/
285
+ app/
286
+ core.ps1
287
+ config.ps1
288
+ providers/
289
+ ui/
290
+ assets/ # 라이선스 정리된 폰트/아이콘만
291
+ start.cmd
292
+ install-startup.cmd
293
+ uninstall-startup.cmd
294
+ README.txt
295
+ LICENSE
296
+ THIRD-PARTY-NOTICES.md
297
+ SHA256SUMS.txt
298
+ ```
299
+
300
+ **제외:** `DSH-Notify.exe`, `build-exe.ps1`(선택 빌드), `start.vbs`, 개발용 테스트/캐시, 사용자 `config.json`.
301
+
302
+ ### 5.4 릴리스·버전 규칙
303
+
304
+ - 웹 플러그인: semver. `0.1.0` → `0.2.0` 기능, `0.1.1` 수정. `engines.dsh`는 최소/최대 범위.
305
+ - 위젯: semver. 앱 UI/설정 변경은 minor, 버그 수정은 patch.
306
+ - 두 패키지는 서로의 버전을 참조하지 않는다. 문서에서 “함께 쓰면 좋다” 정도만 안내.
307
+ - 체인지로그: `CHANGELOG.md` 또는 GitHub Release notes.
308
+ - 아티팩트 체크섬: SHA256. zip과 npm tarball 모두.
309
+
310
+ ---
311
+
312
+ ## 6. 최소 품 패키징
313
+
314
+ ### 6.1 웹 플러그인
315
+
316
+ - 설치: `dsh plugin --profile web add @nolit/dsh-notify` 또는 DSH Plugins 페이지.
317
+ - 활성화: bundle 선택. 실패 시 Plugins 페이지에 오류 표시.
318
+ - 업데이트: `dsh plugin ... add @nolit/dsh-notify@latest` 또는 페이지 Update.
319
+ - 제거: bundle 비활성/제거. `localStorage` 설정은 남으므로 “초기화” 버튼 제공.
320
+ - 요구 사항: DSH 0.2.1-rc.2+, Chrome/Edge 최신, HTTPS 또는 loopback.
321
+ - 권한: 첫 사용 시 패널에서 알림/소리 활성화. 자동 프롬프트 없음.
322
+
323
+ ### 6.2 윈도우 위젯
324
+
325
+ - 요구 사항: Windows 10/11, PowerShell 5.1(내장), 관리자 권한 불필요.
326
+ - 실행: `start.cmd` 또는 `install-startup.cmd`.
327
+ - 자동 시작: 사용자 Startup `.lnk`만. 레지스트리/작업 스케줄러 없음.
328
+ - 데이터: `app\data\` (쓰기 불가 시 `%LOCALAPPDATA%\DSH Notify`).
329
+ - 제거: `uninstall-startup.cmd` → 폴더 삭제. 레지스트리 흔적 없음(최근 문서/MRU 제외).
330
+ - 업그레이드: 새 zip을 같은 폴더에 덮어쓰기. `data\` 보존.
331
+ - 문제 진단: `app\data\logs\core.log`, “정보” 패널에 PowerShell 버전/ExecutionPolicy 표시.
332
+
333
+ ### 6.3 실행 스크립트 설계(개념)
334
+
335
+ `start.cmd`:
336
+
337
+ ```bat
338
+ @echo off
339
+ setlocal
340
+ set "APP=%~dp0app"
341
+ set "PS=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
342
+ start "" "%PS%" -NoProfile -ExecutionPolicy Bypass -Sta -WindowStyle Hidden -File "%APP%\core.ps1"
343
+ ```
344
+
345
+ `install-startup.cmd`:
346
+
347
+ ```bat
348
+ @echo off
349
+ setlocal
350
+ call "%~dp0start.cmd"
351
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0app\install-shortcut.ps1" -Startup
352
+ ```
353
+
354
+ - `start.cmd`는 최초 1회 콘솔이 보일 수 있다. 일상 실행은 생성된 `.lnk`를 쓴다.
355
+ - `.lnk` Target은 `powershell.exe` 고정 경로, Arguments에 `-ExecutionPolicy Bypass -Sta -NoProfile -WindowStyle Hidden -File "..."`.
356
+ - `WindowStyle=7`(최소화) + `-WindowStyle Hidden`으로 콘솔 깜빡임 최소화.
357
+ - `install-shortcut.ps1`은 `WScript.Shell` COM으로 `.lnk` 생성(5.1/7 모두 동작 확인). 실패 시 `start.cmd`만으로도 사용 가능.
358
+ - 자식 프로세스를 만들 경우 반드시 `-ExecutionPolicy Bypass`와 `$PSHOME\powershell.exe`를 쓴다. 가능하면 자식 spawn을 제거하고 인프로세스 팝업으로 대체한다.
359
+
360
+ ---
361
+
362
+ ## 7. 실행 로드맵
363
+
364
+ ### Phase 0 — 정책·라이선스·호환성 스파이크 (1–1.5일)
365
+
366
+ **목표:** 공개 배포 가능 여부를 먼저 확정한다.
367
+
368
+ - BYO 정책 반영: 기본 시스템 폰트/중립 아이콘/합성음, 배포물에서 Digital-7·DeepSeek 로고·`ding.wav` 제외.
369
+ - `LICENSE`(MIT) 추가 완료, 사용자 주입 에셋 면책 문구 확인.
370
+ - 5.1 포팅 스파이크: `Start-ThreadJob`→`Start-Job`, `-AsHashtable` 제거, `core.ps1` 1회 실행.
371
+ - 깨끗한 Windows 11 VM(관리자 아님, 기본 ExecutionPolicy, SAC on)에서 `start.cmd` 테스트.
372
+ - 웹 플러그인: 빈 DSH 프로필에서 `dsh.client` 로드/슬롯/이벤트 4종 스모크.
373
+
374
+ **종료 조건:** 라이선스 문제 해소, 5.1에서 앱 10초 스모크 통과, 웹 플러그인 이벤트 로그 확인.
375
+
376
+ ### Phase 1 — 웹 플러그인 완성 (2–3.5일)
377
+
378
+ - 이벤트 구독/필터/쿨다운.
379
+ - Notification/Web Audio/권한/토스트/배지.
380
+ - Web Locks 리더, 재연결/가시성 복원.
381
+ - 단위 테스트 + 수동 E2E.
382
+ - npm 패키징/`npm pack` 검사.
383
+
384
+ **종료 조건:** 수용 기준 A1–A9 통과, 깨끗한 프로필 설치/제거 성공.
385
+
386
+ ### Phase 2 — 위젯 5.1 포팅·포터블 패키징 (1.5–2.5일)
387
+
388
+ - 5.1 호환 코드(3곳) 수정.
389
+ - `start.cmd`/`install-startup.cmd`/`uninstall-startup.cmd`.
390
+ - Startup `.lnk`, 단일 인스턴스, 데이터 폴더.
391
+ - 5.1/7 양쪽 테스트, WPF/DPI/다중 모니터/한글 경로.
392
+ - `DSH-Notify.exe`/`start.vbs` 제거.
393
+
394
+ **종료 조건:** 관리자 권한 없이 설치/자동 시작/종료/제거, 재부팅 후 자동 시작.
395
+
396
+ ### Phase 3 — 릴리스 엔지니어링 (1–1.5일)
397
+
398
+ - CI: Windows runner에서 5.1/7 테스트, `npm pack`, zip 생성, SHA256.
399
+ - `market/index.json`, `icon.svg`, `locale`, README/CHANGELOG.
400
+ - GitHub Release 초안, 설치 문서, 문제 해결 문서.
401
+ - `npm publish --dry-run` / 실제 publish.
402
+
403
+ **종료 조건:** 아티팩트 재현 가능, 체크섬 일치, 문서만 보고 설치 가능.
404
+
405
+ ### Phase 4 — 클린 VM·브라우저 매트릭스 검증 (1–2일)
406
+
407
+ - Windows 11 clean, Windows 10, SAC on/off, Defender on.
408
+ - Chrome/Edge 최신, Firefox best-effort.
409
+ - DSH 포트 3080/3099/임의 포트, `localhost`/`127.0.0.1`.
410
+ - 권한 허용/거부/차단, 다중 탭, 절전/재개, BFCache.
411
+ - 오탐/차단 로그 수집.
412
+
413
+ **종료 조건:** 치명 결함 0, 알려진 한계 문서화, 릴리스 승인.
414
+
415
+ **총 6–9일.** Phase 0에서 라이선스/5.1이 막히면 일정이 늘어난다.
416
+
417
+ ### 기술적 병목과 해결
418
+
419
+ | 병목 | 왜 | 해결 |
420
+ |---|---|---|
421
+ | 5.1 포팅 | `Start-ThreadJob`/`-AsHashtable`/spawn 정책 | 3곳 교체, 양쪽 호스트 테스트, `Start-Job` 지연 측정 |
422
+ | 탭 discard | 브라우저 정책, 서비스 워커 푸시 없음 | 범위를 “사용 중 알림”으로 한정, resume 시 세션 목록 재동기화 |
423
+ | 알림 권한/오디오 | 출처/제스처/OS 정책 | 배지/패널/테스트 버튼, 토스트 폴백 |
424
+ | DSH API 변경 | 0.2.x 초기 버전 | `engines.dsh` 고정, 이벤트 4종 테스트, 플러그인 격리 |
425
+ | Defender 오탐 | 숨김+자동시작 | 소스 공개, 난독화 금지, 오탐 신고, 선택적 서명 |
426
+ | 라이선스 | 폰트/로고/사운드 | 공개 전 교체가 가장 빠름 |
427
+ | 테스트 매트릭스 | OS/브라우저/DPI/권한 조합 | Phase 0에서 VM 템플릿, 자동화 가능한 것만 CI |
428
+
429
+ ---
430
+
431
+ ## 8. 리스크·법무
432
+
433
+ ### 8.1 공개 배포 전 반드시 해결
434
+
435
+ | 자산 | 현재 상태 | 공개 배포 리스크 | 권장 |
436
+ |---|---|---|---|
437
+ | `ui/assets/digital-7.ttf` | `freeware for home using only`, freeware software use credit needed, commercial license $24.95 | 오픈소스 패키지 재배포 조건 불명확 | **배포물에서 제외**. 기본은 시스템 폰트, 사용자가 `fonts/digital-7.ttf`를 직접 주입할 때만 적용 |
438
+ | `ui/assets/deepseek-logo*.png`, `ui/icons/deepseek-*.ico` | README에 “DeepSeek 상표, 로컬 트레이만” | 상표 재배포/공식 앱 오인 | **배포물에서 제외**. 중립 시계 아이콘 기본, 사용자 `icons/custom.ico` 지원 |
439
+ | `notify/ding.wav` | 라이선스 파일 없음 | 재배포 권리 불명 | **배포물에서 제외**. 웹은 Web Audio 합성, 위젯은 시스템 기본음 |
440
+ | 루트 `LICENSE` | 없음 | 오픈소스 배포 불가 | **MIT LICENSE 추가 완료**. 사용자 주입 에셋 면책 명시 |
441
+ | `DSH-Notify.exe` | 서명 없음, SAC 차단 | 공개 배포 시 혼란 | 공개 zip에서 제외. 선택적 서명 빌드로 분리 |
442
+ | `start.vbs` | VBScript 엔진 꺼짐 | 동작 불가 | 공개 패키지에서 제외 |
443
+
444
+ ### 8.2 기술·운영 리스크
445
+
446
+ | 리스크 | 확률 | 영향 | 완화 |
447
+ |---|---|---|---|
448
+ | 사용자가 “항상 알림”으로 오해 | 높음 | 신뢰 하락 | README/UI에 “DSH 탭이 열려 있을 때” 명시 |
449
+ | 알림 권한 거부 | 높음 | 알림 없음 | 배지/패널/토스트, 명확한 안내 |
450
+ | 오디오 무음 | 높음 | 인지 약함 | 테스트 버튼, 소리 상태, 토스트 |
451
+ | 5.1 포팅 회귀 | 중 | 앱 오동작 | 5.1/7 양쪽 테스트, Phase 0 스파이크 |
452
+ | ExecutionPolicy AllSigned | 중 | 실행 불가 | 사전 감지/문서, 엔터프라이즈 미지원 명시 |
453
+ | Defender 오탐 | 중 | 실행 차단 | 소스 공개, 서명, 오탐 신고 |
454
+ | DSH 마켓 심사 거절 | 중 | 배포 채널 제한 | npm/GitHub 우선, 마켓은 후속 |
455
+ | DSH API 변경 | 중 | 플러그인 깨짐 | 버전 고정/테스트/격리 |
456
+ | 다중 탭 중복 | 중 | 알림 피로 | Web Locks + tag + 쿨다운 |
457
+ | 라이선스 위반 | 낮음/높음 | 법적 문제 | Phase 0에서 교체 |
458
+
459
+ ---
460
+
461
+ ## 9. 결정 필요 항목
462
+
463
+ | # | 결정 | 권장 |
464
+ |---|---|---|
465
+ | 1 | 위젯 PowerShell 기준 | **5.1 기준 + 7 선택** |
466
+ | 2 | 웹 플러그인 배포 이름 | `@nolit/dsh-notify` (가능하면 scope) |
467
+ | 3 | 저장소 | **모노레포 독립 패키지**, 필요 시 분리 |
468
+ | 4 | 위젯 배포 형태 | **GitHub Releases zip** (exe 없음) |
469
+ | 5 | 자동 시작 | 사용자 Startup `.lnk`만 |
470
+ | 6 | 폰트/로고/사운드 | **BYO 정책 확정**: 기본 시스템/중립/합성, 배포물에서 제외 |
471
+ | 7 | 코드 서명 | v1 선택, 없이 출시 |
472
+ | 8 | 웹 알림 범위 | “DSH 사용 중”으로 명시 |
473
+ | 9 | 공식 마켓 | npm/GitHub 먼저, 마켓은 후속 |
474
+ | 10 | 5.1 포팅 시점 | Phase 0 (출시 전) |
475
+
476
+ ---
477
+
478
+ ## 10. 부록
479
+
480
+ ### A. 실측 명령 요약
481
+
482
+ ```powershell
483
+ Get-ExecutionPolicy -List
484
+ Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' |
485
+ Select-Object VerifiedAndReputablePolicyState
486
+ Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled,IsTamperProtected
487
+ ```
488
+
489
+ MOTW/Bypass 검증(임시 파일):
490
+
491
+ ```powershell
492
+ $tmp = Join-Path $env:TEMP 'probe.ps1'
493
+ Set-Content $tmp "Write-Output 'PROBE_OK'"
494
+ Set-Content $tmp -Stream Zone.Identifier "[ZoneTransfer]`r`nZoneId=3"
495
+ powershell.exe -NoProfile -File $tmp # 차단
496
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmp # 통과
497
+ ```
498
+
499
+ 5.1 호환성:
500
+
501
+ ```powershell
502
+ powershell.exe -NoProfile -Sta -Command 'Add-Type -AssemblyName PresentationFramework; [System.Windows.Window]::new()'
503
+ powershell.exe -NoProfile -Command 'Start-Job { "JOB_OK" } | Receive-Job -Wait'
504
+ powershell.exe -NoProfile -Command '(Get-Command Start-ThreadJob -ErrorAction SilentlyContinue) -eq $null'
505
+ ```
506
+
507
+ ### B. 공개 릴리스 체크리스트
508
+
509
+ **웹 플러그인**
510
+ - [ ] `private` 제거, LICENSE/README/CHANGELOG
511
+ - [ ] `dsh.client`, `lib/client.js`, `cordis.patch.yml`, `market/index.json`, `locale`, `icon.svg`
512
+ - [ ] `npm pack` 내용 검사, 절대경로/사용자명/포트 없음
513
+ - [ ] 깨끗한 DSH 프로필 설치/활성/비활성/제거
514
+ - [ ] 알림/소리/권한/다중 탭/재연결 E2E
515
+ - [ ] `engines.dsh` 호환성 확인
516
+
517
+ **위젯**
518
+ - [ ] 5.1/7 스모크, WPF/DPI/다중 모니터/한글 경로
519
+ - [ ] `start.cmd`/`install-startup.cmd`/`uninstall-startup.cmd`
520
+ - [ ] Startup `.lnk` 생성/제거, 관리자 권한 없음
521
+ - [ ] 데이터 폴더 쓰기/읽기 전용 폴백
522
+ - [ ] `DSH-Notify.exe`, `start.vbs` 제외
523
+ - [ ] LICENSE/THIRD-PARTY-NOTICES/SHA256
524
+ - [ ] Defender/SAC/ExecutionPolicy 시나리오 문서화
525
+
526
+ ### C. 참고
527
+
528
+ - 웹 플러그인 단독 사양: [2026-10-23-web-notify-plugin-spec.md](2026-10-23-web-notify-plugin-spec.md)
529
+ - 기존 앱 구조: [README.md](../README.md)
530
+ - 사이드바 매니저 배포 선례: `C:\Users\nolit\Workspace\projects\dsh-workspace\dsh-sidebar-manager` (npm + `market/index.json` + `locale` + `icon.svg`)