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}.css4 파일 - 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/listfetch. 일반 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/notificationsreconnect 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 커밋:
7b86690→7f1b471→3e2e678→a8c151d→85635e8→f38793a(+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-blackwithoutdark: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"}
- 키 없음 → 401
- Nexus dist: tar pipe atomic swap → 1.64MB bundle, HTTPS 200.
새 번들에
dark:hover:bg-white/[0.06]포함 확인.
다음 단계 — 실제 엔드투엔드 동작 확인
- 대시보드에서 테스트 회사 (
노무현) 편집 → API 키 생성 curl -H "X-Api-Key: <32hex>" https://watoneai.cafe24.com/api/v1/ping→ 200 + company_id/features 반환 기대POST /api/v1/posts로 외부 작업 생성 → WorkHub 에서 보이는지 확인- 키 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.jsupdateCompany 에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.js에rateBuckets[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 배포 + 회귀 (358ef8f → ccc4842 버그픽스)
Bridge pull + restart → 실키 발급 → curl 엔드투엔드:
| 체크 | 결과 |
|---|---|
| /v1/ping | 200 + company_id=1 + features 13개 (api_keys ✓ 포함) |
| /v1/posts GET | 200 + 실제 posts 3건 (RLS) |
| /v1/agents | 200 + [] (assigned_agents 매칭 미존재, 정상 동작) |
| /v1/chains | ✗ 초기엔 빈 배열 → agent-chains.json 이 {chains: {키:...}} object 구조라 Array.isArray 실패 |
| X-RateLimit headers | Limit 60 / Remaining 55 정상 |
| Revoke test | PUT api_keys=[] → invalidated_keys: 1 |
| Revoke 후 ping | 401 "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 태스크 생성)
| 심각도 | 발견 | 위치 |
|---|---|---|
| P0 | pattern-tracker.js SQL user_id 미존재 | webhook/modules/pattern-tracker.js:238 |
| P0 | monthly_budget_80pct aggregate 안 aggregate | config/auto-evolution.json |
| P0 | payment_failure_streak status 컬럼 미존재 | 동상 |
| P0 | dashboard-api.js last_login_at 컬럼 미존재 | webhook/modules/dashboard-api.js:68 |
| P0 | protobufjs CVE (critical, arbitrary code exec) | board posthog-js 간접 의존 |
| P0 | dompurify 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_id→DISTINCT 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.conf18곳 +server/nginx-dashboard.conf8곳 전부"Bearer <plaintext>"→$wai_bridge_auth변수 참조.server/deploy-server.sh: sed__BRIDGE_TOKEN__치환 로직 폐기 →/etc/nginx/conf.d/wai-bridge-auth.conf에map $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.jsonlaudit + 뉴런 생성 차단.
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.mdR2 secret 평문 — Loren 시크릿 관리 정책 결정 대기
세션 총 커밋
| repo | 커밋 | 요약 |
|---|---|---|
| w-ai-agents | 6f6fa0a | fix(watchdog/patterns): SQL 3종 + last_login_at |
| w-ai-agents | cb3e227 | tune: pending_posts_overflow 10→12 (prod 관측) |
| w-ai-agents | 1634acb | security: nginx BRIDGE_TOKEN repo 밖 분리 |
| w-ai-agents | e846814 | feat(creative): image-gen sidecar + creative-api |
| w-ai-agents | e3815b4 | feat(security): 인젝션 가드 (neuron+brain-writer) |
| board | ba08fab | chore(audit): protobufjs+dompurify fix |