Initial Commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Specification Quality Checklist: 화면 캡쳐 → 챗 첨부
|
||||
|
||||
**Purpose**: 계획(plan) 넘어가기 전 spec 완성도·품질 확인
|
||||
**Created**: 2026-08-06
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [~] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [~] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- **"No implementation details" (~)**: 스니펫 기능과 동일 — "기존 아키텍처(WebView2 브릿지·멀티핫키·Composer)에 얹는 캡쳐 파이프라인"이 사용자가 못박은 전제라 아키텍처 참조(핫키명·오버레이·브릿지)가 의도적으로 들어감. 다만 구현 앵커는 **Assumptions**에 몰아뒀고, FR·SC는 사용자 관점 동작/결과로 기술.
|
||||
- 나머지 전부 통과. NEEDS CLARIFICATION 없음(브레인스토밍서 A/B 결정 다 확정). `/speckit-clarify` 건너뛰고 `/speckit-plan` 가도 됨.
|
||||
- plan 단계에서 `2_frontend/docs-lib/` 라이브러리 문서 선참조 규칙 적용. base64 이미지 전달 크기 천장·고DPI 보정은 plan/research에서 결정.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Contract: 캡쳐 이미지 브릿지 + 챗 첨부
|
||||
|
||||
## C#→JS 메시지 (신규)
|
||||
|
||||
기존 `SendToWeb`(`CoreWebView2.PostWebMessageAsJson`) 채널 재사용.
|
||||
|
||||
| 방향 | 메시지 | 처리 |
|
||||
|---|---|---|
|
||||
| C#→JS | `{type:"capture.image", dataUrl}` | 프론트가 캡쳐 이미지를 챗 입력창 첨부로 받음 |
|
||||
|
||||
- `dataUrl`: `data:image/png;base64,...` (PNG).
|
||||
- App 캡쳐 완료 순서: `ShowPalette()` → `NavigateApp("/snap/new")` → `SendCaptureImage(dataUrl)`. (navigate·소환은 기존 배선.)
|
||||
- 기존 메시지(navigate·paste.target·window.*·snippets.* 등)와 같은 리스너 허브에서 `type`으로 분기.
|
||||
|
||||
## C# 표면 (신규/확장)
|
||||
|
||||
- `Shell/Platform/ScreenCapture.cs` — `static byte[] CapturePng(int x, int y, int width, int height)`: 물리 좌표 영역을 `CopyFromScreen`으로 PNG 바이트. (좌표 변환은 호출측=오버레이 몫.)
|
||||
- `Views/CaptureOverlayWindow` — 드래그 선택 + DIP→물리 변환 + `ScreenCapture.CapturePng` 호출 → `event Action<string> Captured`(base64 dataUrl) / `event Action Cancelled`.
|
||||
- `App.xaml.cs` — 핫키 id `0x1002`(Ctrl+Shift+9) → 오버레이 표시. `Captured` → 위 순서 실행. `Cancelled` → 무시.
|
||||
- `PaletteWindow.SendCaptureImage(string dataUrl) => Web.SendToWeb(new { type = "capture.image", dataUrl });`
|
||||
|
||||
## 프론트 계약 (`bridgeNavigate.ts`)
|
||||
|
||||
```ts
|
||||
// 마지막 캡쳐 이미지 dataUrl 을 1회 소비(마운트 레이스 대비). 없으면 "".
|
||||
export function consumePendingCaptureImage(): string
|
||||
// capture.image 수신 → pendingCaptureImage 갱신 + window.dispatchEvent(new CustomEvent("bridge:captureImage", { detail: { dataUrl } }))
|
||||
```
|
||||
|
||||
## 프론트 계약 (`Composer.tsx`)
|
||||
|
||||
- 상태 `imageAttachments: string[]`(dataUrl). 마운트 시 `consumePendingCaptureImage()` push + `bridge:captureImage` 구독 push. X 제거. (data-model.md 규칙.)
|
||||
- v1 전송: 텍스트만. 이미지 첨부는 미전송 + 전송 후 정리.
|
||||
|
||||
## 순수 브라우저(dev)
|
||||
|
||||
`SendToWeb`/`chrome.webview` 없음 → capture.image 안 옴, 캡쳐 기능 비활성(데스크톱 전용). 기존 챗은 그대로.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Data Model: 화면 캡쳐 → 챗 첨부 (Phase 1)
|
||||
|
||||
캡쳐 이미지는 저장소 없이 메모리→data URL로만 흐른다. v1 영속 없음.
|
||||
|
||||
## 엔티티
|
||||
|
||||
### 캡쳐 이미지 (Capture Image)
|
||||
선택 영역의 화면 스냅샷 한 장.
|
||||
|
||||
| 표현 | 형태 | 설명 |
|
||||
|---|---|---|
|
||||
| C# 내부 | `Bitmap` → PNG `byte[]` | `CopyFromScreen` 결과 |
|
||||
| 전달 | `dataUrl: string` | `data:image/png;base64,...` |
|
||||
| 프론트 첨부 | `string`(dataUrl) | Composer `imageAttachments` 배열의 한 항목 |
|
||||
|
||||
- v1엔 파일/DB 영속 없음. 챗 첨부 상태로만 존재.
|
||||
|
||||
## 프론트 상태
|
||||
|
||||
### Composer 이미지 첨부 (`Composer.tsx`)
|
||||
- 신규 상태: `imageAttachments: string[]` — 각 항목은 PNG data URL. **텍스트 첨부(`attachments`)와 별개.**
|
||||
- 렌더: 썸네일 `<img>` + 제거 `X` 버튼(칩). 텍스트 첨부 칩 옆/위.
|
||||
- 추가 규칙:
|
||||
- 마운트 시 `consumePendingCaptureImage()` 값 있으면 push(누락 방지).
|
||||
- `bridge:captureImage` 이벤트 수신 시 push(누적 — 여러 번 캡쳐 = 여러 첨부, FR-008).
|
||||
- X → 해당 항목 제거(FR-007).
|
||||
- **전송 시(v1)**: 텍스트만 전송(기존 그대로). 이미지 첨부는 **전송하지 않고**, 전송 후 `imageAttachments`는 비운다(텍스트 첨부와 동일하게 정리 — 안 쓰지만 쌓이지 않게). ← FR-009. 백엔드 붙으면 이 지점에서 payload에 실어 보내도록 확장.
|
||||
|
||||
## 브릿지 모듈 상태 (`bridgeNavigate.ts`)
|
||||
|
||||
- `pendingCaptureImage: string`("" = 없음) — 마지막 수신 캡쳐 dataUrl(마운트 레이스 대비).
|
||||
- `consumePendingCaptureImage(): string` — 값 반환 후 `""`로 비움(read-once).
|
||||
- `capture.image` 수신 시: `pendingCaptureImage = dataUrl` + `bridge:captureImage`(detail: `{dataUrl}`) 발생.
|
||||
- (참고) pasteTarget은 "지속 표시"라 get, 캡쳐는 "1회 소비"라 consume — 세만틱만 다름.
|
||||
|
||||
## 상태 전이
|
||||
|
||||
캡쳐 없음 → (핫키·드래그·확정) → dataUrl 생성/전달 → Composer 이미지 첨부 1개 추가 → (X 제거 | 전송 시 정리) → 없음.
|
||||
취소(Esc·무효선택) → 아무 상태 변화 없음.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Implementation Plan: 화면 캡쳐 → 챗 첨부 (Screen Capture to Chat)
|
||||
|
||||
**Branch**: `002-capture-to-chat` | **Date**: 2026-08-06 | **Spec**: [spec.md](./spec.md)
|
||||
|
||||
**Input**: Feature specification from `specs/002-capture-to-chat/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
전역 핫키 `Ctrl+Shift+9` → 전체화면 투명 오버레이에서 사각형을 드래그해 그 영역을 캡쳐 → 이미지를 챗(snap) 입력창에 썸네일 첨부로 붙인다. **v1은 캡쳐 파이프라인만** — 비전 분석·웹서치·이미지 백엔드 전송은 다음 phase.
|
||||
|
||||
**기술 접근(재사용 우선):** 실질 신규는 **C# 캡쳐 오버레이 창 + 화면 픽셀 캡쳐**와 **Composer 이미지 첨부 UI** 둘뿐. 핫키는 이미 멀티핫키(`HotKeyService`)라 등록만 추가(id `0x1002`). 이미지 전달은 기존 C#→JS `SendToWeb`(`PostWebMessageAsJson`) 채널에 `capture.image` 메시지 한 줄. 프론트 수신은 `bridgeNavigate` 허브 + 마운트 레이스 대비 모듈 보관+이벤트(pasteTarget 패턴 그대로). 팔레트 소환·`/snap/new` navigate도 기존 배선.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: TypeScript 5 / React 18 (Vite) · C# / .NET 8 (WPF, WebView2)
|
||||
|
||||
**Primary Dependencies**: (프론트) 기존 — 신규 없음. (C#) **`System.Drawing.Common`(신규)** — `Graphics.CopyFromScreen`으로 화면 캡쳐. Microsoft.Web.WebView2(기존).
|
||||
|
||||
**Storage**: 없음(캡쳐 이미지는 메모리 → data URL, 챗 첨부 상태로만 존재. v1 영속 없음).
|
||||
|
||||
**Testing**: vitest(프론트: Composer 이미지 첨부 상태·capture.image 수신 로직) · xUnit(C#: 화면 캡쳐 좌표/DPI 변환 순수부분). 오버레이·CopyFromScreen 실물은 헤드리스 검증 불가 → 수동.
|
||||
|
||||
**Target Platform**: Windows 데스크톱(WebView2 런처). 순수 브라우저(dev)엔 캡쳐 없음(데스크톱 전용).
|
||||
|
||||
**Project Type**: 기존 웹앱(2_frontend) + 데스크톱 호스트(3_windowsApp)에 기능 추가.
|
||||
|
||||
**Performance Goals**: 핫키→오버레이 즉시. 드래그 종료→챗 첨부까지 체감 몇 초 내.
|
||||
|
||||
**Constraints**: 멀티모니터 커버(가상화면 전체). 캡쳐 좌표는 물리 픽셀(오버레이 DIP↔픽셀 배율 변환 필요).
|
||||
|
||||
**Scale/Scope**: 개인 단일 PC. 화면 캡쳐 1장/회, 첨부 누적 소수.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Phase 0 전 통과, Phase 1 후 재확인.*
|
||||
|
||||
`.specify/memory/constitution.md`는 빈 템플릿 → 형식 게이트 없음. **실질 캐논 = `CLAUDE.md`**(재사용 우선·feature 컨벤션·docs-lib 선참조·TDD).
|
||||
|
||||
- ✅ **재사용 우선**: 멀티핫키·SendToWeb 브릿지·팔레트 소환·navigate·bridge 이벤트 패턴·Composer 첨부 UI 재사용. 실질 신규는 캡쳐 오버레이+화면캡쳐, Composer 이미지 첨부.
|
||||
- ✅ **feature 위치**: 캡쳐는 챗(snap) 입력 경로라 프론트 신규는 `features/snap`(Composer) + `lib/bridge`. C#는 `CodeAssist.App/Views`(오버레이) + `CodeAssist.Shell/Platform`(캡쳐 헬퍼).
|
||||
- ⚠️ **의도된 편차 — 이미지가 챗 메시지로 전송되지 않음(v1)**: 첨부는 표시·제거만, 전송은 기존 텍스트 그대로. 백엔드 멀티모달 계약 나올 때 전송 payload와 함께 배선. spec Out of Scope에 명시.
|
||||
|
||||
Gate: **PASS**.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/002-capture-to-chat/
|
||||
├── plan.md # 이 파일
|
||||
├── research.md # Phase 0 — 캡쳐 API·DPI·전달·수신·오버레이 결정
|
||||
├── data-model.md # Phase 1 — 캡쳐 이미지·첨부 상태 + 메시지 shape
|
||||
├── contracts/
|
||||
│ └── bridge-capture.md # capture.image 메시지 + 프론트 첨부 계약
|
||||
├── quickstart.md # Phase 1 — 수동 검증 시나리오
|
||||
└── checklists/requirements.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
3_windowsApp/
|
||||
├── CodeAssist.Shell/
|
||||
│ ├── Platform/ScreenCapture.cs # ★ 신규: 물리좌표 영역 → PNG 바이트(CopyFromScreen)
|
||||
│ └── CodeAssist.Shell.csproj # + System.Drawing.Common
|
||||
├── CodeAssist.App/
|
||||
│ ├── Views/CaptureOverlayWindow.xaml(.cs) # ★ 신규: 전체화면 투명 오버레이·드래그 선택·Esc 취소
|
||||
│ ├── App.xaml.cs # 확장: 3번째 핫키(Ctrl+Shift+9) + 캡쳐 코디네이션
|
||||
│ └── Views/PaletteWindow.xaml.cs # 확장: SendCaptureImage(dataUrl) 패스스루
|
||||
|
||||
2_frontend/src/
|
||||
├── lib/bridge/
|
||||
│ └── bridgeNavigate.ts # 확장: capture.image 수신 → 보관+이벤트(consume 세만틱)
|
||||
└── features/snap/components/
|
||||
└── Composer.tsx # 확장: 이미지 첨부(썸네일+X) + 마운트 시 캡쳐 이미지 수령
|
||||
```
|
||||
|
||||
**Structure Decision**: 기존 2폴더 구조 유지. C# 신규 2(오버레이·캡쳐헬퍼) + 확장 2(App·PaletteWindow). 프론트 확장 2(브릿지·Composer). 새 라우트/화면 없음(기존 `/snap/new` 재사용).
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> Constitution 위반 없음. 편차 1건(v1 이미지 미전송)은 spec Out of Scope에 정당화됨. 별도 표 불필요.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Quickstart: 화면 캡쳐 → 챗 첨부 검증
|
||||
|
||||
end-to-end "돌아간다" 확인 가이드. 구현은 tasks.md/구현 단계.
|
||||
|
||||
## 전제
|
||||
- Windows + WebView2. `3_windowsApp` 빌드(.NET SDK), `2_frontend` `npm install`.
|
||||
- DEBUG 실행은 WebView2 안에 vite dev 물어 띄움 → 브릿지 살아있음.
|
||||
|
||||
## 실행
|
||||
```pwsh
|
||||
# 데스크톱 런처(DEBUG) — CodeAssist.App 빌드/실행
|
||||
```
|
||||
|
||||
## 단위 검증 (호스트 없이)
|
||||
```pwsh
|
||||
cd 2_frontend; npm run test # Composer 이미지 첨부 상태·capture.image 수신(consume) 로직
|
||||
dotnet test 3_windowsApp/CodeAssist.Tests # (있으면) 좌표/DPI 변환 순수부
|
||||
```
|
||||
|
||||
## end-to-end 시나리오 (수동 — 오버레이·CopyFromScreen은 헤드리스 불가)
|
||||
|
||||
1. **오버레이** (AS-1): 아무 창 포커스 → `Ctrl+Shift+9` → 모든 모니터가 어두워지며 캡쳐 오버레이 뜸(십자 커서).
|
||||
2. **캡쳐→첨부** (AS-2): 영역 드래그 후 놓음 → 오버레이 닫히고 앱 창이 떠 **새 대화**로 이동, 입력창에 그 영역 **썸네일 첨부** 붙음.
|
||||
3. **내용 일치** (SC-002): 첨부 썸네일이 드래그로 고른 영역과 내용·경계 일치.
|
||||
4. **취소** (AS-3): 오버레이서 `Esc` 또는 드래그 없이 클릭 → 아무 캡쳐 없이 닫힘, 챗·클립보드 변화 없음.
|
||||
5. **멀티모니터** (AS-5/SC-003): 보조 모니터 영역 드래그 → 그 영역 정확히 캡쳐.
|
||||
6. **제거** (AS-4): 첨부의 X → 그 첨부 사라짐.
|
||||
7. **누적** (AS-6): 한 번 더 캡쳐 → 첨부 하나 더 추가(기존 유지).
|
||||
8. **v1 전송 경계** (FR-009): 이미지 첨부 있는 상태로 메시지 전송 → **텍스트만** 나가고 이미지는 백엔드로 안 감(분석 안 일어남). (백엔드 붙기 전까지 정상.)
|
||||
|
||||
## 참조
|
||||
- 메시지·표면 계약: [contracts/bridge-capture.md](./contracts/bridge-capture.md)
|
||||
- 데이터/상태: [data-model.md](./data-model.md)
|
||||
- 결정 근거(캡쳐 API·DPI·전달): [research.md](./research.md)
|
||||
@@ -0,0 +1,50 @@
|
||||
# 완료 보고: 화면 캡쳐 → 챗 첨부 (002-capture-to-chat)
|
||||
|
||||
**브랜치**: `feat/capture-to-chat` · **작성**: 2026-08-06 · **상태**: 최종 승인 대기
|
||||
|
||||
전역 핫키 `Ctrl+Shift+9` → 전체화면 오버레이 드래그 → 그 영역 캡쳐 → 챗 입력창에 이미지 첨부(썸네일+X). **v1 = 캡쳐 파이프라인만**, 멀티모달 분석·웹서치·이미지 백엔드 전송은 다음 phase.
|
||||
|
||||
## 계획 vs 결과
|
||||
|
||||
| 항목 | 계획 | 결과 |
|
||||
|---|---|---|
|
||||
| Setup (T001-T002) | System.Drawing.Common, PerMonitorV2 manifest | ✅ |
|
||||
| C# 캡쳐 (T004-T008) | 좌표변환 순수함수+CopyFromScreen, 오버레이 창, 3번째 핫키, App 코디, SendCaptureImage | ✅ 좌표변환 xUnit 5개(경계 포함), dotnet test 22/22 |
|
||||
| 프론트 (T003,T009,T010) | capture.image 수신(consume), Composer 이미지 첨부 | ✅ bridgeNavigate/Composer 테스트, 프론트 237/237 |
|
||||
| Polish | 검증·quickstart·report | 검증 ✅ / quickstart 수동(사용자) / 이 문서 |
|
||||
|
||||
## 신규 vs 재사용
|
||||
|
||||
**신규(실질):** C# `CaptureOverlayWindow`(전체화면 투명·드래그선택·Esc/무효 취소) + `ScreenCapture`(좌표변환 순수부 + CopyFromScreen→PNG) + Composer 이미지 첨부 UI.
|
||||
**재사용:** 멀티핫키(3번째 등록만), `SendToWeb` 브릿지(`capture.image` + 콜드스타트 버퍼 패턴), 팔레트 소환·`/snap/new` navigate, bridgeNavigate 이벤트 허브, Composer 첨부칩 패턴.
|
||||
|
||||
## 리뷰에서 잡아 고친 것
|
||||
|
||||
- **[C# Imp1]** 캡쳐 실패 예외 미처리 → 앱 크래시 가능. → `pw<=0||ph<=0` 가드 + try/catch로 취소 흡수.
|
||||
- **[C# Imp2]** 콜드스타트 시 `capture.image` 유실(SendToWeb가 navigate만 버퍼). → `capture.image`도 버퍼링+NavigationCompleted flush.
|
||||
- **[C# Min1]** Esc/포커스아웃 경로 마우스캡처 미해제. → `Finish()`로 이동.
|
||||
- **[프론트 Critical]** read-once consume 구멍 — 이벤트 경로가 pending 안 비워 리마운트 시 stale 재첨부. → `onCapture`가 `consumePendingCaptureImage()` 호출로 양쪽 경로 비움 + 회귀 테스트.
|
||||
|
||||
## 검증
|
||||
|
||||
- 프론트 **237/237**, C# **22/22**, 빌드 클린(앱 실행 중이면 App exe copy-lock은 컴파일 이슈 아님).
|
||||
- **런타임 E2E(오버레이·CopyFromScreen·멀티모니터·DPI)는 헤드리스 불가 → 사용자 수동**(quickstart 1~8).
|
||||
|
||||
## 남은 것 / 천장(추후)
|
||||
|
||||
- **[수동확인]** quickstart — 실제 데스크톱에서 캡쳐·첨부·취소·멀티모니터.
|
||||
- **[다음 phase]** 이미지를 실은 메시지를 멀티모달 백엔드로 전송·비전 분석·응답, 웹서치. 전송 payload 배선도 그때.
|
||||
- **[천장]** mixed-DPI 좌표 보정, 대용량 base64 임시파일/blob 최적화.
|
||||
|
||||
## 최종 리뷰 (whole-branch, opus)
|
||||
|
||||
리소스(Bitmap/Graphics using·오버레이 누수 없음·예외 흡수·마우스캡처)·보안(로컬 캡쳐→자기앱, data:URI 스크립트 경로 없음)·회귀(3번째 핫키 독립·버퍼 2개 무간섭·텍스트첨부/전송 무손상) 다 클린.
|
||||
|
||||
신규 Important 1건 잡아 **fix wave로 처리 완료**(재리뷰 통과):
|
||||
- **기존 대화방에서 캡쳐 시 이미지 유실**(내 read-once fix가 연 반대 구멍 — 출발지 Composer가 pending 훔침). → **`acceptCapture` prop 게이팅**(NewChatPage만 캡쳐 수신) + 회귀 테스트.
|
||||
|
||||
**parked (cosmetic, merge 무관):** 캡쳐 직후 붙여넣기-대상 배지가 잠깐 오버레이 자기 창으로 표기됨. 기능 무해(캡쳐는 새 대화行이라 paste-target 안 씀, 다음 소환 때 정상 갱신). 거슬리면 `ForegroundWindow.Capture`에 자기프로세스 HWND 무시 한 줄로 후속 가능.
|
||||
|
||||
**최종 검증:** 프론트 **238/238**, C# **22/22**, 빌드 클린.
|
||||
|
||||
**결론:** 문서화된 천장(멀티모달 미전송·mixed-DPI·대용량 base64) + parked cosmetic 1건 제외하고 **merge 준비 완료.** 단 오버레이·캡쳐·멀티모니터 런타임은 헤드리스 미검 → 사용자 수동 확인 권장.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Research: 화면 캡쳐 → 챗 첨부 (Phase 0)
|
||||
|
||||
브레인스토밍서 큰 결정(챗에 얹기·자체 오버레이·v1 캡쳐만·핫키 Ctrl+Shift+9·순수 첨부까지)은 끝. 여기선 **구현 미해결점**만.
|
||||
|
||||
---
|
||||
|
||||
## R1. 화면 캡쳐 API
|
||||
|
||||
**결정**: **`System.Drawing.Common`** 패키지 + `Graphics.CopyFromScreen(srcX, srcY, 0, 0, size)` → `Bitmap` → PNG(`MemoryStream`) → base64.
|
||||
|
||||
**Rationale**: 스크린샷 고전 방식, 코드 최소. WPF 프로젝트라 기본 참조엔 없어 패키지 추가(Windows 전용 API지만 이 앱은 Windows 데스크톱). `CodeAssist.Shell.csproj`에 추가.
|
||||
|
||||
**Alternatives 기각**: GDI P/Invoke(BitBlt+GetDC/BitmapSource) — 패키지 없이 되지만 코드 많고 손 많이 감. Windows.Graphics.Capture(WinRT) — 최신이나 interop 복잡, YAGNI.
|
||||
|
||||
## R2. 좌표·DPI 변환
|
||||
|
||||
**결정**: 오버레이는 WPF DIP 좌표로 그리고, **선택 사각형(DIP) → 물리 픽셀**로 변환해 `CopyFromScreen`에 넘긴다.
|
||||
- 오버레이 창은 가상화면(`SystemParameters.VirtualScreen*`, DIP) 전체를 덮음.
|
||||
- 변환: 오버레이의 DPI 스케일(`VisualTreeHelper.GetDpi(this)` 또는 `PresentationSource`의 `CompositionTarget.TransformToDevice`)로 (오버레이 원점 + 선택 DIP) → 물리 px.
|
||||
- 앱을 **PerMonitorV2 DPI 인지**로(app.manifest) 두어 스케일 창서도 좌표가 맞게.
|
||||
|
||||
**ponytail 천장**: 서로 배율 다른 멀티모니터(mixed-DPI)에선 가상화면 한 DPI로 계산하면 어긋날 수 있음 → v1은 단일/동일배율 케이스 맞추고, mixed-DPI 보정은 추후. `ponytail:` 주석으로 박음.
|
||||
|
||||
## R3. 오버레이 창
|
||||
|
||||
**결정**: 신규 `CaptureOverlayWindow`(WPF Window).
|
||||
- `WindowStyle.None` + `AllowsTransparency=true` + 반투명 어두운 배경(예: `#55000000`) + `Topmost=true` + `ShowInTaskbar=false`.
|
||||
- 위치·크기 = `VirtualScreen`(DIP) 전체(멀티모니터 커버). `WindowStartupLocation=Manual`.
|
||||
- 위에 `Canvas` + 선택 `Rectangle`. MouseDown(시작점)→MouseMove(사각형 갱신)→MouseUp(확정). `Cursor=Cross`.
|
||||
- **취소**: `Esc` KeyDown, 또는 선택 크기 임계(예: <4px) 미만이면 캡쳐 없이 닫음. 포커스 잃어도(Deactivated) 닫아 안전.
|
||||
- 캡쳐 완료 시 `Captured(string dataUrl)` 이벤트, 취소 시 `Cancelled` — App이 구독. 처리 후 창 Close.
|
||||
|
||||
**Rationale**: 스니핑툴류 표준. 팔레트 창과 별개의 짧은 수명 창.
|
||||
|
||||
## R4. 이미지 전달(C#→JS)
|
||||
|
||||
**결정**: 기존 `SendToWeb`(`PostWebMessageAsJson`) 채널로 `{type:"capture.image", dataUrl}`(PNG base64 data URL). `PaletteWindow.SendCaptureImage(dataUrl)` 패스스루 추가.
|
||||
|
||||
**ponytail 천장**: 대영역 캡쳐면 base64가 수 MB → PostWebMessageAsJson 부담. v1은 그대로, 추후 임시파일(virtual host mapping) 또는 blob 전달로 최적화.
|
||||
|
||||
## R5. 프론트 수신(마운트 레이스)
|
||||
|
||||
**결정**: `bridgeNavigate` 허브(C#→JS 메시지 단일 리스너)에 `capture.image` 분기 추가.
|
||||
- 모듈에 `pendingCaptureImage` 보관 + `bridge:captureImage` CustomEvent 발생.
|
||||
- **read-once(consume) 세만틱**: `consumePendingCaptureImage()`가 값 반환 후 비움. Composer가 **마운트 시 consume**(네비 직후 이벤트를 놓쳐도 반영) + **이벤트 구독**(이미 마운트된 경우). 둘 다 consume/clear 하므로 같은 이미지 이중 첨부 안 됨.
|
||||
|
||||
**Rationale**: pasteTarget/bridgeNavigate에서 검증된 "모듈 보관 + 이벤트" 레이스 패턴 재사용. 단 캡쳐는 표시 지속이 아니라 1회 소비라 consume 세만틱.
|
||||
|
||||
## R6. 챗 첨부·도착 흐름
|
||||
|
||||
**결정**:
|
||||
- App 캡쳐 완료 → `ShowPalette()`(앞창 캡처+표시) + `NavigateApp("/snap/new")` + `SendCaptureImage(dataUrl)`.
|
||||
- Composer에 **이미지 첨부 상태**(`imageAttachments: string[]` data URL) 신규 — 텍스트 첨부 칩과 별개. 썸네일 `<img>` + X(제거). 마운트 시 pending 캡쳐 이미지 consume해 첨부, 이후 이벤트로 추가(누적).
|
||||
- **v1 전송 동작**: 메시지 전송 시 이미지 첨부는 **전송하지 않음**(FR-009). 텍스트만 기존대로. 전송 후 이미지 첨부 처리는 data-model에서 확정(§전송 시).
|
||||
|
||||
**Rationale**: 도착지 새 대화(`/snap/new`)는 spec Assumption. Composer는 이미 텍스트 첨부 칩 패턴이 있어 이미지 첨부도 같은 자리에.
|
||||
|
||||
## R7. 캡쳐 소유·조립
|
||||
|
||||
**결정**: 드래그·DPI변환·`CopyFromScreen`은 `CaptureOverlayWindow`(선택 rect·DPI를 아는 곳)에서. 순수 픽셀 캡쳐(물리 x,y,w,h → PNG 바이트)는 `Shell/Platform/ScreenCapture.cs`로 분리(오버레이는 UI, 캡쳐는 Win32 — 관심사 분리, 좌표변환 순수부는 단위테스트 여지).
|
||||
|
||||
## 구현 시점 확인
|
||||
|
||||
- `System.Drawing.Common` 버전 핀(최신 안정).
|
||||
- app.manifest에 PerMonitorV2 dpiAwareness 설정 여부 확인(이미 있으면 재사용).
|
||||
@@ -0,0 +1,103 @@
|
||||
# Feature Specification: 화면 캡쳐 → 챗 첨부 (Screen Capture to Chat)
|
||||
|
||||
**Feature Branch**: `002-capture-to-chat`
|
||||
|
||||
**Created**: 2026-08-06
|
||||
|
||||
**Status**: Draft
|
||||
|
||||
**Input**: 화면 영역을 사각형으로 캡쳐해서 챗(snap) 입력창에 이미지 첨부로 넣는 기능. **v1은 캡쳐 파이프라인만** — 멀티모달 비전 분석·웹서치는 백엔드 붙는 다음 phase.
|
||||
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
작업 중 화면 어디든 전역 핫키로 사각형을 그려 그 영역을 캡쳐하면, 그 이미지가 곧바로 챗 입력창에 첨부로 붙는다. 나중에 "이미지 놓고 AI랑 얘기"하려는 건데, **이번 v1은 캡쳐해서 챗에 붙이는 데까지**만 한다. 실제 분석(비전 LLM)·웹서치는 백엔드가 준비되면 그 위에 얹는다.
|
||||
|
||||
**한 줄 흐름**: 핫키 → 화면에 캡쳐 오버레이 → 사각형 드래그 → 그 영역 이미지 → 챗 새 대화에 이미지 첨부(썸네일+X) → (사용자가 질문 타이핑).
|
||||
|
||||
---
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - 화면 캡쳐해서 챗에 첨부 (Priority: P1)
|
||||
|
||||
에디터·브라우저·SAP GUI 등 아무 화면을 보다가 전역 핫키(`Ctrl+Shift+9`)를 누르면 화면이 어둑해지며 캡쳐 모드가 뜬다. 원하는 영역을 마우스로 드래그해 사각형으로 잡고 놓으면, 앱 창이 떠서 새 대화 화면으로 가고 그 캡쳐 이미지가 입력창에 첨부(썸네일)로 붙어 있다. 이제 질문을 타이핑하면 된다.
|
||||
|
||||
**Why this priority**: 이게 이 기능의 전부(v1). 캡쳐→첨부가 매끄럽게 되면 나중에 백엔드만 붙이면 "이미지로 AI와 대화"가 완성됨.
|
||||
|
||||
**Independent Test**: 핫키 → 오버레이 → 영역 드래그 → 놓기 → 챗 입력창에 그 영역 이미지가 첨부로 보이는지. 백엔드 없이 단독 검증 가능(전송·분석은 v1 밖).
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 아무 창이 떠있음, **When** `Ctrl+Shift+9`, **Then** 모든 모니터를 덮는 어두운 캡쳐 오버레이가 뜬다.
|
||||
2. **Given** 캡쳐 오버레이가 떠있음, **When** 영역을 드래그해 놓음, **Then** 오버레이가 닫히고 앱 창이 떠서 새 대화 화면으로 이동하며, 그 영역 이미지가 입력창에 썸네일 첨부로 붙는다.
|
||||
3. **Given** 캡쳐 오버레이가 떠있음, **When** `Esc`(또는 드래그 없이 클릭만/영역 너무 작음), **Then** 아무것도 캡쳐하지 않고 오버레이만 닫힌다(부작용 없음).
|
||||
4. **Given** 이미지가 첨부됨, **When** 첨부의 X를 누름, **Then** 그 첨부가 제거된다.
|
||||
5. **Given** 모니터가 여러 개, **When** 보조 모니터 영역을 드래그, **Then** 그 모니터의 해당 영역이 정확히 캡쳐된다.
|
||||
6. **Given** 이미 이미지가 첨부돼 있음, **When** 한 번 더 캡쳐, **Then** 새 이미지가 첨부로 추가된다(기존 것 유지).
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- **드래그 없이 클릭만 / 너무 작은 영역**: 캡쳐 취소로 간주(오버레이만 닫힘).
|
||||
- **`Esc`**: 언제든 캡쳐 취소.
|
||||
- **앱 창이 숨겨진 상태에서 캡쳐**: 오버레이는 별개라 동작하고, 캡쳐 후 앱 창을 소환한다.
|
||||
- **아주 큰 영역**: 이미지 용량이 커질 수 있음(전달 방식 최적화는 추후 — Assumptions 천장 참고).
|
||||
- **고DPI/모니터별 배율 다름**: 좌표·크기 배율 보정이 필요할 수 있음(추후 — 천장 참고).
|
||||
- **첨부를 다 지우거나 캡쳐를 취소해도** 챗은 그대로 쓸 수 있어야 한다.
|
||||
|
||||
---
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
**캡쳐 트리거·오버레이**
|
||||
- **FR-001**: 전역 핫키 `Ctrl+Shift+9`로 화면 캡쳐 모드를 켤 수 있어야 한다. 기존 핫키(스니펫 `Ctrl+Shift+7`, 챗 `Ctrl+Shift+8`)와 별개다.
|
||||
- **FR-002**: 캡쳐 모드는 **모든 모니터를 덮는** 어두운 오버레이를 띄우고, 그 위에서 마우스 드래그로 선택 사각형을 그린다.
|
||||
- **FR-003**: `Esc`, 또는 유효하지 않은 선택(드래그 없이 클릭/너무 작은 영역)이면 아무것도 캡쳐하지 않고 오버레이를 닫는다.
|
||||
|
||||
**캡쳐·전달**
|
||||
- **FR-004**: 드래그로 확정된 영역의 화면 픽셀을 이미지로 캡쳐한다.
|
||||
- **FR-005**: 캡쳐 완료 시 앱 창을 소환하고, **새 대화 화면**으로 이동시킨 뒤 그 이미지를 챗 입력창에 전달한다.
|
||||
|
||||
**챗 첨부(끝점)**
|
||||
- **FR-006**: 전달된 이미지는 챗 입력창에 **썸네일 첨부**로 표시된다.
|
||||
- **FR-007**: 각 이미지 첨부는 **X로 제거**할 수 있다.
|
||||
- **FR-008**: 캡쳐를 여러 번 하면 이미지 첨부가 **누적**된다(기존 첨부 유지).
|
||||
- **FR-009**: v1에서 이미지 첨부는 **표시·제거만** 한다. 메시지 전송은 기존 텍스트 전송 그대로이며, 이미지를 백엔드로 보내거나 분석하지 않는다.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **캡쳐 이미지**: 선택 영역의 화면 스냅샷 한 장. 챗 입력창에 붙는 첨부 단위(썸네일로 표시, X로 제거). v1에선 전송·분석에 쓰이지 않음(표시용).
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: 핫키를 누른 뒤 한 번의 드래그로 이미지가 챗에 첨부되기까지 몇 초 안에 끝난다.
|
||||
- **SC-002**: 첨부된 이미지가 사용자가 드래그로 고른 영역과 내용·경계가 일치한다.
|
||||
- **SC-003**: 모니터가 여러 개여도 어느 모니터의 영역이든 캡쳐된다.
|
||||
- **SC-004**: `Esc`·무효 선택 시 100% 취소되고 챗·클립보드 등에 아무 부작용이 없다.
|
||||
- **SC-005**: 캡쳐~챗 첨부 표시까지 흐름이 끊기지 않고(오버레이 닫힘→앱 소환→첨부) 자연스럽게 이어진다.
|
||||
|
||||
---
|
||||
|
||||
## Assumptions
|
||||
|
||||
- **v1 범위 = 캡쳐 파이프라인만.** 이미지를 실은 메시지를 멀티모달 LLM 백엔드로 보내 **비전 분석·응답**하는 것, **웹서치** 툴은 다음 phase(백엔드 계약 나올 때). **전송-with-이미지 payload 배선도 v1 밖** — v1의 전송은 기존 텍스트 그대로.
|
||||
- **캡쳐 도착지 = 새 대화 화면.** 캡쳐하면 새 챗을 열고 거기에 붙인다(기존 세션에 붙이는 건 v1 밖).
|
||||
- **재사용**: 기존 멀티핫키 등록, C#→JS 브릿지(SendToWeb/PostWebMessageAsJson), 팔레트 창 소환·navigate, 브릿지 이벤트 패턴(pasteTarget/bridgeNavigate 식 — 마운트 레이스 대비 모듈 보관+이벤트), Composer의 첨부 칩 UI 패턴을 최대한 얹는다. 캡쳐 오버레이 창과 캡쳐 로직만 실질 신규.
|
||||
- **현재 이미지 처리와의 관계**: 지금 이미지 붙여넣기는 클립보드 이력 사이드바로만 감(챗에 "보내는 첨부" 개념 없음). 이 기능이 **챗 입력창 이미지 첨부**를 처음 도입한다.
|
||||
- **ponytail 천장(추후 보완)**: ① 고DPI/모니터별 배율 좌표 보정, ② 큰 영역 캡쳐 시 base64가 커지면 임시파일/blob 전달로 최적화.
|
||||
|
||||
## Out of Scope (v1)
|
||||
|
||||
- 이미지 비전 분석·AI 판단·응답(멀티모달 백엔드).
|
||||
- 웹서치.
|
||||
- 이미지를 실은 메시지의 백엔드 전송(payload 포맷 포함).
|
||||
- 기존(진행 중) 세션에 캡쳐 붙이기, 캡쳐 이미지 편집(자르기·주석), 캡쳐 히스토리.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
description: "Task list for 화면 캡쳐 → 챗 첨부 (Screen Capture to Chat)"
|
||||
---
|
||||
|
||||
# Tasks: 화면 캡쳐 → 챗 첨부 (Screen Capture to Chat)
|
||||
|
||||
**Input**: `specs/002-capture-to-chat/` (plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md)
|
||||
|
||||
**Tests**: 포함(superpowers TDD + `npm run test`/`dotnet test` 통과 = 완료 기준). 단 오버레이·`CopyFromScreen`·캡쳐 실물은 헤드리스 검증 불가 → 순수부만 단위테스트 + 나머지 수동.
|
||||
|
||||
**Organization**: user story 1개(P1) = 전부 = MVP. Setup 후 US1.
|
||||
|
||||
## Format: `[ID] [P?] [US#?] 설명 (파일경로)`
|
||||
|
||||
- **[P]**: 다른 파일·무의존 → 병렬. **[US1]**: user story.
|
||||
- 핸드오프: 여기서부터 superpowers 뒷단(`subagent-driven-development` + `test-driven-development`). 단계 끝마다 `specs/002-capture-to-chat/stage-N.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup
|
||||
|
||||
- [ ] T001 `System.Drawing.Common` 패키지 추가 (`3_windowsApp/CodeAssist.Shell/CodeAssist.Shell.csproj`) — `CopyFromScreen`용
|
||||
- [ ] T002 [P] `PerMonitorV2` dpiAwareness 확인/추가 (`3_windowsApp/CodeAssist.App/app.manifest` — 없으면 생성·csproj `ApplicationManifest` 연결). 스케일 창서 캡쳐 좌표 정확도용
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: User Story 1 - 화면 캡쳐해서 챗에 첨부 (P1) 🎯 MVP
|
||||
|
||||
**Goal**: `Ctrl+Shift+9` → 오버레이 드래그 → 캡쳐 → 새 대화 입력창에 이미지 첨부(썸네일+X).
|
||||
|
||||
**Independent Test**: 핫키→드래그→놓기 → 챗 입력창에 그 영역 썸네일 첨부가 뜨는지(백엔드 없이). Esc/무효선택 취소, 멀티모니터, 제거·누적.
|
||||
|
||||
### Tests (RED 먼저)
|
||||
|
||||
- [ ] T003 [P] [US1] `bridgeNavigate` capture.image 수신 + `consumePendingCaptureImage`(read-once) 단위테스트 — mock `chrome.webview`, `snippetBridge.test` 스타일 (`2_frontend/src/lib/bridge/bridgeNavigate.test.ts`)
|
||||
- [ ] T004 [P] [US1] `ScreenCapture` 좌표 변환 순수부(선택 DIP rect + 오버레이 원점 + DPI배율 → 물리 px rect) xUnit (`3_windowsApp/CodeAssist.Tests/ScreenCaptureTests.cs`)
|
||||
|
||||
### Impl — C# 캡쳐
|
||||
|
||||
- [ ] T005 [US1] `ScreenCapture.cs` — `CapturePng(int x,int y,int w,int h): byte[]` (`Graphics.CopyFromScreen`→`Bitmap`→PNG) + 순수 좌표변환 헬퍼(T004 대상) 분리 (`3_windowsApp/CodeAssist.Shell/Platform/ScreenCapture.cs`)
|
||||
- [ ] T006 [US1] `CaptureOverlayWindow.xaml(.cs)` — 전체화면(가상화면) 투명·어두운 오버레이, 십자 커서, 드래그 선택 사각형, `Esc`/무효(너무 작음·클릭만) 취소, 확정 시 DIP→물리 변환→`ScreenCapture.CapturePng`→base64 dataUrl. `event Action<string> Captured` / `event Action Cancelled`. 처리 후 Close (`3_windowsApp/CodeAssist.App/Views/CaptureOverlayWindow.xaml`, `.xaml.cs`)
|
||||
- [ ] T007 [US1] `App.xaml.cs` — 3번째 핫키(id `0x1002`, `MOD_CONTROL|MOD_SHIFT|VK_9`) 등록(멀티핫키 재사용) → 눌리면 `CaptureOverlayWindow` 표시. `Captured`→`ShowPalette()`+`_palette.NavigateApp("/snap/new")`+`_palette.SendCaptureImage(dataUrl)`. `Cancelled`→무시. 핫키 상수 한 곳에 (`3_windowsApp/CodeAssist.App/App.xaml.cs`)
|
||||
- [ ] T008 [US1] `PaletteWindow.SendCaptureImage(string dataUrl)` → `Web.SendToWeb(new { type = "capture.image", dataUrl })` (`3_windowsApp/CodeAssist.App/Views/PaletteWindow.xaml.cs`)
|
||||
|
||||
### Impl — 프론트
|
||||
|
||||
- [ ] T009 [US1] `bridgeNavigate.ts` — `capture.image` 분기: `pendingCaptureImage` 보관 + `bridge:captureImage`(detail `{dataUrl}`) dispatch + `consumePendingCaptureImage()` export(read-once) (`2_frontend/src/lib/bridge/bridgeNavigate.ts`)
|
||||
- [ ] T010 [US1] `Composer.tsx` — `imageAttachments: string[]` 상태 + 썸네일 `<img>`+X 칩 UI(텍스트 첨부 칩 옆). 마운트 시 `consumePendingCaptureImage()` push + `bridge:captureImage` 구독 push(누적, FR-008). X 제거(FR-007). **전송 시 이미지 미전송 + 정리**(FR-009) (`2_frontend/src/features/snap/components/Composer.tsx`)
|
||||
|
||||
**Checkpoint**: US1 단독 동작(MVP). **STOP & VALIDATE** — quickstart 1~8.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Polish & 검증
|
||||
|
||||
- [ ] T011 `npm run format` → `lint`+`test`+`build` 통과 + `dotnet build 3_windowsApp` + `dotnet test 3_windowsApp/CodeAssist.Tests` 통과
|
||||
- [ ] T012 quickstart.md 수동 시나리오 1~8 확인(오버레이·캡쳐 내용일치·Esc/무효 취소·멀티모니터·제거·누적·v1 전송경계)
|
||||
- [ ] T013 [P] `specs/002-capture-to-chat/report.md`(계획 vs 결과) 작성 → 최종 승인용
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
- **Phase 1 Setup** → **US1** → **Polish**.
|
||||
- US1 안: 두 갈래가 `capture.image` 계약에서 만남 —
|
||||
- C# 갈래: T005(캡쳐헬퍼) → T006(오버레이, T005 씀) → T007(핫키·코디, T006 씀) → T008(전송 패스스루).
|
||||
- 프론트 갈래: T009(수신) → T010(Composer, T009 씀).
|
||||
- 두 갈래는 서로 독립(파일 disjoint) → C#/프론트 병렬 가능하나 **implementer는 순차 dispatch**(같은 git index — 스니펫 때 레이스 교훈).
|
||||
- 테스트 T003(프론트)·T004(C#) 서로 [P].
|
||||
|
||||
## Parallel Opportunities
|
||||
|
||||
- Setup T002 [P].
|
||||
- 테스트 T003·T004 [P].
|
||||
- C# 갈래와 프론트 갈래는 파일 disjoint(3_windowsApp vs 2_frontend)라 논리상 병렬이지만, git index 레이스 방지 위해 implementer는 순차로.
|
||||
- **주의**: `App.xaml.cs`·`bridgeNavigate.ts`·`Composer.tsx`는 각 갈래 내 순차(같은 파일 확장).
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
1. Setup(패키지·manifest).
|
||||
2. **US1 = MVP** — C# 캡쳐 파이프라인 + 프론트 수신·첨부. 계약(`capture.image`) 기준 양쪽 맞춤.
|
||||
3. 단계 끝 `stage-N.md`, 완료 전 `verification-before-completion` + `/code-review`, `report.md` → 승인 → `/cmp` merge.
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] = 다른 파일·무의존. [US1] = 추적용.
|
||||
- 정당 편차(plan §Constitution): v1 이미지 미전송(백엔드 phase).
|
||||
- 재사용: 멀티핫키·SendToWeb·팔레트 소환·navigate·bridge 이벤트 패턴·Composer 첨부칩 — 새로 만들지 말 것.
|
||||
- 오버레이·CopyFromScreen 실물은 수동 검증(T012). 순수부(좌표변환·수신 consume)만 단위테스트.
|
||||
Reference in New Issue
Block a user