d3ro-voice/design.md
Yun Chan 5ca3709b32
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s
feat: add mobile shell and unified search
2026-09-28 23:23:36 +09:00

353 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# design.md — D3RO Voice 크로스플랫폼 디자인 결정 기록
> 2026-08-22 전면 타이포그래피 개편 ("타이포그래피 퍼스트 v3").
> 다음 실행(사람이든 에이전트든)은 이 파일을 읽고 같은 결정을 이어간다.
## 브리프 3줄
- 무엇을: 로컬 AI 음성 어시스턴트의 6개 표면(데스크톱 Electron / 관리자 / 웹앱 /
마케팅 사이트 / React Native 모바일 / 공유 DS 패키지)의 통합 타이포그래피·폰트 개편.
- 누구에게: 한국어 기본 사용자(개발자·지식노동자). 도구는 하루 종일 켜져 있다.
- 제약: 기존 스택 유지(MUI 7 / Tailwind 3 / RN StyleSheet), 기능 파괴 금지,
성능 예산(히어로까지 JS 150KB gzip, 이번 개편은 폰트 로드 정리로 감소).
## 컨셉 한 문장 · 프리셋 · 리스크
- 한 문장: **볼드를 버리고 크기·행간·명도로 위계를 만드는 한국형 스위스 미니멀** —
"정밀 계기판의 고요한 밤"에서 "조용히 읽히는 정밀 도구"로.
- 프리셋: swiss-minimal(그리드와 침묵) × dark-instrument(계기판의 정확함).
- 감수한 리스크 하나: 헤드라인 최대 웨이트를 500으로 내린 것. 라틴 마케팅 관례
(700-800 디스플레이)를 버리는 선택이지만, 한글 낱글자의 시각 밀도에서는
500+큰 크기+타이트 트래킹이 더 고급스럽다 (Apple 한국어 페이지가 같은 길).
## 한글 폰트 결정 (리서치 기반)
- **웹/데스크톱: Pretendard Variable 단일 패밀리.**
근거: Inter 기반 네오그로테스크로 한/영 조화(혼용 시 별도 조정 불필요),
크로스플랫폼 웨이트 일관성, 9단계+가변 폰트, 사실상 한국 UI 표준.
SUIT(본문 특화)·Paperlogy(마케팅 특화)를 검토했으나 교체 이득 < 전환 비용.
- **로딩 방식: 다이나믹 서브셋 + woff2 + preload(preconnect)** — 2025-26 표준.
화면에 쓰인 글리프만 분할 로드한다. 기존 정적 pretendard.css 전체 로드를 폐기.
- **모바일(RN): 시스템 폰트 스택.** bare RN이라 커스텀 한글 폰트 번들링(2MB+ ttf,
네이티브 재빌드) 대신 iOS=SF Pro→Apple SD Gothic Neo, Android=Roboto→Noto Sans KR
자동 폴백. Apple HIG/Material 권장 표준. **Menlo/monospace 전면 폐기** —
Menlo에 한글 글리프가 없어 한/영 혼용 입력이 뒤섞이는 버그였음.
- **site: Plus Jakarta Sans 제거.** 한+영 혼용 헤드라인에서 라틴/한글 스트로크
불일치. Pretendard가 Inter 기반이므로 라틴도 Pretendard로 통일(로딩 1종 감소).
## 타이포그래피 원칙 (W3C klreq / 요즘IT DS / KRDS 종합)
1. **볼드 금지(700/800/900 → 0회)**. 최대 600(의미 강조 `strong` 전용),
헤드라인 500, 몸통 400, 라벨 500. 위계는 크기·행간·명도(text token)로.
2. 한글 행간 1.5~1.7 (body 1.6), 헤드라인도 1.2 이상. 블록 글자의 숨 쉬는 공간.
3. 자간 0 근처. 음수는 대형 헤드라인만 -0.01~-0.02em. 양수(+0.02~0.04em)는
라틴 대문자 라벨에만. 한글에 양수 트래킹 금지.
4. **한글 최소 크기 11px**. 8-9px 토큰(engrave/micro/nano)은 상향
(engrave/micro 10px, nano 9px — 라틴 전용 텔레메트리).
5. 폰트 1패밀리 + 웨이트 3종(400/500/600). mono는 텔레메트리·코드·숫자에만.
## 토큰 변경 (SSOT: packages/ui/src/theme.ts `d3roTypo` v3)
| 토큰 | v2 (구) | v3 (신) |
|---|---|---|
| hero | 32px/700/-0.5px/1.2 | 32px/500/-0.02em/1.3 |
| title | 24px/700/-0.3px/1.25 | 24px/500/-0.01em/1.35 |
| value | 20px/600/0/1.1 | 20px/500/0/1.2 |
| heading | 16px/600/0/1.4 | 16px/500/0/1.5 |
| body | 14px/400/0/1.55 | 14px/400/0/1.6 |
| label | 11px/600/0.08em/1.2 | 11px/500/0.04em/1.3 |
| engrave | 9px/700/1px | 10px/500/0.06em/1.2 |
| nano | 8px/700/0.5px | 9px/500/0.03em/1.15 |
- MUI 기본값 동반 변경: h4/h5 700→500, h6/button/caption 600→500,
body1 1.5→1.6, Chip/Tab/ListItemButton 600→500.
- `d3roFontSans`에 한글 폴백 추가: Apple SD Gothic Neo, Noto Sans KR, Malgun Gothic.
- RN 대응(`packages/ui-native/theme.ts` `d3roNativeTypo`) 동일 철학으로 동기화.
label 10px/700/ls2 → 11px/500/ls0.4.
- site(Tailwind) type scale: hero lh 1.14→1.22, ls -0.035em→-0.02em 등 한글 보정.
## 표면별 변경 요약
| 표면 | 변경 |
|---|---|
| packages/ui (SSOT) | d3roTypo v3, MUI 오버라이드 탈볼드, 한글 폰트 스택 |
| packages/ui-native | 타이포 v3, sans 스택, PhosphorText 텍스트 글로우 제거, PhysicalButton uppercase·과도 트래킹 제거, StatusBar 8px→10px |
| apps/desktop | **보더리스**: Win/Linux `frame:false` + 커스텀 TitleBar, macOS `titleBarStyle:'hiddenInset'` + 네이티브 교통신호(커스텀 컨트롤 숨김, 78px 여백). 기존 `frame:true`+커스텀 타이틀바 이중 크롬 버그 해소. 인라인 700×95/800×10/900×2 → 500/600, 팝업 CSS 동일 |
| apps/admin | Pretendard 이중 로드(link+@import)→다이나믹 서브셋 단일, 미사용 JetBrains Mono 로드 제거, 700×84→500/800×2→600, 프리셋(table/filter/badge/button) 600→500, `strong`→600 CSS |
| apps/web | **Pretendard 다이나믹 서브셋 로드 추가**(기존 폰트 0로딩→OS 폴백 렌더 버그 수정), login 하드코딩 스택 교체, 700×24/800×13/900×2 스윕 |
| site | font-bold 13/semibold 37 → medium, Plus Jakarta 제거, 한글 type scale 보정, `strong` 600 |
| apps/mobile-rn | Menlo/monospace 전면 제거(입력창 18곳), TabBar 인셋 기반(52+insets.bottom)+라벨 9px mono→11px sans, LoginScreen 세이프에어리어 추가, 스택 화면 8곳 하단 인셋 Math.max(insets.bottom,24), letterSpacing 디-모노화, 700×3→500 |
## 표준 준수 노트
- **모바일 세이프에어리어**: 모든 화면 `useSafeAreaInsets` 기반. 탭 화면은 탭 바가
인셋을 담당, 스택 화면은 화면이 담당. 고정 padding(60/120) 잔여 없음.
- **iOS/macOS**: 데스크톱 macOS는 hiddenInset(교통신호 유지 — HIG 준수).
모바일 iOS는 시스템 폰트+자동 폴백. KeyboardAvoidingView 플랫폼 분기 유지.
- **웹**: `font-display: swap` + preconnect. 웹뷰 폰트 이중 로드 제거.
## 성능 예산과 실측
- 폰트: admin 2회 로드→1회, site 2패밀리→1패밀리+mono, web 0→1(다이나믹 서브셋).
- site 빌드 실측: 360.53KB JS(gzip 111.04KB), CSS 40.37KB(gzip 7.81KB) — 예산 내.
- 애니메이션 정책 변화 없음(transform/opacity만, 기존 유지).
## 의도적으로 하지 않은 것
- **관리자 상태값 라벨은 대문자 유지.** ACTIVE/DISABLED/PRO+ VIP/POST/PATCH 등은
코드 비교·API 값이라 문장형 변환 금지. 대신 페이지 부제(• 구분 설명문)와
사이드바 그룹 라벨·대시보드 카드 라벨은 문장형으로 전환했다(2026-08-22).
- **site의 CRT/crosshair CSS는 유지.** 감사에서 '레거시'로 분류했으나 실사용
확인(HowItWorks·Privacy·Crosshair) — 제거하지 않는다.
## 2026-08-22 추가 완료 (2차: 브랜드 통일 + 오프시스템 정리)
- **브랜드 색 통일 — amber → blue 완료.** site(brand-amber 134곳→brand-blue,
favicon/download.html/LED 시맨틱 포함), mobile-rn(다크 #3b82f6/pressed #2563eb/
onAccent 흰색 반전, 라이트 #2563eb), ui-native 레거시(#3b82f6).
근거: 데스크톱·웹·관리자 + 6개 테마가 이미 blue(Midnight Glass)인데 site/모바일만
구 CRT amber 시대 잔재였다. 이제 전 표면 단일 액센트.
- **apps/web 오프시스템 색 전면 토큰화.** download(잔여 hex 0), login(잔여 0),
chat-panel/mic-recorder/commands/billing/history/sidebar의 구 amber #ff5c35
37곳 + zinc 표면 90여 곳 → d3roPalette/--d3-* var로 교체. 6테마 연동.
download·login은 이제 테마 반응형. on-accent 대비 보정(챗 전송 버튼 hover
흰색). `--d3-tag-*` CSS 변수를 SSOT buildCssVars에 추가(color-mix 틴트 파생용).
- **관리자 라벨 문장형 전환.** 페이지 부제 10곳(• 나열 → 쉼표 문장형, 약어
STT/LLM/SLA/RAG/VIP 보존), 사이드바 그룹·대시보드 카드 라벨 CSS uppercase 제거
(원본 문자열은 이미 문장형). 죽은 "Service Telemetry NOT CONNECTED" 패널 제거.
- **관리자 globals.css :root 죽은 변수 제거.** SSOT와 이름이 충돌하는 19개 로컬
변수(소비자 0) 삭제, body는 --d3-bg-app/--d3-text-primary으로 직결.
- **모바일 커스텀 한글 폰트 번들링 — 최종 결정: 하지 않음(확정).** Apple HIG와
Material 가이드가 모두 시스템 폰트를 권장하고, iOS Apple SD Gothic Neo /
Android Noto Sans KR 폴백 품질이 이미 우수하며, 정적 ttf 번들(2웨이트 약
5-10MB)은 다이나믹 서브셋이 없는 RN에서 비용 대비 효과가 낮다. 이 항목은
미확정이 아니라 확정된 결정이다.
## 다음 실행을 위한 지침
- 새 텍스트는 `d3roTypo`/`d3roNativeTypo` 토큰 또는 MUI 기본값만 쓴다.
인라인 `fontWeight`는 금지(예외: 조건부 400↔500 전환).
- 한글 문자열에 `letterSpacing` 양수 값을 직접 넣지 않는다.
- 11px 미만 텍스트를 추가하지 않는다 (라틴 텔레메트리 예외 하한 9px).
## 2026-09-22 Local Flow Intelligence extension
- 기존 **swiss-minimal × dark-instrument** 방향과 token/typography/a11y 규칙을 그대로 적용한다. Flow Radar·Edit Friction·App Quality는 색만으로 상태를 구분하지 않고 텍스트 라벨과 수치를 함께 보인다.
- Privacy Receipt은 `dl`의 정의/값 구조로 보존 기간과 실제 row count를 짝지어 읽게 한다. raw key 이벤트/키코드/콘텐츠 스트림 미저장과, 옵트인 학습 텍스트의 별도 보존 정책을 혼동시키지 않는다. `personal_phrases`는 나이 기반 자동 만료가 없고 개별 삭제·전체 삭제·동의 철회로 제거됨을 정확히 보인다.
- suggestion overlay는 좁은 폭을 유지한다. Why This Suggestion은 한 줄의 provenance와 count만 보이며 raw memory evidence를 노출하지 않는다.
## 2026-08-24 광고 수익화 E2E RED 스위트 (652 시나리오)
- 위치: `apps/desktop/tests/red/ads/` (ads-mediation 370 / admob-ssv 142 / ad-settlement 140)
- 계열: A 입찰 거버넌스 110 · B 크리에이티브 안전성 80 · C 노출·클릭 추적 70 ·
D 리워드 60 · E SSV 검증 142 · F 정산 원장 90 · G 설정 거버넌스 40 · H 엔진↔정산 60
- 실측: **GREEN 582 / RED 70** (전체 데스크톱 스위트 1,242개, 기존 590개 회귀 0)
- RED 백로그(구현해야 수입 무결성 확보):
1. SSV 리플레이 방지 60 — 동일 transaction_id 재검증·과거/미래 timestamp 수용(미구현)
2. setConfig 검증 10 — 음수 rewardTokensAmount 허용(미검증)
- 이식성 경고: admob-ssv의 derEcdsaSignatureToRaw는 Deno WebCrypto가 DER 서명을
반환한다는 동작에 의존. Node(≥22)는 RAW(r||s)를 반환한다 — Deno 업그레이드 시
서명 형식이 바뀌면 검증이 전면 실패할 수 있음. 테스트 픽스처는 RAW→DER 변환기로
실 AdMob 형식을 재현한다.
## 2026-08-24 RED→GREEN 완료 + 수익화 계획
- admob-ssv.ts: 리플레이 방지(tx_id 25h 캐시) + 타임스탬프 신선도(±1h) 구현 → 엣지함수 재배포
- AdMediationEngine.setConfig: 음수/0 이하 설정값 검증 추가
- 광고 스위트 652/652 GREEN, 데스크톱 전체 1,242/1,242 GREEN, tsc 0
- 수익화 로드맵: docs/monetization-plan.md (AdMob 프로덕션 ID 발급이 다음 관문)
## 2026-09-26 랜딩 사이트(site/) 정리 — 진실성 · 접근성 · 단순화
연장 경로(`design.md` 방향 유지: swiss-minimal × dark-instrument, 탈볼드, Pretendard).
레퍼런스 1′: superwhisper.com 실측 — 히어로 CTA 2개, 히어로 수치 없음, 가격 3단 "이전 티어 전부 +".
- 브리프: 무엇을 = Windows 음성 받아쓰기 홍보 사이트 / 누구에게 = 한국어 기본 지식노동자·개발자,
목표 행동 = **다운로드 1순위 + 구독(Pro/Pro+)** / 제약 = 10개 로케일 전부 번역, 기존 토큰 유지.
- 확정한 사실(사용자): 가격 Free ₩0 · Pro ₩2,900/월 · Pro+ ₩8,900/월(원화, 월간만), 오픈 소스 주장 삭제(저장소 비공개).
- 섹션: 히어로(예시 1개) → 기능 → 작동 방식 → 개인정보 → 요금 → 다운로드 → FAQ → 푸터.
레이아웃 패밀리: 분할(히어로) / 제목 고정+목록(기능·FAQ) / 4열 번호 행(작동 방식) / 사양서 `dl`(개인정보) / 카드 3장(요금) / 8:4 패널(다운로드).
- 사실 SSOT: 단축키 = `packages/core/src/keybinding.ts`(오른쪽 Alt 누른 채), 사용량 = `packages/core/src/constants.ts`
`PREMIUM_MODEL_LIMITS`, 가격·결제 URL = `site/src/pricing.ts`, 버전·게시일 = `site/src/release.ts`(version:sync 대상).
- 토큰 변경: `brand.blue-deep #1d4ed8` 추가(주 버튼 hover). 주 버튼 채움은 `blue-dark`(흰 글자 5.2:1) —
`blue`(3.7:1)는 본문 크기 버튼 글자에 AA 미달. 그림자는 `chassis`만, 애니메이션은 `glow-pulse`만 남김.
- 컴포넌트 클래스: `.btn/.btn-primary/.btn-secondary`(최소 44px, 줄바꿈 허용), `.kbd`(sans — 한글 키 이름), `.link`, `.skip-link`.
- 한글 줄바꿈 클래스는 `keep-ko`(`:lang(ko)`에서만 keep-all). **Tailwind 내장 `break-keep`을 쓰지 말 것** —
중국어·일본어에서 줄바꿈 기회를 없애 320px에서 가로 넘침이 났다(실측).
- 접근성: 본문 건너뛰기 링크, 섹션 `aria-labelledby`, 모바일 메뉴·언어 선택 `aria-expanded`+Esc+포커스 복귀,
FAQ 디스클로저(`aria-controls`/`region`), 예시 상태 `aria-live`, 감소 모션(전역 CSS + 예시는 결과로 바로),
앵커 `scroll-margin-top`, 새 탭 링크 표기, `<html lang>`·title·description 로케일 추종.
- 검증: 10개 로케일 × 320/390/1440 가로 넘침 0, axe(wcag2a/aa/21aa/22aa/best-practice, ko·ja) 위반 0,
390 터치 타깃 24px 미만 0, 페이지 길이 1440 10,536→5,062px / 390 14,753→7,826px,
빌드 JS 111.0→98.0KB gzip, CSS 7.8→5.2KB gzip.
### 의도적으로 하지 않은 것
- **히어로 수치·상태 뱃지 없음.** "3배 빠름", "10/10", "<1.2s", "시스템 정상 가동", "프로토콜" 등은 근거가 없어 삭제. 되살리지 말 것.
- **경쟁사 비교표·광고/리워드 문구·SHA-256 계산기·릴리스 히스토리 없음.** 계산기는 비교 기준이 없어 무의미했고, 무결성은 `latest.yml` 링크로 대신한다.
- **설치 파일 크기 표기 없음.** `version:sync`가 갱신하지 않는 값이라 다음 릴리스에 틀린 숫자가 된다. 게시일(동기화 대상)만 쓴다.
- **"인기" 뱃지·연간 요금·통화 토글·Team·14일 체험 없음.** 결제 카탈로그·앱에 근거 없음.
- **기능 카드에 FREE/PRO 태그 없음.** 로컬 기능은 전부 free다(`LicenseService.FEATURE_MIN_TIER`).
- **화자 분리·RAG를 대표 기능으로 내세우지 않음.** 화자 구분은 추정 단계라 약속하지 않는다.
- **CRT 움직이는 스캔 효과 제거**(정지 주사선만 히어로 예시 패널에 유지). 이전 기록의 "CRT CSS 유지"는 예시 패널 한 곳으로 좁혔다.
- ~~미확정: 서버 가격 불일치·결제 URL 이원화~~ → 2026-09-26 Wave 3에서 해소(`packages/core` plan-catalog/web-urls 단일 정본, `/app/billing` 하나).
### 2026-09-26 추기 · 히어로 반복 예시(앱 녹음 캡슐 재현)
- 사용자 요청: "작동 방식 보기" CTA 대신 우측에서 앱과 똑같은 예시가 계속 돌게. 히어로 CTA는 다운로드 1개로 줄였다.
- `site/src/components/AppLoopDemo.tsx` — 메모 창 + 화면 아래 녹음 캡슐. 대기 → 오른쪽 Alt 누른 채 말하기(파형·경과 시간·부분 전사)
→ 처리 중(점근 진행 막대) → 커서 자리에 붙여넣기 → 다음 예시. 언어마다 예시 2개.
- 수치는 앱 `apps/desktop/src/renderer/popups/recording-tip`과 같다: 막대 9개 cos 가중치, 100ms 갱신, 스무딩 0.5, 무작위 ±35%,
높이 2–28px, 막대별 색, 진행 막대 120px `min(95, (1-1/(1+1.5t))*100)%`. 캡슐 CSS 값은 앱 style.css를 옮겨 적었다
(Electron 팝업 CSS는 전역 리셋을 포함해 사이트에서 직접 import할 수 없다 — 값이 바뀌면 두 곳을 함께 고친다).
- 접근성: 5초 넘게 움직이므로 일시정지 버튼(WCAG 2.2.2, aria-pressed). 화면 밖·숨은 탭에서 멈춤. 감소 모션은 정지 화면 한 장.
애니메이션은 aria-hidden, 화면 낭독용 설명문(demo.description)을 둔다. 중·일은 띄어쓰기가 없어 세 글자씩 나타난다.
- 하지 않은 것: 실제 음성 녹음·마이크 권한 데모(브라우저에서 로컬 모델을 돌릴 수 없고, 가짜 인식은 진실성 위반).
- 제거: 이전 버튼식 예시(DictationDemo), WaveBars, Crosshair, CRT 화면 CSS — 이전 기록의 "CRT CSS 유지"는 이로써 종료.
- 부수: `site/public/404.html`(없는 경로가 첫 화면으로 떨어지던 SPA 폴백 종료), 법률 페이지의 옛 주황 강조색을 파랑으로.
- 앱 쪽 발견(미수정): 녹음 캡슐의 "처리 중..."은 HTML에 한국어로 고정돼 있어 앱 언어를 따르지 않는다.
### 2026-09-27 추기 · 검색·AI 검색 최적화 (SEO + GEO)
- 문제: 사이트가 클라이언트 렌더링이라 크롤러가 받는 HTML 본문이 107자였다(Googlebot·GPTBot·ClaudeBot·PerplexityBot·네이버 Yeti 동일). AI 크롤러 대부분은 JS를 실행하지 않는다.
- **빌드 때 언어별 정적 HTML을 미리 그린다**(`site/scripts/prerender.mjs` + `src/entry-server.tsx`, React hydrateRoot로 이어받음). 주소: 한국어 `/`, 나머지 `/{en,ja,zh,es,fr,de,pt,ru,vi}/`. 언어 선택은 해당 주소로 가는 링크(크롤링 가능). 자동 언어 리다이렉트는 하지 않는다(구글 권장).
- `<head>`는 언어마다 title·description·canonical·hreflang(10+x-default=/en/)·Open Graph·X 카드. 구조화 데이터(JSON-LD @graph): Organization, WebSite, WebPage, SoftwareApplication(가격 3종 KRW·버전·게시일·다운로드 URL — 정본에서), HowTo(작동 방식 4단계), FAQPage. 가짜 평점(aggregateRating)은 넣지 않는다.
- AI 검색용: `/llms.txt`(llmstxt.org 형식, 영어+한국어 요약·핵심 사실·주요 주소), `/llms-full.txt`(영·한 전문). 모두 i18n 번역과 정본 값에서 빌드 때 생성 — 사이트 문구와 어긋날 수 없다.
- `robots.txt`: 전체 허용 + `Content-Signal: search=yes, ai-input=yes, ai-train=yes`(홍보 목적), `/app/`·`/api/`·`/accept-invite/` 제외, sitemap 안내. Cloudflare의 AI 봇 차단·Bot Fight Mode는 꺼져 있음을 확인.
- `sitemap.xml`: 언어별 페이지(xhtml:link 대체 언어)+법률 문서. IndexNow 키 파일(`/5d886cca….txt`)과 배포 후 알림(`scripts/ci/ping-indexnow.mjs`) — Bing(ChatGPT·Copilot 검색 기반)·네이버·Yandex.
- 보이는 내용 보강: "한눈에 보기" 사실 요약 섹션(정의형 문장 8개, 버전·가격은 정본), FAQ 2개 추가(인식 언어 — 앱 설정의 자동/한·영·일·중, 데이터 저장 위치).
- 공유·아이콘: 언어별 1200×630 공유 이미지(`public/og/<lang>.png`), 파비콘 PNG·apple-touch-icon·PWA 아이콘·manifest. 생성: `npx vite-node site/scripts/render-brand-assets.ts`(문구·로고가 바뀌면 다시 실행해 커밋).
- 빌드 경로 `base: '/'`(언어 하위 경로에서도 자산 경로가 맞도록).
- 검증: 본문 3,600~7,700자/언어, 하이드레이션 경고 0, 10개 언어 320/1440 넘침 0, axe 0, Lighthouse(모바일) SEO·접근성·권장사항 100.
- 하지 않은 것: 언어별 별도 콘텐츠(번역 외 추가 페이지)·블로그, 가짜 후기·평점, 자동 언어 리다이렉트.
## 2026-09-28 전면 개편 — 데스크톱·팝업·모바일·공유 DS ("기록이 주인공인 조용한 계기")
전체 경로 + 리디자인 감사. 방향(swiss-minimal × dark-instrument, 탈볼드, Pretendard, blue)은 계승하고,
정보 구조·토큰 v4·팝업·모바일 내비를 새로 정했다. 물결별 분리 커밋(① 토큰·DS → ② 데스크톱 구조 →
③ 받아쓰기 팝업 → ④ 모바일 → ⑤ 통합 검색).
### 브리프 (사용자 인터뷰 확정)
- 무엇을: 데스크톱(메인 앱·팝업 6종)·모바일 RN·공유 DS의 시각 언어 + 정보 구조·흐름 전면 개편.
- 누구에게: 1순위 회의 많은 직장인 → 2순위 지식노동자·개발자 → 3순위 일반 사용자.
목표: 기록·회의 결과를 쉽게 찾고 다시 쓰며, 받아쓰기가 끊김 없이 흐른다.
- 제약: 톤 = **조용한 정밀 도구**(사용자가 "극적인 정밀 기기(CRT·메탈·LED)"를 명시적으로 기각).
1급 = 기록·메모, 회의. 모바일 = 독립 완결형(1급 구조 동일). 기존 스택·기능 보존, 10개 로케일, 6테마.
- 우선순위: 구조(기능 산만) → 받아쓰기 흐름 → 기록 재사용 → 두 앱 일치.
- 기본 언어: ko-KR. 새 카피는 한국어, 영어는 기술 용어·키 이름만.
### 감사 (개편 전, 증거: 1280×800 dark/light 캡처 + 코드 감사 4건)
- 하드 게이트 위반: 데스크톱 사이드바 Box onClick(키보드 불가) / "100% LOCAL PRIVACY · ZERO CLOUD SYNC"(v1.9.0 기기 간
동기화와 모순) / 받아쓰기 Esc 취소 없음·처리 중 클릭 시 텍스트 소실 / 삭제 7종 확인·되돌림 없음 /
대비 미달 토큰 약 100곳(muted 1.42:1, dimLabel 2.72:1) / 감소 모션 전무.
- 산만함: 진입점 데스크톱 약 45개·모바일 27개. 회의는 기록에 저장되지 않고 통합 검색 없음. "메모"가 세 의미.
- 시각: 모든 카드가 글래스+글로우+그라디언트 링 → 같은 무게. 한글 라벨에 모노 폰트 → "누 름 유 지"처럼 벌어짐
(모노 폰트에 한글 글리프가 없다). 팝업 radius 999/16/10 혼재, 팔레트 웹·네이티브 3벌.
### 레퍼런스 (관찰은 scratch 아닌 이 기록에 요약)
- R1 구조(같은 시장): 클로바노트(노트+폴더, 요약·메모 영역 분리, 복사 2종), 다글로(보드, 요약/전사 탭, 모바일 가운데 +).
- R1 구조(목적): Granola(사용자 노트 검정·AI 회색, 근거 인용 🔍), Otter(Summary/Transcript 2탭, View in transcript).
- R1' 받아쓰기: Wispr Flow(화면 하단 작은 상태 바, 날짜별 기록+검색+hover 복사), Superwhisper(로딩/녹음/처리/완료 상태색, ESC 취소).
- R2 톤: Linear(요소마다 무게 다르게, 사이드바 몇 단계 어둡게, 크롬의 파랑 최소), Apple HIG(탭 바는 이동용, 빈번한 상호작용에 모션 자제).
- 가져온 원리: 요약 먼저·전사 근거 링크 / 재사용 행동 한 곳, 복사는 요약·전사 분리 / 받아쓰기는 날짜별 기록 /
녹음 중 작은 상시 표시 + 상태색 분리 / 탭 바는 이동, 녹음은 플로팅 버튼.
### 정보 구조 (두 앱 공통)
- 1급: **홈(오늘) · 기록 · 회의** — 같은 순서·같은 이름. 통합 검색(기록+회의, 데스크톱 Ctrl+K).
- 2급 "도구": AI 대화 · 명령어 · 사전 · 지식 · 템플릿 · 팀 · 음성 액션 · 데이터. 설정은 데스크톱 전체 화면.
- '메모 태그' → 기록 안의 '태그' 필터. 회의 안 메모는 '회의 메모'. 대시보드 통계는 홈 한 줄 요약.
- 모바일 하단: **탭 4(홈·기록·회의·더보기) + 떠 있는 녹음 버튼**. 녹음 버튼을 누르면 위로 시작 메뉴
(받아쓰기·회의 녹음·파일 가져오기)가 펼쳐진다(사용자 지시, M3 FAB 메뉴 패턴). 탭 바에 행동을 두지 않는다(HIG).
- 기록↔회의: 데이터는 분리 유지(스키마 변경 없음), 통합 검색 + 상호 링크(사용자 선택).
### 토큰 v4 (SSOT: `packages/ui/src/theme-raw.ts` — MUI·DOM 의존 없음)
- 구조: 테마마다 기본값(표면 7·텍스트 3단+disabled·경계 3·액센트 7·그림자 2)만 적고 `deriveRaw()`가 v2 호환
키(glass/gradient/glow/crt/label/inactive/dimLabel/muted)를 **평면 값으로 파생**. 컴포넌트 수백 곳을 고치지 않고 이펙트를 끈다.
glow.* = none, glass.sheen = none, blur = 0px, gradient.* = 단색, shadow.card/chassis/inset/button* = none.
- 텍스트 3단: tertiary 는 app/card/elevated/sidebar 모든 표면에서 4.5:1 이상(dark #7e8eb0 4.62, light #56647c,
nord #a6b0c4, solarized #a1aca9 4.70 + secondary #b7bdb6, catppuccin #939cbf, dracula #a1a7c1).
- accent.solid/solidHover/ink: 채운 버튼. dark·light #2563eb + 흰 글자 5.17:1(이전 #3b82f6 3.68:1 미달),
solarized #8a6800 5.17:1(이전 3.21), nord·catppuccin·dracula 는 밝은 채움 + 어두운 잉크. light accent.light #1d4ed8(글자용).
- 그림자는 떠 있는 층에만: `d3roShadow.overlay`(다이얼로그·메뉴·팝오버·플로팅 버튼), tooltip.
- radius: card 10 · outer 12 · inner 8 · button 8 · small 6 · xs 4 (정밀 도구 — 작고 일정). MUI shape 16→6
(sx borderRadius:2 가 32px 이던 버그 해소).
- 타입: hero 32→28, title 24→22, meta 11/500/0.02em→12/400/0, label·engrave·micro 11/500/**자간 0**,
nano 9→10(라틴 수치 전용). 모든 PhosphorText 변형 산세리프·대문자 변환 없음.
- 컨트롤 36/28, 모션 100/160/240/400ms + easeOut. `d3roTransition()`, `d3roFocusRing` 헬퍼 추가.
- MUI: ripple 끔, 전역 `:focus-visible` 링, 전역 prefers-reduced-motion, Card = 평면+1px, Chip·palette 상태색 = 테마별 STATUS
(고정 tag 는 점·아이콘 전용), Backdrop blur 제거.
- 팝업: `theme-vars.ts`가 같은 원시값에서 파생(손 복제 폐기). 정의 안 된 채 쓰이던 `--d3-overlay-strong`,
`--d3-accent-glow-dim` 정의. 파형 9단 = 액센트 단색. 'system' 테마 해석 함수 `resolvePopupThemeKey` 추가.
- DS: MetalCard·DoubleBezelCard·ScreenPanel 평면화, TiltCard = MetalCard(틸트 제거), PhysicalButton 톤 5종 평면
(accent 만 채움, 600→500, 떠오름 제거, 액센트 잉크 테마 반응), TactileBadge 자간 0·500·STATUS·키보드 조작,
Led 글로우 제거·상태색 STATUS, StatRing 다색 원뿔 → 1px 원, GradientWave 기본 유휴 진폭 0(대기 중 파형이 "살아있는 척"
움직이던 거짓 신호 제거)·글로우 0.
### 의도적으로 하지 않은 것 (물결 ①)
- **옛 토큰 키 삭제 안 함.** glass/gradient/glow/crt 키는 @deprecated 로 남긴다(웹·관리자·데스크톱 수백 곳). 새 코드는 쓰지 않는다.
- **6테마 액센트 통일 안 함.** nord·catppuccin·dracula·solarized 는 사용자가 고른 테마라 고유 액센트 유지, 대비만 보정.
- **`--d3-text-inverse` 정의 안 함.** 웹 앱 17곳이 제목 색으로 오용 중 — 값을 정하면 라이트 테마 제목이 사라진다. 웹 정리 때 함께.
### 2026-09-28 추기 · 물결 ② 데스크톱 구조
- 셸: `AppLayout` = 타이틀바 + `shell/AppSidebar` + 본문. 상태바 삭제(버전·모델은 설정, 시계는 OS, "100% LOCAL PRIVACY ·
ZERO CLOUD SYNC"는 동기화와 모순). 사이드바 맨 아래 한 줄 = 조회한 상태만(로컬 AI 연결/꺼짐/확인 중, 자막·회의 녹음 중).
- 내비 SSOT: `renderer/navigation/app-navigation.tsx` — 라우트 home·records·meetings·conversation·commands·dictionary·
knowledge·settings, `navigate(route, params)`·`back()`·`useRouteRequest(route, handler)`(홈 → 특정 회의/기록 이동).
Ctrl+1..3 = 1급, Ctrl+, = 설정(입력 중 무시). 사이드바 항목은 전부 `<button aria-current>`. 툴팁은 `describeChild`
(MUI 툴팁이 aria-label 을 "Ctrl+1"로 덮어쓰던 것 실측).
- 홈(`pages/HomePage.tsx`): 제목·날짜·받아쓰기 키 → 시작(새 회의·자막) → 막힌 것(조회된 상태만) → 한 줄 요약 →
최근 기록 6 · 최근 회의 4 → 파일 전사 → (무료) 한도 있는 사용량. 카드 없이 제목 + 1px 선.
- 설정: 모달 → 전체 화면(왼쪽 세로 탭 208px + 본문 최대 760px). 본문은 `components/settings/SettingsRows`
(구획 h3 + 행: 왼쪽 라벨·설명 / 오른쪽 컨트롤).
- 공유 부품: PageHeader(h1) · EmptyStateCard · SearchInput("/" 실제 동작, Esc) · ConfirmDialog(되돌릴 수 없는 행동) ·
`notify`(아래 가운데 알림 하나, "삭제했습니다 · 되돌리기" 지연 삭제 — onExpire 는 상태 갱신 함수 밖에서 한 번).
- 라벨 SSOT: `utils/display-labels.ts`(feature·tier·record mode → 번역 키). 문자열 조립 키 금지 — 누락 키가 화면에
키 문자열로 보였다(premium_llm·cloud_sync·team_workspace·team·enterprise).
- 모노 스택에 라틴 모노 다음 Pretendard 를 넣어 한글이 고정폭 한글 폰트로 대체되던 "누 름 유 지" 해소(플랫폼 폰트 실측:
한글 4글리프 Pretendard, 공백 1 Consolas).
- 기록: 날짜 묶음 한 열 목록 + 줄 펼침(요약→본문→태그→행동), 검색·태그·모드·즐겨찾기 필터 동시, 더 불러오기,
한 건 삭제 되돌리기 / 전체 삭제 확인.
### 2026-09-28 추기 · 물결 ③ 받아쓰기 팝업
- 공통 바탕 `popups/_shared/popup-base.css`(Pretendard @font-face, 판 radius 10, 선택 = 옅은 오버레이 + 왼쪽 2px 액센트,
kbd 칩, 감소 모션) + 목록형 `_shared/list-popup.css`. 치수는 `main/windows/popup-list-geometry.ts` 와 짝.
- 녹음 캡슐: 판 모양·단색 막대·입력 0이면 막대 정지·처리 중 무한 막대(꾸며낸 % 폐기)·받아쓰던 문장 흐리게 유지·
"Esc 취소" 안내 · 완료 "입력함" 700ms(`RecordingTipPresenter.onSessionCompleted`). 클릭 취소는 녹음 중에만.
- Esc: `VoiceModeService.requestEscape()`(액션 큐 직렬화) + bootstrap 전역 keydown(수정자 없는 Esc). 앞의 앱에도 Esc 가 전달된다.
- 오류 문구: `@d3ro/core/voice-error-message` 한 곳이 코드 → 종류 분류, 팁과 메인 창 알림이 같은 번역 키.
bootstrap 이 `error.code` 를 넘기지 않아 번역 분기가 한 번도 타지 않던 버그 수정.
- 결과 팝업: 제목(왜 떴는지) · 복사(글자 버튼) · 닫기 · 높이 180px 스크롤. 기록·명령 팝업: 제목·listbox·kbd 힌트,
빈 상태 잘림 해소, 명령 `0` 키 등록. 자막: 테마 무관 어두운 받침은 **의도된 예외**(임의 화면 위 가독성).
- 팝업 테마 'system' → OS 다크 모드로 해석(+ OS 전환 시 재주입), 문서 lang = 앱 언어.
- 사이트 데모(`site/src/components/AppLoopDemo.tsx`)를 새 캡슐과 같게 맞췄다(done 단계, 무한 막대, Esc 안내, 10개 로케일 문구).
### 의도적으로 하지 않은 것 (물결 ②·③)
- **자막 받침 테마화 안 함** — 위 예외.
- **첫 실행 온보딩 Esc 닫기 안 함** — 기존 onboarding-dismiss 정책·테스트 존중, 대신 "나중에 설정" 버튼.
- **다른 8개 로케일 번역 안 함** — ko·en 만 새 문구. 나머지는 en 폴백. 한 번에 번역 맞출 것.
### 2026-09-28 추기 · 물결 ④ 모바일 셸·네이티브 DS
- 하단은 이동 전용 **홈 · 기록 · 회의 · 더보기** 4탭이다. `Record`는 녹음 상태를 보존하는 숨은 탭 경로이고,
떠 있는 녹음 버튼은 홈·기록·회의에서만 보인다. 메뉴의 받아쓰기·회의 녹음·파일 가져오기는 각각 `Record`,
`Meetings(createRequest)`, `Record(importRequest)`로 한 번씩 전달된다.
- 홈은 오늘 요약과 최근 기록·회의, 통합 검색 진입을 한 화면에 둔다. 더보기는 AI 대화·명령어·사전·지식·템플릿·
태그·팀·음성 액션·알림·데이터, 계정·요금제·기기·설정을 묶고 Admin은 역할이 있을 때만 보인다.
- `packages/ui-native`는 데스크톱 v4 원시 팔레트와 같은 dark/light 의미 토큰을 쓴다. 한글 본문은 시스템 산세리프,
숫자만 tabular numeral을 쓰며, 공용 컨트롤 최소 높이는 48dp다. 삭제된 장식용 `Header`·`AppStatusBar` 대신
새 `UnifiedSearch`와 이번 v4에서 전환한 화면은 앱의 `ScreenHeader`로 뒤로가기를 제공하고 탭 루트에는 뒤로가기를
두지 않는다. 기존 stack 화면 전부가 전환됐다는 뜻은 아니다.
- 중복 진입점이던 `DashScreen`과 `WorksHubScreen`은 삭제했다. `WorksHub`의 화면 내부 도구 검색·고정은 내비 상태를
이중 관리하던 복잡도를 줄이기 위해 의도적으로 종료했다. 목적지는 1급 탭과 더보기에서 그대로 열리며 사용자 기록·회의·
도구 데이터는 삭제하지 않는다. 과거 `works-hub-pins.v1` AsyncStorage 키는 비활성 상태로 남을 수 있다.
- 전체 모바일 Jest는 86 suites / 589 tests가 통과했다. 같은 로컬 E2E APK에서 320dp dark는
`scratch/design-audit/wave45/navigation-shell-search-320-dark-verified.xml`(1/1, 88초), 411dp light는
`scratch/design-audit/wave45/navigation-shell-search-411-light-verified.xml`(1/1, 100초)로 통과했다. 셸·두 viewport/theme
조합의 완료 증거이며 실제 TalkBack·font scale·rotation 증거는 아니다. 추적 가능한 요약은
`docs/evidence/wave45-mobile-shell-search-2026-09-28.md`에 둔다.
### 2026-09-28 추기 · 물결 ⑤ 기록+회의 통합 검색
- 검색 계약은 `@d3ro/core/types`의 `UnifiedSearch*` 타입이다. 결과는 원본을 합치지 않고 `source + id`로 기록·회의
상세에 연결하며, 검색 범위는 기록 제목/원문/교정문/요약과 회의 제목/원문 전사/편집 전사다.
- 데스크톱은 `Ctrl+K`와 사이드바 검색에서 SQLite `UnifiedSearchService`를 호출하고, 모바일은 홈에서 Supabase의
사용자 범위 기록·회의를 병렬 조회해 최신순으로 합친다. 빈 검색은 조회하지 않고 오류·재시도·빈 결과를 구분한다.
- 모바일 320dp dark와 411dp light 경로는 기록·회의 결과와 두 상세 화면 이동까지 위 canonical 보고서에서 통과했다.
통합 검색 자체는 아직 부분 완료다. 데스크톱은 소스와 자동 테스트까지만 확인했고 Electron에서 `Ctrl+K`·결과 이동을
수동으로 실행한 증거가 없다. 별도 `transcripts` 구간 검색도 없고, 모바일은 검색 때 원문 필드를 과전송하며 실행된 요청을
취소하지 않는다. 어느 표면도 FTS/index 또는 실제 대규모 데이터 성능을 증명하지 않았다.