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
353 lines
34 KiB
Markdown
353 lines
34 KiB
Markdown
# 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 또는 실제 대규모 데이터 성능을 증명하지 않았다.
|