AxeShell
먼저 보고, 그다음 계약을 읽습니다
서비스 문서를 감싸는 런타임 워크스페이스 셸 — 레일·전환기는 런타임이, 세션·프롬프트는 위젯 iframe 이 채운다.
Component intent의도와 경계 읽기
AxeShell 은 정적 골격(axe-shell-skeleton.html) + <head> 동기 스텁(axe-shell-stub.js, ≤1KB) + defer 런타임(axe-shell.js) 세 조각이다. 서비스가 하는 일은 골격 두 반쪽 사이에 본문을 넣고 매니페스트 carrier(`<script type="application/json" id="axe-shell-manifest">`) 1개를 싣는 것뿐이다. 레일 메뉴·서비스/법인/계정 전환기·접기·⌘K 는 런타임이 기존 .axe-workspace-* 어휘로 그리고, 우측 세션 목록과 하단 프롬프트는 https://axelabs.ai/shell/* 위젯 iframe 이 채운다. 이 문서의 클래스는 위젯 호스트·프레임·퇴행 링크·스크림과 Claude Code 식 프롬프트 어휘다. ≥1440px 에서 패널은 3열의 마지막 트랙, 1024–1439px 에서는 우측 시트, ≤1023px 에서는 전체 폭 시트다. iframe 은 한 번 장착되면 옮기지 않는다(옮기면 재로드) — 폭 전환·시트·확장은 모두 CSS 기하다. 계정 메뉴의 세션 행은 매니페스트 viewer.auth 가 정한다: loginHref(게스트) = '로그인' 한 행, 로그인 뒤 = logoutHref '로그아웃' · switchHref '다른 계정으로 로그인'(생략 시 플랫폼 OP 계정 선택). 게스트에게는 결속할 신원(subHash)이 없어 위젯을 장착하지 않는다. 서비스 전환기 목록은 viewer.services(테넌트가 쓰는 서비스 key 배열)로 거른다 — 없으면 opt-in 아닌 카탈로그 전체, 현재 서비스는 늘 남는다. opt-in 항목(orbit·magnet)은 viewer.services 에 명시될 때만 서고, 목적지는 viewer.serviceHrefs(key → key 별 정적 허용 오리진 위 https URL)가 준다. React 소비자는 AxeShell 컴포넌트가 같은 골격을 렌더하고 boot/refresh/destroy 만 부른다. 정본 = src/lib/shell/SPEC.md.
예제
SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.
import { AxeShell } from "@axe/ui";<AxeShell manifest={manifest}> <h1>결재함</h1></AxeShell><head> <script src="{kit}/shell/axe-shell-stub.js"></script> <script type="application/json" id="axe-shell-manifest" data-axe-shell-entity>{…}</script> <script src="{kit}/shell/axe-shell.js" defer></script> <link rel="stylesheet" href="{kit}/axe-ui.css"></head><body> <!-- {kit}/shell/axe-shell-skeleton.before.html --> <h1>결재함</h1> <!-- {kit}/shell/axe-shell-skeleton.after.html --></body><main id="main" class="axe-app-shell__content">…</main><section id="axe-shell-panel" class="axe-shell-panel" aria-label="세션" data-axe-shell-slot="panel"></section><!-- __main 밖, 셸 루트 직계 --><section class="axe-shell-prompt" aria-label="프롬프트" data-axe-shell-slot="prompt"></section><div class="axe-shell-scrim" data-axe-shell-slot="scrim" hidden></div>Props
TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
manifest | AxeShellManifest | null | 필수 | — | 서버가 만든 AxeShellManifest@1. 래퍼가 serializeManifestForScript 로 carrier <script> 에 직렬화한다. null = 아직 모름(오리진을 클라이언트에서만 아는 정적 렌더) — 골격만 렌더하고 부팅을 미룬다. |
children | React.ReactNode | 필수 | — | 서비스 본문. 골격의 <main id="main"> 안(콘텐츠 표식 자리)에 들어간다. |
widgetBase | string | — | SHELL_WIDGET_ORIGIN | 위젯 문서 URL 접두어(오리진 + 선택 경로 접두어). 수신 검증·targetOrigin 은 그 오리진만 쓴다. design 데모·테스트만 바꾼다. DOM 속성에서는 읽지 않는다. |
widgets | boolean | — | true | false = 레일만. 위젯 호스트를 숨기고 본문 하단 예약을 푼다(루트 data-axe-shell-widgets="off"). SSR 부터 그 상태로 렌더해 부팅 때 3열·프롬프트 예약이 걷히는 시프트가 없다. 게스트 매니페스트(viewer.subHash 없음)도 같다. |
deferBoot | boolean | — | false | true = carrier·골격은 렌더하되 런타임 부팅을 미룬다. 뷰어를 클라이언트에서야 확정하는 정적 렌더 소비자(design)가 게스트로 한 번, 로그인으로 또 한 번 부팅하지 않도록 세션 확정 뒤 false 로 내린다. |
onNavigate | (url: URL) => void | — | — | 위젯의 navigate 를 허용목록 재판정 뒤 받는다. 기본 = location.assign. SPA 소비자가 클라이언트 라우팅에 쓴다. |
className | string | — | — | 셸 루트에 덧붙일 클래스(골격 클래스는 유지). 소비자 편집 표면의 스코프용. |
contentClassName | string | — | — | <main id="main"> 에 덧붙일 클래스. |
.axe-* 클래스 계약
React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.
| 클래스 | 안정성 | 용도 |
|---|---|---|
.axe-shell-panel | stable | 골격의 우측 세션 위젯 호스트 <section>. ≥1440 은 3열 sticky, <1440 은 루트 data-axe-shell-panel="open" 일 때만 보이는 우측 시트. |
.axe-shell-panel-toggle | stable | 바 우측의 시트 토글 버튼(.axe-workspace-bar__icon-button 과 함께). ≥1440 에서는 숨는다. |
.axe-shell-prompt | stable | 골격의 하단 프롬프트 위젯 호스트 <section>. 가운데 열 경계에 맞춘 고정 바, 루트 data-axe-shell-prompt="expanded" 에서 바 아래부터 뷰포트 바닥까지 캔버스를 채운다. 접힘 기본 높이는 84px. |
.axe-shell-frame | internal | 런타임이 호스트 안에 만드는 위젯 <iframe>. 호스트를 테두리 없이 채운다. |
.axe-shell-fallback | internal | 위젯이 10초 안에 ready 를 보내지 않을 때 런타임이 호스트에 남기는 워크스페이스 링크. |
.axe-shell-scrim | internal | 시트(<1440 패널)·모바일 프롬프트 확장 뒤 배경. 표시는 CSS 가 상태 속성으로 정하고, 클릭은 런타임이 collapse 로 받는다. |
.axe-prompt | stable | mono 프롬프트 루트. data-state=expanded 는 100vh, collapsed 는 auto. |
.axe-prompt__{log|box|caret|input|menu|option|option-key|option-desc|option-empty|foot|foot-start|foot-end|link} | stable | 대화 스크롤·입력·후보 listbox·마지막 한 줄 foot 어휘. 선택 후보·텍스트 링크는 accent. |
.axe-prompt__{status|status-title|status-spacer} | stable | 세션 모드의 mono 12.5px 상태 행·ink semibold 말줄임 제목·flex:1 spacer. 좁은 폭에서 감긴다. |
.axe-prompt-live | stable | 도트 + nowrap 라벨. data-state=waiting(success halo) / working(accent halo) / none(faint) / reconnecting(faint 빈 원). 모션 0. |
.axe-prompt-live__dot | stable | 8px live 상태 원, halo 는 정적 box-shadow. forced-colors 에서는 시스템 색 채움·빈 원으로 가시성을 보존한다. |
.axe-prompt-line__{head|meta|tag} | stable | 게재 제목과 우측 메타의 baseline flex 머리(좁으면 감김)·faint 12px nowrap 메타·11.5px muted 테두리 tag(세션에 올리지 않음). |
.axe-prompt-line__body--md | stable | __body 와 함께 쓰는 Markdown 본문. white-space:normal, mono 전사 안 p/ul/ol/li/pre/code/blockquote/h1–h4/table compact 간격, 긴 pre·table 내부 스크롤. |
.axe-session-item__dot--live | stable | __dot 과 함께 쓰는 연결된 로컬 에이전트의 success 도트·정적 halo. forced-colors 는 CanvasText 채움. |
.axe-session-item__presence | stable | 패널 세션 행 이름 아래의 small muted 한 줄 말줄임. __link 의 직계 자식, 일반·compact 목록에서 도트 다음 열에 선다. 상태는 글로도 알린다. |
.axe-prompt-line | stable | 마커 2.2ch + 본문 대화 행. |
.axe-prompt-line__{marker|body|name} | stable | 글리프·pre-wrap 본문·굵은 도구명. |
.axe-prompt-line--{user|ai|tool|result|spin|alert} | stable | 사용자 밴드·답변 ink·도구 success·결과 ⎿·진행 accent·오류 danger. |
.axe-prompt-welcome | stable | accent 테두리의 시작 안내. |
.axe-prompt-welcome__title | stable | ✻ 마커와 굵은 이름. |
.axe-prompt-confirm | stable | 대화 안 동의 상자, strong 테두리·muted 문단. |
.axe-prompt-confirm__title | stable | 동의 상자의 굵은 제목. |
.axe-prompt-choices | stable | 동의 선택지 listbox 열. |
.axe-prompt-choice | stable | 텍스트 버튼. aria-selected=true 는 accent 와 ❯ 마커. |
.axe-prompt-help | stable | 14ch 키 + 본문 도움말 그리드. |
.axe-prompt-help__head | stable | 두 열 전체를 차지하는 muted 제목. |
접근성
키보드, ARIA, 구현 노트를 함께 검토합니다.
- ⌘K / Ctrl+K — 프롬프트 위젯에 이동 모드로 포커스
- ⌘J / Ctrl+J — 프롬프트 위젯에 질문 모드로 포커스(AI 가 꺼진 서비스는 이동 모드)
- Esc(포커스가 부모 문서에 있을 때) — 패널 시트 → 프롬프트 확장 → 레일 서랍 순으로 닫고 연 트리거로 포커스 복원
두 호스트는 aria-label 이 붙은 <section>("세션"·"프롬프트"), 위젯 <iframe> 은 title 을 가진다. 시트 토글은 aria-controls="axe-shell-panel" + aria-expanded 로 상태를 알린다. 스킵 링크는 #main 으로 간다.
닫힌 시트는 visibility:hidden 이라 포커스·접근성 트리에서 빠지지만 iframe 은 재로드되지 않는다. ≥1024 프롬프트 확장은 본문만 inert, 레일·바·패널은 상호작용 가능하다. 시트는 배경 전체가 inert 다. 모션 없음(고빈도 UI). forced-colors 에서는 호스트 경계가 CanvasText 1px 로 선다.