## 2026-05-02 — Cloudflare Tunnel 커스텀 도메인 + 모바일 Git 패널/커밋 long-press + 탭 호버 도움말

### 배경

Quick Tunnel/Named Tunnel의 `*.trycloudflare.com` / `*.cfargotunnel.com` URL이 Safe Browsing/SmartScreen에 도메인 평판 문제로 "위험한 사이트" 경고를 띄우는 케이스 — 사용자 본인 도메인으로 named tunnel을 라우팅하면 그 도메인의 평판으로 표시되어 경고 회피. 별도로 Git 탭이 768px 이하에서 사실상 사용 불가였던 점을 반응형으로 보강하고, 길어서 잘리는 모바일 커밋 메시지를 long-press tooltip으로 노출. 마지막으로 프로젝트 상세 탭에 hover help 툴팁을 portal 패턴으로 추가해서 첫 사용자도 어떤 탭이 무엇인지 클릭 없이 파악 가능.

### 주요 변경

#### 1. Cloudflare Tunnel 커스텀 도메인 라우팅 (`21ab419`)

브라우저 평판 문제이므로 cloudflared CLI args는 그대로 두고 표시/설정 레이어만 추가. 사용자가 `tunnel route dns`는 직접 실행해야 한다는 가정.

- **DB**: 신규 `app_settings(key, value, updated_at)` KV 테이블 — 글로벌 설정용 (per-project가 아닌). `db/app-settings.ts` 헬퍼 (`getSetting`/`setSetting`)
- **`tunnel-manager.ts`**: `startNamedTunnel`이 optional `customHostname` 수용. 존재 시 표시 URL을 `https://<hostname>`으로 교체 (없으면 `${name}.cfargotunnel.com` fallback)
- **routes/tunnel.ts**:
  - 신규 `GET/PUT /api/tunnel/config` — hostname validation (localhost/공백/non-domain 거부, hostname pattern 정규식, customHostname 있으면 tunnelName 필수)
  - `POST /start`가 DB-first로 config를 읽음 (env는 fallback)
- **`index.ts`**: 부팅 시 tunnel auto-start도 DB-first
- **`bin/clitrigger.js`**: 신규 `config tunnel hostname <host>|clear` 서브커맨드, `TUNNEL_HOSTNAME` env propagation, summary line에 hostname 표시
- **클라이언트**:
  - `api/tunnel.ts` config GET/PUT wrapper
  - `TunnelSettings.tsx` (Modal-based) — status 배지, 검증, restart 버튼, collapsible cloudflared 셋업 가이드
  - Sidebar gear 버튼이 새 settings 모달 open
  - `i18n.tsx`에 `tunnel.*` 키 (ko/en)
- **회귀 보장**:
  - 빈 tunnelName + 빈 hostname → Quick Tunnel (현재 default)
  - tunnelName만 → `*.cfargotunnel.com` (현재 동작)
  - 둘 다 → `https://<customHostname>` (신규 path)

#### 2. 모바일 Git 패널 반응형 (`9be8331`)

768px 이하에서 224px 고정 사이드바가 화면 대부분 잡아먹고, commit history의 graph + author 컬럼이 메시지 공간을 다 빼앗던 문제. `useMediaQuery('(max-width: 767px)')` 분기 (SessionWindow와 동일 breakpoint). 데스크탑 동작은 그대로.

- **외곽 레이아웃**: 사이드바를 main view 위로 stack. 224px vertical `WorkspaceMenu` 카드 → 단일 행 segmented control + "Branches" 토글 버튼 (탭하면 `RefsSidebar`가 40vh accordion으로 확장)
- **`WorkspaceMenu`**: optional `orientation` prop ('vertical' | 'horizontal') — horizontal 모드는 workspace 헤더 없이 flex-1 탭 렌더
- **Commit history**: 모바일에서 graph + author 컬럼 hide → description/date/short hash만 360px row에 fit
- **Commit detail**: 240px 고정폭 file list + 옆 diff (diff 공간 0) → file list를 diff 위에 stack
- **Working changes**: `isMobile` prop, file pane의 `minWidth: 360` 제거, staged/unstaged/commit-bar를 diff viewer 위에 stack
- **Resizer 3개 모두 모바일에서 hide** (터치 입력에 무용)

#### 3. 모바일 커밋 메시지 long-press 미리보기 (`e2542050`)

모바일 commit history는 description을 row에 fit시키려 잘랐는데, hover가 없어 기존 `title` 툴팁이 닿지 않음 → 긴 메시지 읽을 방법이 없던 상태. 500ms long-press → tooltip popup, 짧은 tap은 기존대로 detail panel open.

- **`LongPressTooltip`** (portal 컴포넌트): 뷰포트 clamping (touch point 기준 위/아래 flip + 좌우 8px margin clamp), measured 후 fade-in (첫 frame flash 회피). 프로젝트 UI 가이드라인의 floating element 패턴 (`MoreMenu`/`DeprecatedModelBadge` mirror) — portal + `position: fixed` + viewport clamp + `z-tooltip`
- **Long-press 트래킹**: pointerdown/move/up handler — 500ms 타이머, >8px 움직임 시 cancel (수직 스크롤 우선), 발동 시 `navigator.vibrate(10)` (지원 브라우저만)
- **Click 억제**: long-press 후 fire되는 click을 suppress (detail panel 의도치 않게 안 열리게). 다음 tap을 위해 fired flag reset
- **자동 dismiss**: tap-outside / scroll(capture) / resize / 모바일 breakpoint 벗어남 시 tooltip 정리
- **iOS context-menu/text-selection magnifier 차단**: 모바일 row에 `touchAction: pan-y`, `WebkitTouchCallout: none`, `onContextMenu preventDefault`

#### 4. 프로젝트 상세 탭 hover 도움말 툴팁 (`1d46e4c`)

탭 위에 호버하면 portal-rendered 툴팁으로 각 탭 (Wiki/Planner/Auto Tasks/Sessions/Discussions/Schedules/Git/Analytics + Harness 플러그인 탭) 설명을 노출. 모든 탭을 클릭해 보지 않아도 surface 발견 가능.

- **`TabHoverHelp`** 인라인 컴포넌트 (ProjectDetail.tsx 내) — portal + `position:fixed` + viewport clamping (`DeprecatedModelBadge` mirror), 250ms open delay (인접 탭을 sweep할 때 flicker 방지), `pointer-events:none` (tooltip이 hover trap 안 되도록)
- 각 탭 버튼을 `TabHoverHelp`로 wrap, tab array가 i18n에서 가져온 per-tab help string carry
- `tabs.<key>.help` 키 (ko/en) 추가: wiki, planner, tasks, sessions, discussions, schedules, git, analytics
- 플러그인 탭은 manifest translations에 `tabs.harness.help` 같은 키 — 새 manifest 필드 도입 없이 동일 컨벤션 따름
- `tabs.<id>.help` 키 없는 플러그인 탭은 툴팁 없이 fallback (wrapper가 빈 body면 children만 렌더)

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/server/db/schema.ts` | 신규 `app_settings(key, value, updated_at)` KV 테이블 |
| `src/server/db/app-settings.ts` | NEW — getSetting/setSetting 헬퍼 |
| `src/server/services/tunnel-manager.ts` | startNamedTunnel(customHostname) — 표시 URL 교체 |
| `src/server/routes/tunnel.ts` | GET/PUT /config, hostname 검증, /start DB-first |
| `src/server/index.ts` | tunnel auto-start DB-first |
| `bin/clitrigger.js` | `config tunnel hostname` 서브커맨드, TUNNEL_HOSTNAME propagation |
| `src/client/src/components/TunnelSettings.tsx` | NEW — 모달 기반 tunnel 설정 UI |
| `src/client/src/components/Sidebar.tsx` | gear 버튼이 TunnelSettings 모달 open |
| `src/client/src/api/tunnel.ts` | getConfig/saveConfig wrapper |
| `src/client/src/components/GitStatusPanel.tsx` | useMediaQuery, 모바일 layout 분기, mobile resizer hide |
| `src/client/src/components/WorkspaceMenu.tsx` | optional orientation prop (horizontal 모드) |
| `src/client/src/components/CommitHistory*.tsx` | 모바일에서 graph/author 컬럼 hide, detail panel stack |
| `src/client/src/components/WorkingChangesView.tsx` | isMobile prop, minWidth 제거, stack 레이아웃 |
| `src/client/src/components/CommitDescription.tsx` | LongPressTooltip 포팅 (portal + clamping) |
| `src/client/src/components/ProjectDetail.tsx` | TabHoverHelp 인라인 컴포넌트, 탭별 help wiring |
| `src/client/src/i18n.tsx` | tunnel.* 키, tabs.<key>.help 키 |
| `src/client/src/plugins/harness/manifest.ts` | tabs.harness.help 번역 키 |

### 아키텍처 결정

1. **app_settings KV 테이블**: tunnel hostname은 per-project가 아닌 인스턴스-글로벌 설정. project 컬럼에 squat 시키는 대신 별도 KV로 — 향후 다른 글로벌 설정도 같은 테이블에 (e.g. 디폴트 모델, UI prefs)
2. **Tunnel CLI args 무변경**: 서버가 hostname을 routing하지 않음. 사용자가 `cloudflared tunnel route dns <name> <hostname>`을 직접 실행 (Cloudflare API 자격 필요). 표시/검증 레이어만 추가해서 책임 분리
3. **모바일 Git: 데스크탑 코드 path는 무손실**: `isMobile` 분기로만 처리, 768px 이상에서 추가 비용 없음. resizer hide도 동일 — 데스크탑은 그대로
4. **Long-press tooltip pattern을 floating element 가이드라인에 합류**: `MoreMenu`/`DeprecatedModelBadge`와 동일 portal + fixed + clamp. 새 floating UI는 항상 이 패턴 사용
5. **플러그인 탭 도움말은 manifest translations 재활용**: 새 manifest 필드 (`tabHelp` 등) 도입 대신 i18n 키 컨벤션 (`tabs.<id>.help`) 만으로 통일. 플러그인은 키만 추가하면 됨

---
