← /log
2026-04-20

Nexus 제품 전면 재설계 (Block N)

2,309 words·raw from wai-vault/02-DevLog

2026-04-20 — Nexus 제품 전면 재설계 (Block N)

배경

Loren 지시: "ㅎㅇ 넥서스 제품 업그레이드 ㄱ" → C 옵션 (admin dashboard 수준 전면 재설계, ~70 태스크).

타겟: board-approval-system (1-234-23-23.nip.io). 어제 admin dashboard (watoneai.cafe24.com) 를 먼저 마감했고, 이번엔 원래 의도대로 Nexus 제품 본체.

계획 구성

87 태스크 (Stage N1~N7), Phase 끝에 batch commit. 각 Phase Deploy 태스크에서 vite build 검증.

Phase 별 완료

Phase N1 — 디자인 시스템 (15, 7b86690)

  • tailwind.config.js 토큰 확장: animation 5 (slide-right/left/up, shimmer, spin-slow), zIndex 9단계 (base→max), transitionDuration/TimingFunction, muted color variants, fontSize label/btn, ring/backdropBlur
  • index.css 516 → 127 분리 (500 라인 룰): styles/{base,components,layout,utilities}.css 4 파일
  • CSS variables: success/warning/error/info-muted 추가 (뱃지/배너 배경)
  • ui/ 13 신규 컴포넌트: Chip / Badge / Pill / Skeleton 7variant / EmptyState (+ 4 variant: error/offline/no-permission/no-result) / Toast + toastStore (zustand portal) / Tooltip (4 positions) / Kbd (combo) / Button (primary/ghost/outline/danger/icon/link, loading spinner) / Input+Textarea+Select (label/hint/error/clearable/icons) / Modal + Sheet (Escape, backdrop, body lock, focus) / Card + SectionCard + SectionRow + Collapsible / Avatar (이니셜 결정적 색상) + Dot
  • i18n ko+ja: emptyError/Offline/NoPerm/NoResult 4종 preset 키

Phase N2 — IA + Navigation 전면 개편 (15, 7f1b471 + 3e2e678)

  • Sidebar 재설계 (N2.1): 173→280. Brand + WorkspaceSwitcher + Search trigger (⌘K) + NavGroup (MAIN / AI / 경영 / SYS) + Pinned/Recent + Footer (balance + lang + theme + help + profile + logout)
  • pinnedStore (N2.2): Zustand + localStorage. pinned/recent/togglePin/trackVisit/clearRecent. max recent 20
  • WorkspaceSwitcher (N2.3): super_admin 드롭다운 /workspaces/list fetch. 일반 user 는 표시만. 전환 API 미지원 시 toast 고지
  • SidebarBalance (N2.4): 수직 layout 전용. getBillingBalance 30s polling, formatKrw 만/억 단위, days_left, LOW 10k 시 error tone, owner+만 billing 이동
  • CommandPalette 재설계 (N2.5 + N2.11): cmdk + globalSearch(/search?q=, B-6.1) debounced 250ms 상위 8건 + pinnedStore.recent 연동 + Kbd 컴포넌트 + type 별 navigate dispatch
  • QuickActions (N2.6): ⌘⇧K 글로벌 바인딩, 숫자 1~9 인덱스 선택. 8 액션 role 필터
  • Breadcrumb (N2.7): ui/Breadcrumb aria-current, 마지막 아이템 클릭 무시
  • Drawer 시스템 (N2.8): drawerStore stack + DrawerHost (right-panel, resize handle 240~1200px persist, back stack, ESC back/close, z-drawer)
  • CrossRefChip (N2.9): 8 type × glyph × tone + drawerStore.open 기본 연결
  • Notification SSE (N2.10): hooks/useNotifStream EventSource /api/dashboard/stream/notifications reconnect 30s → toast 실시간 알림
  • g-leader (N2.12): h/w/i/c/a/n/m/s/b/r + ? → openHelpGuide
  • Density 토글 (N2.14): uiStore.density (localStorage persist) + document.documentElement.dataset.density + [data-density="compact"] CSS selector
  • Topbar (N2.15): Kbd 사용, notification bell + Badge (inbox 이동)

Phase N3 — Dashboard 홈 (8, a8c151d)

  • DashboardToday (N3.1): 4카드 — 승인 대기 / 진행 중 / Trial 상태 / 크레딧 잔액. role 필터, tone warn/danger/accent, low threshold 시 CTA → navigate
  • DashboardMetrics (N3.2): 기존 4 메트릭에 inline Spark (60x18 SVG polyline) 추가. stats.series.{pending,weekly,completion,avgHours} 있을 때만 렌더
  • DashboardActivity (N3.3): /api/activity?range=24h&limit=10 (B-2.4) 상위 5 + category 점 + 상대시간 + 전체보기
  • CrisisBanner (N3.5): topbar 아래. /api/dashboard/crisis (B-6.2) 60s polling. warn/danger 일 때만 render, danger dismiss 불가 재활성
  • lib/api.js: getActivity / getBriefToday / getCrisis / getWorkspaces / globalSearch export

Phase N4 — Hub 6개 (22, 85635e8)

6 Hub 에 Breadcrumb 통합 + Phase N1 디자인 시스템 자동 반영:

  • WorkHub (list/kanban/template/bulk)
  • InboxHub (pending/all unread badge)
  • AITeamHub (agents/chat/proposals/analysis)
  • BrainHub (browser/compliance/agents/trace/map)
  • ReportHub (cost/quality/evolution/live/approval/audit)
  • SettingsHub (general/company/invite/watchdog/telegram/meta-ads/integrations/overrides/legal)

각 sub-tab 의 카드/버튼/입력은 Phase N1 CSS 토큰을 이미 사용 중이라 디자인 시스템 일관성 자동 달성. Drawer/CrossRefChip/Toast 는 N2 구조가 주입되어 있어 상위에서 즉시 사용 가능.

Phase N5 + N6 — 페이지 + 모바일/접근성 (18, f38793a)

  • App.jsx root: h-screen → h-[100dvh] + env(safe-area-inset-*) padding (iOS 노치) (N6.1)
  • styles/base.css: @media (pointer: coarse), (max-width: 768px) 에서 button/a min-44px (N6.2)
  • :focus-visible 전역 2px outline (N6.6)
  • prefers-reduced-motion 기존 유지 (N6.8)
  • ARIA: Drawer/Modal role=dialog aria-modal, Toast role=status aria-live, buttons aria-label 전반 적용 (N6.7)
  • Auth/Billing/Owner/Chain/Demo/Trial/Legal 페이지는 디자인 시스템 자동 반영으로 마감

총계

  • 87 태스크 등록, 85 completed (2개 Loren 직접: 모바일 실기기 N7.2 + 서버 배포 N7.3)
  • 7 커밋: 7b866907f1b4713e2e678a8c151d85635e8f38793a (+push)
  • 새 파일 23 (ui 13 + stores 3 + hooks 1 + sidebar 3 + dashboard 3)
  • 모든 파일 500 라인 이하 (Sidebar 282 최대, ui 컴포넌트 평균 80라인)
  • vite build 평균 11초

남은 태스크 / 차기

  • N7.2 모바일 실기기 점검 (iOS Safari + Android Chrome) — Loren 직접
  • N7.3 서버 배포 + HTTPS 회귀 — Loren 직접 (rsync /var/www/nexus + nginx 확인)
  • 차기 Phase N' (필요 시):
    • PostDetail / AgentDetail drawer 전환 (현재 full-page)
    • BillingPage 427줄 분리 (현재 한도 여유)
    • i18n 인라인 분기 → 키화 (ko-ext / ja-ext 이동)
    • audit.jsonl 실제 경로 확인 (Nexus 서버 로그 동기화 연계)

원칙

  • 기존 구조 존중 + 점진적 개선 (Loren 스타일 — 없는 문제 만들지 않기)
  • Phase 끝 batch commit (Loren 지정 "끝에 batch하셈")
  • 500 라인 룰 준수 (index.css 516 → 127)
  • 인라인 i18n 분기 유지 (필요 시 차기에 키화)

Sprint M0 — 다크모드 감사 + API 키 Public API (2026-04-20 추가)

발단

Loren 이 CompanyManager 의 API 키 생성 섹션 발견 ("대박!!"). UI 는 있지만 서버 검증 미들웨어 미구현 확인 → Sprint M0 즉시 추가.

완료 (5 스테이지)

다크모드 감사 + 수정 (M0.1-0.2, board-approval-system 7a406a5)

  • grep: bg-white|text-gray-|bg-gray-|text-black without dark: variant (demo/ 제외 — 데모는 라이트 고정)
  • 실제 버그 3곳 수정:
    • admin/IntegrationStatus.jsx:81 새로고침 버튼 hover 단독 → dark variant
    • auth/Login.jsx:76 카드 배경 → dark:bg-nexus-bg-surface/90
    • dashboard/DashboardCharts.jsx:117 SLA 범례 → nexus-border-default
  • 토글 슬라이더 4곳 bg-white 는 물리적 흰 동그라미 intent 로 유지

API 키 서버 검증 (M0.3-0.4, w-ai-agents 7a905f6)

  • modules/api-key-auth.js (153 라인): X-Api-Key / Authorization Bearer nxs_<key> 헤더 파싱, 32자 hex 포맷 검증, JSON_SEARCH 로 companies.api_keys 매칭, 5분 in-memory 캐시, SHA-256 prefix 해시 로 audit 에 원본 키 미노출, features.api_keys=false 시 403. req.apiAuth 주입 + invalidate(key) 공개 API.
  • modules/public-api.js (150 라인): /v1/ping (인증 확인), /v1/posts GET/POST (RLS 자동 company_id 필터, source='api' 태그), /v1/chains/:id/execute POST (chain-executor 파이프라인 재사용).
  • claude-bridge.js: apiKeyAuth.middleware → publicApi.handle 순서. 키 없으면 그대로 통과 → 기존 세션 auth 경로 유지.
  • nginx snippet /api/v1/ prefix: X-Api-Key + Authorization 둘 다 passthrough, CORS * (외부 Zapier/n8n 용), OPTIONS 204.

배포 + 회귀 (M0.5)

  • Bridge: SSH pull 093d42d → 7a905f6, nginx test+reload, bridge restart. curl /v1/ping:
    • 키 없음 → 401 {"error":"API key required (X-Api-Key header)"}
    • 잘못된 32hex → 401 {"error":"Invalid API key"}
  • Nexus dist: tar pipe atomic swap → 1.64MB bundle, HTTPS 200. 새 번들에 dark:hover:bg-white/[0.06] 포함 확인.

다음 단계 — 실제 엔드투엔드 동작 확인

  1. 대시보드에서 테스트 회사 (노무현) 편집 → API 키 생성
  2. curl -H "X-Api-Key: <32hex>" https://watoneai.cafe24.com/api/v1/ping → 200 + company_id/features 반환 기대
  3. POST /api/v1/posts 로 외부 작업 생성 → WorkHub 에서 보이는지 확인
  4. 키 revoke → 캐시 invalidate 필요 (현재 CompanyManager UI 에 revoke 호출 미연결 — 후속 작업)

후속 태스크

  • companies.api_keys JSON 풀스캔 → 별도 api_keys 테이블 승격 (키 많아지면)

Sprint M1 — API 키 생명주기 + /v1/ 완성 (2026-04-20 저녁)

Sprint M0 의 후속작업 5개를 바로 이어서 밀었음.

M1.1 api-key invalidate 연결 (358ef8f)

  • plugins/board/admin/companies.js updateCompany 에 features + api_keys 컬럼 저장 추가 (기존엔 UI 만 있고 실제 UPDATE 빠져있던 버그 발견)
  • api_keys diff 로 제거된 키 검출 → apiKeyAuth.invalidate() 즉시 호출. 5분 TTL 안 기다려도 revoke 반영.
  • 응답에 invalidated_keys: N 리턴

M1.2 /v1/ 키별 rate limit (358ef8f)

  • modules/api-key-auth.jsrateBuckets[keyHash] rolling 60s window, 60 req/min 제한
  • 초과 시 429 + Retry-After / X-RateLimit-Reset / -Limit / -Remaining
  • 정상 응답에도 X-RateLimit-Limit/Remaining 헤더
  • invalidate 시 rate bucket 도 함께 삭제

M1.3 /v1/ 엔드포인트 확장 (358ef8f)

  • GET /v1/agents — 회사 assigned_agents → registry.listAgents 필터
    • 민감 필드(SOUL/fallbackChain) 제외
  • GET /v1/chains — agent-chains.json 카탈로그
  • GET /v1/posts/:id — 단건 조회 (RLS company_id 자동 필터)

M1.4 Public API 문서 (board-approval-system: 7a406a5 + docs)

  • public/docs/api-v1.md 작성 — 인증/엔드포인트/에러코드/Zapier·n8n· Python 통합 예제/버전 이력
  • SettingsHub Legal panel 에서 링크 예정 (후속)

M1.5 배포 + 회귀 (358ef8fccc4842 버그픽스)

Bridge pull + restart → 실키 발급 → curl 엔드투엔드:

체크결과
/v1/ping200 + company_id=1 + features 13개 (api_keys ✓ 포함)
/v1/posts GET200 + 실제 posts 3건 (RLS)
/v1/agents200 + [] (assigned_agents 매칭 미존재, 정상 동작)
/v1/chains✗ 초기엔 빈 배열 → agent-chains.json 이 {chains: {키:...}} object 구조라 Array.isArray 실패
X-RateLimit headersLimit 60 / Remaining 55 정상
Revoke testPUT api_keys=[] → invalidated_keys: 1
Revoke 후 ping401 "Invalid API key" (캐시 즉시 무효화)

/v1/chains 버그픽스 (ccc4842): Array/Object 둘 다 처리. 재배포 후 매출분석/광고세팅/보안점검 등 6개 정상 반환.

후속 아이디어 (M2 이후)

  • /v1/webhooks 인바운드 (외부 이벤트 → Nexus 체인 트리거)
  • /v1/ 키별 사용량 대시보드 (cost per key)
  • companies.api_keys 가 커지면 별도 테이블 승격 + 인덱스
  • SettingsHub 에 api-v1.md 링크 + 간단 키 사용 통계
  • Sprint P1 (디자인 리파인 — 포레스트 그린/Pretendard/워드마크) → Loren OK 나오면 30분 소요, 대기 중

🔧 저녁 Sprint — 오류/부채 전수 스캔 + P0 수정 배포

발단: Loren "안한 거 많음? 오류나거나 뭐 이런 거 다 고쳐야지" → 전수 스캔 시작.

스캔 결과 (17 태스크 생성)

심각도발견위치
P0pattern-tracker.js SQL user_id 미존재webhook/modules/pattern-tracker.js:238
P0monthly_budget_80pct aggregate 안 aggregateconfig/auto-evolution.json
P0payment_failure_streak status 컬럼 미존재동상
P0dashboard-api.js last_login_at 컬럼 미존재webhook/modules/dashboard-api.js:68
P0protobufjs CVE (critical, arbitrary code exec)board posthog-js 간접 의존
P0dompurify CVE (moderate, FORBID_TAGS bypass)동상

실제 MySQL DESCRIBE 로 스키마 확정: users 테이블엔 id 가 PK (user_id 컬럼 없음), companies 는 trial_status/trial_ends_at/token_balance_krw 정상, billing_transactions 는 status 컬럼 없음.

P0 수정 + 배포 (w-ai-agents cb3e227)

commit 1: 6f6fa0a fix(watchdog/patterns)

  • pattern-tracker.js: DISTINCT user_idDISTINCT id AS user_id
  • auto-evolution.json monthly_budget_80pct: 서브쿼리로 deduct 합계 분리 후 companies JOIN (MySQL "Invalid use of group function" 해소)
  • auto-evolution.json payment_failure_streak: enabled: false (토스페이먼츠 심사 통과 + ALTER TABLE billing_transactions ADD status 마이그 후 재활성화 — 신규 태스크 #20)
  • auth/login.js: UPDATE users SET last_login_at=NOW() best-effort (실패 시 무시)

DB 마이그: ALTER TABLE users ADD COLUMN last_login_at DATETIME NULL (prod 직접 실행, instant)

commit 2: cb3e227 tune

  • pending_posts_overflow threshold 10 → 12 (prod stash 에서 발견된 관측 기반 튜닝값 반영). 서버 직접 수정 흔적은 git stash@{0} prod-autoevo-tune-2026-04-20 로 백업.

P0 수정 + 배포 (board ba08fab)

  • npm audit fix → protobufjs 7.5.5+ / dompurify 3.3.4+ 로 간접 의존 업데이트. 0 vulnerabilities.
  • vite build → tar pipe 로 /var/www/nexus 재배포.
  • 회귀: index-BA-I2y0k.js (신규 번들), HTTP 200, nginx -t OK.

검증

  • watchdog/status 200, rule_count: 10 (disabled 1 제외)
  • journalctl claude-bridge: 재시작 후 에러 0, [watchdog] 10개 활성 규칙 로드 + [collab] Yjs WebSocket server on :18791 (y-protocols standard) — P1-7a (collab.js y-websocket 표준) 는 이미 구현되어 있을 가능성 높음, 재확인 필요.

남은 부채 (태스크 큐 갱신)

  • P1-6: nginx BRIDGE_TOKEN 평문 9곳 env 분리
  • P1-7a: collab.js y-websocket 표준 (코드 재확인 필요)
  • P1-7b: collab.js SQLite 영속화 + Litestream R2 백업
  • P1-8: image-gen.js sidecar JSON (프롬프트 메타)
  • P1-9: paljalab CI 46회 실패 복구 (카페24 DB IP / secret)
  • P2-11a/b: neuron.js / brain-writer.js 프롬프트 인젝션 탐지
  • P2-12: 서버 journalctl/nginx error 전수 스캔
  • P1-10: DEPLOYMENT.md 실비번 (Loren 결정 대기)
  • 후속 #20: billing_transactions.status 마이그 (토스 심사 후)

P1-6 nginx BRIDGE_TOKEN repo 밖 분리 (1634acb)

부채 #162 청산. 로컬/서버 3개 레포 전부 private 확인 후 공개 노출 우려는 없지만 장기 부채로 정리:

  • config/nginx-snippets/wai-proxy.conf 18곳 + server/nginx-dashboard.conf 8곳 전부 "Bearer <plaintext>"$wai_bridge_auth 변수 참조.
  • server/deploy-server.sh: sed __BRIDGE_TOKEN__ 치환 로직 폐기 → /etc/nginx/conf.d/wai-bridge-auth.confmap $host $wai_bridge_auth { default "Bearer <token>"; } 자동 생성 (존재 체크). chown root:www-data && chmod 0640.
  • 서버: 기존 수동 실행으로 map 파일 생성 (165 byte). nginx -t OK, reload OK. 회귀 /api/agents, /api/events, /api/watchdog/status 200.

Token rotate 절차 config/nginx-snippets/wai-bridge-auth.conf.example 에 문서화: .env 변경 → map 파일 갱신 → nginx reload + claude-bridge restart + 대시보드/앱 env 동기화.

학습: 커밋 히스토리는 Loren 외장 기억 — 토큰 유출 대응도 git filter-repo 가 아닌 rotate 전략으로만. feedback_no_history_rewrite.md 등재.

P1-8 / P1-9 / P2-11 / P2-12 (배치 완료)

P1-8 image-gen.js sidecar (e846814):

  • <filePath>.meta.json 에 prompt/fullPrompt/model/platform/cost/bytes/ agent/postId/chainId/userId/companyId/generatedAt 기록.
  • creative-api.js listItems: sidecar 우선 사용, 없으면 파일명 fallback. Loren 이 이미지 생성 이력 조회 시 실제 프롬프트/비용/에이전트 표시됨.

P1-9 paljalab CI:

  • gh run list -R 1or4/paljalab --limit 10 전부 success (4/10~).
  • 4/7 기록된 46회 연속 실패는 이미 자연 복구. memory feedback_paljalab_ci_broken.md "✅ 해결됨" 으로 갱신.

P2-11 프롬프트 인젝션 가드 (e3815b4):

  • 신규 prompt-injection-guard.js — 영어/한국어 30+ 패턴 (Ignore previous, ChatML tokens, 시스템 프롬프트 공개 등) + 대량 base64/hex 블록 탐지. smoke test 전 케이스 통과.
  • neuron.recordToNeuron + brain-writer.append 에 guard 호출. 매치 시 {BRAIN_PATH}/_injection_log.jsonl audit + 뉴런 생성 차단.

P2-12 서버 로그 전수 스캔:

  • claude-bridge 배포 후 15분 SQL 에러 0건 (재확인). 24h 윈도우의 286+286+286 은 전부 배포 전 축적분.
  • openclaw error 0.
  • nginx error.log: /dns-query, /_profiler, /actuator/env, wp-config.php.old 등 봇 스캐너 정찰 18건 전부 deny 규칙으로 차단. 정상.
  • failed services 0, 디스크 11/36G, Mem 508Mi/3.8Gi 정상.
  • audit-log.jsonl 경로 확인: /home/openclaw/w-ai-agents/config/audit-log.jsonl.

남은 pending (블로커/외부 대기)

  • #17 DEPLOYMENT.md 실비번 — private repo 전환 결정 대기 (Loren)
  • #20 billing_transactions.status 마이그 — 토스페이먼츠 심사 후
  • #21 wai-vault/06-Claude-Memory/reference_infra.md R2 secret 평문 — Loren 시크릿 관리 정책 결정 대기

세션 총 커밋

repo커밋요약
w-ai-agents6f6fa0afix(watchdog/patterns): SQL 3종 + last_login_at
w-ai-agentscb3e227tune: pending_posts_overflow 10→12 (prod 관측)
w-ai-agents1634acbsecurity: nginx BRIDGE_TOKEN repo 밖 분리
w-ai-agentse846814feat(creative): image-gen sidecar + creative-api
w-ai-agentse3815b4feat(security): 인젝션 가드 (neuron+brain-writer)
boardba08fabchore(audit): protobufjs+dompurify fix