UI
한 줄 소개: axelabs 플랫폼 (메인 사이트 · docs · cortex · frame · hive · matrix · blueprint) 이 공통으로 쓰는 디자인 시스템 SSOT — 토큰 (color/spacing/typography) + 40+ React 컴포넌트 + .axe-app-shell 통합 layout chrome (D-ui-1). 외부 데모 = design.axelabs.ai (구 ui.axelabs.ai → D-ui-3 에서 rename·폐기).
도메인
| URL | 용도 |
|---|---|
| https://design.axelabs.ai | 디자인 시스템 라이브 사이트 (variant 토글 + 본문 + 데모 모두 한 페이지에 통합) |
localhost:3902/design | 운영자 로컬 dev preview (port 3902 — :3900 은 docker production 점유) |
design.axelabs.ai 의 sub-path 는 root 로 308 redirect (단일 페이지 통합 — 운영자 결정 2026-05-29).
메인 axelabs.ai/design/* 는 404 차단 (디자인 시스템 surface 는 design.axelabs.ai 단일 host).
구 ui.axelabs.ai 는 D-ui-3 에서 완전 폐기 — tunnel ingress 제거로 catch-all 404.
기술 스택
| 항목 | 값 |
|---|---|
| 패키지 이름 | @axe/ui |
| 위치 (SSOT) | /Users/axe/axelabs/src/lib/ |
| 프레임워크 | Next.js 16 (App Router) + React 19 |
| 호스팅 | axelabs.ai 와 동일 Next.js 컨테이너 (multi-domain rewrite, D-ui-1) |
| 토큰 | CSS 변수 — tokens/{colors,typography,spacing,fonts}.css |
| 컴포넌트 | 40+ React (.tsx) + 14 CSS 그룹 (styles/*.css) |
| Layout chrome | .axe-app-shell + variant (`data-shell=“docs |
| 다크모드 | data-theme="dark" + .dark/.light 동시 set (next-themes 호환) |
| 폰트 | Pretendard (본문) + Sarasa Fixed K (mono) + Clash Display (옵션 display) |
배포 모델 (D-ui-1 · 도메인 D-ui-3)
design.axelabs.ai 는 별도 컨테이너가 아닙니다. axelabs.ai (회사 홈) 와 같은 Next.js standalone 컨테이너가 host header 를 보고 분기:
Host: axelabs.ai또는www.axelabs.ai→ 메인 사이트Host: design.axelabs.ai→ root 를/design으로 rewrite (proxy.ts, host 분기 — D-ui-3)
분기 메커니즘은 next.config.mjs 의 rewrites() 가 아니라 proxy.ts (Next.js 16 convention, 구 middleware.ts) 다 — 정적 prerender 된 / 에는 next.config rewrites 가 안 걸리는 함정 때문 (proxy 는 항상 runtime).
axelabs.ai/design 으로 직접 접근도 살아있음 — dev/preview + canary 용도.
이유: 디자인 시스템 코드가 본 레포에 살고, build 산출물 공유가 자연스러움. 단일 docker compose 운영 단순.
tunnel ingress 변경은 로컬
config.yml이 아님 —axelabs-tunnel은 Cloudflare remote-managed (source=cloudflare, version-tracked). ingress 추가/변경/삭제는PUT /accounts/<acct>/cfd_tunnel/<uuid>/configurations(token = vaultCloudflare API - axelabs). 상세 = known-gaps Cortex 7함정 #7.
Chrome variants
| Variant | 형태 | 사용처 |
|---|---|---|
docs | TopNav + Sidebar + Content (80ch) + TOC + Footer | docs.axelabs.ai, 가이드 페이지 |
dashboard | TopNav + Sidebar + Content (1280px) + Footer | cortex / frame admin / blueprint workspace |
landing | TopNav + Content (full width) + Footer | axelabs.ai 메인, 마케팅 페이지 |
CSS 변수 (variant 별 width):
| 변수 | 기본값 |
|---|---|
--app-shell-sidebar-w | 240px |
--app-shell-toc-w | 220px |
--app-shell-gutter | var(--space-6) (32px) |
--app-shell-topbar-h | 56px |
Layout primitives + ThemeToggle (Phase 16)
Chrome 가 페이지 macro 골격이라면, primitives 는 그 안의 micro 레이아웃 — display:flex/grid + gap 을 매번 inline style 로 재발명하던 것을 SSOT 클래스로 흡수 (D-ui-2). Chrome 과 동일하게 styling 은 100% CSS 클래스 → React 아닌 소비자 (cortex maud, jinja) 도 같은 markup 재사용.
| 컴포넌트 | CSS 클래스 | 역할 | 주요 prop |
|---|---|---|---|
Stack | .axe-stack | 세로 flex + gap | gap 0–10 (기본 4) · align · as |
Cluster | .axe-cluster | 가로 flex + wrap + gap (toolbar/액션 줄) | gap 0–10 (기본 3) · align(기본 center) · justify · nowrap · as |
Grid | .axe-grid | 반응형 grid | cols 1–6 (기본 3) · gap 0–6 (기본 4) · auto+min · responsive |
Container | .axe-container | max-width 중앙 + gutter | size sm/md/lg/xl/full (기본 lg) · as |
ThemeToggle | .axe-theme-toggle | light/system/dark 전환 | mode segment|cycle (기본 segment) · size |
- gap 은 spacing scale 와 1:1 —
gap={4}→var(--space-4). 매직 px 금지. Grid auto—min(기본 15rem) 이하로 안 줄게auto-fillwrap. cortex 의.cortex-metric-grid재발명 대체.ThemeToggle은useTheme위 thin UI — DOM/storage 반영을ThemeProvider에 위임 (이전 design 페이지가 Provider 우회해document.documentElement를 직접 만지던 버그 제거). 아이콘은 inline SVG (외부 dep 0).
CSS-only 소비자는 동일 클래스를 직접 쓴다:
<div class="axe-stack axe-stack--gap-4">
<div class="axe-cluster axe-cluster--gap-2 axe-cluster--justify-between">…</div>
<div class="axe-grid axe-grid--auto axe-grid--gap-3" style="--axe-grid-min: 220px">…</div>
</div>채택 (2026-05-29 브라우저 검증): app/page.tsx (axelabs.ai 홈 — Hero/Section/Footer 컴포지트 + Stack/Cluster/Grid) · app/matrix (MatrixNav ThemeToggle + page 전체 레이아웃) · app/design (design.axelabs.ai 본 페이지). cortex/docs/blueprint 는 순차 적용 대기.
문서 템플릿 (Phase 17)
Chrome·primitives 가 화면 레이아웃이라면, 문서 템플릿은 인쇄물 — AXE 가 외부로 발신하는 문서 (IC Memo · LP 서한 등) 를 .axe-doc CSS-class HTML 로 통일 (D-ui-3). 화면 컴포넌트와 동일하게 styling 100% CSS 클래스 → React 쇼케이스와 jinja2 렌더가 같은 markup 을 공유.
| 템플릿 | 위치 | 용도 |
|---|---|---|
| IC Memo | src/lib/templates/documents/ic-memo.html.jinja | 투자심의 메모 — 권고 callout + deal/returns KPI + 번호 섹션 + 리스크/표결 표 + 서명 |
| LP 서한 | src/lib/templates/documents/lp-letter.html.jinja | 출자자 서한 — 분기(quarterly)/수시(adhoc) 분기 + NAV KPI + 서명 |
| 스타일 SSOT | src/lib/styles/document.css | .axe-doc* 전 클래스 (components.css 가 @import → globals 경유 전역 로드) |
가로·세로 모두 지원 (문서 성격별 고정 X — 운영자 결정): .axe-doc--portrait (A4 210×297mm) / .axe-doc--landscape (297×210mm). landscape 본문엔 .axe-doc__cols (2단) 적용 가능.
| 측면 | 방식 |
|---|---|
| 단위 | pt (화면·인쇄 일관) |
| 페이지 | named @page axe-doc-portrait / axe-doc-landscape + .axe-doc 의 page: 속성으로 선택 — 전역 bare @page 금지 (사이트 전 페이지에 A4 누수) |
| 테마 | .axe-doc 가 자체 --doc-* 팔레트 정의 → light/dark 무관 동일 (흰 종이·따뜻한 잉크) |
| 인쇄 | @media print 가 stage·toolbar 숨김 + box-shadow 제거 + print-color-adjust:exact |
렌더 파이프라인 (기존 md-to-pdf 재사용)
병렬 렌더러 신설 X — 기존 마크다운→PDF 파이프라인에 끼움:
- 본문 (markdown → HTML) — pandoc 가 narrative 를 HTML 로 (md-to-pdf skill). 결과를 jinja 컨텍스트의
memo.body_html/letter.body_html슬롯에safe필터로 주입. - wrapper (jinja → HTML) —
.axe-docchrome (masthead·meta·KPI·callout·표·서명) 으로 본문을 감쌈. CSS 는axe_css(inline, production 권장) 또는css_href(dev<link>). - HTML → PDF —
chromium --headless --print-to-pdf(md-to-pdf skillrender.sh와 동일 엔진). orientation 클래스가 named@page를 선택. - 품질 게이트 — ic skill
check_pdf_quality.py로 검증.
쇼케이스
design.axelabs.ai § 문서 템플릿 에 라이브 데모 — IC Memo ↔ LP 서한 · 세로 ↔ 가로 · zoom 토글 + “인쇄·PDF” 버튼 (현재 문서 노드만 새 창에 복제해 window.print() → AppShell chrome 없는 깨끗한 PDF). 컨텍스트 스키마는 각 .jinja 파일 상단 주석 참조.
Multi-stack
React 가 아닌 stack (cortex 의 Rust+maud 등) 도 동일 markup 으로 동일 chrome:
<div class="axe-app-shell" data-shell="dashboard">
<div class="axe-app-shell__topbar">
<nav class="axe-topnav">…</nav>
</div>
<div class="axe-app-shell__main">
<aside class="axe-app-shell__sidebar"><nav class="axe-sidebar">…</nav></aside>
<main class="axe-app-shell__content">…</main>
</div>
<div class="axe-app-shell__footer">
<footer class="axe-footer">…</footer>
</div>
</div>cortex 의 scripts/sync-axe-ui.sh 가 layout.css 를 bundle 에 포함시키도록 갱신하면 즉시 채택 가능. 가이드 = /Users/axe/cortex/CLAUDE.md (D-cortex-design-axe-ui).
소비자 (현재 + 예정)
| 서비스 | 현재 chrome | 목표 variant | 상태 |
|---|---|---|---|
| axelabs.ai (메인) | TopNav + Hero/Section/Footer + Stack/Cluster/Grid | landing | 채택 (2026-05-29 dogfood) |
| docs.axelabs.ai | Nextra theme | docs (slot override) | 대기 (C2 — Nextra navbar/sidebar 슬롯에 axe 컴포넌트 끼움) |
| cortex | TopNav-only (maud) | dashboard | 다음 (scripts/sync-axe-ui.sh + page() 갱신) |
| frame · hive · matrix | — | dashboard | 대기 (신규 admin UI 시작 시) |
| Blueprint | 자체 | dashboard | 대기 (기존 chrome 분리 후) |
환경 변수
본 서비스는 별도 환경 변수 없음. axelabs.ai 와 동일 컨테이너 + 정적 자원.
함정
:3900dev 띄우기 — docker production container 가 점유 중. 로컬 dev 는npx next dev --port 3902(reference) 또는.claude/launch.json의axe-ui-design-preview항목 사용.- host header rewrite 검증 —
axelabs.ai/design(404 차단) 와design.axelabs.ai(200, /design 서빙) 가 같게 동작하면 host 분기 실패. cloudflared 의httpHostHeaderoverride 없이 default (preserve) 사용 가정. 컨테이너 직접 검증:curl -H 'Host: design.axelabs.ai' http://127.0.0.1:3900/→ 200,curl -H 'Host: axelabs.ai' http://127.0.0.1:3900/design→ 404. - 신규 1-level 호스트는 IPv6-only 로 잠깐 보일 수 있음 — 새
design.axelabs.ai직후 로컬 resolver 가 AAAA(Cloudflare IPv6 edge)만 반환 → IPv6 무라우팅 머신에서curl이No route to host(HTTP 000). production 문제 아님.curl -4또는--resolve design.axelabs.ai:443:104.21.66.67로 IPv4 edge 검증.