2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.3> Use this file to discover all available pages before exploring further.
4 4
5# 모드로 인터페이스에 그리기5# mod로 인터페이스에 그리기
6 6
7> Claude Code 모드에서 창, 프롬프트 위의 밴드, 버튼, 텍스트 필드를 그리고, 누름과 입력을 처리하며, 다시 그릴 때와 세션 간에 상태를 유지합니다.7> Claude Code mod에서 창, 프롬프트 위의 밴드, 버튼, 텍스트 필드를 그리고, 버튼 누름과 입력을 처리하며, 다시 그리기와 세션 간에 상태를 유지합니다.
8 8
9모드는 Claude Code에서 자신의 인터페이스를 그릴 수 있고 Claude Code가 이미 그리는 인터페이스의 일부를 변경할 수 있습니다. 모드가 그릴 수 있는 각 위치를 [렌더 사이트](/docs/ko/plugins/mods/reference#render-sites)라고 하며, 창, 프롬프트 위의 밴드, 또는 스피너 같은 것들이 있습니다. Claude Code는 렌더 사이트를 그리려고 할 때마다 [`ui.render`](/docs/ko/plugins/mods/reference#interface) 이벤트를 발생시키고, 해당 이벤트에 대한 훅이 그곳에 그릴 내용을 반환합니다.9mod는 Claude Code에 자체 인터페이스를 그릴 수 있으며, Claude Code가 이미 그리는 인터페이스의 일부를 변경할 수도 있습니다. mod가 그릴 수 있는 각 위치를 [렌더링 지점](/docs/ko/plugins/mods/reference#render-sites)이라고 하며, 창, 프롬프트 위의 밴드, 스피너 등이 이에 해당합니다. Claude Code는 렌더링 지점을 그리기 직전마다 [`ui.render`](/docs/ko/plugins/mods/reference#interface) 이벤트를 발생시키며, 해당 이벤트에 대한 훅은 그곳에 그릴 내용을 반환합니다.
10 10
11이 지도는 터미널 세션에서 모드가 그릴 수 있는 위치를 보여줍니다:11다음 맵은 터미널 세션에서 mod가 그릴 수 있는 위치를 보여 줍니다.
12 12
13<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Claude Code 터미널 세션의 지도. 모드는 오른쪽에 사이드바로 창을 추가하고, 대화 기록의 오른쪽 위에 토스트를 추가하고, 대화 기록에 로그 줄을 추가하고, 프롬프트 위에 밴드를 추가하고, 프롬프트 아래에 상태 줄을 추가할 수 있습니다. 모드는 메시지, 도구 호출 행, 스피너를 다시 그릴 수 있습니다. 프롬프트는 Claude Code 자신의 것입니다." width="600" height="336" data-path="images/mods-screen-map.svg" />13<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Claude Code 터미널 세션의 맵. mod는 오른쪽에 사이드바로 창을, 트랜스크립트 오른쪽 상단에 토스트를, 트랜스크립트에 로그 줄을, 프롬프트 위에 밴드를, 프롬프트 아래에 상태줄을 추가할 수 있습니다. mod는 메시지, 도구 호출 행, 스피너를 다시 그릴 수 있습니다. 프롬프트는 Claude Code 자체의 것입니다." width="600" height="336" data-path="images/mods-screen-map.svg" />
14 14
15<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Claude Code 터미널 세션의 지도. 모드는 오른쪽에 사이드바로 창을 추가하고, 대화 기록의 오른쪽 위에 토스트를 추가하고, 대화 기록에 로그 줄을 추가하고, 프롬프트 위에 밴드를 추가하고, 프롬프트 아래에 상태 줄을 추가할 수 있습니다. 모드는 메시지, 도구 호출 행, 스피너를 다시 그릴 수 있습니다. 프롬프트는 Claude Code 자신의 것입니다." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />15<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Claude Code 터미널 세션의 맵. mod는 오른쪽에 사이드바로 창을, 트랜스크립트 오른쪽 상단에 토스트를, 트랜스크립트에 로그 줄을, 프롬프트 위에 밴드를, 프롬프트 아래에 상태줄을 추가할 수 있습니다. mod는 메시지, 도구 호출 행, 스피너를 다시 그릴 수 있습니다. 프롬프트는 Claude Code 자체의 것입니다." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />
16 16
17더 좁은 터미널에서는 창이 대화 기록 옆이 아니라 프롬프트 위에 있습니다.17더 좁은 터미널에서는 창이 트랜스크립트 옆이 아닌 프롬프트 위에 배치됩니다.
18 18
19여기서 시작하기 전에 [첫 번째 모드](/docs/ko/plugins/mods/create)를 만드세요. 두 개의 탭과 카운터가 있는 창을 만드는 작업 예제로 시작한 다음, 변경하려는 각 부분에 대한 섹션을 읽으세요.19여기서 시작하기 전에 [첫 번째 mod](/docs/ko/plugins/mods/create)를 만들어 보십시오. 두 개의 탭과 카운터가 있는 창을 만드는 실습 예제부터 시작한 다음, 변경하려는 각 부분에 해당하는 섹션을 읽으십시오.
20 20
21<Note>21<Note>
22 한 가지 속성이나 제한을 찾으려면 [참조](/docs/ko/plugins/mods/reference#render-sites)를 보세요.22 개별 prop이나 제한을 찾아보려면 [레퍼런스](/docs/ko/plugins/mods/reference#render-sites)를 참조하십시오.
23</Note>23</Note>
24 24
25<h2 id="build-a-pane-with-tabs">25<h2 id="build-a-pane-with-tabs">
26 탭이 있는 창 만들기26 탭이 있는 창 만들기
27</h2>27</h2>
28 28
29이 섹션에서는 `/hello-tabs` 명령을 추가하는 모드를 만들고, 이 명령은 창을 엽니다. 창은 넓은 전체 화면 터미널에서는 대화 기록 옆의 사이드바이거나, 그렇지 않으면 프롬프트 위의 프레임된 영역입니다. 이 창은 두 개의 탭을 표시하고, 두 번째 탭에는 카운터에 1을 더하는 버튼이 있습니다. Claude Code를 다시 시작한 후에도 카운트는 여전히 있습니다.29이 섹션에서는 `/hello-tabs` 명령을 추가하는 mod를 만들며, 이 명령은 창을 엽니다. 창은 너비가 넓은 전체 화면 터미널에서는 트랜스크립트 옆의 사이드바로, 그 외의 경우에는 프롬프트 위의 테두리가 있는 영역으로 표시됩니다. 이 창에는 두 개의 탭이 표시되며, 두 번째 탭에는 카운터를 1씩 늘리는 버튼이 있습니다. 카운트는 Claude Code를 다시 시작한 후에도 유지됩니다.
30 30
31완성된 모드는 다음과 같습니다. 녹화는 창을 열고, 두 번째 탭으로 전환하고, 버튼을 몇 번 누르고, 첫 번째 탭으로 돌아갑니다:31완성된 mod는 다음과 같습니다. 녹화 영상은 창을 열고, 두 번째 탭으로 전환한 뒤, 버튼을 몇 번 누르고, 첫 번째 탭으로 돌아갑니다.
32 32
33<Frame>33<Frame>
34 <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="/hello-tabs 명령이 Claude Code 프롬프트에 입력되고 프레임된 창이 위에 열리며, 맨 위에 '1: One'과 '2: Two'가 있고 '이것은 첫 번째 탭입니다.'라는 텍스트가 있습니다. 두 번째 탭은 'Count: 1' 옆에 'Add one' 버튼을 표시하고, 카운트는 3으로 올라갑니다. 창은 그 다음 첫 번째 탭으로 돌아갑니다." data-path="images/mods-hello-tabs-light.mp4" />34 <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="Claude Code 프롬프트에 /hello-tabs 명령을 입력하면 그 위에 테두리가 있는 창이 열리며, 상단에 '1: One'과 '2: Two'가 표시되고 'This is the first tab.'이라는 텍스트가 나타납니다. 두 번째 탭에는 'Count: 1' 옆에 'Add one' 버튼이 표시되고, 카운트가 3까지 올라갑니다. 그런 다음 창은 첫 번째 탭으로 돌아갑니다." data-path="images/mods-hello-tabs-light.mp4" />
35 35
36 <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="/hello-tabs 명령이 Claude Code 프롬프트에 입력되고 프레임된 창이 위에 열리며, 맨 위에 '1: One'과 '2: Two'가 있고 '이것은 첫 번째 탭입니다.'라는 텍스트가 있습니다. 두 번째 탭은 'Count: 1' 옆에 'Add one' 버튼을 표시하고, 카운트는 3으로 올라갑니다. 창은 그 다음 첫 번째 탭으로 돌아갑니다." data-path="images/mods-hello-tabs-dark.mp4" />36 <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="Claude Code 프롬프트에 /hello-tabs 명령을 입력하면 그 위에 테두리가 있는 창이 열리며, 상단에 '1: One'과 '2: Two'가 표시되고 'This is the first tab.'이라는 텍스트가 나타납니다. 두 번째 탭에는 'Count: 1' 옆에 'Add one' 버튼이 표시되고, 카운트가 3까지 올라갑니다. 그런 다음 창은 첫 번째 탭으로 돌아갑니다." data-path="images/mods-hello-tabs-dark.mp4" />
37</Frame>37</Frame>
38 38
39Claude Code에는 기본 제공 탭 요소가 없으므로 탭은 행의 두 버튼입니다. 모드는 어느 것이 활성인지 추적하고 그 탭의 내용을 행 아래에 그립니다.39탭은 한 줄로 나열된 두 개의 버튼입니다. mod는 어느 탭이 활성 상태인지 추적하고, 그 줄 아래에 해당 탭의 콘텐츠를 그립니다.
40 40
41<Steps>41<Steps>
42 <Step title="플러그인 만들기">42 <Step title="플러그인 만들기">
43 모드는 매니페스트, 코드를 가리키는 `hooks.json`, 그리고 코드 파일이 있는 플러그인입니다. [모드 만들기](/docs/ko/plugins/mods/create#write-a-mod-yourself)는 각각을 설명합니다. `hello-tabs`라는 디렉토리를 만들고 그 안에 `.claude-plugin`과 `hooks` 디렉토리를 만든 다음 처음 두 파일을 저장하세요.43 mod는 매니페스트, 코드를 가리키는 `hooks.json`, 그리고 코드 파일로 구성된 플러그인입니다. 각 항목에 대한 설명은 [mod 만들기](/docs/ko/plugins/mods/create#write-a-mod-yourself)를 참조하세요. `hello-tabs`라는 디렉터리를 만들고 그 안에 `.claude-plugin` 및 `hooks` 디렉터리를 만든 다음, 처음 두 파일을 저장합니다.
44 44
45 매니페스트를 `hello-tabs/.claude-plugin/plugin.json`으로 저장하세요:45 매니페스트를 `hello-tabs/.claude-plugin/plugin.json`으로 저장합니다.
46 46
47 ```json hello-tabs/.claude-plugin/plugin.json theme={null}47 ```json hello-tabs/.claude-plugin/plugin.json theme={null}
48 {48 {
53 }53 }
54 ```54 ```
55 55
56 `hello-tabs/hooks/hooks.json`에서 진입점의 이름을 지정하세요:56 `hello-tabs/hooks/hooks.json`에 진입점을 지정합니다.
57 57
58 ```json hello-tabs/hooks/hooks.json theme={null}58 ```json hello-tabs/hooks/hooks.json theme={null}
59 {59 {
62 ```62 ```
63 </Step>63 </Step>
64 64
65 <Step title="코드 작성">65 <Step title="코드 작성하기">
66 코드는 각 훅에서 하나씩 세 가지 작업을 수행합니다:66 다음 목록은 코드에 나타나는 순서대로 각 훅이 하는 일을 설명합니다.
67 67
68 * `/hello-tabs` 명령 추가68 * `/hello-tabs` 명령을 추가하고, 이전 세션에서 저장한 카운트를 불러옵니다
69 * 해당 명령을 실행할 때 창 열기69 * 해당 명령을 실행하면 창을 엽니다
70 * 창의 내용 그리기: 탭 행과 열린 탭의 본문70 * 창의 콘텐츠, 즉 탭 줄과 열린 탭의 본문을 그립니다
71 71
72 두 개의 모듈 수준 변수인 `tab`과 `count`는 창의 상태를 유지합니다.72 두 개의 모듈 수준 변수 `tab`과 `count`가 창의 상태를 보관합니다.
73 73
74 이를 `hello-tabs/hooks/register.js`로 저장하세요:74 다음을 `hello-tabs/hooks/register.js`로 저장합니다.
75 75
76 ```javascript hello-tabs/hooks/register.js theme={null}76 ```javascript hello-tabs/hooks/register.js theme={null}
77 // 창의 id, 창을 열고 그릴 때 인식하는 데 사용됨77 // The pane's id, used to open the pane and to recognize it when drawing
78 const PANE = 'hello-tabs'78 const PANE = 'hello-tabs'
79 79
80 // 창이 표시하는 것: 어느 탭이 열려 있는지, 그리고 카운터의 값80 // What the pane shows: which tab is open, and the counter's value
81 let tab = 'one'81 let tab = 'one'
82 let count = 082 let count = 0
83 83
84 export function register(on) {84 export function register(on) {
85 // 첫 프롬프트 전에 실행되고, 다시 로드 후에도 실행됨85 // Runs before your first prompt, and again after a reload
86 on('session.start', async ($, e, next) => {86 on('session.start', async ($, e, next) => {
87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })87 await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
88 // 이전 세션이 저장한 카운트를 로드합니다 (있는 경우)88 // Load the count an earlier session saved, if there is one
89 const saved = await $.store.get('count')89 const saved = await $.store.get('count')
90 if (typeof saved === 'number') count = saved90 if (typeof saved === 'number') count = saved
91 return next(e)91 return next(e)
92 })92 })
93 93
94 // /hello-tabs를 입력할 때 실행됨94 // Runs when you type /hello-tabs
95 on('command.run', { command: 'hello-tabs' }, async ($) => {95 on('command.run', { command: 'hello-tabs' }, async ($) => {
96 // 창을 열고, 키보드를 주고, Esc로 닫을 수 있게 함96 // Open the pane, give it the keyboard, and let Esc close it
97 await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })97 await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
98 // 대화 기록에 아무것도 인쇄하지 않음98 // Print nothing in the transcript
99 return {}99 return {}
100 })100 })
101 101
102 // Claude Code가 창을 그릴 때마다 실행됨102 // Runs each time Claude Code draws a pane
103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {103 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
104 // 다른 모드의 창은 그대로 두기104 // Leave other mods' panes alone
105 if (e.requestId !== PANE) return next(e)105 if (e.requestId !== PANE) return next(e)
106 // 이 앱이 그릴 수 있는 요소 가져오기106 // Get the elements this app can draw
107 const { Box, Text, Button } = $.ui.resolve(e)107 const { Box, Text, Button } = $.ui.resolve(e)
108 // Claude Code가 이 훅을 다시 실행하도록 요청108 // Ask Claude Code to run this hook again
109 const redraw = () => $.ui.invalidate('ui.render')109 const redraw = () => $.ui.invalidate('ui.render')
110 110
111 // 한 탭: 누르면 해당 탭으로 전환하는 버튼111 // One tab: a button that switches to its tab when pressed
112 const tabButton = (name, label, hotkey) =>112 const tabButton = (name, label, hotkey) =>
113 Button({113 Button({
114 key: 'tab-' + name,114 key: 'tab-' + name,
115 label,115 label,
116 hotkey,116 hotkey,
117 plain: true,117 plain: true,
118 // 열려 있지 않은 탭을 어둡게 함118 // Dim the tab that isn't open
119 dimColor: tab !== name,119 dimColor: tab !== name,
120 onPress: () => {120 onPress: () => {
121 tab = name121 tab = name
123 },123 },
124 })124 })
125 125
126 // 어느 탭이 열려 있는지에 따라 탭 아래에 가는 것126 // What goes under the tabs, depending on which one is open
127 const body =127 const body =
128 tab === 'one'128 tab === 'one'
129 ? [Text({ children: ['This is the first tab.'] })]129 ? [Text({ children: ['This is the first tab.'] })]
139 onPress: async () => {139 onPress: async () => {
140 count += 1140 count += 1
141 redraw()141 redraw()
142 // 카운트를 저장하여 다시 시작 후에도 있도록 함142 // Save the count so it's there after a restart
143 await $.store.set('count', count)143 await $.store.set('count', count)
144 },144 },
145 }),145 }),
148 }),148 }),
149 ]149 ]
150 150
151 // 전체 창: 탭 행, 빈 줄, 그 다음 본문151 // The whole pane: the row of tabs, a blank line, then the body
152 return Box({152 return Box({
153 flexDirection: 'column',153 flexDirection: 'column',
154 children: [154 children: [
165 }165 }
166 ```166 ```
167 167
168 각 훅은 코드가 명확하게 하지 않는 것도 수행합니다:168 각 훅은 코드만으로는 드러나지 않는 작업도 수행합니다.
169 169
170 * \*\*[`session.start`](/docs/ko/plugins/mods/reference#session)\*\*는 또한 [`$.store`](#keep-state)에서 저장된 카운트를 읽습니다. 이는 세션 간에 지속되는 키-값 저장소입니다.170 * **[`session.start`](/docs/ko/plugins/mods/reference#session)** 는 세션 간에 유지되는 키-값 저장소인 [`$.store`](#keep-state)에서 저장된 카운트도 읽습니다.
171 * \*\*[`command.run`](/docs/ko/plugins/mods/api#add-a-command)\*\*은 Claude Code에 창이 존재한다고만 알립니다. 창을 열어도 아무것도 그려지지 않습니다: Claude Code는 그 다음 `ui.render`를 발생시켜 그곳에 무엇을 넣을지 묻습니다.171 * **[`command.run`](/docs/ko/plugins/mods/api#add-a-command)** 은 Claude Code에 창이 존재한다는 사실만 알립니다. 창을 여는 것만으로는 아무것도 그려지지 않습니다. 그 후 Claude Code가 `ui.render`를 발생시켜 창에 무엇이 들어갈지 묻습니다.
172 * \*\*`ui.render`\*\*는 요소 트리를 반환합니다. 이는 다른 상자, 텍스트, 버튼을 보유하는 `Box`이며, 실행될 때마다 `tab`과 `count`에서 다시 빌드합니다.172 * **`ui.render`** 는 다른 박스, 텍스트, 버튼을 담는 `Box`인 요소 트리를 반환하며, 실행될 때마다 `tab`과 `count`로부터 트리를 다시 빌드합니다.
173 173
174 버튼을 누르면 `onPress` 콜백이 실행되고, 이는 변수를 변경하고 `redraw`를 호출합니다. Claude Code는 그 다음 `ui.render` 훅을 다시 실행하고, 훅은 새로운 값에서 새로운 트리를 빌드합니다. 모든 대화형 그리기는 그 렌더 사이클을 사용합니다: 콜백이 상태를 변경하고, 훅이 새로운 상태에서 다시 렌더링합니다.174 버튼을 누르면 해당 `onPress` 콜백이 실행되어 변수를 변경하고 `redraw`를 호출합니다. 그러면 Claude Code가 `ui.render` 훅을 다시 실행하고, 훅은 새 값으로부터 새 트리를 빌드합니다. 모든 인터랙티브 그리기는 이 렌더 주기를 사용합니다. 콜백이 상태를 변경하면, 훅이 새 상태로부터 다시 렌더링합니다.
175 </Step>175 </Step>
176 176
177 <Step title="창 열기">177 <Step title="창 열기">
178 셸에서 `claude --plugin-dir ./hello-tabs`로 Claude Code를 시작하세요. Claude Code 프롬프트에서 `/hello-tabs`를 실행하세요. 맨 위에 `1: One`과 `2: Two`가 있는 창이 열립니다. `2`를 누르고, 그 다음 **Add one**의 핫키인 `a`를 몇 번 누르세요. 카운트가 올라갑니다.178 셸에서 `claude --plugin-dir ./hello-tabs`로 Claude Code를 시작합니다. Claude Code 프롬프트에서 `/hello-tabs`를 실행합니다. 상단에 `1: One`과 `2: Two`가 표시된 창이 열립니다. `2`를 누른 다음, **Add one**의 단축키인 `a`를 몇 번 누릅니다. 카운트가 올라갑니다.
179 </Step>179 </Step>
180 180
181 <Step title="카운트가 저장되었는지 확인">181 <Step title="카운트가 저장되었는지 확인하기">
182 Esc를 눌러 창을 닫고 세션을 종료하세요. 셸에서 같은 `claude --plugin-dir ./hello-tabs` 명령으로 Claude Code를 다시 시작하고, Claude Code 프롬프트에서 `/hello-tabs`를 실행하세요. 카운트는 남겨둔 곳에 있습니다.182 Esc를 눌러 창을 닫은 다음 세션을 종료합니다. 셸에서 동일한 `claude --plugin-dir ./hello-tabs` 명령으로 Claude Code를 다시 시작하고, Claude Code 프롬프트에서 `/hello-tabs`를 실행합니다. 카운트가 마지막으로 남겨 둔 값 그대로 유지되어 있습니다.
183 183
184 카운트를 지우려면 모드가 `$.store.delete('count')`를 호출하도록 하세요. [상태 유지](#keep-state)는 각 종류의 값이 얼마나 오래 지속되는지를 다룹니다.184 카운트를 지우려면 mod가 `$.store.delete('count')`를 호출하도록 합니다. 각 종류의 값이 얼마나 오래 유지되는지는 [상태 유지하기](#keep-state)에서 다룹니다.
185 </Step>185 </Step>
186</Steps>186</Steps>
187 187
188<h2 id="pick-where-to-draw">188<h2 id="pick-where-to-draw">
189 그리기 위치 선택189 그릴 위치 선택하기
190</h2>190</h2>
191 191
192`ui.render` 훅은 원하는 위치로 좁히지 않는 한 모든 렌더 사이트에서 실행됩니다. 렌더 사이트를 선택하려면 [매처](/docs/ko/plugins/mods/events#filter-which-events-a-hook-handles)라고 불리는 필터를 `on`의 두 번째 인수로 전달합니다. `{ component: 'Pane' }`은 훅을 창에서만 실행합니다. 훅에서 `e.component`는 사이트의 이름을 지정하고, `e.surface`는 어떤 앱이 그리고 있는지 나타내며, `e.props`는 사이트 자체의 데이터를 보유합니다. 창의 경우 `e.requestId`는 열 때 사용한 `id`입니다.192`ui.render` 훅은 그리려는 렌더링 지점 하나로 범위를 좁히지 않으면 모든 렌더링 지점에서 실행됩니다. 렌더링 지점을 선택하려면 [matcher](/docs/ko/plugins/mods/events#filter-which-events-a-hook-handles)라고 하는 필터를 `on`의 두 번째 인수로 전달합니다. `{ component: 'Pane' }`은 pane에서만 훅을 실행합니다. 훅 안에서 `e.component`는 지점의 이름을, `e.surface`는 어느 앱이 그리고 있는지를 나타내며, `e.props`에는 해당 지점 고유의 데이터가 담깁니다. pane의 경우 `e.requestId`는 pane을 열 때 사용한 `id`입니다.
193 193
194두 사이트는 모드가 채울 때까지 비어 있으며, 창과 밴드입니다. 탭을 선택하여 각각이 무엇인지, 그리고 어떻게 그리는지 확인합니다:194pane과 band는 mod가 채우기 전까지 비어 있습니다. 탭을 선택하여 각각이 무엇이며 어떻게 그리는지 확인하십시오.
195 195
196<Tabs>196<Tabs>
197 <Tab title="Pane">197 <Tab title="Pane">
198 창은 넓은 전체 화면 터미널에서 대화 옆의 사이드바이거나, 그렇지 않으면 프롬프트 위의 프레임 영역입니다. 여러 창이 열려 있으면 각각 제목을 표시하는 탭을 가집니다.198 pane은 너비가 넓은 전체 화면 터미널에서는 트랜스크립트 옆의 사이드바이며, 그 외의 경우에는 프롬프트 위의 테두리가 있는 영역입니다. 여러 pane이 열려 있으면 각 pane에 제목을 표시하는 탭이 생깁니다.
199 199
200 창은 모드가 `$.ui.open`을 `id`와 함께 호출할 때 나타나며, 예를 들어 `$.ui.open({ id: 'hello-tabs' })`입니다. [올바른 시간에 창 열기](#open-a-pane-at-the-right-time)는 다른 필드와 창이 더 넓은 터미널을 기다릴 때를 다룹니다.200 pane은 mod가 직접 정한 `id`로 `$.ui.open`을 호출할 때 나타납니다. 예: `$.ui.open({ id: 'hello-tabs' })`. 다른 필드와 pane이 더 넓은 터미널을 기다리는 경우는 [적절한 시점에 pane 열기](#open-a-pane-at-the-right-time)에서 다룹니다.
201 201
202 창에 그리려면 `{ component: 'Pane' }`으로 필터링하고 `e.requestId`가 `id`인지 확인합니다.202 pane에 그리려면 `{ component: 'Pane' }`으로 필터링하고 `e.requestId`가 해당 `id`인지 확인합니다.
203 </Tab>203 </Tab>
204 204
205 <Tab title="Band above the prompt">205 <Tab title="프롬프트 위의 band">
206 밴드는 프롬프트 입력 바로 위의 스트립입니다. 항상 있으며 모든 모드가 공유합니다.206 band는 프롬프트 입력란 바로 위에 있는 띠 영역입니다. 항상 존재하며 모든 mod가 공유합니다.
207 207
208 훅은 밴드에 무언가를 표시하는 트리를 반환하거나, 아무것도 표시하지 않으려면 `next(e)`를 반환합니다. 트리는 모드 [이후의 것들](/docs/ko/plugins/mods/events#the-order-mods-run-in)이 그곳에 그리는 것을 대체합니다. 그들의 것을 유지하려면 `await next(e)`의 결과를 트리의 [`Box`](#build-a-tree-from-elements)의 자식 중에 넣습니다.208 band에 무언가를 표시하려면 훅이 트리를 반환하고, 아무것도 표시하지 않으려면 `next(e)`를 반환합니다. 트리는 [뒤에 실행되는](/docs/ko/plugins/mods/events#the-order-mods-run-in) mod가 그곳에 그리는 내용을 대체합니다. 다른 mod의 내용을 유지하려면 `await next(e)`의 결과를 트리 안 [`Box`](#build-a-tree-from-elements)의 children에 넣으십시오.
209 209
210 밴드에 그리려면 `{ component: 'AbovePrompt' }`으로 필터링합니다.210 band에 그리려면 `{ component: 'AbovePrompt' }`로 필터링합니다.
211 </Tab>211 </Tab>
212</Tabs>212</Tabs>
213 213
214<h3 id="change-what-claude-code-already-draws">214<h3 id="change-what-claude-code-already-draws">
215 Claude Code가 이미 그리는 것 변경215 Claude Code가 이미 그리는 요소 변경하기
216</h3>216</h3>
217 217
218Claude Code는 대부분의 인터페이스를 자체적으로 그립니다: 메시지, 도구 호출 행, 스피너 등. 이러한 각 부분도 렌더 사이트이므로 모드는 이를 다시 스타일링하거나 대체할 수 있습니다. 하나를 변경하려면 이 표의 이름으로 `ui.render` 훅을 필터링합니다:218Claude Code는 메시지, 도구 호출 행, 스피너 등 인터페이스의 대부분을 직접 그립니다. 이러한 각 부분도 렌더링 지점이므로 mod로 스타일을 변경하거나 대체할 수 있습니다. 하나를 변경하려면 다음 표에 있는 이름으로 `ui.render` 훅을 필터링합니다.
219 219
220| Site | What it is |220| 지점 | 설명 |
221| :- | :- |221| :- | :- |
222| `UserMessage`, `AssistantMessage` | 대화의 메시지 |222| `UserMessage`, `AssistantMessage` | 트랜스크립트의 메시지 |
223| `ToolUse`, `ToolResult`, `ToolGroup` | 도구 호출의 행, 그 결과, 그리고 접힌 호출 실행 |223| `ToolUse`, `ToolResult`, `ToolGroup` | 도구 호출의 행, 그 결과, 접힌 호출 그룹 |
224| `CommandOutput` | 명령이 인쇄한 행 |224| `CommandOutput` | 명령이 출력한 행 |
225| `AskUserQuestion` | Claude가 질문을 하기 위해 열 수 있는 대화 |225| `AskUserQuestion` | Claude가 사용자에게 질문하기 위해 여는 대화 상자 |
226| `Spinner`, `ToolProgress`, `TurnDuration` | 턴의 상태 줄: Claude가 작업하는 동안 애니메이션되는 줄, 실행 중인 도구의 실시간 진행 줄, 턴을 닫는 줄 |226| `Spinner`, `ToolProgress`, `TurnDuration` | 턴의 상태줄: Claude가 작업하는 동안 애니메이션되는 줄, 실행 중인 도구의 실시간 진행 상황 줄, 턴을 마무리하는 줄 |
227| `InfoNotice`, `SessionMode`, `PromptHint` | 로고 아래의 상태 줄, 바닥글의 모드 레이블, 프롬프트 아래의 힌트 줄 |227| `InfoNotice`, `SessionMode`, `PromptHint` | 로고 아래의 상태줄, 푸터의 모드 레이블, 프롬프트 아래의 힌트 줄 |
228 228
229Claude Code가 이미 그리는 사이트에서 훅에는 세 가지 선택이 있습니다: 세부 사항 변경, 그리기 대체, 또는 그대로 두기. 탭을 선택하여 각각을 스피너에 적용한 것을 확인합니다. 예제는 [튜토리얼 모드](/docs/ko/plugins/mods/create#write-a-mod-yourself)처럼 다른 훅이 계산하는 `calls` 변수를 읽습니다.229Claude Code가 이미 그리는 지점에서 훅은 세부 사항을 변경하거나, 그리기를 대체하거나, 그대로 둘 수 있습니다. 탭을 선택하여 각 방식을 스피너에 적용한 예를 확인하십시오. 예제는 [튜토리얼 mod](/docs/ko/plugins/mods/create#write-a-mod-yourself)에서처럼 다른 훅이 세는 `calls` 변수를 읽습니다.
230 230
231<Tabs>231<Tabs>
232 <Tab title="Change a detail">232 <Tab title="세부 사항 변경">
233 Claude Code의 그리기를 유지하고 그 일부를 변경하려면 변경된 `props`로 이벤트의 복사본을 `next`에 전달합니다. 이 훅은 스피너의 단어 뒤의 텍스트를 변경합니다:233 Claude Code의 그리기를 유지하면서 일부만 변경하려면 `props`를 변경한 이벤트 복사본을 `next`에 전달합니다. 다음 훅은 스피너 단어 뒤의 텍스트를 변경합니다.
234 234
235 ```javascript theme={null}235 ```javascript theme={null}
236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {236 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
239 })239 })
240 ```240 ```
241 241
242 스피너는 애니메이션과 단어를 유지하고 텍스트가 단어를 따릅니다:242 스피너는 애니메이션과 단어를 유지하고, 단어 뒤에 사용자가 지정한 텍스트가 이어집니다.
243 243
244 ```text theme={null}244 ```text theme={null}
245 Thinking · tool calls: 2…245 Thinking · tool calls: 2…
246 ```246 ```
247 </Tab>247 </Tab>
248 248
249 <Tab title="Replace the drawing">249 <Tab title="그리기 대체">
250 사이트 위치에 자신의 것을 그리려면 트리를 반환하고 `next`를 호출하지 마십시오. 이 훅은 스피너가 있을 위치에 한 줄의 텍스트를 그립니다:250 지점 대신 직접 만든 내용을 그리려면 트리를 반환하고 `next`를 호출하지 않습니다. 다음 훅은 스피너가 있을 자리에 텍스트 한 줄을 그립니다.
251 251
252 ```javascript theme={null}252 ```javascript theme={null}
253 on('ui.render', { component: 'Spinner' }, async ($, e) => {253 on('ui.render', { component: 'Spinner' }, async ($, e) => {
257 })257 })
258 ```258 ```
259 259
260 Claude가 작업하는 동안 줄이 표시되고 Claude Code의 스피너는 표시되지 않습니다:260 Claude가 작업하는 동안 사용자가 지정한 줄이 표시되고 Claude Code의 스피너는 표시되지 않습니다.
261 261
262 ```text theme={null}262 ```text theme={null}
263 Claude has made 2 tool calls263 Claude has made 2 tool calls
264 ```264 ```
265 </Tab>265 </Tab>
266 266
267 <Tab title="Leave it alone">267 <Tab title="그대로 두기">
268 사이트를 Claude Code가 그리는 대로 두려면 `next(e)`를 반환합니다. 훅은 종종 일부 이벤트에 대해서는 그렇게 하고 다른 이벤트에 대해서는 그렇지 않습니다. 이 훅은 계산할 호출이 있을 때까지 스피너를 그대로 둡니다:268 지점을 Claude Code가 그리는 그대로 두려면 `next(e)`를 반환합니다. 훅은 흔히 일부 이벤트에서만 이렇게 합니다. 다음 훅은 셀 호출이 생길 때까지 스피너를 그대로 둡니다.
269 269
270 ```javascript theme={null}270 ```javascript theme={null}
271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {271 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
275 })275 })
276 ```276 ```
277 277
278 첫 번째 도구 호출 전에 스피너는 모드 없이 하는 방식으로 보입니다:278 첫 번째 도구 호출 전에는 스피너가 mod가 없을 때와 똑같이 보입니다.
279 279
280 ```text theme={null}280 ```text theme={null}
281 Thinking…281 Thinking…
283 </Tab>283 </Tab>
284</Tabs>284</Tabs>
285 285
286권한 프롬프트는 렌더 사이트가 아니므로 모드는 표시되는 것을 변경할 수 없습니다. 질문 대화 `AskUserQuestion`은 하나이므로 모드는 그것을 변경할 수 있습니다.286이러한 지점에서 `next(e)`는 뒤에 실행되는 mod가 자체 트리를 반환하지 않는 한 Claude Code의 그리기에 대한 참조인 `{ type: 'engine', ref }`를 반환합니다. 그 그리기의 내용을 변경하려면 **세부 사항 변경** 탭에서처럼 prop을 다르게 한 이벤트 복사본을 `next`에 전달합니다. 참조를 그대로 반환하거나, `Box` 안에 직접 만든 요소와 나란히 배치할 수 있습니다.
287 287
288터미널과 Desktop 앱은 모두 동일한 사이트를 발생시키지 않습니다. `Pane`, `AbovePrompt`, `Spinner`, 그리고 대화 사이트는 둘 다에서 작동합니다. 몇 가지 다른 상태 줄은 터미널에서만 발생합니다. [렌더 사이트 표](/docs/ko/plugins/mods/reference#render-sites)는 각각이 발생하는 위치를 나열합니다.288```javascript theme={null}
289on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
290 const { Box, Text } = $.ui.resolve(e)
291 const theirs = await next(e)
292 return Box({ flexDirection: 'column', children: [theirs, Text({ children: ['under the spinner'] })] })
293})
294```
295
296Claude가 작업하는 동안 스피너는 이전처럼 애니메이션되고, 그 아래에 `under the spinner`가 나타납니다.
297
298권한 프롬프트는 렌더링 지점이 아니므로 mod가 표시 내용을 변경할 수 없습니다. 질문 대화 상자인 `AskUserQuestion`은 렌더링 지점이므로 mod가 변경할 수 있습니다. 대화 상자용 트리는 참조를 정확히 한 번 포함해야 하며, 직접 만든 요소는 그 위에 있어야 합니다. 그렇지 않으면 Claude Code가 자체 대화 상자를 그립니다.
299
300터미널과 Desktop 앱이 발생시키는 지점이 모두 같지는 않습니다. `Pane`, `AbovePrompt`, `Spinner` 및 트랜스크립트 지점은 양쪽 모두에서 작동합니다. 일부 다른 상태줄은 터미널에서만 발생합니다. 각 지점이 발생하는 위치는 [렌더링 지점 표](/docs/ko/plugins/mods/reference#render-sites)에 나와 있습니다.
289 301
290<h3 id="open-a-pane-at-the-right-time">302<h3 id="open-a-pane-at-the-right-time">
291 올바른 시간에 창 열기303 적절한 시점에 pane 열기
292</h3>304</h3>
293 305
294창은 모드가 열 때만 나타납니다. 어떻게 그리고 언제 열 것인지는 키보드 포커스를 받는지, 얼마나 많은 공간을 요청하는지, 그리고 좁은 터미널에서 전혀 표시되는지 여부를 결정합니다.306pane은 mod가 열 때만 나타납니다. pane을 여는 방법과 시점에 따라 키보드 포커스를 가져가는지, 얼마만큼의 공간을 요청하는지, 좁은 터미널에서 표시되는지 여부가 결정됩니다.
295 307
296창을 열려면 선택한 `id`로 [`$.ui.open`](/docs/ko/plugins/mods/reference#mods-api-methods)을 호출합니다. `id`는 창의 이름입니다: `ui.render` 훅이 확인하고, 창을 닫을 때 다시 전달합니다.308pane을 열려면 직접 정한 `id`로 [`$.ui.open`](/docs/ko/plugins/mods/reference#mods-api-methods)을 호출합니다. `id`는 pane의 이름입니다. `ui.render` 훅이 이 값을 확인하며, pane을 닫을 때도 다시 전달합니다.
297 309
298```javascript theme={null}310```javascript theme={null}
299await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })311await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
300```312```
301 313
302창을 닫으려면 열 때 사용한 `id`로 `$.ui.close`를 호출합니다:314pane을 닫으려면 열 때 사용한 `id`로 `$.ui.close`를 호출합니다.
303 315
304```javascript theme={null}316```javascript theme={null}
305await $.ui.close({ id: 'hello-tabs' })317await $.ui.close({ id: 'hello-tabs' })
306```318```
307 319
308`id` 외에도 `$.ui.open`은 다음 선택적 필드를 사용합니다:320`$.ui.open`은 `id` 외에 다음 선택적 필드를 받습니다.
309 321
310| Field | What it does |322| 필드 | 기능 |
311| :- | :- |323| :- | :- |
312| `title` | 둘 이상의 창이 열려 있을 때 창의 탭 레이블 |324| `title` | 둘 이상의 pane이 열려 있을 때 pane의 탭 레이블 |
313| `focus` | [키보드 포커스](#know-which-keys-your-mod-can-receive) 요청 |325| `focus` | [키보드 포커스](#know-which-keys-your-mod-can-receive)를 요청 |
314| `closeOnEscape` | Esc가 창을 닫게 합니다 |326| `closeOnEscape` | Esc로 pane을 닫도록 설정 |
315| `holdToasts` | 창이 닫힐 때까지 [`$.ui.toast`](/docs/ko/plugins/mods/api#show-something-without-starting-a-turn)의 작은 알림인 토스트를 유지합니다 |327| `holdToasts` | pane이 닫힐 때까지 토스트([`$.ui.toast`](/docs/ko/plugins/mods/api#show-something-without-starting-a-turn)의 작은 알림)를 보류 |
316| `rows` | 창이 프롬프트 위에 있을 때 요청할 높이입니다. 기본값은 공간의 1/3입니다. |328| `rows` | pane이 프롬프트 위에 있을 때 요청할 높이. 기본값은 공간의 3분의 1입니다. |
317| `columns` | 창이 대화 옆에 있을 때 요청할 너비입니다 |329| `columns` | pane이 트랜스크립트 옆에 있을 때 요청할 너비 |
318 330
319`focus`, `closeOnEscape`, 그리고 `holdToasts`는 선택적이며 `true`만 허용합니다. 하나를 생략하려면 이를 빼십시오. `false`를 전달하면 `ui.open: focus is true or left out`과 같은 오류가 발생합니다. 조건부로 하나를 설정하려면 조건이 유지될 때만 필드를 추가합니다. 이 호출은 `items`가 비어 있지 않을 때만 키보드 포커스를 요청합니다:331`focus`, `closeOnEscape`, `holdToasts`는 선택 사항이며 `true`만 허용합니다. 사용하지 않으려면 생략하십시오. `false`를 전달하면 `ui.open: focus is true or left out`과 같은 오류가 발생합니다. 이 중 하나를 조건부로 설정하려면 조건이 충족될 때만 필드를 추가합니다. 다음 호출은 `items`가 비어 있지 않을 때만 키보드 포커스를 요청합니다.
320 332
321```javascript theme={null}333```javascript theme={null}
322const pane = { id: 'hello-tabs', title: 'Hello tabs' }334const pane = { id: 'hello-tabs', title: 'Hello tabs' }
323await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)335await $.ui.open(items.length > 0 ? { ...pane, focus: true } : pane)
324```336```
325 337
326Claude가 작업하는 동안 명령이 창을 열 수 있도록 하려면 [명령을 등록](/docs/ko/plugins/mods/api#add-a-command)할 때 `immediate: true`를 추가합니다. 없으면 턴 중에 입력된 명령은 턴이 끝날 때까지 기다립니다.338Claude가 작업하는 동안 명령으로 pane을 열 수 있게 하려면 [명령을 등록](/docs/ko/plugins/mods/api#add-a-command)할 때 `immediate: true`를 추가합니다. 이 설정이 없으면 턴 중에 입력한 명령은 턴이 끝날 때까지 대기합니다.
327 339
328<h4 id="when-a-pane-waits-for-a-wider-terminal">340<h4 id="when-a-pane-waits-for-a-wider-terminal">
329 창이 더 넓은 터미널을 기다릴 때341 pane이 더 넓은 터미널을 기다리는 경우
330</h4>342</h4>
331 343
332모드가 요청받지 않고 열 수 있는 창은 좁은 터미널에 나타나지 않으므로 작은 화면을 차지할 수 없습니다. 나타나는지 여부는 열 수 있는 것에 따라 다릅니다:344사용자의 요청 없이 mod가 연 pane은 좁은 터미널에서 나타나지 않으므로 작은 화면을 차지할 수 없습니다. 표시 여부는 pane을 연 주체에 따라 달라집니다.
333 345
334* **사용자가 한 것**, 예를 들어 실행한 명령이나 누른 버튼과 같이 사용자가 한 것으로 열린 경우, 창은 모든 너비에서 나타납니다346* **사용자가 실행한 명령이나 누른 버튼처럼 사용자의 동작으로 열린 경우**, pane은 너비와 관계없이 나타납니다
335* **모드가 자체적으로 작동하여 열린 경우**, 예를 들어 타이머 또는 [`turn.start`](/docs/ko/plugins/mods/events#follow-a-turn) 훅에서, 창은 최소 144개 열의 터미널에서만 나타납니다. 사용자가 그 창을 직접 한 번 열었으면 110개 열이면 충분합니다.347* **타이머나 [`turn.start`](/docs/ko/plugins/mods/events#follow-a-turn) 훅처럼 mod가 스스로 동작하여 열린 경우**, pane은 너비가 144열 이상인 터미널에서만 나타납니다. 사용자가 해당 pane을 직접 한 번 연 후에는 110열이면 충분합니다.
336 348
337창이 나타나면 `$.ui.open`은 `{ isPlaced: true }`로 해결됩니다. 창이 대기 중이면 `isPlaced`는 `false`이고 `reason`은 이유를 설명하는 문자열입니다. 대기 중인 창은 사용자가 열거나 터미널을 넓힐 때 나타납니다. 창을 열지 않고 무언가를 사용할 수 있다고 말하려면 `$.ui.toast('Your message')`를 호출합니다. 이는 몇 초 후에 사라지는 작은 알림을 표시합니다.349pane이 나타나면 `$.ui.open`은 `{ isPlaced: true }`로 resolve됩니다. pane이 대기 중이면 `isPlaced`는 `false`이고 `reason`은 그 이유를 설명하는 문자열입니다. 대기 중인 pane은 사용자가 열거나 터미널 너비를 넓히면 나타납니다. pane을 열지 않고 무언가를 사용할 수 있음을 알리려면 `$.ui.toast('Your message')`를 호출하여 토스트 알림을 표시합니다.
338 350
339<h2 id="build-a-tree-from-elements">351<h2 id="build-a-tree-from-elements">
340 요소에서 트리 만들기352 요소로 트리 만들기
341</h2>353</h2>
342 354
343`ui.render` 훅이 반환하는 것은 요소 트리입니다: 상자, 텍스트, 그리고 서로 중첩된 컨트롤로 만든 그릴 것의 설명. 그리기를 설명하고, Claude Code는 터미널이나 데스크톱 앱에서 렌더링합니다.355`ui.render` 훅이 반환하는 것은 요소 트리입니다. 요소 트리는 무엇을 그릴지에 대한 설명으로, 서로 중첩된 박스, 텍스트, 컨트롤로 구성됩니다. 그릴 내용을 기술하면 Claude Code가 이를 터미널 또는 Desktop 앱에서 렌더링합니다.
344 356
345요소를 얻으려면 훅에서 `$.ui.resolve(e)`를 호출하세요. 예를 들어 `const { Box, Text, Button } = $.ui.resolve(e)`. 각 요소는 함수입니다. 속성을 전달하고, 그 안에 가는 요소와 문자열을 `children`에 넣습니다.357요소를 가져오려면 `const { Box, Text, Button } = $.ui.resolve(e)`처럼 훅에서 `$.ui.resolve(e)`를 호출합니다. 각 요소는 함수입니다. 함수에 prop을 전달하고, 그 안에 들어갈 요소와 문자열은 `children`에 넣습니다.
346 358
347대부분의 그리기는 네 가지 요소를 사용합니다. 탭을 선택하여 각각과 터미널이 어떻게 그리는지 보세요:359탭을 선택하면 가장 일반적인 각 요소와 터미널에서 그려지는 모습을 확인할 수 있습니다.
348 360
349<Tabs>361<Tabs>
350 <Tab title="텍스트">362 <Tab title="Text">
351 `Text`는 `bold`와 `color` 같은 선택적 스타일링으로 문자열을 그립니다:363 `Text`는 문자열을 그리며, `bold`, `color` 같은 스타일을 선택적으로 적용할 수 있습니다.
352 364
353 ```javascript theme={null}365 ```javascript theme={null}
354 Text({ children: ['This is the first tab.'] })366 Text({ children: ['This is the first tab.'] })
359 ```371 ```
360 </Tab>372 </Tab>
361 373
362 <Tab title="상자">374 <Tab title="Box">
363 `Box`는 그 안에 있는 것을 행이나 열로 배열합니다. 이것은 버튼과 텍스트 줄을 나란히 놓고, 두 열 떨어져 있습니다:375 `Box`는 내부 요소를 행 또는 열로 배치합니다. 다음 예시는 버튼과 텍스트 한 줄을 두 열 간격으로 나란히 배치합니다.
364 376
365 ```javascript theme={null}377 ```javascript theme={null}
366 Box({378 Box({
378 ```390 ```
379 </Tab>391 </Tab>
380 392
381 <Tab title="버튼">393 <Tab title="Button">
382 `Button`은 사용자가 누를 수 있는 컨트롤입니다. `onPress` 콜백을 실행합니다. `plain: true`로 괄호가 없고 핫키를 표시합니다:394 `Button`은 사용자가 누를 수 있는 컨트롤입니다. 누르면 `onPress` 콜백을 실행합니다. `plain: true`를 지정하면 대괄호 없이 단축키가 표시됩니다.
383 395
384 ```javascript theme={null}396 ```javascript theme={null}
385 Button({ key: 'more', label: 'Add one', onPress: addOne })397 Button({ key: 'more', label: 'Add one', onPress: addOne })
392 ```404 ```
393 </Tab>405 </Tab>
394 406
395 <Tab title="입력">407 <Tab title="Input">
396 `Input`은 텍스트 필드입니다. 사용자가 Enter를 누르면 텍스트로 `onSubmit` 콜백을 실행합니다:408 `Input`은 텍스트 필드입니다. 사용자가 Enter를 누르면 입력된 텍스트와 함께 `onSubmit` 콜백을 실행합니다.
397 409
398 ```javascript theme={null}410 ```javascript theme={null}
399 Input({411 Input({
407 ```419 ```
408 420
409 ```text theme={null}421 ```text theme={null}
410 Note: Type a note and press Enter ⏎ add422 Note: Type a note and press Enter
411 ```423 ```
412 </Tab>424 </Tab>
413</Tabs>425</Tabs>
414 426
415이 표는 모든 요소를 나열합니다:427[인터페이스 갤러리](/docs/ko/plugins/mods/gallery)에서 대부분의 요소에 대한 샘플과 스크린샷을 확인할 수 있습니다. 다음 표는 모든 요소를 나열합니다.
416 428
417| 요소 | 무엇을 그리는가 | 어디서 |429| 요소 | 그리는 내용 | 사용 위치 |
418| :- | :- | :- |430| :- | :- | :- |
419| `Box` | 플렉스 컨테이너. `flexDirection`, `columnGap`, `padding`, `borderStyle`, `width` 같은 레이아웃 속성을 사용합니다. | 모든 곳 |431| `Box` | flex 컨테이너입니다. `flexDirection`, `columnGap`, `padding`, `borderStyle`, `width` 같은 레이아웃 prop을 받습니다. | 모든 곳 |
420| `Text` | 스타일이 지정된 텍스트. `color`, `bold`, `dimColor`, `italic`, `wrap`을 사용합니다. `color`는 테마 키 또는 `'red'` 같은 색입니다. `wrap`은 `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'`, 또는 `'truncate-end'`입니다. | 모든 곳 |432| `Text` | 스타일이 적용된 텍스트입니다. `color`, `bold`, `dimColor`, `italic`, `wrap`을 받습니다. `color`는 테마 키 또는 `'red'` 같은 색상입니다. `wrap`은 `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'`, `'truncate-end'` 중 하나입니다. | 모든 곳 |
421| `Button` | `onPress`를 호출하는 컨트롤 | 모든 곳 |433| `Button` | `onPress`를 호출하는 컨트롤 | 모든 곳 |
422| `Link`, `Code`, `Markdown` | `href`와 선택적 `label`이 있는 링크, 코드 블록, Claude의 답변 방식으로 포맷된 텍스트. `Markdown`은 `children`이 아닌 `text` 속성에서 내용을 가져오고, `onLinkPress`를 전달할 때 `key`가 필요합니다. | 모든 곳 |434| `Link`, `Code`, `Markdown` | `href`와 선택적 `label`을 갖는 링크, 코드 블록, 그리고 Claude의 응답과 같은 방식으로 서식이 지정된 텍스트입니다. `Markdown`은 내용을 `children`이 아닌 `text` prop으로 받으며, `onLinkPress`를 전달할 때는 `key`가 필요합니다. | 모든 곳 |
423| `Input`, `Select` | 텍스트 필드와 선택기 | 터미널, 데스크톱 |435| `Input`, `Select` | 텍스트 필드와 드롭다운 | 터미널, Desktop |
424| `Svg` | SVG 문서 | 데스크톱 |436| `Svg` | SVG 문서 | Desktop |
425| `Client` | 두 번째 파일로 그려진 영역, 애니메이션과 포인터 입력용. 그 파일은 모드 API를 받지 않습니다. 데이터를 게시하여 훅에만 도달하며, 이는 `ui.message` 이벤트로 도착합니다. | 터미널, 데스크톱 |437| `Client` | 애니메이션과 포인터 입력을 위해 별도로 작성한 두 번째 파일이 그리는 영역입니다. 이 파일은 mod API를 사용할 수 없습니다. 훅과는 데이터를 게시하는 방식으로만 통신하며, 게시된 데이터는 `ui.message` 이벤트로 전달됩니다. | 터미널, Desktop |
426| `Raster`, `Image` | [색상 셀의 그리드](#draw-a-grid-of-colored-cells), 그리고 그림 | 터미널 |438| `Raster`, `Image` | [색상 셀 그리드](#draw-a-grid-of-colored-cells)와 이미지 | 터미널 |
427 439
428모듈이 `.tsx` 또는 `.jsx` 파일이면 트리를 JSX로 쓸 수 있습니다. `$.ui.resolve(e)`에서 요소를 분해하세요. 훅 모듈에는 요소 전역이 없기 때문입니다.440모듈이 `.tsx` 또는 `.jsx` 파일이라면 트리를 JSX로 작성할 수 있습니다. 먼저 `$.ui.resolve(e)`에서 요소를 구조 분해하십시오.
429 441
430트리가 앱이 없는 요소, 요소가 사용하지 않는 속성, 또는 아무것도 가지 않는 곳에 자식을 사용하면 Claude Code는 자신의 버전의 사이트를 그립니다.442트리에 앱에 없는 요소, 요소가 받지 않는 prop, 또는 자식이 들어갈 수 없는 곳의 자식이 사용되면 Claude Code는 해당 영역을 자체 버전으로 그립니다.
431 443
432`--plugin-dir`로 시작한 세션에서 대화 기록 줄은 그렇게 말합니다. 예를 들어 `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. [디버그 로그](/docs/ko/plugins/mods/troubleshoot#read-the-debug-log)는 `ui.render (Pane): a hook returned a tree that does not validate`로 같은 이유로 기록합니다. 세션에 다른 것도 나타나지 않으므로 그리기가 표시되지 않으면 그 줄이나 로그를 확인하세요.444`--plugin-dir`로 시작한 세션에서는 `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`과 같은 트랜스크립트 줄로 이를 알려 줍니다. [디버그 로그](/docs/ko/plugins/mods/troubleshoot#read-the-debug-log)에는 같은 사유와 함께 `ui.render (Pane): a hook returned a tree that does not validate`로 기록됩니다. 세션에는 그 밖에 아무것도 표시되지 않으므로, 그린 내용이 나타나지 않는다면 해당 줄이나 로그를 확인하십시오.
433 445
434<h3 id="draw-a-grid-of-colored-cells">446<h3 id="draw-a-grid-of-colored-cells">
435 색상 셀의 그리드 그리기447 색상 셀 그리드 그리기
436</h3>448</h3>
437 449
438열 지도, 스파크라인, 또는 터미널의 게임 보드의 경우 각 셀에 대해 하나의 `Box`가 아닌 하나의 `Raster`를 그리세요. `Raster`는 `key`, `columns`과 `rows`의 크기, 그리고 모든 셀을 하나의 문자열로 압축하는 `cells`를 사용합니다. 각 셀은 세 개의 숫자입니다: 문자의 코드 포인트, 색, 배경색. 색은 빨강, 녹색, 파랑 각각 두 자리의 16진수 숫자입니다. 예를 들어 빨강의 경우 `0xc62828`, 터미널의 기본값의 경우 `0x01000000`.450터미널에서 히트맵, 스파크라인, 게임 보드를 그리려면 셀마다 `Box`를 사용하지 말고 `Raster` 하나를 그리십시오. `Raster`는 `key`, `columns`와 `rows`로 지정하는 크기, 그리고 모든 셀을 담은 base64 문자열인 `cells`를 받습니다. 각 셀은 세 개의 숫자로 구성됩니다. 문자의 코드 포인트, 문자 색상, 배경 색상입니다. 색상은 빨간색의 `0xc62828`처럼 16진수로 표기한 24비트 RGB 값입니다. 이 범위보다 1 큰 값인 `0x01000000`은 터미널의 기본 색상을 의미합니다.
439 451
440데스크톱 앱에는 `Raster`가 없으므로 `e.surface`를 확인하고 거기에 텍스트를 그리세요. 이 창 본문은 3x2 열 지도를 그립니다:452Desktop 앱에는 `Raster`가 없으므로 `e.surface`를 확인하여 그곳에서는 텍스트를 그리십시오. 다음 창 본문은 3×2 히트맵을 그립니다.
441 453
442```javascript theme={null}454```javascript theme={null}
443// "터미널의 기본 색을 사용"을 의미하는 값455// The value that means "use the terminal's default color"
444const DEFAULT_COLOR = 0x01000000456const DEFAULT_COLOR = 0x01000000
445 457
446// [문자, 색] 쌍의 행을 Raster가 사용하는 하나의 문자열로 압축458// Pack rows of [character, color] pairs into the one string a Raster takes
447// 한 셀은 세 개의 숫자: 문자의 코드 포인트, 색, 배경색459// One cell is three numbers: the character's code point, its color, and its background
448function cellsOf(rows) {460function cellsOf(rows) {
449 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])461 const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
450 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()462 return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
451}463}
452 464
453on('ui.render', { component: 'Pane' }, async ($, e, next) => {465on('ui.render', { component: 'Pane' }, async ($, e, next) => {
454 // id가 'heat'인 창에서만 그리기466 // Draw only in the pane opened with the id 'heat'
455 if (e.requestId !== 'heat') return next(e)467 if (e.requestId !== 'heat') return next(e)
456 const { Box, Text, Raster } = $.ui.resolve(e)468 const { Box, Text, Raster } = $.ui.resolve(e)
457 // 3개의 셀 각각이 블록 문자와 색인 2행469 // Two rows of three cells, each a block character and its color
458 const rows = [470 const rows = [
459 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],471 [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
460 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],472 [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
469})481})
470```482```
471 483
472터미널에서 창은 그리드를 표시합니다:484터미널에서는 창에 그리드가 표시됩니다.
473 485
474<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-heat-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=b91bcce3bad74bc851149133d4acc5d5" alt="색상 블록의 작은 그리드를 보유하는 터미널의 창, 2행 3개. 맨 위 행은 녹색, 호박색, 빨강입니다. 아래 행은 녹색, 녹색, 호박색입니다." width="360" height="132" data-path="images/mods-heat-map.svg" />486<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-heat-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=b91bcce3bad74bc851149133d4acc5d5" alt="터미널의 창에 색상 블록으로 된 작은 그리드가 두 행, 세 열로 표시되어 있습니다. 위쪽 행은 초록색, 호박색, 빨간색이고, 아래쪽 행은 초록색, 초록색, 호박색입니다." width="360" height="132" data-path="images/mods-heat-map.svg" />
475 487
476`rows` 배열은 변경할 부분이고, `cellsOf`는 그것을 압축된 문자열로 변환합니다. 훅은 `id`가 `heat`인 창에서만 그리므로 [`hello-tabs` 예제](#build-a-pane-with-tabs)가 창을 열 때처럼 명령에서 `$.ui.open({ id: 'heat' })`로 하나를 열어야 합니다.488변경할 부분은 `rows` 배열이며, `cellsOf`가 이를 압축된 문자열로 변환합니다. 이 훅은 `id`가 `heat`인 창에서만 그리므로, [`hello-tabs` 예시](#build-a-pane-with-tabs)가 창을 여는 것처럼 명령에서 `$.ui.open({ id: 'heat' })`로 창을 여십시오.
477 489
478각 문자는 한 셀 너비여야 합니다. 이미 화면에 있는 `Raster`를 애니메이션하려면 창의 `id`를 `requestId`로, `Raster`의 `key`를 `key`로, 같은 크기, 그리고 새로운 셀로 `$.ui.blit`을 호출하세요. 이 예제의 경우 `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`입니다. 그것은 `ui.render` 훅을 다시 실행하지 않고 그 하나의 요소를 다시 칠합니다.490각 문자는 한 셀 너비여야 합니다. 이미 화면에 있는 `Raster`에 애니메이션을 적용하려면 창의 `id`를 `requestId`로, `Raster`의 `key`, 동일한 크기, 새 셀을 지정하여 `$.ui.blit`을 호출하십시오. 이 예시에서는 `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`입니다. 이 호출은 `ui.render` 훅을 다시 실행하지 않고 해당 요소 하나만 다시 그립니다.
479 491
480<h2 id="respond-to-presses-and-typing">492<h2 id="respond-to-presses-and-typing">
481 누름과 입력에 응답493 누르기와 입력에 응답하기
482</h2>494</h2>
483 495
484사용자가 버튼을 누르거나, 필드에 입력하거나, 모드가 그린 목록에서 선택하면 Claude Code는 해당 컨트롤에 준 함수를 호출하고, 모듈에서 실행됩니다. 각 컨트롤은 자신의 콜백을 사용합니다:496사용자가 mod가 그린 버튼을 누르거나, 필드에 입력하거나, 목록에서 항목을 고르면 Claude Code는 해당 컨트롤의 콜백을 호출하며, 이 콜백은 모듈에서 실행됩니다. 각 컨트롤은 자체 콜백을 받습니다.
485 497
486* **`Button`**: `onPress(e)`를 사용합니다. 여기서 `e.surface`는 누름이 온 앱입니다498* **`Button`**: `onPress(e)`를 받으며, 여기서 `e.surface`는 누르기가 발생한 앱입니다
487* **`Input`**: `onSubmit(value)`와 `onInput(value)`를 사용합니다499* **`Input`**: `onSubmit(value)`와 `onInput(value)`를 받습니다
488* **`Select`**: `onSelect(value)`를 사용합니다. 선택지는 `options`에 있으며, 고유한 값을 가진 최소 하나의 선택지 목록입니다. 예를 들어 `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`500* **`Select`**: `options`에 선택지를 담은 `onSelect(value)`를 받습니다. `options`는 고유한 값을 가진 선택지를 하나 이상 포함하는 목록이며, 예를 들어 `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`와 같습니다
489 501
490테스트는 `key`로 컨트롤을 누르거나 입력하므로 각각에 하나를 주세요. 컨트롤의 각 사용은 또한 [`ui.press`, `ui.input`, 또는 `ui.select`](/docs/ko/plugins/mods/reference#interface)를 `e.element`의 `key`로 발생시키고, 다른 모드는 이러한 이벤트를 훅할 수 있습니다. 그 훅은 콜백 전에 실행되므로 사용자가 `Input`에 입력하는 것을 보고 변경하거나 콜백 대신 답할 수 있습니다. 모드 API에는 다른 모드의 버튼을 누르는 메서드가 없습니다.502테스트는 `key`를 통해 컨트롤을 누르거나 컨트롤에 입력하므로 각 컨트롤에 `key`를 지정합니다. 또한 컨트롤을 사용할 때마다 `e.element`에 `key`를 담아 [`ui.press`, `ui.input` 또는 `ui.select`](/docs/ko/plugins/mods/reference#interface)가 발생하며, 다른 mod가 이러한 이벤트를 처리할 수 있습니다. 해당 훅은 콜백보다 먼저 실행되므로 사용자가 `Input`에 입력하는 내용을 볼 수 있고, 이를 변경하거나 콜백 대신 응답할 수 있습니다. mods API에는 다른 mod의 버튼을 누르는 메서드가 없습니다.
491 503
492<h3 id="know-which-keys-your-mod-can-receive">504<h3 id="know-which-keys-your-mod-can-receive">
493 키보드 포커스와 핫키505 키보드 포커스와 단축키
494</h3>506</h3>
495 507
496모드는 절대 키보드를 직접 읽지 않습니다. 사용자가 키를 누르고, Claude Code는 어느 컨트롤이 그것인지 결정하고, 그 컨트롤의 콜백이 실행됩니다. [밴드의 숫자 핫키](/docs/ko/plugins/mods/reference#elements)를 제외하고, 이는 창이나 밴드가 키보드 포커스를 가질 때만 발생합니다. 나머지 시간에는 키가 프롬프트로 갑니다.508mod는 키보드를 직접 읽지 않습니다. 사용자가 키를 누르면 Claude Code가 그 키가 어느 컨트롤을 위한 것인지 결정하고, 해당 컨트롤의 콜백이 실행됩니다. [밴드의 숫자 단축키](/docs/ko/plugins/mods/reference#elements)를 제외하면 이는 창이나 밴드에 키보드 포커스가 있는 동안에만 일어납니다. 그 외에는 키가 프롬프트로 전달됩니다.
497 509
498<h4 id="how-a-pane-gets-keyboard-focus">510<h4 id="how-a-pane-gets-keyboard-focus">
499 창이 키보드 포커스를 얻는 방법511 창이 키보드 포커스를 얻는 방법
500</h4>512</h4>
501 513
502창은 세 가지 방법 중 하나로 키보드 포커스를 얻습니다:514창은 다음과 같은 경우 키보드 포커스를 얻습니다.
503 515
504* 모드가 명령이나 누름에서 `focus: true`로 열기516* mod가 명령이나 누르기에서 `focus: true`로 창을 여는 경우
505* 사용자가 Ctrl+X를 누른 다음 Tab517* 사용자가 Ctrl+X를 누른 다음 Tab을 누르는 경우
506* 사용자가 클릭518* 사용자가 창을 클릭하는 경우
507 519
508Claude Code는 프롬프트가 비어 있고 다른 것이 키보드 포커스를 가지지 않을 때만 `focus: true`를 부여합니다. 사용자가 입력하는 동안 열리는 창은 그들의 키 입력을 가져가지 않습니다.520Claude Code는 프롬프트가 비어 있고 다른 어떤 것도 키보드 포커스를 갖고 있지 않을 때만 `focus: true`를 허용합니다. 사용자가 입력하는 중에 열리는 창은 사용자의 키 입력을 가져가지 않습니다.
509 521
510<h4 id="what-each-key-does">522<h4 id="what-each-key-does">
511 각 키가 하는 것523 각 키의 동작
512</h4>524</h4>
513 525
514이 표는 창이나 밴드가 키보드 포커스를 가질 때 키가 하는 것을 나열합니다:526다음 표는 창이나 밴드에 키보드 포커스가 있는 동안 각 키가 수행하는 동작을 보여 줍니다.
515 527
516| 키 | 무엇을 하는가 |528| 키 | 동작 |
517| :- | :- |529| :- | :- |
518| Tab | 다음 컨트롤로 이동 |530| Tab | 다음 컨트롤로 이동합니다 |
519| 위 및 아래 | 그리기가 맞을 때 컨트롤 간 이동. 창이나 밴드가 표시할 수 있는 것보다 더 많은 행을 가지면 스크롤합니다. |531| Up 및 Down | 그린 내용이 들어맞는 동안에는 컨트롤 사이를 이동합니다. 창이나 밴드에 표시할 수 있는 것보다 많은 행이 있으면 스크롤합니다. |
520| Enter | 포커스된 `Button`을 누르거나, 포커스된 `Input`을 제출하거나, `Select`에서 선택 |532| Enter | 포커스된 `Button`을 누르거나, 포커스된 `Input`을 제출하거나, `Select`에서 항목을 고릅니다 |
521| 버튼의 핫키 | 그 버튼을 누릅니다. `Input`이 포커스를 가질 때 모든 인쇄 가능한 키는 필드로 갑니다. |533| 버튼의 단축키 | 해당 버튼을 누릅니다. `Input`에 포커스가 있는 동안에는 출력 가능한 모든 키가 필드로 전달됩니다. |
522| Esc | 키보드 포커스를 프롬프트로 반환합니다. `closeOnEscape: true`로 창도 닫습니다. |534| Esc | 키보드 포커스를 프롬프트로 되돌립니다. `closeOnEscape: true`를 지정하면 창도 닫습니다. |
523 535
524모드는 Tab이나 화살표 키를 다른 것에 바인드할 수 없으므로 게임은 `w`, `a`, `s`, `d`로 조종합니다.536mod는 Tab이나 화살표 키를 다른 동작에 바인딩할 수 없으므로, 게임은 `w`, `a`, `s`, `d`로 조작합니다.
525 537
526<h4 id="set-a-hotkey-and-the-first-focus">538<h4 id="set-a-hotkey-and-the-first-focus">
527 핫키와 첫 포커스 설정539 단축키와 첫 포커스 설정하기
528</h4>540</h4>
529 541
530컨트롤의 두 속성이 키보드가 어떻게 도달하는지 결정합니다:542컨트롤의 다음 prop은 키보드가 해당 컨트롤에 도달하는 방식을 결정합니다.
531 543
532* **`hotkey`**: 사용자가 `Button`을 한 키로 누르도록 하려면 `hotkey: 'a'`처럼 한 자리 또는 한 소문자의 `hotkey`를 주세요544* **`hotkey`**: 사용자가 키 하나로 `Button`을 누를 수 있게 하려면 `hotkey: 'a'`처럼 숫자 하나 또는 소문자 하나로 된 `hotkey`를 지정합니다
533* **`autoFocus`**: 창이 열릴 때 어느 컨트롤이 포커스를 가지는지 선택하려면 `autoFocus: true`를 추가하세요. 다른 것에서는 속성을 생략하세요. Claude Code는 `autoFocus: false`를 거부합니다.545* **`autoFocus`**: 창이 열릴 때 어느 컨트롤에 포커스를 둘지 선택하려면 해당 컨트롤에 `autoFocus: true`를 추가합니다. 이 prop은 `true`만 허용하므로 다른 컨트롤에서는 생략합니다.
534 546
535핫키가 표시되는 방식은 버튼과 앱에 따라 다릅니다:547단축키가 표시되는 방식은 버튼과 앱에 따라 다릅니다.
536 548
537| 버튼 | 터미널에서 | 데스크톱 앱에서 |549| 버튼 | 터미널에서 | Desktop 앱에서 |
538| :- | :- | :- |550| :- | :- | :- |
539| 괄호 포함, 기본값 | `[ Add one ]`, 핫키 표시 없음 | 옆에 작은 키가 있는 레이블 |551| 대괄호 포함(기본값) | `[ Add one ]`, 단축키는 표시되지 않음 | 레이블 옆에 작은 키가 표시됨 |
540| `plain: true` 포함 | `1: One` | 옆에 작은 키가 있는 레이블 |552| `plain: true` 사용 | `1: One` | 레이블 옆에 작은 키가 표시됨 |
541 553
542터미널에서 괄호가 있는 버튼의 레이블에 키의 이름을 지정하거나 `plain: true`를 사용하여 사용자가 누를 것을 볼 수 있도록 하세요. [요소 참조](/docs/ko/plugins/mods/reference#elements)는 다른 `Button` 규칙을 가집니다: `action`, 밴드의 숫자 핫키, 그리고 한 핫키의 두 버튼.554터미널에서는 사용자가 무엇을 눌러야 하는지 알 수 있도록 대괄호 버튼의 레이블에 키 이름을 넣거나 `plain: true`를 사용합니다. [요소 레퍼런스](/docs/ko/plugins/mods/reference#elements)에서 `action`, 밴드의 숫자 단축키, 하나의 단축키에 연결된 두 버튼 등 `Button`의 다른 규칙을 확인할 수 있습니다.
543 555
544<h3 id="take-typed-input-and-draw-a-row-for-each-item">556<h3 id="take-typed-input-and-draw-a-row-for-each-item">
545 입력된 텍스트를 가져오고 각 항목에 대해 행을 그리기557 입력을 받고 항목마다 행 그리기
546</h3>558</h3>
547 559
548많은 창은 텍스트 필드와 그 아래 목록입니다. 이 섹션의 예제는 노트 창입니다: 노트를 입력하고 Enter를 눌러 추가하고, 각 노트에는 삭제하는 `x` 버튼이 있습니다. 두 개의 노트가 추가되면 터미널은 창을 이렇게 그립니다:560많은 창은 텍스트 필드 아래에 목록이 있는 형태입니다. 이 섹션의 예시는 메모 창입니다. 메모를 입력하고 Enter를 눌러 추가하며, 각 메모에는 메모를 삭제하는 `x` 버튼이 있습니다. 메모 두 개를 추가하면 터미널은 창을 다음과 같이 그립니다.
549 561
550```text theme={null}562```text theme={null}
551╭──────────────────────────────────────────────────────────╮563╭──────────────────────────────────────────────────────────╮
555╰──────────────────────────────────────────────────────────╯567╰──────────────────────────────────────────────────────────╯
556```568```
557 569
558예제는 두 가지 기술을 사용합니다:570이 예시는 다음 기법을 사용합니다.
559 571
560* **입력된 텍스트 가져오기**: `Input`은 사용자가 Enter를 누르면 필드의 텍스트로 `onSubmit(value)`를 호출하고, 모든 변경에 `onInput(value)`를 호출합니다572* **입력 받기**: `Input`은 사용자가 Enter를 누르면 필드의 텍스트로 `onSubmit(value)`를 호출하고, 변경이 있을 때마다 `onInput(value)`를 호출합니다
561* **목록 그리기**: 데이터를 각각 하나의 행으로 매핑하고, 모든 행의 버튼에 자신의 `key`를 주세요573* **목록 그리기**: 데이터를 항목마다 한 행으로 매핑하고, 모든 행의 버튼에 고유한 `key`를 지정합니다
562 574
563이 훅은 창의 내용을 그립니다:575다음 훅은 창의 내용을 그립니다.
564 576
565```javascript theme={null}577```javascript theme={null}
566// 창이 그리는 목록578// The list the pane draws
567let notes = []579let notes = []
568 580
569on('ui.render', { component: 'Pane' }, async ($, e, next) => {581on('ui.render', { component: 'Pane' }, async ($, e, next) => {
570 // id가 'notes'인 창에서만 그리기582 // Draw only in the pane opened with the id 'notes'
571 if (e.requestId !== 'notes') return next(e)583 if (e.requestId !== 'notes') return next(e)
572 const { Box, Text, Button, Input } = $.ui.resolve(e)584 const { Box, Text, Button, Input } = $.ui.resolve(e)
573 const redraw = () => $.ui.invalidate('ui.render')585 const redraw = () => $.ui.invalidate('ui.render')
579 key: 'new-note',591 key: 'new-note',
580 label: 'Note',592 label: 'Note',
581 placeholder: 'Type a note and press Enter',593 placeholder: 'Type a note and press Enter',
582 // 매번 필드를 비워서 그리기, 제출 후 지우기594 // Draw the field empty each time, which clears it after a submit
583 value: '',595 value: '',
584 submitLabel: 'add',596 submitLabel: 'add',
585 autoFocus: true,597 autoFocus: true,
586 // 필드에서 Enter를 누를 때 실행598 // Runs when you press Enter in the field
587 onSubmit: async (value) => {599 onSubmit: async (value) => {
588 // 빈 줄 무시600 // Ignore an empty line
589 if (!value.trim()) return601 if (!value.trim()) return
590 notes = [...notes, value.trim()]602 notes = [...notes, value.trim()]
591 redraw()603 redraw()
592 await $.store.set('notes', notes)604 await $.store.set('notes', notes)
593 },605 },
594 }),606 }),
595 // 각 노트에 대해 한 행: 삭제 버튼, 그 다음 노트의 텍스트607 // One row for each note: a delete button, then the note's text
596 ...notes.map((note, i) =>608 ...notes.map((note, i) =>
597 Box({609 Box({
598 flexDirection: 'row',610 flexDirection: 'row',
599 columnGap: 1,611 columnGap: 1,
600 children: [612 children: [
601 Button({613 Button({
602 // 자신의 키, 각 행의 버튼을 구별할 수 있도록614 // A key of its own, so each row's button can be told apart
603 key: 'delete-' + i,615 key: 'delete-' + i,
604 label: 'x',616 label: 'x',
605 plain: true,617 plain: true,
618})630})
619```631```
620 632
621창을 시도하려면:633창을 사용해 보려면 다음을 수행합니다.
622 634
623* **노트 추가**: 줄을 입력하고 Enter를 누르세요. 줄이 새 행으로 나타나고 필드가 비워집니다.635* **메모 추가**: 한 줄을 입력하고 Enter를 누릅니다. 입력한 줄이 새 행으로 나타나고 필드가 비워집니다.
624* **노트 삭제**: Tab을 누르다가 노트의 `x` 버튼이 포커스를 가질 때까지, 그 다음 Enter를 누르세요. `x`는 버튼의 레이블이고 핫키가 아니므로 문자를 입력해도 누르지 않습니다.636* **메모 삭제**: 메모의 `x` 버튼에 포커스가 갈 때까지 Tab을 누른 다음 Enter를 누릅니다. `x`는 버튼의 레이블이며 단축키가 아니므로, 해당 문자를 입력해도 버튼이 눌리지 않습니다.
625 637
626각 변경은 `hello-tabs`와 같은 렌더 사이클을 따릅니다: 콜백이 `notes`를 변경하고, `redraw`를 호출하고, 목록을 `$.store`에 저장합니다.638각 변경은 `hello-tabs`와 동일한 렌더링 주기를 따릅니다. 콜백이 `notes`를 변경하고, `redraw`를 호출하고, 목록을 `$.store`에 저장합니다.
627 639
628필드는 `value` 속성 때문에 각 제출 후 비워집니다. `value`는 필드가 그려질 때 보유하는 텍스트이고, 사용자의 입력은 훅이 필드를 다시 그릴 때까지 그것을 대체합니다. 예제는 항상 필드를 `''`로 그립니다.640제출할 때마다 필드가 비워지는 것은 `value` prop 때문입니다. `value`는 필드를 그릴 때 필드에 담기는 텍스트이며, 훅이 필드를 다시 그릴 때까지는 사용자의 입력이 이를 대체합니다. 이 예시는 항상 `''`로 필드를 그립니다.
629 641
630예제는 노트를 저장하고 로드하지 않습니다. 다음 세션에서 그들을 다시 가져오려면 `hello-tabs`가 `count`를 읽는 방식처럼 `session.start` 훅에서 읽으세요.642이 예시는 메모를 저장하지만 불러오지는 않습니다. 다음 세션에서 메모를 다시 불러오려면 `hello-tabs`가 `count`를 읽는 방식처럼 `session.start` 훅에서 메모를 읽습니다.
631 643
632세 가지 속성이 필드의 줄을 구성합니다. `Note: Type a note and press Enter ⏎ add`:644다음 prop이 필드의 줄 `Note: Type a note and press Enter ⏎ add`를 구성합니다.
633 645
634| 속성 | 예제에서 | 무엇인가 |646| Prop | 예시의 값 | 설명 |
635| :- | :- | :- |647| :- | :- | :- |
636| `label` | `Note` | 필드 앞의 텍스트. 터미널은 그 뒤에 `: `를 그립니다. |648| `label` | `Note` | 필드 앞의 텍스트입니다. 터미널은 그 뒤에 `: `를 그립니다. |
637| `placeholder` | `Type a note and press Enter` | 필드가 비어 있는 동안 표시되는 흐린 텍스트 |649| `placeholder` | `Type a note and press Enter` | 필드가 비어 있는 동안 표시되는 흐린 텍스트입니다 |
638| `submitLabel` | `add` | `⏎` 뒤의 단어로 Enter가 하는 것을 말합니다 |650| `submitLabel` | `add` | `⏎` 뒤에 오는 단어로, Enter가 수행하는 동작을 나타냅니다 |
639 651
640`Input`을 제출해도 [`$.prompt.submit`](/docs/ko/plugins/mods/api#start-a-turn-from-a-background-job)을 호출하지 않으면 턴을 시작하지 않습니다.652콜백이 [`$.prompt.submit`](/docs/ko/plugins/mods/api#start-a-turn-from-a-background-job)을 호출하지 않는 한, `Input`을 제출해도 턴이 시작되지 않습니다.
641 653
642<h2 id="redraw-when-something-changes">654<h2 id="redraw-when-something-changes">
643 사이트 다시 그리기655 사이트 다시 그리기
644</h2>656</h2>
645 657
646그리기는 스냅샷입니다: 훅이 마지막으로 실행되었을 때 `ui.render` 훅이 반환한 것을 보여줍니다. 새로운 것을 표시하려면 훅이 다시 실행되어야 합니다. Claude Code는 일부 변경에 대해 다시 실행하고, 모드는 나머지를 요청합니다.658그리기는 스냅샷입니다. 즉, `ui.render` 훅이 마지막으로 실행되었을 때 반환한 내용을 보여 줍니다. 새로운 내용을 표시하려면 훅이 다시 실행되어야 합니다. Claude Code는 일부 변경에 대해서는 훅을 다시 실행하며, 나머지 경우에는 mod가 다시 그리기를 요청합니다.
647 659
648<h3 id="when-claude-code-redraws-without-being-asked">660<h3 id="when-claude-code-redraws-without-being-asked">
649 Claude Code가 요청받지 않고 다시 그릴 때661 요청 없이 Claude Code가 다시 그리는 경우
650</h3>662</h3>
651 663
652Claude Code는 사이트의 속성이 변경되거나 터미널의 너비가 변경될 때 `ui.render` 훅을 다시 실행합니다. 타이머에서 훅을 실행하지 않고, 모듈의 변수가 변경되는 것을 알 수 없습니다.664Claude Code는 사이트의 prop이 변경되거나 터미널의 너비가 변경되면 `ui.render` 훅을 다시 실행합니다. 타이머에 따라 훅을 실행하지는 않으며, 모듈 안의 변수가 변경되는 시점을 알 수도 없습니다.
653 665
654<h3 id="redraw-when-your-data-changes">666<h3 id="redraw-when-your-data-changes">
655 데이터가 변경될 때 다시 그리기667 데이터가 변경될 때 다시 그리기
656</h3>668</h3>
657 669
658데이터가 변경된 후 사이트를 다시 그리려면 `$.ui.invalidate('ui.render')`를 호출하세요. 이 창은 누름을 세습니다. 버튼의 콜백은 `count`를 변경한 다음 다시 그리기를 요청합니다:670자체 데이터가 변경된 후 사이트를 다시 그리려면 `$.ui.invalidate('ui.render')`를 호출합니다. 다음 창은 누른 횟수를 셉니다. 버튼의 콜백은 `count`를 변경한 다음 다시 그리기를 요청합니다.
659 671
660```javascript theme={null}672```javascript theme={null}
661let count = 0673let count = 0
672 label: 'Add one',684 label: 'Add one',
673 onPress: () => {685 onPress: () => {
674 count += 1686 count += 1
675 // 데이터가 변경되었으므로 Claude Code에 창을 다시 그리도록 요청687 // The data changed, so ask Claude Code to draw the pane again
676 $.ui.invalidate('ui.render')688 $.ui.invalidate('ui.render')
677 },689 },
678 }),690 }),
682})694})
683```695```
684 696
685각 누름은 창의 숫자를 올립니다. [`hello-tabs` 예제](#build-a-pane-with-tabs)는 같은 호출을 `redraw` 함수로 래핑합니다.697버튼을 누를 때마다 창의 숫자가 올라갑니다. [`hello-tabs` 예제](#build-a-pane-with-tabs)는 동일한 호출을 `redraw` 함수로 감싸고 있습니다.
686 698
687[`$.state`](#keep-a-value-in-\$-state)에 보관하는 값은 호출이 필요하지 않습니다. 값을 쓰면 그것을 읽는 사이트가 다시 그려지기 때문입니다.699[`$.state`](#keep-a-value-in-\$-state)에 보관하는 값은 이 호출이 필요하지 않습니다. 값을 쓰면 해당 값을 읽는 사이트가 다시 그려지기 때문입니다.
688 700
689<h3 id="redraw-on-a-timer">701<h3 id="redraw-on-a-timer">
690 타이머에서 다시 그리기702 타이머에 따라 다시 그리기
691</h3>703</h3>
692 704
693시계, 카운트다운, 또는 세션 외부의 값을 최신 상태로 유지하려면 일정에 따라 다시 그리세요. 모듈의 `session.start` 훅에서 타이머를 시작하세요. 모듈이 이미 하나를 가지고 있으면, `hello-tabs`처럼, [`$.clock.every`](/docs/ko/plugins/mods/api#run-work-in-the-background) 줄을 추가하세요:705시계, 카운트다운 또는 세션 외부의 값을 최신 상태로 유지하려면 일정에 따라 다시 그립니다. 모듈의 `session.start` 훅에서 타이머를 시작합니다. `hello-tabs`처럼 모듈에 이미 해당 훅이 있다면 [`$.clock.every`](/docs/ko/plugins/mods/api#run-work-in-the-background) 줄을 추가합니다.
694 706
695```javascript theme={null}707```javascript theme={null}
696on('session.start', async ($, e, next) => {708on('session.start', async ($, e, next) => {
697 // 1000밀리초마다 Claude Code에 사이트를 다시 그리도록 요청709 // Every 1000 milliseconds, ask Claude Code to draw your sites again
698 $.clock.every(1000, () => $.ui.invalidate('ui.render'))710 $.clock.every(1000, () => $.ui.invalidate('ui.render'))
699 return next(e)711 return next(e)
700})712})
701```713```
702 714
703Claude Code는 이제 `ui.render` 훅을 초당 한 번 실행합니다. 모듈이 다시 로드되면 타이머가 중지되고, 새 복사본이 자신의 것을 시작합니다.715이제 Claude Code는 `ui.render` 훅을 1초에 한 번씩 실행합니다. 타이머는 모듈이 다시 로드되면 중지되며, 모듈의 새 인스턴스가 자체 타이머를 시작합니다.
704 716
705<h3 id="how-often-a-site-can-redraw">717<h3 id="how-often-a-site-can-redraw">
706 사이트가 얼마나 자주 다시 그릴 수 있는가718 사이트를 다시 그릴 수 있는 빈도
707</h3>719</h3>
708 720
709Claude Code는 사이트가 얼마나 자주 다시 그려지는지 제한하므로 모드는 데이터가 변경될 때마다 `$.ui.invalidate`를 호출할 수 있습니다. 보이는 창과 밴드는 다른 사이트보다 높은 제한을 가지고, [제한 표](/docs/ko/plugins/mods/reference#limits)는 숫자를 가집니다.721Claude Code는 사이트의 다시 그리기를 제한하므로, mod는 데이터가 변경될 때마다 `$.ui.invalidate`를 호출해도 됩니다. 각 사이트를 다시 그릴 수 있는 빈도는 [제한 표](/docs/ko/plugins/mods/reference#limits)를 참조하세요.
710 722
711제한보다 빠르게 오는 호출은 하나의 다시 그리기로 결합됩니다. 그 다시 그리기는 훅을 한 번 실행하고, 훅은 그 순간의 데이터를 읽으므로 최신 값이 표시되고 그 사이의 값은 표시되지 않습니다. 애니메이션은 제한보다 빠르게 실행될 수 없습니다.723제한보다 빠르게 들어오는 호출은 하나의 다시 그리기로 병합됩니다. 이 다시 그리기는 훅을 한 번 실행하며, 훅은 그 시점의 데이터를 읽으므로 최신 값이 표시되고 그 사이의 값은 표시되지 않습니다. 애니메이션은 제한보다 빠르게 실행될 수 없습니다.
712 724
713<h2 id="keep-state">725<h2 id="keep-state">
714 상태 유지726 상태 유지
715</h2>727</h2>
716 728
717모드는 값을 유지하는 세 곳이 있고, 값이 얼마나 오래 지속되는지에 따라 다릅니다: 모듈이 다시 로드될 때까지, 세션이 끝날 때까지, 또는 한 세션에서 다음 세션까지. 값이 얼마나 오래 지속되어야 하는지에 따라 선택하세요:729mod가 값을 어디에 보관하는지에 따라 그 값이 유지되는 기간이 결정됩니다. 모듈이 다시 로드될 때까지, 세션이 끝날 때까지, 또는 한 세션에서 다음 세션까지 유지될 수 있습니다. 값이 유지되어야 하는 기간에 따라 선택합니다.
718 730
719| 여기에 유지 | 지속되는 기간 | 사용 대상 |731| 보관 위치 | 유지 기간 | 용도 |
720| :- | :- | :- |732| :- | :- | :- |
721| 모듈 수준 변수 | 모듈이 다시 로드될 때까지. 개발 중에 파일을 저장할 때마다 발생 | `hello-tabs`의 `tab`처럼 잃을 수 있는 값 |733| 모듈 수준 변수 | 모듈이 다시 로드될 때까지. 개발 중에는 파일을 저장할 때마다 다시 로드됩니다 | `hello-tabs`의 `tab`처럼 잃어도 되는 값 |
722| `$.state` | 세션이 끝나거나 사용자가 `/clear`, `/resume`, 또는 `/branch`를 실행할 때까지 | 그리기가 의존하는 값으로 다시 로드를 생존해야 함 |734| `$.state` | 세션이 끝나거나 사용자가 `/clear`, `/resume`, `/branch`를 실행할 때까지 | 그리기에 사용되며 다시 로드된 후에도 유지되어야 하는 값 |
723| `$.store` | 모드가 삭제하거나, 세션이 [`cleanupPeriodDays`](/docs/ko/settings-reference#cleanupperioddays) 동안 저장소를 읽거나 쓰지 않을 때까지. 저장소는 `~/.claude/plugins/store/` 아래 플러그인 자신의 JSON 파일로 저장되는 키-값 저장소입니다. | 설정, 기록, 사용자가 다음에 찾을 것으로 예상하는 모든 것 |735| `$.store` | mod가 삭제하거나, [`cleanupPeriodDays`](/docs/ko/settings-reference#cleanupperioddays) 동안 어떤 세션도 스토어를 읽거나 쓰지 않을 때까지. 스토어는 키-값 스토어이며, `~/.claude/plugins/store/` 아래에 플러그인 전용 JSON 파일로 저장됩니다. | 설정, 기록 등 사용자가 다음에도 찾을 수 있기를 기대하는 모든 것 |
724 736
725`$.store.get(key)`는 값 또는 `undefined`로 해결되고, `$.store.set(key, value)`는 모든 JSON 값을 사용합니다.737`$.store.get(key)`는 값 또는 `undefined`로 resolve되며, `$.store.set(key, value)`는 모든 JSON 값을 받습니다.
726 738
727<h3 id="keep-a-value-in-state">739<h3 id="keep-a-value-in-$-state">
728 `$.state`에 값 유지740 `$.state`에 값 보관하기
729</h3>741</h3>
730 742
731`$.state`는 세션 길이의 값을 유지하고 당신을 위해 다시 그립니다. 이는 반응형 상태입니다: 값을 읽는 `ui.render` 훅이 그것을 구독하므로 Claude Code는 값을 쓸 때마다 그 사이트를 다시 그리고, `$.ui.invalidate`를 호출할 필요가 없습니다. `$.state`의 값은 또한 변수가 하지 않는 모듈의 다시 로드를 생존합니다.743`$.state`는 세션 동안 값을 보관하며, 자동으로 다시 그려 줍니다. 이는 반응형 상태입니다. 값을 읽는 `ui.render` 훅은 해당 값을 구독하므로, 값을 쓸 때마다 Claude Code가 해당 사이트를 다시 그리며 `$.ui.invalidate`를 호출할 필요가 없습니다. 또한 `$.state`에 있는 값은 모듈이 다시 로드되어도 유지되지만, 변수는 그렇지 않습니다.
732 744
733설정하려면 값을 선언하고, 매니페스트를 선언으로 가리키고, 각 값을 정의하고 사용하세요. 예제는 `hello-tabs`에서 `count`를 `$.state`로 이동합니다.745설정하려면 값을 선언하고, 매니페스트가 해당 선언을 가리키도록 한 다음, 각 값을 정의하고 사용합니다. 예제에서는 `hello-tabs`의 `count`를 `$.state`로 옮깁니다.
734 746
735<h4 id="declare-the-values">747<h4 id="declare-the-values">
736 값 선언748 값 선언하기
737</h4>749</h4>
738 750
739타입 파일에서 값을 선언하세요. 외부 키는 플러그인의 이름이고, 그 아래의 각 항목은 값과 그 타입입니다. 이를 `hello-tabs/types/index.d.ts`로 저장하세요:751타입 선언 파일에 값을 선언합니다. 바깥쪽 키는 플러그인 이름이며, 그 아래의 각 항목은 값과 그 타입입니다. 다음을 `hello-tabs/types/index.d.ts`로 저장합니다.
740 752
741```typescript hello-tabs/types/index.d.ts theme={null}753```typescript hello-tabs/types/index.d.ts theme={null}
742declare module 'claude-code' {754declare module 'claude-code' {
750```762```
751 763
752<h4 id="point-the-manifest-at-the-declaration">764<h4 id="point-the-manifest-at-the-declaration">
753 매니페스트를 선언으로 가리키기765 매니페스트가 선언을 가리키도록 하기
754</h4>766</h4>
755 767
756`claude plugin validate`가 코드를 해당 파일에 대해 확인하도록 하려면 선언의 경로로 매니페스트에 `types` 필드를 추가하세요:768`claude plugin validate`가 해당 파일을 기준으로 코드를 검사할 수 있도록, 매니페스트에 파일 경로를 담은 `types` 필드를 추가합니다.
757 769
758```json hello-tabs/.claude-plugin/plugin.json theme={null}770```json hello-tabs/.claude-plugin/plugin.json theme={null}
759{771{
769 값 정의, 읽기, 쓰기781 값 정의, 읽기, 쓰기
770</h4>782</h4>
771 783
772모듈에서 각 값을 기본값으로 정의하고, 그리는 동안 읽고, 콜백에서 쓰세요. `atom`은 값과 기본값의 이름을 지정하고, `read`는 반환하고, `update`는 씁니다. 세 가지 도우미는 `$.state.get`과 `$.state.set`을 호출합니다:784모듈에서 각 값을 기본값과 함께 정의하고, 그리는 동안 읽고, 콜백에서 씁니다. `atom`은 값의 이름과 기본값을 지정하고, `read`는 값을 반환하며, `update`는 값을 씁니다. 이 세 헬퍼가 대신 `$.state.get`과 `$.state.set`을 호출합니다.
773 785
774```javascript theme={null}786```javascript theme={null}
775import { atom, read, update } from 'claude-code'787import { atom, read, update } from 'claude-code'
776 788
777// 모듈의 맨 위: 값의 이름을 지정하고 기본값을 주기789// At the top of the module: name the value and give its default
778const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)790const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)
779 791
780// ui.render 훅에서: 값을 읽어 그리기792// In the ui.render hook: read the value to draw it
781const n = await read($, count)793const n = await read($, count)
782 794
783// 버튼에서: 이전 값에서 새 값을 쓰기795// In a Button: write a new value from the old one
784onPress: () => update($, count, (value) => value + 1)796onPress: () => update($, count, (value) => value + 1)
785```797```
786 798
787`ui.render` 훅이 `count`를 읽었기 때문에 Claude Code는 버튼이 그것을 쓸 때마다 훅을 다시 실행합니다.799`ui.render` 훅이 `count`를 읽었기 때문에, 버튼이 값을 쓸 때마다 Claude Code가 훅을 다시 실행합니다.
788 800
789코드에 세 가지 규칙이 적용됩니다:801코드에는 다음 규칙이 적용됩니다.
790 802
791* **`plugin`과 `key`를 리터럴 문자열로 쓰기**: `claude plugin validate`는 소스에서 읽습니다803* **`plugin`과 `key`를 문자열 리터럴로 작성합니다**: `claude plugin validate`가 소스에서 이 값을 읽습니다
792* **타입 파일에서 모든 값을 선언하기**: 그렇지 않으면 검증이 `hello-tabs.count is not declared`로 실패합니다804* **모든 값을 타입 선언 파일에 선언합니다**: 그렇지 않으면 `hello-tabs.count is not declared`와 함께 검증이 실패합니다
793* **콜백이나 다른 이벤트의 훅에서 쓰기**: `ui.render` 훅은 상태를 읽을 수 있고 쓸 수 없으므로 `onPress`, `onSubmit`, 또는 다른 이벤트의 훅에서 쓰세요805* **콜백이나 다른 이벤트의 훅에서 씁니다**: `ui.render` 훅은 상태를 읽을 수는 있지만 쓸 수는 없으므로, `onPress`, `onSubmit` 또는 다른 이벤트의 훅에서 씁니다
794 806
795<h4 id="change-hello-tabs-to-use-state">807<h4 id="change-hello-tabs-to-use-$-state">
796 `hello-tabs`를 `$.state` 사용으로 변경808 `$.state`를 사용하도록 `hello-tabs` 변경하기
797</h4>809</h4>
798 810
799`hello-tabs`에서 `count`를 `$.state`로 이동하려면 그것을 사용하는 모든 줄을 변경하세요:811`hello-tabs`의 `count`를 `$.state`로 옮기려면 이를 사용하는 모든 줄을 변경합니다.
800 812
801* **모듈의 맨 위**: `import` 줄을 추가하고, `let count = 0`을 `atom` 줄로 대체813* **모듈 상단**: `import` 줄을 추가하고, `let count = 0`을 `atom` 줄로 바꿉니다
802* **`ui.render` 훅에서**: `tabButton` 전에 `read` 줄을 추가하고, `Text`에서 `'Count: ' + n`을 그리기814* **`ui.render` 훅**: `tabButton` 앞에 `read` 줄을 추가하고, `Text`에 `'Count: ' + n`을 그립니다
803* **Add one 버튼에서**: `onPress`를 [한 세션 이상에서 저장](#save-from-more-than-one-session)의 것으로 대체합니다. 이는 카운트를 저장하고 쓰기도 합니다815* **Add one 버튼**: `onPress`를 [여러 세션에서 저장하기](#save-from-more-than-one-session)에 있는 것으로 바꿉니다. 이는 카운트를 쓸 뿐 아니라 저장도 합니다
804* **`session.start` 훅에서**: `saved`를 읽는 두 줄을 [/clear 후 저장된 값 다시 로드](#load-a-saved-value-again-after-clear)의 `loadCount` 호출로 대체816* **`session.start` 훅**: `saved`를 읽는 두 줄을 [`/clear` 후 저장된 값 다시 로드하기](#load-a-saved-value-again-after-clear)의 `loadCount` 호출로 바꿉니다
805 817
806`tab`이 여전히 변수이므로 `redraw`를 유지하세요.818`tab`은 여전히 변수이므로 탭 버튼에는 `redraw`를 유지합니다.
807 819
808<h3 id="load-a-saved-value-again-after-clear">820<h3 id="load-a-saved-value-again-after-clear">
809 `/clear` 후 저장된 값 다시 로드821 `/clear` 후 저장된 값 다시 로드하기
810</h3>822</h3>
811 823
812모드가 `$.store`에서 저장된 값을 `session.start`에서 `$.state`로 복사하면, `/clear`, `/resume`, 또는 `/branch` 후에 다시 복사해야 합니다. 이 명령은 모든 `$.state` 값을 기본값으로 되돌리고, `session.start`는 다시 발생하지 않습니다. [`classic.SessionStart`](/docs/ko/plugins/mods/events#hook-the-settings-hook-events)는 각각 후에 발생하고, `e.source`는 `clear`, `resume`, 또는 `fork`로 설정되므로 값을 다시 복사하세요. 그렇지 않으면 그리기는 기본값을 표시하고, `$.state` 값을 저장하는 콜백은 저장한 것 위에 기본값을 씁니다.824mod가 `session.start`에서 `$.store`의 저장된 값을 `$.state`로 복사한다면, `/clear`, `/resume`, `/branch` 후에 다시 복사해야 합니다. 이러한 명령은 모든 `$.state` 값을 기본값으로 재설정하며, `session.start`는 다시 실행되지 않습니다. [`classic.SessionStart`](/docs/ko/plugins/mods/events#hook-the-settings-hook-events)는 각 명령 후에 `e.source`가 `clear`, `resume` 또는 `fork`로 설정된 채 실행되므로, 이 이벤트의 훅에서 값을 다시 복사합니다. 그렇지 않으면 그리기에 기본값이 표시되고, `$.state` 값을 저장하는 콜백이 저장해 둔 값을 기본값으로 덮어씁니다.
813 825
814이 코드는 두 훅에서 `count`를 로드합니다. `$.state` 버전의 `hello-tabs`를 기반으로 하며, `count`는 원자이고 `update`는 가져옵니다. `loadCount`를 `register` 위에 놓고, 이미 가지고 있는 `session.start` 훅에 `loadCount` 호출을 추가하세요. `classic.SessionStart`는 또한 시작 시 그리고 압축 후에 발생하며, 이는 `$.state`를 재설정하지 않으므로 `source`의 필터는 훅을 세 가지 재설정으로 유지합니다:826다음 코드는 두 훅 모두에서 `count`를 로드합니다. 이는 `count`가 atom이고 `update`를 import한 `$.state` 버전의 `hello-tabs`를 기반으로 합니다. `loadCount`를 `register` 위에 두고, 이미 있는 `session.start` 훅에 `loadCount` 호출을 추가합니다. `classic.SessionStart`는 시작 시와 압축 후에도 실행되는데, 이때는 `$.state`가 재설정되지 않으므로 `source` 필터를 사용해 훅을 세 가지 재설정으로 제한합니다.
815 827
816```javascript theme={null}828```javascript theme={null}
817// $.store에서 저장된 카운트를 $.state로 복사하거나, 아무것도 저장되지 않으면 0829// Copy the saved count from $.store into $.state, or 0 if nothing is saved
818async function loadCount($) {830async function loadCount($) {
819 const saved = Number((await $.store.get('count')) ?? 0)831 const saved = Number((await $.store.get('count')) ?? 0)
820 await update($, count, () => saved)832 await update($, count, () => saved)
821}833}
822 834
823// 첫 프롬프트 전에 실행되고, 다시 로드 후에도 실행됨835// Runs before your first prompt, and again after a reload
824on('session.start', async ($, e, next) => {836on('session.start', async ($, e, next) => {
825 await loadCount($)837 await loadCount($)
826 return next(e)838 return next(e)
827})839})
828 840
829// /clear, /resume, /branch 후에 다시 실행되며, fork를 보고합니다841// Runs again after /clear, /resume, and /branch, which reports fork
830on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {842on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
831 await loadCount($)843 await loadCount($)
832 return next(e)844 return next(e)
833})845})
834```846```
835 847
836두 훅이 제자리에 있으면 창은 `/clear` 후 저장된 카운트를 표시하고 `0`이 아니며, **Add one**의 다음 누름은 저장된 카운트에 추가합니다.848두 훅을 모두 적용하면 `/clear` 후 창에 `0`이 아닌 저장된 카운트가 표시되며, 다음에 **Add one**을 누르면 저장된 카운트에 더해집니다.
837 849
838`loadCount`는 `$.state`의 값 위에 저장된 값을 쓰고, `session.start`는 모듈이 다시 로드될 때마다 다시 발생합니다. 저장소가 뒤처지지 않도록 하려면 모든 변경에 저장하세요. **Add one** 버튼이 하는 것처럼.850`loadCount`는 저장된 값으로 `$.state`의 값을 덮어쓰며, `session.start`는 모듈이 다시 로드될 때마다 다시 실행됩니다. 스토어가 뒤처지지 않도록 하려면 **Add one** 버튼처럼 변경될 때마다 저장합니다.
839 851
840다시 로드를 세션 없이 확인하려면 [`/clear` 후 그리기를 테스트](/docs/ko/plugins/mods/test#test-a-drawing-after-clear)하세요.852세션 없이 다시 로드를 확인하려면 [`/clear` 후 그리기를 테스트합니다](/docs/ko/plugins/mods/test#test-a-drawing-after-clear).
841 853
842<h3 id="save-from-more-than-one-session">854<h3 id="save-from-more-than-one-session">
843 한 세션 이상에서 저장855 여러 세션에서 저장하기
844</h3>856</h3>
845 857
846머신의 모든 세션이 모드를 실행하면 하나의 `$.store`를 공유합니다. `get` 다음에 `set`은 원자적이지 않습니다. 두 세션이 각각 값을 읽고, 변경하고, 다시 쓸 때 경쟁하고, 두 번째 쓰기가 첫 번째를 대체합니다.858머신에서 mod를 실행하는 모든 세션은 하나의 `$.store`를 공유합니다. `get` 다음에 `set`을 실행하는 것은 원자적이지 않습니다. 두 세션이 각각 값을 읽고, 변경하고, 다시 쓰면 경쟁 상태가 발생하며, 두 번째 쓰기가 첫 번째 쓰기를 대체합니다.
847 859
848두 가지 선택이 그것을 덜 가능하게 합니다:860이러한 가능성을 줄이려면 다음을 따릅니다.
849 861
850* **각 항목에 자신의 키를 주기**: `set`은 자신의 키만 변경하므로 다른 키를 쓰는 세션은 서로를 덮어쓰지 않습니다862* **각 항목에 고유한 키를 부여합니다**: `set`은 자신의 키만 변경하므로, 서로 다른 키에 쓰는 세션은 서로를 덮어쓰지 않습니다
851* **쓰기 바로 전에 다시 읽기**: 여러 세션이 변경하는 값의 경우, 콜백에서 키를 `get`하고 `session.start`에서 로드한 복사본이 아닌 그것에서 새 값을 빌드하세요. 다른 세션의 쓰기는 여전히 `get`과 `set` 사이에 착지하면 손실됩니다.863* **쓰기 직전에 다시 읽습니다**: 여러 세션이 변경하는 값의 경우, 콜백에서 키를 `get`하고 `session.start`에서 로드한 복사본이 아니라 그 값으로 새 값을 만듭니다. 다른 세션의 쓰기가 `get`과 `set` 사이에 발생하면 여전히 손실됩니다.
852 864
853이 버튼은 저장소가 지금 보유하는 것에 1을 더한 다음 그리기를 업데이트합니다:865다음 버튼은 스토어에 현재 있는 값에 1을 더한 다음 그리기를 업데이트합니다.
854 866
855```javascript theme={null}867```javascript theme={null}
856onPress: async () => {868onPress: async () => {
857 // 저장소가 지금 보유하는 것을 읽기, 다른 세션이 변경했을 수 있음869 // Read what the store holds now, which another session may have changed
858 const saved = Number((await $.store.get('count')) ?? 0)870 const saved = Number((await $.store.get('count')) ?? 0)
859 // 새 카운트를 저장한 다음 표시871 // Save the new count, then show it
860 await $.store.set('count', saved + 1)872 await $.store.set('count', saved + 1)
861 await update($, count, () => saved + 1)873 await update($, count, () => saved + 1)
862}874}
863```875```
864 876
865두 번째 세션이 이 세션이 시작된 이후로 자신의 버튼을 세 번 눌렀다면, 이 누름은 그 세 개를 포함하는 카운트를 표시하고 저장합니다.877이 세션이 시작된 후 두 번째 세션이 자신의 버튼을 세 번 눌렀다면, 이번 누름은 그 세 번을 포함한 카운트를 표시하고 저장합니다.
866 878
867<h2 id="next-steps">879<h2 id="next-steps">
868 다음 단계880 다음 단계
869</h2>881</h2>
870 882
871* [이벤트에 반응](/docs/ko/plugins/mods/events): 도구 호출과 턴에서 그리기 공급883* [이벤트에 반응하기](/docs/ko/plugins/mods/events): 도구 호출과 턴으로 드로잉에 데이터를 공급합니다
872* [모드 API 사용](/docs/ko/plugins/mods/api): 타이머와 모델 호출에서 그리기 공급884* [mods API 사용하기](/docs/ko/plugins/mods/api): 타이머와 모델 호출로 드로잉에 데이터를 공급합니다
873* [그리기 테스트](/docs/ko/plugins/mods/test#test-a-drawing): 테스트에서 버튼을 누르기, 한 개 이상의 표면에서885* [드로잉 테스트하기](/docs/ko/plugins/mods/test#test-a-drawing): 테스트에서 버튼을 눌러 보고, 여러 사용 환경에서 확인합니다
874* [렌더 사이트](/docs/ko/plugins/mods/reference#render-sites)와 [요소](/docs/ko/plugins/mods/reference#elements): 각 사이트의 속성과 각 요소의 속성886* [렌더링 지점](/docs/ko/plugins/mods/reference#render-sites) 및 [요소](/docs/ko/plugins/mods/reference#elements): 각 지점의 prop과 각 요소의 prop을 설명합니다