authoring-viewlisted
Install: claude install-skill relayax/relayagent
# 화면 짓기
화면이 기판과 말하는 길은 셋뿐이다. 이 셋을 넘어가려 하면 대개 길을 잘못 든 것이다.
| 무엇 | 어떻게 | 방향 |
|---|---|---|
| **좌표** | `window.__RELAY_CONTEXT` (기판이 서빙 시점에 심는다) | 기판 → 화면 |
| **동사** | `POST {base}/script/<이름>` | 화면 → 기판 |
| **브리지** | `setScene` · `openChat` · `onAgentTurn` (`@relay/chat`) | 화면 ↔ 같은 문서의 위젯 |
## 두 형태 중 고르기
```yaml
surfaces:
view:
source: surfaces/view # out 없음 = 무빌드. index.html 을 그대로 서빙한다
```
```yaml
surfaces:
view:
source: surfaces/view
out: out # 있음 = 설치가 굽는다(Next 정적 산출)
```
**무빌드를 먼저 시도하라.** `index.html` 한 장에 인라인 CSS·스크립트면 충분한 화면이 아주
많다. 빌드가 없으면 설치가 빠르고, 의존성이 없고, 고칠 때 다시 굽지 않아도 된다.
번들형은 **React 상태가 실제로 필요할 때만** 쓴다 — 폼이 여럿이고 라우팅이 있고 브리지의
`AgentScope`·`useAgentBinding` 을 쓰는 화면. 이때 `next.config.mjs` 는 `output: "export"`,
`basePath` 는 `process.env.RELAY_BASE_PATH` 에서 읽는다. **직접 `npx next build` 하지 마라** —
접두사가 비어 구워져서 서빙된 화면이 자기 스타일시트를 404 받는다. 굽는 길은 하나다:
```sh
relay build <패키지>
```
## 좌표 — 파싱하지 말고 읽어라
```js
const ctx = window.__RELAY_CONTEXT; // { base: "/pkg/<설치이름>", root, instanceId }
const base = ctx.base;
```
설치 이름은 설치 시점에 정해진다. 화면은 그것을 알 필요가 없고, 알려고 URL 을 파싱하면
다른 마운트에 서는 순간 깨진다.
## 동사 부르기 — 화면의 백엔드
`scripts/` 의 동사는 에이전트만의 것이 아니다. 화면도 같은 함수를 부른다.
```js
async function verb(name, input = {}) {
const res = await fetch(`${base}/script/${name}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ input }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok