Initial Commit
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# snap 채팅 재진입 시 대화 안 보임 (됐다 안 됐다)
|
||||
|
||||
## 증상
|
||||
- 답변 스트리밍 중 목록 갔다 오면 대화가 비어 보이고, 새로고침해야 나타남
|
||||
- 재진입 시 답변을 처음부터 타자기로 다시 뿌림
|
||||
- "되묻기(의도 애매)" 답변에서 유독 자주 아무것도 안 보임
|
||||
- 재현이 타이밍 따라 들쭉날쭉 (됐다 안 됐다)
|
||||
|
||||
## 원인 (겹친 층 4개)
|
||||
1. **전역 `staleTime: 30_000` (진짜 뿌리)** — 세션 detail 캐시가 30초간 refetch 안 됨.
|
||||
빈/부분 스냅샷이 캐시에 박히면 재진입해도 그대로 나옴. 테스트 들락날락이 다 30초 창
|
||||
안이라 "새로고침해야 나타남"으로 체감. 아래 3개 수정이 다 "새 데이터 오면" 작동하는
|
||||
구조였는데 새 데이터 자체가 안 오고 있었음.
|
||||
2. **remount 시 무조건 재seed** — per-instance `initedRef` 가드는 remount 마다 리셋 →
|
||||
stale DB 스냅샷이 라이브 store 를 덮고 스트림도 abort(seed 가 abort 함).
|
||||
3. **타자기 진행도가 컴포넌트 로컬** — `useSmoothedText` 의 displayed 가 `""` 시작이라
|
||||
remount 마다 처음부터 재생.
|
||||
4. **재진입 시 "생성 중" 표시 없음** — 생성 중 재진입하면 DB 엔 user 만 있어서 시머 없이
|
||||
백지. 되묻기 경로는 응답이 느려서(관측치 43초) 이 백지 창이 유독 길었음 → "되묻는
|
||||
부분에서 안 보임"으로 체감.
|
||||
|
||||
## 확정 근거
|
||||
- dev DB 직접 조회(asyncpg): 되묻기 포함 모든 메시지 정상 저장돼 있었음 → 백엔드 무죄.
|
||||
세션 34b5b639: user 06:59:57 → 되묻기 assistant 07:00:40 (len=302, **43초**) → 후속
|
||||
user 07:00:49 → 본답변 07:01:00 (len=1347).
|
||||
- 백엔드 streaming.py: persist → usage → done 순서. 클라 끊겨도 producer 가 끝까지 저장
|
||||
→ DB 가 신뢰 가능한 복구원.
|
||||
|
||||
## 수정 (전부 2_frontend/src/features/snap + lib/streaming)
|
||||
| 파일 | 내용 |
|
||||
|---|---|
|
||||
| `api/snap.api.ts` | detail 쿼리 `staleTime: 0` (재진입마다 refetch) + `isGenerating` 이면 1.5s 폴링 |
|
||||
| `pages/SessionChatPage.tsx` | seed 가드: 라이브면 보존, 아니면 "내용량 많은 쪽으로 단조 수렴"(DB > store 일 때만 seed). 재진입+백엔드 생성 중이면 시머 표시 + Composer 잠금(409 방지) |
|
||||
| `lib/streaming/useSmoothedText.ts` | displayed 초기값 `""` → `full` (remount 시 기존 내용 즉시, 새 토큰만 타자기) |
|
||||
| `hooks/useSnapChat.ts` | `onDone` 에서 detail 캐시 invalidate |
|
||||
|
||||
## 교훈
|
||||
- "됐다 안 됐다" = 레이스. 코드 추리로 5번 고치다 실패 → DB 직접 조회 한 방에 백엔드
|
||||
무죄 확정 + 프론트로 범위 좁힘. **증거 수집을 먼저 했어야 함.**
|
||||
- 전역 캐시 정책(staleTime)은 실시간성 데이터(스트리밍 대화)에 그대로 두면 안 됨 —
|
||||
쿼리별 override 필수.
|
||||
- 하위 층을 아무리 고쳐도 최상층(캐시)이 데이터를 안 내려주면 전부 무력화됨. 층이 겹친
|
||||
버그는 층별로 분리해서 검증할 것.
|
||||
|
||||
## 남은 한계 (알고 수용)
|
||||
- STOP 으로 얼린 부분 답변은 재진입 시 DB 완성본으로 교체됨(백엔드 cancel 미구현이라
|
||||
어차피 끝까지 생성·저장). "정지 자리 유지"가 필요하면 백엔드 cancel 이 선행.
|
||||
- 멀티탭 동시 스트림은 백엔드 단일 프로세스 전제(레지스트리)라 이번 범위 밖.
|
||||
@@ -0,0 +1,19 @@
|
||||
# snap 답변의 ASCII 다이어그램이 말풍선 밖으로 튀어나감
|
||||
|
||||
## 증상
|
||||
- 챗봇 답변에 든 ASCII 다이어그램(`┌───┐` 박스 라인)이 회색 인라인 코드 배경으로 줄줄이 렌더되면서 말풍선 오른쪽 밖으로 넘침
|
||||
|
||||
## 원인
|
||||
- `Message.tsx` 가 `pre` 를 fragment 로 언랩(`pre: ({children}) => <>{children}</>`)하고, 코드펜스는 `code` 컴포넌트의 `language-*` className 매칭으로만 CodeBlock 처리하고 있었음
|
||||
- **언어 라벨 없는 코드펜스**(LLM 이 ``` 만 쓰고 다이어그램 넣는 경우)는 className 이 없어서 인라인 `code` 분기로 떨어짐 → `<pre>` 없는 인라인 코드가 됨
|
||||
- 인라인은 공백에서만 줄바꿈되는데 `─────` 연속 문자는 줄바꿈 지점이 없어서 그대로 overflow
|
||||
- react-markdown v10 은 v8 의 `inline` prop 이 없어짐 — className 만으로 블록/인라인 구분하면 이 케이스가 샘
|
||||
|
||||
## 수정
|
||||
- `Message.tsx`: 블록 코드 판별을 `pre` 컴포넌트로 이동 — 펜스는 언어 유무와 무관하게 전부 `CodeBlock`(overflow-auto 스크롤 컨테이너)으로. `code` 는 순수 인라인만 담당
|
||||
- 재현 테스트: `Message.test.tsx` (언어 없는 펜스 → CodeBlock, 언어 있는 펜스 유지, 인라인 유지)
|
||||
- `StreamingText.tsx` 는 `pre` 를 살려두고 `overflow-x-auto` 도 있어서 무관 — 안 건드림
|
||||
|
||||
## 교훈
|
||||
- react-markdown 에서 "블록 코드 = pre 의 자식" 이 유일하게 안전한 구분 기준. `language-*` className 은 언어 없는 펜스에서 빠짐
|
||||
- `pre` 를 언랩할 거면 언어 없는 펜스가 어디로 떨어지는지 반드시 확인
|
||||
@@ -0,0 +1,42 @@
|
||||
# 2026-08-05 스니펫 팔레트가 비어있음 (시드 실패)
|
||||
|
||||
## 증상
|
||||
데스크톱 앱 실행 후 `Ctrl+1` 팔레트에 스니펫이 **하나도 안 뜸**. "기존 SQLite를 복사해서 시드하기로" 했는데 빈 목록.
|
||||
|
||||
## 조사 (systematic-debugging)
|
||||
- 앱 로그(`%TEMP%\codeassist-webview.log`): `SnippetSeed: 시드 소스 없음(빈 DB로 시작): D:\project\003.short-auto\snippets.db`
|
||||
- `find`로 003 트리 전체 스캔 → `.db`/`.sqlite` **하나도 없음**. 003(파이썬)이 실제로 한 번도 안 돌아가서 `snippets.db`가 생긴 적이 없었음.
|
||||
- 타겟 `%LOCALAPPDATA%\CodeAssist\snippets.db`는 이미 존재(앱이 첫 실행 때 빈 스키마 생성). `SnippetSeed`는 "타겟 있으면 스킵"이라 **재시드도 막힘**.
|
||||
|
||||
## 근본 원인
|
||||
1. **시드 소스(SQLite)가 애초에 존재하지 않았음.** 진짜 스니펫 데이터는 C# 원본의 **XML** `0_reference/CodeTemplates.xml`(231개 `<Code>`)에 있음. 시드는 없는 SQLite를 복사하려 했으니 빈 DB.
|
||||
2. 빈 타겟 db가 이미 생성돼 있어 소스를 고쳐도 재시드 안 됨.
|
||||
|
||||
## 해결
|
||||
1. `CodeTemplates.xml` → 앱 스키마(SnippetRepository) SQLite로 **1회 변환**(아래 스크립트). 231개 중 218개 저장(빈 body 10·중복 이름 3 스킵). name은 `NormalizeName`(Trim·대문자·공백→`_`)과 동일 규칙, category 기본 `"코드"`.
|
||||
2. 생성물을 시드 소스 경로 `D:\project\003.short-auto\snippets.db`에 두고(향후 재시드용), 앱 종료 후 빈 타겟(`%LOCALAPPDATA%\CodeAssist\snippets.db` + wal/shm) 제거 → 218개 db로 교체.
|
||||
3. 앱 재실행 → 218개 표시.
|
||||
|
||||
## 재발 방지 / 메모
|
||||
- 코드 변경 없음(시드 로직 자체는 정상 — 소스 파일이 없었던 게 문제). 소스 경로에 db가 생겼으니 타겟 삭제 시 시드가 정상 복구.
|
||||
- 데이터 원천은 **SQLite가 아니라 XML**이었음. XML이 갱신되면 아래 스크립트로 재생성하면 됨.
|
||||
- `change_word1/2`(XML의 ChangeWord1/2)는 앱 스키마에 없음 — 의도적으로 안 가져옴.
|
||||
|
||||
## 변환 스크립트 (보존)
|
||||
```python
|
||||
import xml.etree.ElementTree as ET, sqlite3, os
|
||||
SRC_XML = r"D:\project\003.short-auto\0_reference\CodeTemplates.xml"
|
||||
OUT_DB = r"D:\project\003.short-auto\snippets.db"
|
||||
def norm(n): return n.strip().upper().replace(" ", "_") # SnippetRepository.NormalizeName 동일
|
||||
root = ET.parse(SRC_XML).getroot()
|
||||
rows, seen = [], set()
|
||||
for code in root.findall("Code"):
|
||||
name = norm(code.findtext("Name") or ""); desc = (code.findtext("Desc") or "").strip(); body = code.findtext("Snippet") or ""
|
||||
if not name or not body.strip() or name in seen: continue
|
||||
seen.add(name); rows.append((name, desc, body, "코드"))
|
||||
if os.path.exists(OUT_DB): os.remove(OUT_DB)
|
||||
con = sqlite3.connect(OUT_DB)
|
||||
con.executescript("CREATE TABLE snippets(name TEXT PRIMARY KEY,desc TEXT NOT NULL DEFAULT '',body TEXT NOT NULL DEFAULT '',category TEXT NOT NULL DEFAULT '코드');CREATE TABLE usage(name TEXT PRIMARY KEY,count INTEGER NOT NULL DEFAULT 0,last_used INTEGER NOT NULL DEFAULT 0);")
|
||||
con.executemany("INSERT INTO snippets(name,desc,body,category) VALUES(?,?,?,?)", rows); con.commit(); con.close()
|
||||
# 그다음: 앱 종료 → %LOCALAPPDATA%\CodeAssist\snippets.db(+wal/shm) 삭제 → OUT_DB 복사 → 앱 재실행
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
# Tauri 단축키 마이그레이션 점검
|
||||
|
||||
## 원인
|
||||
|
||||
- 닷넷 `App.xaml.cs`는 Ctrl+Shift+7/8/9, Tauri `lib.rs`는 동시 비교용 Ctrl+Alt+7/8/9를 등록하고 있었음.
|
||||
- 프론트 `bridgeNavigate.ts`가 `chrome.webview`만 구독해서 Rust `bridge` 이벤트를 받지 못함.
|
||||
- `webviewBridge.ts`도 닷넷에만 전송해서 현재 route 보고와 Esc 창 숨김이 Tauri에서 무반응이었음.
|
||||
|
||||
## 수정과 대조 결과
|
||||
|
||||
| 키 | 닷넷 동작 | Tauri 점검 결과 |
|
||||
|---|---|---|
|
||||
| Ctrl+Shift+7 | 스니펫 소환, 검색창 포커스 | 조합 수정, 화면 이동·동일 route 재소환 이벤트 연결. 저장소는 여전히 미구현 |
|
||||
| Ctrl+Shift+8 | 챗봇 표시/숨김, 마지막 비스니펫 route 복귀 | 조합 수정, 현재 route 보고·이동 수신 연결 |
|
||||
| Ctrl+Shift+9 | 영역 선택 → 새 대화에 이미지 첨부 | 조합만 수정. Rust 캡처 핸들러는 여전히 로그만 남기는 스텁 |
|
||||
| Esc | 목록 검색 초기화 후 창 숨김, 대화 화면에서는 목록 복귀 | 같은 React 코드 사용. 누락된 Tauri 창 숨김 전송 연결 |
|
||||
| Ctrl+N | 새 대화 | 같은 SnapLayout 코드 사용 |
|
||||
| ↑/↓, Enter | 대화 목록 선택/열기 | 같은 SessionListPage 코드 사용 |
|
||||
| Enter / Shift+Enter | 메시지 전송 / 줄바꿈 | 같은 Composer 코드 사용 |
|
||||
| 스니펫 ↑/↓, F2, Ctrl+N, Ctrl+←/→ | 선택, 편집, 생성, 분류 이동 | 같은 React 핸들러. 데이터 요청 통로·Rust 저장소 미구현으로 전체 사용 흐름은 미완료 |
|
||||
| 스니펫 Enter | 직전 앱에 붙여넣기 | Rust paste_code가 스텁. 단축키 변경만으로 해결되지 않음 |
|
||||
|
||||
스니펫 핸들러 주석의 Alt+숫자는 실제 분기에 없음. 두 호스트가 공유하는 기존 프론트의 상태이므로 마이그레이션 누락과 구분함.
|
||||
|
||||
## 검증
|
||||
|
||||
- 공식 Tauri mockIPC 및 이벤트 mock으로 현재 route 보고, 숨김, 화면 이동, 같은 route 재소환, 중복 구독 방지, 닷넷 우선, 브라우저 no-op 확인.
|
||||
- 프론트 전체 테스트 43파일·242개 통과.
|
||||
- 프론트 프로덕션 빌드 통과. 전체 lint는 기존 FileDropZone·skill-mapping 오류 10개와 경고 10개로 실패했고, 이번 변경 파일의 오류는 없음.
|
||||
- `cargo test --locked` 통과. Rust 테스트 0개이므로 실제 키 입력 검증을 뜻하지 않음.
|
||||
- 네이티브 실제 키 입력 검증은 미실시. 기존 요청에 따라 Computer Use는 사용하지 않음.
|
||||
|
||||
## 남은 사항
|
||||
|
||||
- 닷넷과 Tauri를 함께 실행하면 같은 키를 선점하므로 한 번에 한 호스트로 확인해야 함. 기존 비교용 Ctrl+Alt 조합 정책은 이번 사용자 요청으로 대체됨.
|
||||
- 핫키 등록 실패 안내는 닷넷 트레이 알림과 달리 Tauri 콘솔 로그만 있음.
|
||||
- 저장소·직전 앱 붙여넣기·캡처 구현은 `specs/004-tauri-shell/tasks.md`의 미완료 항목으로 유지.
|
||||
@@ -0,0 +1,7 @@
|
||||
# 스니펫 Enter 동작을 클립보드 복사로 변경
|
||||
|
||||
사용자 기대는 선택한 스니펫 본문 복사였으나 기존 Enter는 미구현 Tauri `paste_code`를 호출한 뒤 창을 숨겼음.
|
||||
|
||||
기존 코드 뷰어와 같은 `navigator.clipboard.writeText` 사용. 원문 공백·개행을 유지하고 복사 완료 후에만 사용기록 요청과 창 숨김 실행. 실패하면 오류 안내와 함께 창 유지. IME 조합·키 반복·진행 중 중복 복사 방지.
|
||||
|
||||
페이지 테스트로 항목 선택 후 Enter, 원문 보존, 비동기 성공 전 창 유지, 실패 시 사용기록·창 숨김 미실행을 검증함. Tauri 사용기록 저장은 기존 스텁 상태이며 복사 기능과 별개임.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Tauri 스니펫 목록이 비어 보임
|
||||
|
||||
## 원인과 수정
|
||||
|
||||
기존 닷넷 DB는 `%LOCALAPPDATA%/CodeAssist/snippets.db`에 219개가 정상 보관돼 있었음. Tauri 목록 명령이 스텁이고 프론트 스니펫 요청도 닷넷만 지원해서 표시되지 않았음.
|
||||
|
||||
- Rust 목록 명령이 기존 DB를 읽기 전용으로 열고 snippets/usage를 조인해 기존 프론트 계약으로 반환하도록 구현.
|
||||
- Tauri 스니펫 요청을 invoke로 전달하고 오류 문자열을 Error로 변환.
|
||||
- 조회 오류가 나면 빈 목록 안내 대신 오류 원인을 표시.
|
||||
- 원본 복사나 덮어쓰기 없이 같은 파일을 읽음. 스니펫 쓰기와 직전 앱 붙여넣기는 아직 미구현.
|
||||
|
||||
## 검증
|
||||
|
||||
- Python SQLite quick_check 정상, 219개 확인.
|
||||
- Rust 스키마 테스트 통과: 한글 본문·설명과 사용기록 없는 행의 기본값 검증.
|
||||
- `cargo test --locked reads_existing_dotnet_database -- --ignored --nocapture`: 실제 기존 DB 219개 조회 성공.
|
||||
- 프론트 전체 실행에서 기존 244개 통과, 신규 테스트의 invoke 기본 인자 기대값 1개 수정 후 해당 파일 6개 전부 통과.
|
||||
- 프론트 프로덕션 빌드 및 변경 파일 ESLint 통과. 전체 lint의 기존 오류는 직전 작업에서 확인된 상태.
|
||||
- 개발 앱 자동 재빌드·재실행과 응답 정상 확인. 실제 화면 목록의 시각 확인은 사용자 확인이 남음.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Tauri 앱이 떠 있어도 스니펫 단축키 화면 전환이 안 됨
|
||||
|
||||
## 원인
|
||||
|
||||
Windows Tauri도 WebView2를 사용해서 `window.chrome.webview`가 존재함. 기존 호스트 판별은 이 객체만 보고 닷넷 통로를 선택했음. Rust의 `bridge` 이벤트를 구독하지 않아 Ctrl+Shift+7로 창은 표시해도 `/snippet`으로 이동하지 못함. route 보고와 Esc도 같은 오판의 영향을 받음.
|
||||
|
||||
설치된 `wry-0.55.1/src/webview2/mod.rs:885`에서 Tauri 기반 웹뷰가 `window.chrome.webview.postMessage`를 사용하는 것을 확인함. 이전 테스트는 두 객체가 공존할 때 닷넷을 우선하는 잘못된 기대를 갖고 있었음.
|
||||
|
||||
## 수정
|
||||
|
||||
`webview()`가 Tauri의 invoke 존재 여부를 먼저 확인하고, Tauri이면 닷넷 전용 통로를 반환하지 않도록 수정. 기존 공통 접근자를 통해 이벤트 구독과 창 제어 모두 Tauri 통로로 연결함.
|
||||
|
||||
## 검증
|
||||
|
||||
- Windows처럼 두 객체가 공존하는 회귀 테스트에서 수정 전 화면 이동·창 숨김 2개 실패를 재현함.
|
||||
- 수정 후 전체 43개 테스트 파일, 243개 테스트 통과.
|
||||
- 실행 중 Vite의 변경 반영과 페이지 재로드 로그 확인.
|
||||
- 실제 단축키 입력에 따른 화면 전환은 사용자 확인이 남음. Tauri 스니펫 저장소 구현은 별도 미완료 항목임.
|
||||
@@ -0,0 +1,29 @@
|
||||
# 저장된 로그인 정보 때문에 sessions·refresh 401 반복
|
||||
|
||||
## 증상
|
||||
|
||||
- 앱을 열면 로그인 화면 대신 `/api/v1/chat/sessions`를 먼저 호출하고 401이 남음.
|
||||
- 이어서 `/api/v1/auth/refresh`가 호출되지만 refresh 쿠키가 없어 `REFRESH_TOKEN_MISSING` 401이 남음.
|
||||
|
||||
## 원인
|
||||
|
||||
`authStore`는 user만 localStorage에 저장하고 실제 토큰은 httpOnly 쿠키로 관리함. 그런데 `ProtectedRoute`가 저장된 user만 보고 보호 화면을 열었음. 쿠키가 이미 사라져도 user가 남아 있으면 `SessionListPage`가 먼저 마운트되어 sessions API를 호출함.
|
||||
|
||||
서버 진실값을 확인하려 만든 `useMe()`는 어디에도 마운트되지 않아 실제 로그인 검증에 쓰이지 않았음. `/users/me`의 refresh까지 실패하면 공통 interceptor가 요청을 세션 만료 모달 큐에 넣어 Promise를 끝내지 않으므로, 앱 시작 검증에도 그대로 쓸 수 없었음.
|
||||
|
||||
## 수정
|
||||
|
||||
| 파일 | 내용 |
|
||||
| --- | --- |
|
||||
| `2_frontend/src/shared/components/ProtectedRoute.tsx` | 저장된 user가 있으면 `useMe()` 검증이 끝날 때까지 보호 화면을 열지 않음. 검증 실패 시 user를 지우고 로그인으로 이동 |
|
||||
| `2_frontend/src/features/auth/api/auth.api.ts` | 앱 시작 `/users/me` 요청에 세션 만료 큐 제외 표시 추가 |
|
||||
| `2_frontend/src/lib/api/client.ts` | refresh 실패 시 앱 시작 검증 요청은 모달 큐에 넣지 않고 401을 호출자에게 반환 |
|
||||
| `2_frontend/src/shared/components/ProtectedRoute.test.tsx` | 검증 중 보호 화면 차단과 실패 시 로그인 이동 회귀 검사 |
|
||||
| `2_frontend/src/lib/api/client.test.ts` | 앱 시작 refresh 실패가 세션 만료 큐에 갇히지 않는 회귀 검사 |
|
||||
|
||||
## 검증
|
||||
|
||||
- 회귀 검사 RED: 보호 화면이 즉시 열렸고, 초기 `/users/me` 요청은 세션 만료 큐에서 5초 timeout 됨.
|
||||
- 수정 후 전체 frontend 검사: 47 files, 268 tests 통과.
|
||||
- `npm run build` 통과.
|
||||
- 브라우저에서 stale user + 쿠키 없음으로 `/snap` 진입: `/users/me` → `/auth/refresh` 뒤 `/login?from=%2Fsnap` 이동, localStorage user 제거 확인. `/chat/sessions` 요청은 발생하지 않음.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 로그인 입력 영역이 창 위쪽에 치우침
|
||||
|
||||
## 증상
|
||||
|
||||
로그인 창을 열면 이메일·비번 입력 영역이 가로로만 가운데 정렬되고 세로로는 위쪽에 붙어 보였음.
|
||||
|
||||
## 원인
|
||||
|
||||
`LoginPage`가 `mx-auto`와 `py-12`만 사용해서 세로 가운데 정렬 기준이 없었음. 공통 `Layout`의 `main`도 flex 자식이 남은 높이를 채울 수 없는 구조였음.
|
||||
|
||||
## 수정
|
||||
|
||||
- 공통 `main`을 세로 flex 컨테이너로 바꿈
|
||||
- 로그인 페이지 바깥 영역이 남은 높이를 채우고 `items-center justify-center`로 입력 영역을 가운데 배치함
|
||||
- 가운데 정렬 class를 확인하는 회귀 테스트를 추가함
|
||||
|
||||
## 확인
|
||||
|
||||
- `npm run lint` 통과 (기존 warning 9개)
|
||||
- 전체 테스트 269개 통과
|
||||
- production build 통과
|
||||
@@ -0,0 +1,19 @@
|
||||
# 접힌 사이드바에서 로그아웃을 찾을 수 없음
|
||||
|
||||
## 증상
|
||||
|
||||
사이드바가 접히면 사용자 이름이 보이지 않아 사용자 메뉴 안의 로그아웃도 찾기 어려웠음.
|
||||
|
||||
## 원인
|
||||
|
||||
로그아웃이 사용자 이름을 눌러야 열리는 메뉴 안에만 있었음.
|
||||
|
||||
## 수정
|
||||
|
||||
사이드바 하단에 로그아웃을 독립 버튼으로 배치함. 펼친 상태에는 글자와 아이콘, 접힌 상태에는 아이콘과 툴팁이 표시됨.
|
||||
|
||||
## 확인
|
||||
|
||||
- 사용자 이름이 없어도 로그아웃 버튼이 동작하는 회귀 테스트 추가
|
||||
- 전체 테스트 272개 통과
|
||||
- lint 및 production build 통과
|
||||
@@ -0,0 +1,24 @@
|
||||
# 캡처 영역 확정 뒤 챗봇 창이 안 뜸
|
||||
|
||||
## 증상
|
||||
|
||||
- `Ctrl+Shift+9`를 누르면 캡처 영역 선택 화면까지는 정상적으로 떴음.
|
||||
- 영역을 선택해도 챗봇 창과 새 대화 화면이 열리지 않았음.
|
||||
- 캡처 이미지는 클립보드에도 들어가지 않았음. 클립보드는 원래 바꾸지 않는 설계라 이 부분은 정상임.
|
||||
|
||||
## 원인
|
||||
|
||||
- 영역 확정 처리에서 `DestroyWindow`를 먼저 호출했음.
|
||||
- `DestroyWindow`가 `WM_NCDESTROY`를 동기로 처리하며 `OverlayState`를 해제했음.
|
||||
- 그 뒤 해제된 상태의 `sender`로 선택 영역을 보내려 해서 캡처 완료 콜백까지 결과가 전달되지 않았음.
|
||||
|
||||
## 수정
|
||||
|
||||
- 선택 영역을 채널로 먼저 보낸 뒤 마우스 캡처를 풀고 오버레이 창을 닫게 순서를 바꿈.
|
||||
- 결과 전달 뒤에는 `OverlayState`를 다시 읽지 않게 함.
|
||||
|
||||
## 확인
|
||||
|
||||
- 캡처 좌표 단위 테스트를 통과함.
|
||||
- Rust 전체 테스트와 빌드로 기존 기능 회귀가 없는지 확인함.
|
||||
- Win32 오버레이의 실제 마우스 조작은 데스크톱 앱에서 수동 확인이 필요함.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Ctrl+Shift+8이 챗봇 대신 다른 화면을 염
|
||||
|
||||
## 증상
|
||||
|
||||
- `Ctrl+Shift+8`로 CodeAssist를 열면 챗봇이 아닌 로그인·파일 드롭 같은 화면이 다시 뜨었음.
|
||||
- 챗봇이 아닌 화면에서 단축키를 누르면 창이 숨어 토글 동작도 틀렸음.
|
||||
|
||||
## 원인
|
||||
|
||||
- route 보고 상태가 `/snippet`만 아니면 모든 경로를 `last_snap`으로 저장했음.
|
||||
- 창 숨김 조건도 `/snippet`이 아닌 모든 화면을 챗봇으로 보고 있었음.
|
||||
|
||||
## 수정
|
||||
|
||||
- `last_snap`은 `/snap` 또는 `/snap/...` 경로에서만 바꾸게 함.
|
||||
- 현재 화면이 `/snap` 계열이고 창이 보일 때만 숨기고, 나머지는 마지막 챗봇 화면으로 이동하게 함.
|
||||
|
||||
## 확인
|
||||
|
||||
- 파일 드롭 화면이 `last_snap`을 덮어쓰는 재현 테스트를 먼저 추가해 실패를 확인함.
|
||||
- 파일 드롭 화면에서 단축키를 누르면 숨지 않고 마지막 챗봇으로 가는 회귀 테스트를 추가함.
|
||||
Reference in New Issue
Block a user