PRD.md §4 엣지케이스가 "안전 관련이라 v1 최소 버전 필요"로 명시한 항목(P0)인데
관련 로직이 0건이었던 마지막 콘텐츠 갭(roadmap.md §2.7-C, deploy-checklist.md
N4-C3a/b)을 채운다. Track C(콘텐츠 갭) A/B/C 전체 완료.
- core-backend/flood_detect.go(신규): floodMessageThreshold=5건 /
floodWindow=2분 상수 + floodDetected()/floodReason(). 안전을 위해 반드시
있어야 하는 기술적 최소값이라 명시적으로 placeholder로 남기고 구현했다 —
roadmap.md §3의 "PoC 결과가 있어야 정할 수 있는 것"(자율성 기본값 등 UX
기본값)과는 성격이 다름. 카운트는 대화방 내 sender_id != 소유자인 메시지만
집계(트윈 자동발송·소유자 본인 발송은 소유자 ID로 남으므로 자동 제외).
- core-backend/main.go: POST /conversations/:id/messages 하드게이트에
peer-veto → 그룹 대화 차단 다음, 에스컬레이션 체크 이전 지점으로 추가 —
트윈 자동발송 시도가 전부 지나가는 동일한 우회 불가 지점. 이미
TwinDisabledByFlood가 켜져 있으면 재계산 없이 즉시 차단(중복 로그/쿼리
방지). 새로 임계치를 넘기면 대화방을 영구 차단하고 EscalationLog 기록 +
notifyUser 푸시.
- core-backend/models.go: Conversation.TwinDisabledByFlood 필드 추가.
TwinDisabledByPeer(거부권, 사람의 일방적 선택 — v1엔 되돌리기 API 없음)와
달리 이건 시스템이 자동으로 취하는 조치라서 AGENTS.md "every automatic
action needs post-hoc notification + one-tap undo"가 그대로 적용됨 —
POST /conversations/:id/flood-reset로 되돌릴 수 있게 별도 필드로 분리.
- core-backend/a1_a2_routes.go: GET /conversations 응답에
twin_disabled_by_flood 추가(twin_disabled_by_peer와 동일한 자리).
- core-backend/flood_detect_test.go(신규): 임계치 경계값(정확히 N건은
통과, N+1건은 차단), 사람 발송은 게이트 안 걸림, 윈도우 밖 과거 메시지는
집계 제외, flood-reset으로 재개, 존재하지 않는 대화방 404 — 5개 테스트.
- mobile/lib/models/models.dart, services/api_client.dart: 모델·API
클라이언트에 twinDisabledByFlood/resetFlood() 추가.
- mobile/lib/screens/conversation_list_screen.dart: 대화 목록에 도배 차단
배지(거부권과 같은 자리, 다른 아이콘/문구).
- mobile/lib/screens/chat_screen.dart: 채팅방을 열면(목록에서 넘어온 초기
상태) 또는 발송 시도가 다시 차단되면 배너에 "자동응대 재개" 버튼을 보여줌
— one-tap undo. 새 알림 메커니즘을 만들지 않고 기존 EscalationLog/
InboxScreen을 그대로 재사용.
- mobile/test/models_test.dart: twin_disabled_by_flood 파싱 테스트 추가.
- docs/roadmap.md §2.7-C, docs/deploy-checklist.md N4-C3a/b·Track C 요약·
"바로 다음 5개"를 완료로 갱신.
테스트: go test ./... 51/51, python3 -m pytest tests/ -q 47/47,
flutter analyze 클린 + flutter test 5/5 모두 통과.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
PRD.md §2.1-②·§3.1 P0 갭 우선순위 #2. close/formal 2종 관계 티어를 추가하고,
초안 생성 시 상대별 톤을 다르게 낸다.
- core-backend: TwinSettings.RelationshipTier(전역 기본값, 안전 우선 formal
기본), Contact.RelationshipTier(1:1 연락처별 오버라이드, nullable).
persona.go의 resolveRelationshipTier()가 연락처 오버라이드 → 전역 기본값 →
formal 순으로 해석하고, 그룹 대화는 상대가 여럿이라 항상 전역 기본값만 사용.
POST /conversations/:id/draft는 기존에 인증을 요구하지 않던 동작을 깨지
않도록 currentUser(..., false)로 선택적 인증 처리 후 티어를 주입.
- 안전 관련 부수 수정: 그룹 대화에서는 전역 자율성 레벨(L1/L2)과 무관하게
와카뷰 자동 발송을 무조건 차단(단톡 따라잡기는 L0 고정이 맞음).
- ai-service: RELATIONSHIP_TIER_INSTRUCTIONS + system_prompt_for_tier()로
Gemini system_instruction에 관계 톤 지침을 주입. 에스컬레이션/정체성 게이팅
로직은 티어와 무관하게 그대로 유지.
- mobile: 온보딩(말투 샘플 다음 단계)과 자율성 설정 화면에 전역 기본값
SegmentedButton, 연락처 추가/수정 다이얼로그에 _RelationshipTierPicker로
상대별 오버라이드 추가.
- 테스트: core-backend persona_test.go 6개(해석 순서·그룹 예외·비인증
기본값 포함), ai-service 6개(system_prompt_for_tier + draft_reply 경로)
전부 추가, 기존 스위트 모두 통과(go test, pytest 47/47, flutter analyze/test).
실제 Flutter 빌드 + Playwright로 온보딩→연락처 오버라이드→목록 표시까지
전체 라운드트립 시각 검증 완료.
- docs: roadmap.md §2.7-B, deploy-checklist.md N4-C2a~d 완료 처리. 다음
우선순위는 Track C3(스팸/도배 감지 최소 버전).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014YSB5PqF38raTxP5ABgr9m
Default ThemeMode.light with white messenger stage, blue mine bubbles,
and gray peer bubbles. Dark theme uses elevated charcoal instead of
pure black; composer and CTAs follow common messenger patterns.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Closes the biggest content gap found against PRD.md §3.1 P0: group chat
catch-up was one of only two v1 MVP scenarios and had zero implementation.
Backend (core-backend):
- ConversationParticipant.LastReadMessageID read marker.
- POST /conversations/:id/read advances the caller's marker (never backward).
- GET /conversations/:id/summary builds context from messages since that
marker and calls ai-service's new POST /summarize; returns unread_count
and needs_reply.
- GET /conversations now reports unread_count per room.
- Safety: group conversations now unconditionally block twin-authored
sends (POST /conversations/:id/messages), regardless of the sender's
global autonomy level. PRD.md §2.3-③ requires this scenario stay L0-fixed
with no auto-send; autonomy level is a per-user global setting today, so
this closes the only path a global L2 whitelist match could otherwise
auto-send into a group.
AI service (ai-service): new summarize.py module (same Gemini-call shape as
generation.py's draft_reply, no escalation/identity gating since nothing
generated here is ever sent) + POST /summarize.
Mobile: "새 대화" dialog now supports adding/removing multiple peer fields
(2+ peers -> is_group:true automatically); unread badge on conversation
rows; ChatScreen takes isGroup and renders its L1 panel as L0-locked for
groups; new "안 본 동안 요약" AppBar action opens a dialog with the summary
and, when a reply looks needed, a button that feeds straight into the
existing draft-request flow.
Verified with a real Flutter build against a live core-backend + ai-service
instance (Playwright driving 3 demo accounts through group creation, unread
badges, and the summary dialog) — this caught a real ordering bug: marking
the read marker on chat *open* meant the summary was always empty by the
time you could tap it, since opening the room already advanced the marker
past everything you'd come to catch up on. Fixed by marking read on screen
*exit* (dispose) instead, so the marker reflects what was unread that whole
visit and updates once you leave.
go test / pytest / flutter analyze+test all pass.
Replace Quiet Ink teal chrome with neutral-first messenger UI: ink CTAs,
thin blue-gray accent, unified radii, no decorative icon tiles or number
badges, flatter lists/chat panels in light and dark.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Use teal containers for sample indices instead of amber secondary,
and mute dark-mode teal so it reads calmer on charcoal.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Replace aurora-glass purple with a calm messenger system: off-white/
charcoal canvas, Plus Jakarta Sans, solid teal CTAs, and quieter
panels across signup, chats, contacts, inbox, and settings.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Actually installed the Flutter SDK, built the app (web + a local screenshot
harness through a real backend), and looked at the rendered screens instead
of judging the design from source alone. The previous gradient/glassmorphism
pass read as flat and generic: glass cards barely contrasted against the
pale background, the brand mark was a stock sparkle icon, and buttons were
flat solid fills.
Changes:
- Stronger, more saturated aurora gradient + blurred glow orbs in
GradientBackdrop for real depth instead of a flat 3-stop pastel.
- Glass cards/inputs get higher-contrast fill/border and a brand-tinted
shadow so they visibly lift off the backdrop.
- New BrandMark (gradient monogram), GradientText (gradient-filled
wordmark), and PrimaryGradientButton (gradient CTA with shadow) replace
the generic Material icon/flat button on splash, signup, onboarding and
the conversation list empty state.
- Fixed a real pre-existing layout bug found via screenshot: the onboarding
header badge was a direct ListView child with an explicit width, which
Flutter's sliver list silently stretches to full width regardless of the
Container's own width — invisible before because its old flat pale fill
blended into the background. Wrapped it in Align to restore its intended
size.
Verified with flutter analyze (0 errors) and flutter test (all passing),
plus manual screenshots of signup, onboarding, conversation list and a live
chat exchanged between two demo users through a real core-backend instance.
Add PushTokenService with Firebase Messaging fallback to install
placeholders, conditional google-services Gradle plugin, fcm-setup.md,
and mark Android UI checklist api-done via prod e2e 16/16.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Add PATCH contact for missing peer IDs, contacts banner and edit UI,
enrich /demo with pairing steps, and update tester/signup guidance.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Show/copy numeric user ID, require peer ID for contacts, start chat in one tap,
clarify empty states, and route L0 twin drafts into the human composer.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Add Postgres + internal AI + core-backend + Flutter web (nginx API/WS
proxy). Document Portainer usage and env template; mark N2-B1–B7 done.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
- Resolve visual-direction conflicts (app_theme, main.dart, signup/onboarding
screens, index.html) in favor of the soft-gradient + glassmorphism design;
drop the competing Twin Shadow palette and TwinTokens usage.
- Port non-visual additions from the parallel branch: CORS middleware,
FlutterError/PlatformDispatcher crash handlers + boot timeout/fallback
screen in main.dart, and the shared demo-invite feature
(core-backend/demo.go, demo_test.go, mobile/lib/config.dart), reskinning
the demo panel to match the glass UI.
- Rename the shared demo invite code DEMO-BUNSIN -> DEMO-YKAVU on both
client and server so the tester-facing feature keeps working.
- Purge remaining "분신"/"bunsin" identifiers app-wide: Dart package name
(bunsin_mobile -> ykavu_mobile), Android applicationId/namespace
(com.bunsin.bunsin_mobile -> com.ykavu.ykavu_mobile, incl. Kotlin source
dir move), on-device DB filename, keystore alias/docs, PoC draft-generator
system prompt, and the static web boot placeholder div.
명칭 변경:
- decision-log.md Q6 확정: "분신"(가칭) → "와카뷰 (Ykavu)" — 焚身(분신자살)
동음이의 리스크도 있었고 메신저 브랜드로 부르기 무거웠음. Master가 최종
선택한 이름으로 변경(2026-07-31), 파생 문서(AGENTS.md/CLAUDE.md/PRD 등)·
Flutter 앱 텍스트·web manifest/index.html·AndroidManifest 라벨까지 전부 반영
- ai-service: 본인확인 고정 문구(identity.py)·시스템 프롬프트(generation.py)도
갱신 — 새 이름 기준으로 "본인이야 와카뷰야?" 류 질문을 감지하도록 정규식도
같이 손봄(단순 문자열 치환만으론 어미 형태가 안 맞아서 테스트 추가/조정)
- poc/tone-corpus/의 실험용 프롬프트는 의도적으로 그대로 둠(ai-service README에
이미 명시된 대로 프로덕션과 분리된 실험 도구)
UI/UX:
- mobile/lib/theme/app_theme.dart: 시드 컬러를 인디고/라벤더로 변경, 화면 전체에
깔리는 소프트 그라디언트(라이트: 라벤더→스카이→핑크 파스텔, 다크: 딥 인디고→
네이비→플럼) + 카드/인풋/칩을 반투명 "글래스" 서피스로 전환
- mobile/lib/widgets/gradient_backdrop.dart(신규): MaterialApp.builder에 연결해
모든 화면에 자동으로 그라디언트 배경 적용
- mobile/lib/screens/splash_screen.dart(신규) + main.dart 재구성: 기존엔
session.restore()를 기다리는 동안 아무 것도 안 그려서 흰 화면/텍스트만 뜨는
구간이 있었음 — runApp을 먼저 하고 restore 동안 브랜드 스플래시가 뜨도록 변경
- chat_screen·autonomy_settings_screen·data_flow_screen의 커스텀 패널들도
글래스 스타일로 맞춤
BunsinApp -> YkavuApp (mobile/lib/main.dart, test/widget_test.dart 동기화)
Flutter SDK가 없는 환경이라 flutter analyze/run으로 직접 컴파일 검증은 못했음 —
중괄호/괄호 균형과 기존에 검증된 API 패턴 위주로 신중하게 작성함. go test·
pytest는 전부 통과.
Seed a reusable demo invite (ALLOW_DEMO_INVITE, default on) and surface it
on the Flutter signup screen with one-tap fill so others can try without
asking for a one-off admin mint.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
일관된 디자인 토큰(mobile/lib/theme/app_theme.dart)을 새로 만들고
전체 화면에 적용:
- 라이트/다크 ColorScheme을 명시적으로 정의(기존엔 라이트만 있어서
다크 모드 기기에서 깨져 보였음), AppBar/Card/입력창/버튼/칩/배너 등
컴포넌트 테마를 통일. "분신" 뱃지 전용 twin 강조색(amber) 분리
- 화면마다 제각각이던 TextField 테두리를 전부 테마 하나로 통일
(chat_screen의 L1 편집창에 하드코딩돼 있던 Colors.white70 배경도
제거 — 다크 모드에서 흰 배경에 흰 글씨로 안 보이던 실제 버그였음)
화면별 개선:
- signup: 로고 마크 + 안내 문구 추가, 아이콘 붙인 입력창
- 대화 목록: 앱바 아이콘 4개를 겹쳐놨던 걸 정리(더보기 메뉴로 이동),
색상 아바타, 빈 상태 안내, 확장형 FAB
- message_bubble: 좌우 비대칭 말풍선 모양(꼬리 느낌), 타임스탬프 추가,
분신 뱃지를 아이콘+칩 형태로
- chat: 거부권 버튼에 확인 다이얼로그 추가(실수로 눌러도 되돌릴 수
없는 동작이라 안전장치), L1/에스컬레이션 패널 카드화
- contacts/inbox/sessions/autonomy_settings/data_flow/onboarding:
아바타, 섹션 카드, 빈 상태, 화이트리스트 칩 UI 등 톤 통일
주의: 이 작업 환경에는 Flutter SDK가 없어 flutter analyze/run/test로
직접 검증하지 못했음. 아이콘 이름 등은 기존 코드에 이미 쓰인 것 위주로
맞췄지만, 로컬에서 flutter analyze 먼저 돌려보는 걸 권장.
dart:ffi and SQLCipher cannot compile for Chrome. Split AppDatabase into
native (Drift+SQLCipher) and web in-memory stub via conditional export, and
enable the web platform so flutter run -d chrome works for UI preview.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
- Flutter: drift + SQLCipher local store for tone samples/KV with
secure-storage passphrase; migrate legacy SharedPreferences
- Core: FCM notifyUser on escalation, admin push-test, push metrics,
DELETE session for multi-device logout
- Docs/roadmap B checkboxes updated; Linux SQLCipher apt notes
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
- Record draft/escalate latency and error rates on /admin/metrics
- Add minimal /admin/dashboard and full message JSON on send
- Fix identity answers in ai-service with stable copy + tests
- Flutter DataFlowScreen + SessionsScreen; device-token API stub
- Update roadmap B checkboxes after A3 API E2E
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
scripts/e2e_a3.py exercises the messenger checklist against a live
core-backend (16/16). Mark A3 E2E done; keep Android UI steps in README.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Add conversation/contact/history/escalation-log endpoints, contact-scoped
whitelist matching, bearer sessions, ADMIN_API_TOKEN guards, and
`go run . migrate`. Update Flutter client to persist/send session tokens
and mark A1/A2 complete in the prioritized checklist.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>
Scaffold `mobile/` against core-backend APIs (signup, chat, draft,
veto, retract, autonomy/whitelist). Update roadmap/README/AGENTS so
participant PoC #1/#3 stays the final Phase 1 step after app build.
Co-authored-by: okuma <o0kuma@users.noreply.github.com>