d3ro-voice/docs/REFACTOR_POLICY.md
Yun Chan eedd127ea7
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 1m13s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / 모바일 린트·타입·Jest (push) Failing after 1m4s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 37s
ci / .NET API 서버 테스트 (push) Successful in 27s
deploy-site / deploy (push) Failing after 20s
refactor(billing): remove Stripe; payments are Payple (web) and Google Play (mobile)
Stripe is not used. Keeping its checkout, portal and webhook paths meant a
second payment provider, a second return-URL format and dead UI.

- Delete the stripe-checkout, stripe-portal and stripe-webhook functions and
  their config; billing-catalog serves Payple prices only, and the web parser
  rejects a catalog that still mixes in Stripe prices.
- Web: drop the Stripe checkout/portal buttons, provider toggle and return
  notices; billing shows Payple only. Past rows with provider='stripe' are
  still displayed ("Stripe (종료)") with a support contact instead of a portal.
- Desktop: delete the Stripe checkout modal, payment IPC channels, preload
  namespace and their types; "Remove ads with Pro" opens the web billing page
  via license.openBilling. Support/refund copy names Payple.
- billingUrl() loses the Stripe-only success/canceled result option; the
  Deno contract is regenerated.
- Migrations and the DB's accepted provider values are untouched (history).
- Docs and the backlog record the removal (MON-04, EXT-STRIPE-01, GAP-BILL-03).

Verified: typecheck (desktop/web/admin/api-client/mobile), contract:check,
deno check all functions, deno test 80/80, desktop 1478/1480 on the Electron
runtime (2 known environment failures), web and admin builds, release
metadata and mobile boundary self-tests, eslint on changed files.
2026-09-26 20:56:18 +09:00

8.4 KiB

D3RO-VOICE 리팩토링 정책 (SSOT)

마지막 갱신: 2026-07-22 — Wave 1 정책 수립 적용 범위: apps/desktop + packages/{core,ui,i18n} (web/admin/site/server/mobile은 별도 wave)

0. 전제: 기존 3계층 규칙 SSOT와의 관계

D3RO는 이미 3계층 강제 규칙 체계를 가진다. 본 정책은 이를 재정의하지 않고 인용한다.

계층 위치 성격
L1 코딩 규칙 CLAUDE.md 사람이 읽는 규칙 (헌법)
L2 강제 규칙 훅 .claude/settings.json (UserPromptSubmit) 매 프롬프트마다 자동 주입
L3 피드백 memory/*.md 사용자 피드백 축적

본 문서(L0) 는 리팩토링 Wave 한정 의사결정 기록(decision log) 역할. L1/L2와 충돌 시 L1/L2가 우선하고 본 문서를 갱신한다. (harness-gate 스킬이 이 충돌을 감시.)


1. 8개 결정포인트 (표준)

DP1. 디자인 토큰 SSOT — packages/ui/src/theme.ts

  • 색상 = d3roPalette, 그림자 = d3roShadow(CSS var), 타이포 = d3roTypo, 라디우스 = d3roRadius
  • 금지: theme.ts/theme-vars.ts 제외 모든 .ts/.tsx에서 hex 하드코딩 (#3b82f6 등). L1/L2 강제.
  • 매직 넘버: 화면 1개에 고유한 수치(WAVE_BAR_COUNT = 9 등)는 토큰화 금지, file-private 명명상수로 격리. (이식 인사이트 P4)

DP2. IPC 채널·타입 SSOT — packages/core/src/ipc-channels.ts + types.ts + errors.ts

  • 채널명 = IPC_CHANNELS 객체, 형식 ${feature}:${action}. 설계서 02 준수.
  • 에러 = D3ROError + ErrorCode enum (errors.ts). throw new Error() 금지.
  • 새 채널/타입은 SSOT 파일에 먼저 추가 → apps가 import.

DP3. i18n — packages/i18n + t() 함수

  • UI 문자열은 반드시 t('key'). 12 locale(ko/en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th). L1/L2 강제.
  • 서비스/유틸/팝업이 한국어·영어 리터럴을 반환하면 다국어 불가 → 함수는 i18n 키(또는 enum)를 반환, 렌더링 시점에 t()로 풀이. 메인 프로세스 문자열(팝업/트레이 메뉴)도 동일. (이식 인사이트 P5)
  • 고아 locale 키(코드에서 미사용)는 정리 대상.

DP4. 서비스 경계 — 싱글톤 + EventEmitter

  • 모든 서비스: getInstance() 싱글톤 + EventEmitter. 설계서 01 인터페이스 준수.
  • apps/desktop/src/main/services/에 32개 서비스 존재 (CLAUDE.md 명시분보다 다수 — 감사에서 실제 목록 정합).

DP5. 상태 관리 — RecognitionState(9) + AudioState(4) 이중 상태머신

  • VoiceModeService 오케스트레이터. 설계서 01 준수. (이번 wave는 상태머신 재설계 X, 정합 점검만)

DP6. packages ↔ apps 의존 방향 — 단방향 (apps → packages)

  • packages는 apps에 의존 금지. (이미 구조적으로 보장.)
  • 주의: packages/ui, packages/i18n은 web/admin/site/mobile도 공유 → 변경 시 영향 받는 모든 앱 typecheck 필수 (R3 모노레포). 이번 wave는 desktop+packages라도, ui/i18n/core 변경은 모노레포 전체 typecheck로 검증.

DP7. DRY 임계치 — 3회 (제안)

  • 동일 로직/상수/매직스트링이 3곳 이상 중복 → 추출(SSOT로). (사례: PREMIUM_MODEL_LIMITS 3곳 → @d3ro/core/constants 통합 완료.)
  • 2곳 중복은 맥락에 따라 판단(강제 X). 임계치 아래는 의도적 중복 허용.
  • 확정: 3회 (2026-07-22 합의)

DP8. 자동화 — typecheck + lint + test + 강제 규칙 훅

  • WS 통합 후 반드시 GREEN: npm run typecheck(turbo) · npm run lint · npm run test · npm run build(빌드 영향 WS).
  • UI 변경: npm run dev로 화면 1회 확인. (WS3은 화면별 즉시)

2. 본 Wave 한정 추가 결정 (감사 후 확정 가능)

DP9. 미사용/데드코드 처리 강도 — 공격적 (제안)

  • R2 공격적 모드: "false negative > false positive". export/컴포넌트/서비스/의존성 미사용은 DELETE 우선.
  • 단, 외부 API(라이브러리 entry, IPC handler)로 노출된 export는 KEEP. 감사에서 grep references로 확인 후 판정.
  • 알려진 잔여: MetalDial(CLAUDE 명시 미사용), @mui/icons-material 의존성(lucide 전환 후 잔여), LemonSqueezy 코드 잔존(LicenseService activate/deactivate).
  • 확정: 공격적 DELETE (2026-07-22 합의) — 단 외부 API 노출 export는 grep references 후 KEEP.

DP10. 워크스트림 분할 (R3) — 파일 영역 비충돌

WS 영역 의존
WS1 토큰/SSOT 정의 — packages/ui/src/{theme,theme-vars}.ts, DS 컴포넌트 —
WS2 메인 프로세스 — apps/desktop/src/main/, preload, packages/core 타입/IPC —
WS3 렌더러 UI — apps/desktop/src/renderer/ (화면별 분담 가능) WS1 의존
WS4 i18n — packages/i18n/src/, t() 키 정리 —
WS5 빌드/설정/팝업 — package.json, electron.vite.config, electron-builder.yml, Vanilla JS 팝업 —
  • 1차 wave: WS1/WS2/WS4/WS5 병렬. WS3는 WS1 완료 후 발사.
  • 워크스트림당 1 커밋 (R5). git add -A 금지, 정밀 add.

DP11. SKIP / 이월 기준

  • "복잡해서 SKIP" 금지 (안티패턴 A6). SKIP은 기술적 제약 + 별도 Phase 필요 명시.
  • 대규모 재설계(상태머신 재구성, 서비스 쪼개기)는 이번 wave에서 순수 이동(P3) 먼저 시도, 진짜 재설계는 압력(props 20+개 등)이 명백할 때만.

3. 이식 검증 인사이트 (HaramLog wave 1~10)

  • P1: HIGH 확신도 보고도 착수 전 30초 재검증(Read/Grep). false positive 기록.
  • P2: "순수 이동" 분할에도 동작 변경이 숨는다. diff 1:1 대조(훅 체인, useEffect deps, 구독/해제).
  • P3: 거대 파일 분할은 순수 이동이 90%. 재설계는 분리 후 남는 압력에서만.
  • P4: 화면 고유 수치는 토큰화 금지, file-private 상수.
  • P5: 비컴포넌트 함수의 한국어 반환은 i18n 키 반환으로 전환.
  • P6: 이름 충돌은 역할 접미어 예방(RecordingState enum vs RecordingStateColor 토큰).

4. 합의 이력

항목 결정 일시
Wave 1 범위 Desktop + 공유 packages 2026-07-22
Wave 1 초점 데드코드/토큰SSOT/i18n/타입-IPC-아키텍처 전부 2026-07-22
커밋 주기 WS별 분리 커밋 + 즉시 검증 (R5/R7) 2026-07-22
DP7 DRY 임계치 3회 2026-07-22
DP9 데드코드 강도 공격적 DELETE (외부 API 노출 export는 grep 후 KEEP) 2026-07-22
DP6 typecheck 범위 전체 모노레포 typecheck 강제 (ui/i18n/core 공유) 2026-07-22

Wave 3 (2026-09-26) — 표면·계약 단일화

사용자 결정: "두 벌씩 만든 것을 전부 하나로." 확정 사항과 정본은 아래와 같다.

# 결정 포인트 정본 파생(자동·검사)
W3-1 요금 packages/core/src/plan-catalog.ts PLAN_PRICE_KRW (Free 0 / Pro 2,900 / Pro+ 8,900, 월). 기존 구독자도 다음 갱신부터 적용 Deno _shared/core-contract.generated.ts (npm run contract:sync, CI contract:check)
W3-2 공개 URL packages/core/src/web-urls.ts — 사이트 /, 웹앱 /app(Next basePath), billingUrl(), SITE_URLS 같은 생성 파일
W3-3 결제 진입 웹앱 /app/billing 하나. 데스크톱·모바일·사이트는 모두 billingUrl(). 결제는 페이지 안 Payple 창에서 끝나므로 복귀 쿼리는 없다(Stripe와 success=1/canceled=1 복귀 쿼리는 2026-09-26 제거) —
W3-4 웹앱 배포 NAS Docker(d3ro_voice_web) → Cloudflare Tunnel 호스트 → 사이트 브리지 워커가 /app/*를 전달 —
W3-5 랜딩·다운로드·법률·초대·assetlinks site/ 한 벌. apps/web·api-server wwwroot 사본 삭제, 웹앱 /download는 사이트로 리다이렉트 —
W3-6 설치 파일 Forgejo feed만. 저장소에 추적된 바이너리 사본 삭제 —
W3-7 CI Forgejo(.forgejo/workflows) 한 벌. .github는 GitHub 원격이 없어 실행된 적이 없으므로 필요한 잡만 옮기고 삭제 —
W3-8 사이트 배포 Cloudflare Pages 한 경로. GitHub Pages·NAS wwwroot 복사 경로 삭제 —

DRY 원칙 보강: Deno처럼 정본을 직접 import할 수 없는 런타임은 생성 사본 + --check 게이트로만 복제를 허용한다(version:sync 패턴과 같음). 손으로 옮겨 적은 사본은 금지.