@hanzo/build 0.2.80 → 0.2.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +13 -0
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +1 -20
  5. package/lib/api/agents.js +16 -8
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/links.d.ts +1 -1
  29. package/lib/api/links.js +1 -1
  30. package/lib/api/machines.d.ts +0 -15
  31. package/lib/api/machines.js +6 -5
  32. package/lib/api/mcp.d.ts +0 -9
  33. package/lib/api/mcp.js +3 -2
  34. package/lib/api/members.d.ts +0 -15
  35. package/lib/api/members.js +4 -3
  36. package/lib/api/memory.d.ts +0 -11
  37. package/lib/api/memory.js +2 -1
  38. package/lib/api/places.d.ts +1 -9
  39. package/lib/api/places.js +12 -1
  40. package/lib/api/plugins.d.ts +1 -16
  41. package/lib/api/plugins.js +4 -3
  42. package/lib/api/profile.d.ts +0 -10
  43. package/lib/api/profile.js +5 -4
  44. package/lib/api/projects.d.ts +0 -14
  45. package/lib/api/projects.js +11 -10
  46. package/lib/api/provider.d.ts +2 -14
  47. package/lib/api/provider.js +12 -10
  48. package/lib/api/sandbox.d.ts +0 -17
  49. package/lib/api/sandbox.js +5 -4
  50. package/lib/api/sessions.d.ts +0 -17
  51. package/lib/api/sessions.js +5 -4
  52. package/lib/api/skills.d.ts +1 -15
  53. package/lib/api/skills.js +5 -4
  54. package/lib/api/studio.d.ts +0 -14
  55. package/lib/api/studio.js +6 -5
  56. package/lib/api/turn.d.ts +6 -28
  57. package/lib/api/turn.js +105 -49
  58. package/lib/api/verdict.d.ts +0 -9
  59. package/lib/api/verdict.js +2 -1
  60. package/lib/api/webhooks.d.ts +1 -14
  61. package/lib/api/webhooks.js +5 -4
  62. package/lib/ask.js +7 -4
  63. package/lib/automations.d.ts +5 -4
  64. package/lib/automations.js +95 -74
  65. package/lib/back.js +10 -4
  66. package/lib/builder.js +7 -5
  67. package/lib/carry.js +20 -16
  68. package/lib/customize/agents.js +78 -31
  69. package/lib/customize/connectors.js +62 -48
  70. package/lib/customize/index.js +43 -10
  71. package/lib/customize/plugins.js +34 -22
  72. package/lib/customize/say.d.ts +13 -1
  73. package/lib/customize/say.js +15 -21
  74. package/lib/customize/skills.js +31 -22
  75. package/lib/customize/ui.d.ts +5 -9
  76. package/lib/customize/ui.js +26 -16
  77. package/lib/data.d.ts +1 -1
  78. package/lib/data.js +3 -2
  79. package/lib/desk.js +45 -33
  80. package/lib/door.js +35 -10
  81. package/lib/environment.js +25 -19
  82. package/lib/files.js +43 -34
  83. package/lib/find.js +12 -9
  84. package/lib/foot.js +7 -4
  85. package/lib/forge.js +61 -40
  86. package/lib/gear.d.ts +2 -1
  87. package/lib/gear.js +6 -4
  88. package/lib/git.js +29 -19
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/merge.d.ts +2 -2
  93. package/lib/meter.d.ts +1 -1
  94. package/lib/meter.js +14 -5
  95. package/lib/mic.js +12 -5
  96. package/lib/mind.d.ts +1 -0
  97. package/lib/mind.js +5 -3
  98. package/lib/nav.d.ts +6 -5
  99. package/lib/nav.js +37 -30
  100. package/lib/panel.d.ts +2 -1
  101. package/lib/panel.js +52 -36
  102. package/lib/plan.d.ts +4 -20
  103. package/lib/plan.js +26 -10
  104. package/lib/plans.js +49 -30
  105. package/lib/plugins/design.js +3 -2
  106. package/lib/plugins/index.d.ts +23 -4
  107. package/lib/plugins/index.js +11 -26
  108. package/lib/plugins/pdf.js +2 -1
  109. package/lib/plugins/project.js +7 -4
  110. package/lib/plugins/research.js +3 -2
  111. package/lib/plugins/sketch.js +6 -3
  112. package/lib/plugins/slides.js +3 -2
  113. package/lib/plugins/template.js +6 -3
  114. package/lib/plus.js +25 -13
  115. package/lib/prefs.d.ts +0 -14
  116. package/lib/prefs.js +2 -1
  117. package/lib/project.js +69 -62
  118. package/lib/prompt.d.ts +12 -11
  119. package/lib/prompt.js +38 -20
  120. package/lib/publish.js +17 -12
  121. package/lib/reply.js +6 -3
  122. package/lib/run.js +72 -54
  123. package/lib/say.d.ts +2 -0
  124. package/lib/say.js +7 -0
  125. package/lib/scheduled.js +54 -43
  126. package/lib/section.d.ts +1 -0
  127. package/lib/section.js +8 -2
  128. package/lib/settings/account.d.ts +1 -0
  129. package/lib/settings/account.js +31 -21
  130. package/lib/settings/billing.js +55 -28
  131. package/lib/settings/capabilities.js +26 -15
  132. package/lib/settings/card.js +19 -12
  133. package/lib/settings/code.js +22 -11
  134. package/lib/settings/credits.d.ts +4 -4
  135. package/lib/settings/credits.js +34 -21
  136. package/lib/settings/environments.js +21 -14
  137. package/lib/settings/general.js +26 -14
  138. package/lib/settings/index.js +6 -3
  139. package/lib/settings/integrations.js +42 -28
  140. package/lib/settings/keys.js +37 -19
  141. package/lib/settings/list.d.ts +0 -5
  142. package/lib/settings/list.js +24 -15
  143. package/lib/settings/machines.d.ts +3 -0
  144. package/lib/settings/machines.js +63 -38
  145. package/lib/settings/members.js +23 -14
  146. package/lib/settings/memory.js +12 -6
  147. package/lib/settings/notifications.js +32 -19
  148. package/lib/settings/plan.d.ts +1 -1
  149. package/lib/settings/plan.js +40 -17
  150. package/lib/settings/privacy.js +26 -13
  151. package/lib/settings/ui.d.ts +3 -2
  152. package/lib/settings/ui.js +9 -3
  153. package/lib/settings/usage.js +7 -3
  154. package/lib/shelf.js +20 -17
  155. package/lib/side.d.ts +1 -26
  156. package/lib/side.js +11 -5
  157. package/lib/standing.d.ts +2 -10
  158. package/lib/standing.js +5 -3
  159. package/lib/suggest.d.ts +8 -28
  160. package/lib/suggest.js +39 -4
  161. package/lib/suggestions.js +14 -5
  162. package/lib/summary.d.ts +1 -1
  163. package/lib/summary.js +4 -1
  164. package/lib/sync.js +22 -12
  165. package/lib/tools.d.ts +4 -3
  166. package/lib/tools.js +10 -7
  167. package/lib/transcript.js +10 -8
  168. package/package.json +4 -2
package/lib/run.js CHANGED
@@ -24,6 +24,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
24
  * the run. Share opens its story to the public build route.
25
25
  */
26
26
  import { SizableText, useMedia, XStack, YStack } from '@hanzo/gui';
27
+ import { phrase } from '@hanzo/i18n';
28
+ import { useI18n } from '@hanzo/i18n/react';
27
29
  import { ChevronDown, ChevronRight, Clock, Copy, ExternalLink, GitPullRequest, Globe, Pencil, Share } from '@hanzogui/lucide-icons-2';
28
30
  import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
29
31
  import { Steer } from '@hanzo/ui/agents';
@@ -53,7 +55,16 @@ import { PAGE, useDeck, useKey, usePanel } from './tabs.js';
53
55
  import { compose, Files, pick } from './tools.js';
54
56
  import { Transcript } from './transcript.js';
55
57
  const LIVE = new Set(['running', 'paused', '']);
58
+ /** A run's status as the line under its title says it; one the service adds later is drawn as it comes. */
59
+ const STATUS = {
60
+ running: phrase('running'),
61
+ paused: phrase('paused'),
62
+ done: phrase('done'),
63
+ error: phrase('error'),
64
+ stopped: phrase('stopped'),
65
+ };
56
66
  export function Run({ id }) {
67
+ const { t: say } = useI18n();
57
68
  const host = useHost();
58
69
  const t = useTarget();
59
70
  // Signed out, there is nothing to read: the gateway answers a bare run with a refusal.
@@ -85,11 +96,11 @@ export function Run({ id }) {
85
96
  const mode = record?.mode ?? '';
86
97
  // A run that narrated no ask of its own opens with the one its title records, once it has said anything.
87
98
  const shown = useMemo(() => {
88
- const drawn = cards(events, mode);
99
+ const drawn = cards(events, mode, say);
89
100
  const ask = record ? headline(record.title, record.repo) : '';
90
101
  const opens = drawn[0]?.kind === 'said' && drawn[0].who === 'person';
91
102
  return ask && drawn.length && !opens ? [{ kind: 'said', key: 'ask', who: 'person', text: ask }, ...drawn] : drawn;
92
- }, [events, mode, record]);
103
+ }, [events, mode, record, say]);
93
104
  const end = outcome(events);
94
105
  const plan = useMemo(() => steps(events), [events]);
95
106
  const kept = useMemo(() => settled(events), [events]);
@@ -106,7 +117,7 @@ export function Run({ id }) {
106
117
  useEffect(() => {
107
118
  if (!end.published)
108
119
  return;
109
- side.open({ kind: 'preview', title: 'Preview' });
120
+ side.open({ kind: 'preview', title: say('Preview') });
110
121
  setDesk(true);
111
122
  // On each publish, not on each render.
112
123
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -124,7 +135,7 @@ export function Run({ id }) {
124
135
  const steering = running && !(paused && held);
125
136
  // A follow-up clones what the run kept, so it waits until the run has said where that is.
126
137
  const waiting = (paused && held && !kept.settled) || (finished && stopping && !kept.settled);
127
- const title = record?.title || (detail.loading ? '' : 'Untitled run');
138
+ const title = record?.title || (detail.loading ? '' : say('Untitled run'));
128
139
  // A setup run explores and installs before it answers, which takes minutes.
129
140
  const setup = mode === 'setup';
130
141
  const [seen, setSeen] = useKept('hanzo.build.setup.seen', false);
@@ -158,17 +169,17 @@ export function Run({ id }) {
158
169
  if (!notify || running)
159
170
  return;
160
171
  if (Notification.permission === 'granted')
161
- new Notification(record?.title || 'Run finished', { body: 'This run has finished.' });
172
+ new Notification(record?.title || say('Run finished'), { body: say('This run has finished.') });
162
173
  setNotify(false);
163
174
  }, [notify, running, record?.title]);
164
175
  const ask = async () => {
165
176
  if (typeof Notification === 'undefined') {
166
- setNote('This browser cannot send a notification.');
177
+ setNote(say('This browser cannot send a notification.'));
167
178
  return;
168
179
  }
169
180
  const perm = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission();
170
181
  if (perm !== 'granted') {
171
- setNote('Notifications stay off until this browser allows them.');
182
+ setNote(say('Notifications stay off until this browser allows them.'));
172
183
  return;
173
184
  }
174
185
  setNotify(true);
@@ -199,7 +210,7 @@ export function Run({ id }) {
199
210
  // A setup run's work IS its environment: the panel opens on it.
200
211
  useEffect(() => {
201
212
  if (mode === 'setup')
202
- side.open({ kind: 'environment', title: 'Environment' });
213
+ side.open({ kind: 'environment', title: say('Environment') });
203
214
  // Once the run says what it is.
204
215
  // eslint-disable-next-line react-hooks/exhaustive-deps
205
216
  }, [mode]);
@@ -214,7 +225,7 @@ export function Run({ id }) {
214
225
  */
215
226
  const carry = async (said, how = 'follow') => {
216
227
  if (!earlier)
217
- throw new Error('This run is still being read');
228
+ throw new Error(say('This run is still being read'));
218
229
  if (paused)
219
230
  await stop(t, id, 'Continued in a follow-up run').catch(() => undefined);
220
231
  const ask = how === 'approve' ? approve(earlier, said) : how === 'retry' ? retry(earlier, said) : followUp(earlier, said);
@@ -232,7 +243,7 @@ export function Run({ id }) {
232
243
  await message(t, id, words);
233
244
  setDraft('');
234
245
  setFiles([]);
235
- return 'Sent — recorded on this run';
246
+ return say('Sent — recorded on this run');
236
247
  });
237
248
  return;
238
249
  }
@@ -244,13 +255,13 @@ export function Run({ id }) {
244
255
  void act(async () => {
245
256
  await stop(t, id);
246
257
  setStopping(true);
247
- return 'Stop requested — the run keeps its work on its branch';
258
+ return say('Stop requested — the run keeps its work on its branch');
248
259
  });
249
260
  return;
250
261
  case 'pause':
251
262
  void act(async () => {
252
263
  await pause(t, id);
253
- return held ? 'Pause requested — the run keeps its work on its branch and waits' : 'Pause asked — the machine pauses when it reads it';
264
+ return held ? say('Pause requested — the run keeps its work on its branch and waits') : say('Pause asked — the machine pauses when it reads it');
254
265
  });
255
266
  return;
256
267
  case 'resume':
@@ -259,7 +270,7 @@ export function Run({ id }) {
259
270
  else
260
271
  void act(async () => {
261
272
  await resume(t, id);
262
- return 'Resume asked — the machine goes on when it reads it';
273
+ return say('Resume asked — the machine goes on when it reads it');
263
274
  });
264
275
  return;
265
276
  }
@@ -270,13 +281,13 @@ export function Run({ id }) {
270
281
  setCopied(what);
271
282
  }
272
283
  catch {
273
- setNote('This browser would not copy.');
284
+ setNote(say('This browser would not copy.'));
274
285
  }
275
286
  };
276
287
  const share = (on) => act(async () => {
277
288
  await publish(t, id, on);
278
289
  detail.reload();
279
- return on ? 'Shared — anyone with the link can read this run’s story' : 'No longer shared';
290
+ return on ? say('Shared — anyone with the link can read this run’s story') : say('No longer shared');
280
291
  });
281
292
  // What the title's menu does to the run.
282
293
  const manage = [
@@ -284,7 +295,7 @@ export function Run({ id }) {
284
295
  ? [
285
296
  {
286
297
  key: 'rename',
287
- label: 'Rename',
298
+ label: say('Rename'),
288
299
  icon: _jsx(Pencil, { size: 16 }),
289
300
  onSelect: () => {
290
301
  setName(title);
@@ -293,15 +304,15 @@ export function Run({ id }) {
293
304
  },
294
305
  ]
295
306
  : []),
296
- { key: 'copy', label: copied === 'id' ? 'Copied' : 'Copy run id', icon: _jsx(Copy, { size: 16 }), description: id, onSelect: () => void copy('id', id) },
307
+ { key: 'copy', label: copied === 'id' ? say('Copied') : say('Copy run id'), icon: _jsx(Copy, { size: 16 }), description: id, onSelect: () => void copy('id', id) },
297
308
  // What this run was asked, as a task that runs again on a schedule: the ask it opened with.
298
309
  ...(signed && record
299
310
  ? [
300
311
  {
301
312
  key: 'schedule',
302
- label: 'Schedule this',
313
+ label: say('Schedule this'),
303
314
  icon: _jsx(Clock, { size: 16 }),
304
- description: 'Run it again on a schedule',
315
+ description: say('Run it again on a schedule'),
305
316
  onSelect: () => {
306
317
  const first = shown.find((c) => c.kind === 'said' && c.who === 'person');
307
318
  schedule(first && 'text' in first ? first.text : headline(record.title, record.repo), host.go);
@@ -315,77 +326,84 @@ export function Run({ id }) {
315
326
  const sharing = signed && record?.project
316
327
  ? [
317
328
  record.published
318
- ? { key: 'unshare', label: 'Stop sharing', icon: _jsx(Globe, { size: 16 }), onSelect: () => void share(false) }
319
- : { key: 'share', label: 'Share publicly', icon: _jsx(Globe, { size: 16 }), description: 'Anyone with the link can read it', onSelect: () => void share(true) },
320
- ...(link ? [{ key: 'link', label: copied === 'link' ? 'Copied' : 'Copy public link', icon: _jsx(Copy, { size: 16 }), description: link, onSelect: () => void copy('link', link) }] : []),
329
+ ? { key: 'unshare', label: say('Stop sharing'), icon: _jsx(Globe, { size: 16 }), onSelect: () => void share(false) }
330
+ : { key: 'share', label: say('Share publicly'), icon: _jsx(Globe, { size: 16 }), description: say('Anyone with the link can read it'), onSelect: () => void share(true) },
331
+ ...(link ? [{ key: 'link', label: copied === 'link' ? say('Copied') : say('Copy public link'), icon: _jsx(Copy, { size: 16 }), description: link, onSelect: () => void copy('link', link) }] : []),
321
332
  ]
322
333
  : [];
323
334
  const save = () => void act(async () => {
324
335
  await rename(t, id, name);
325
336
  setNaming(false);
326
337
  detail.reload();
327
- return 'Renamed';
338
+ return say('Renamed');
328
339
  });
329
340
  const placeholder = !signed
330
- ? 'Sign in to follow up'
341
+ ? say('Sign in to follow up')
331
342
  : waiting
332
343
  ? paused
333
- ? 'Pausing — the run is keeping its work…'
334
- : 'Stopping — the run is keeping its work…'
344
+ ? say('Pausing — the run is keeping its work…')
345
+ : say('Stopping — the run is keeping its work…')
335
346
  : paused && held
336
- ? 'Continue this run with a follow up'
347
+ ? say('Continue this run with a follow up')
337
348
  : running
338
349
  ? setup
339
- ? 'Add a follow up for the setup agent'
340
- : 'Add a follow up'
350
+ ? say('Add a follow up for the setup agent')
351
+ : say('Add a follow up')
341
352
  : finished
342
- ? 'Follow up — continues in a new run'
343
- : 'Reading this run…';
344
- const desk_ = (sheet) => (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', site: site, published: end.published, events: events, live: running, menu: [...manage, ...sharing], refused: signed ? refused || (detail.error ? detail.error.message : '') : 'Sign in to follow this run.', retry: signed ? 'Retry' : 'Sign in', onRetry: signed ? detail.reload : () => host.signIn?.(), side: side, sheet: sheet, onHide: () => setDesk(false) }));
345
- return (_jsxs(XStack, { "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ref: split.main, style: split.mainStyle, ...pane, flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(XStack, { px: "$4", pt: "$3", pb: "$2", gap: "$2", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Manage this run", items: "center", gap: "$1.5", maxW: "100%", px: "$1.5", mx: "$-1.5", py: "$0.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { render: "h1", size: "$5", color: "$ink", numberOfLines: 1, minW: 0, children: title }), _jsx(ChevronDown, { size: 14 })] }), items: manage }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, maxW: "100%", children: [state || (detail.loading ? 'reading' : ''), record?.repo, record?.branch, record?.published ? 'shared' : ''].filter(Boolean).join(' · ') })] }), pr.href ? (_jsx(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: _jsxs(XStack, { items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(GitPullRequest, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: pr.label }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }) })) : null, sharing.length ? (_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Share", items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(Share, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Share" })] }), items: sharing })) : null, _jsx(PanelToggle, { open: desk, onToggle: () => setDesk(!desk) })] }), _jsx(Transcript, { cards: shown, onOpen: look, onDiff: () => {
346
- side.open({ kind: 'diff', title: 'Diff' });
353
+ ? say('Follow up — continues in a new run')
354
+ : say('Reading this run…');
355
+ const desk_ = (sheet) => (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', site: site, published: end.published, events: events, live: running, menu: [...manage, ...sharing], refused: signed ? refused || (detail.error ? say(detail.error.message) : '') : say('Sign in to follow this run.'), retry: signed ? say('Retry') : say('Sign in'), onRetry: signed ? detail.reload : () => host.signIn?.(), side: side, sheet: sheet, onHide: () => setDesk(false) }));
356
+ return (_jsxs(XStack, { "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ref: split.main, style: split.mainStyle, ...pane, flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(XStack, { px: "$4", pt: "$3", pb: "$2", gap: "$2", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": say('Manage this run'), items: "center", gap: "$1.5", maxW: "100%", px: "$1.5", mx: "$-1.5", py: "$0.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { render: "h1", size: "$5", color: "$ink", numberOfLines: 1, minW: 0, children: title }), _jsx(ChevronDown, { size: 14 })] }), items: manage }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, maxW: "100%", children: [state ? say(STATUS[state] ?? state) : detail.loading ? say('reading') : '', record?.repo, record?.branch, record?.published ? say('shared') : ''].filter(Boolean).join(' · ') })] }), pr.href ? (_jsx(Out, { href: pr.href, label: say('Open pull request {pr}', { pr: pr.label }), children: _jsxs(XStack, { items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(GitPullRequest, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: pr.label }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }) })) : null, sharing.length ? (_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": say('Share'), items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(Share, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: say('Share') })] }), items: sharing })) : null, _jsx(PanelToggle, { open: desk, onToggle: () => setDesk(!desk) })] }), _jsx(Transcript, { cards: shown, onOpen: look, onDiff: () => {
357
+ side.open({ kind: 'diff', title: say('Diff') });
347
358
  setDesk(true);
348
- }, listen: signed ? listen : undefined, end: failed ? (_jsx(Failure, { onRetry: busy ? undefined : () => void act(() => carry(again, 'retry')), children: end.problem ? 'This run stopped before it finished.' : 'This run stopped with an error. Its work so far is on its branch.' })) : null,
359
+ }, listen: signed ? listen : undefined, end: failed ? (_jsx(Failure, { onRetry: busy ? undefined : () => void act(() => carry(again, 'retry')), children: end.problem
360
+ ? say('This run stopped before it finished.')
361
+ : `${say('This run stopped with an error.')} ${say('Its work so far is on its branch.')}` })) : null,
349
362
  // A paused run's agent was stopped where it stood; what it was running is cut off.
350
- live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden} earlier ${hidden === 1 ? 'turn is' : 'turns are'} not shown. The run’s Git tab has everything it pushed.` })) : null, onApprove: planned && earlier ? (p) => void act(() => carry(p, 'approve')) : undefined, approving: busy, onVerdict: (v) => verdict(t, id, record?.project ?? '', v), empty: signed ? (_jsx(SizableText, { size: "$2", color: "$soft", children: detail.error
351
- ? detail.error.message
363
+ live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden === 1 ? say('{n} earlier turn is not shown.', { n: hidden }) : say('{n} earlier turns are not shown.', { n: hidden })} ${say('The run’s Git tab has everything it pushed.')}` })) : null, onApprove: planned && earlier ? (p) => void act(() => carry(p, 'approve')) : undefined, approving: busy, onVerdict: (v) => verdict(t, id, record?.project ?? '', v), empty: signed ? (_jsx(SizableText, { size: "$2", color: "$soft", children: detail.error
364
+ ? say(detail.error.message)
352
365
  : detail.loading
353
- ? 'Reading this run…'
366
+ ? say('Reading this run…')
354
367
  : running
355
368
  ? setup
356
- ? 'Setting up environment'
357
- : 'Starting…'
358
- : 'Waiting for the run’s first step…' })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Sign in to follow this run." }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" })] })) }), _jsxs(YStack, { pb: "$3", pt: "$2", gap: "$2", children: [_jsxs(YStack, { ...measure, gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "The branch is pushed, and the pull request could not be opened." })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null, plan.length ? (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-label": `Steps · ${plan.length}`, "aria-expanded": unfolded, onPress: () => setUnfolded(!unfolded), px: "$3", py: "$1.5", gap: "$2", items: "center", hoverStyle: { bg: '$hover' }, children: [unfolded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: `Steps · ${plan.length}` }), current && !unfolded ? (_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: { textAlign: 'right' }, children: current.name })) : null] }), unfolded
369
+ ? say('Setting up environment')
370
+ : say('Starting…')
371
+ : say('Waiting for the run’s first step…') })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: say('Sign in to follow this run.') }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: say('Sign in') })] })) }), _jsxs(YStack, { pb: "$3", pt: "$2", gap: "$2", children: [_jsxs(YStack, { ...measure, gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: say('The branch is pushed, and the pull request could not be opened.') })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null, plan.length ? (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-label": say('Steps · {n}', { n: plan.length }), "aria-expanded": unfolded, onPress: () => setUnfolded(!unfolded), px: "$3", py: "$1.5", gap: "$2", items: "center", hoverStyle: { bg: '$hover' }, children: [unfolded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: say('Steps · {n}', { n: plan.length }) }), current && !unfolded ? (_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, text: "end", children: current.name })) : null] }), unfolded
359
372
  ? plan.map((s) => {
360
373
  const on = s === current;
361
374
  return (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: s.done || on ? '$ink' : '$soft', children: s.done ? '✓' : on ? '●' : '○' }), _jsx(SizableText, { size: "$2", color: s.done ? '$soft' : '$ink', numberOfLines: 1, children: s.name })] }, s.name));
362
375
  })
363
376
  : null] })) : null, setup && running && !seen ? _jsx(Onboarding, { onDone: () => setSeen(true) }) : null, running ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 160, children: paused
364
377
  ? kept.pushed
365
- ? 'Paused — its work so far is on its branch.'
366
- : 'Paused.'
378
+ ? say('Paused — its work so far is on its branch.')
379
+ : say('Paused.')
367
380
  : setup
368
381
  ? span
369
- ? `Environment setup takes ~${span[0] === span[1] ? span[0] : `${span[0]}–${span[1]}`} minutes.`
370
- : 'Environment setup takes several minutes.'
371
- : 'This run is still working.' }), _jsx(Steer, { onCommand: command, withhold: [
382
+ ? say('Environment setup takes ~{minutes} minutes.', { minutes: span[0] === span[1] ? span[0] : `${span[0]}–${span[1]}` })
383
+ : say('Environment setup takes several minutes.')
384
+ : say('This run is still working.') }), _jsx(Steer, { onCommand: command, withhold: [
372
385
  ...(paused ? ['pause'] : ['resume']),
373
386
  ...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
374
- ] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? 'You will be notified' : 'Notify me' })] })) : null] }), _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: send, ready: Boolean(draft.trim()), disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: steering ? 'Steer this run' : 'Follow up on this run', head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, plus: { upload: () => pick(files, setFiles, setNote) },
387
+ ] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? say('You will be notified') : say('Notify me') })] })) : null] }), _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: send, ready: Boolean(draft.trim()), disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: steering ? say('Steer this run') : say('Follow up on this run'), head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, plus: { upload: () => pick(files, setFiles, setNote, say) },
375
388
  // What a new run from here is made with; words that steer this run use neither.
376
389
  mind: signed && !steering ? mind : null, limits: limits, voice: voice })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: split.aside, ...pane,
377
- // A pane of its own beside the run's, the gutter between them.
378
- ml: gap, position: "relative", shrink: 0, minH: 0, style: split.asideStyle, children: [split.grip ? (_jsx(Gutter, { ...split.grip, label: "Resize the side panel", inset: pane.borderWidth })) : null, _jsx(SideProvider, { value: split.side, children: desk_(false) })] })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: "Rename this run" }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": "The run\u2019s name", maxLength: 512, onKeyDown: (e) => {
390
+ // A pane of its own beside the run's, the gutter between them (useSplit reads it as the inline-start margin).
391
+ marginInlineStart: gap, position: "relative", shrink: 0, minH: 0, style: split.asideStyle, children: [split.grip ? (_jsx(Gutter, { ...split.grip, label: say('Resize the side panel'), inset: pane.borderWidth })) : null, _jsx(SideProvider, { value: split.side, children: desk_(false) })] })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: say('Rename this run') }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": say('The run’s name'), maxLength: 512, onKeyDown: (e) => {
379
392
  if (e.key === 'Enter')
380
393
  save();
381
- } }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || !name.trim(), onPress: save, children: "Save" })] })] }) })] }));
394
+ } }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children: say('Cancel') }), _jsx(Button, { size: "sm", disabled: busy || !name.trim(), onPress: save, children: say('Save') })] })] }) })] }));
382
395
  }
383
396
  /** What a setup run does, said once per browser the first time one is watched. */
384
397
  function Onboarding({ onDone }) {
398
+ const { t } = useI18n();
385
399
  const steps = [
386
- 'Explore, install and check the codebase',
387
- 'Name the secrets it needs — you set their values',
388
- 'Propose the environment for you to review and save',
400
+ t('Explore, install and check the codebase'),
401
+ t('Name the secrets it needs — you set their values'),
402
+ t('Propose the environment for you to review and save'),
389
403
  ];
390
- return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: "Set up a cloud environment" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "An environment lets every later run install, start, test and check its changes the way an engineer does. Setup is agent-led and takes several minutes. The agent will:" }), _jsx(YStack, { gap: "$1.5", children: steps.map((s, i) => (_jsxs(XStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", width: 12, children: String(i + 1) }), _jsx(SizableText, { size: "$2", color: "$ink", children: s })] }, s))) }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Interrupt anytime to steer the agent or ask it questions." }), _jsx(Button, { size: "sm", onPress: onDone, children: "Got it" })] }));
404
+ return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: t('Set up a cloud environment') }), _jsx(SizableText, { size: "$2", color: "$soft", children: [
405
+ t('An environment lets every later run install, start, test and check its changes the way an engineer does.'),
406
+ t('Setup is agent-led and takes several minutes.'),
407
+ t('The agent will:'),
408
+ ].join(' ') }), _jsx(YStack, { gap: "$1.5", children: steps.map((s, i) => (_jsxs(XStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", width: 12, children: String(i + 1) }), _jsx(SizableText, { size: "$2", color: "$ink", children: s })] }, s))) }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('Interrupt anytime to steer the agent or ask it questions.') }), _jsx(Button, { size: "sm", onPress: onDone, children: t('Got it') })] }));
391
409
  }
package/lib/say.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { Say } from '@hanzo/i18n/react';
2
+ export declare const english: Say;
package/lib/say.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `t` where no component is asking: the English, its `{name}` arguments filled.
3
+ * A helper that builds words outside a render (a rail's rows, a formatter)
4
+ * takes the caller's @hanzo/i18n `t` and falls back to this.
5
+ */
6
+ import { translate } from '@hanzo/i18n';
7
+ export const english = ((source, values) => translate({}, source, values));
package/lib/scheduled.js CHANGED
@@ -12,16 +12,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  * a read every half minute keeps them right when the stream is down. A new task
13
13
  * is said in plain words — "every Monday at 9am" — and the platform says back
14
14
  * what it understood before anything is created.
15
+ *
16
+ * Its words are the reader's language (@hanzo/i18n `t`); a task's name and
17
+ * prompt, a run's title, summary and error, and what the platform says back
18
+ * are drawn as given.
15
19
  */
16
20
  import { SizableText, XStack, YStack } from '@hanzo/gui';
17
21
  import { Clock, ExternalLink, MoreHorizontal, Pencil, Play, Plus, ScrollText, Trash2 } from '@hanzogui/lucide-icons-2';
22
+ import { useI18n } from '@hanzo/i18n/react';
18
23
  import { Button, DropdownMenu, Input, Switch, Textarea } from '@hanzo/ui';
19
24
  import { StatusDot } from '@hanzo/ui/chat';
20
25
  import { ModelPicker } from '@hanzo/ui/models';
21
26
  import { useLimits } from '@hanzo/ui/product/useLimits';
22
27
  import { useEffect, useMemo, useRef, useState } from 'react';
23
28
  import { automations, create, cron, events, jobs, remove, save, start, words } from './api/auto.js';
24
- import { Refusal } from './api/call.js';
29
+ import { Refusal, told } from './api/call.js';
25
30
  import { ENSO, limits as readLimits, models } from './api/models.js';
26
31
  import { Confirm, Field, Line, Sheet } from './customize/ui.js';
27
32
  import { local, PLAN, shaped, tidy, unplanned, unready, When, zone } from './automations.js';
@@ -92,12 +97,13 @@ function useLive(on) {
92
97
  return live;
93
98
  }
94
99
  export function Scheduled() {
100
+ const { t } = useI18n();
95
101
  const host = useHost();
96
- const t = useTarget();
102
+ const target = useTarget();
97
103
  const signed = Boolean(host.person);
98
104
  const [tick, setTick] = useState(0);
99
- const tasks = useRead(signed ? () => automations(t) : null, [], [t, signed, tick]);
100
- const runs = useRead(signed ? () => jobs(t, { limit: 50 }) : null, [], [t, signed, tick]);
105
+ const tasks = useRead(signed ? () => automations(target) : null, [], [target, signed, tick]);
106
+ const runs = useRead(signed ? () => jobs(target, { limit: 50 }) : null, [], [target, signed, tick]);
101
107
  const [shownTasks, setTasks] = useState([]);
102
108
  const [shownRuns, setRuns] = useState([]);
103
109
  useEffect(() => setTasks(tasks.value), [tasks.value]);
@@ -134,58 +140,62 @@ export function Scheduled() {
134
140
  await work();
135
141
  }
136
142
  catch (e) {
137
- setProblem(unplanned(e) ? PLAN : `${what}: ${e.message}`);
143
+ setProblem(unplanned(e) ? t(PLAN) : t('{name}: {error}', { name: what, error: told(e, t) }));
138
144
  }
139
145
  };
140
146
  const flip = (a, on) => act(a.name, async () => {
141
- const next = await save(t, a.id, { enabled: on });
147
+ const next = await save(target, a.id, { enabled: on });
142
148
  setTasks((was) => was.map((x) => (x.id === next.id ? next : x)));
143
149
  });
144
150
  const run = (a) => act(a.name, async () => {
145
- const r = await start(t, a.id);
151
+ const r = await start(target, a.id);
146
152
  if (r.status === 'skipped')
147
- setProblem(`${a.name}: not started, the previous run is still going.`);
153
+ setProblem(t('{name}: not started, the previous run is still going.', { name: a.name }));
148
154
  setTick((n) => n + 1);
149
155
  });
150
156
  const edit = (id) => host.go(path({ kind: 'automation', id }));
151
- return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", children: "Scheduled tasks" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "What runs without you watching: prompts on a schedule, background runs, Studio renders and workflows." })] }), _jsxs(Button, { size: "sm", shrink: 0, disabled: !signed, onPress: () => setMaking(''), children: [_jsx(Plus, { size: 14 }), "New task"] })] }), _jsx(Line, { children: problem }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", children: "Tasks" }), !signed ? (_jsx(Said, { says: "Sign in to see your scheduled tasks.", children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) })) : tasks.error && !shownTasks.length ? (_jsx(Said, { says: tasks.error.message })) : tasks.loading && !shownTasks.length ? (_jsx(Said, { says: "Reading your tasks\u2026" })) : !shownTasks.length ? (_jsx(Said, { says: "No scheduled tasks yet. Ask Chat or Dev to do something on a schedule, or start one here.", children: _jsx(Button, { size: "sm", onPress: () => setMaking(''), children: "New task" }) })) : (_jsx(YStack, { role: "list", "aria-label": "Your tasks", borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownTasks.map((a, i) => (_jsx(Task, { a: a, first: i === 0, onOpen: () => edit(a.id), onSwitch: (on) => void flip(a, on), onRun: () => void run(a), onDelete: () => setDeleting(a) }, a.id))) }))] }), signed ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", flex: 1, children: "Activity" }), _jsx(SizableText, { size: "$1", color: "$soft", children: live ? 'Live' : '' })] }), runs.error && !shownRuns.length ? (_jsx(Said, { says: runs.error.message })) : runs.loading && !shownRuns.length ? (_jsx(Said, { says: "Reading what ran\u2026" })) : !shownRuns.length ? (_jsx(Said, { says: "Nothing has run yet. A task's runs, Dev's background runs and Studio's renders show here as they go." })) : (_jsx(YStack, { role: "list", "aria-label": "Activity", borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownRuns.map((j, i) => (_jsx(Ran, { j: j, first: i === 0, now: now, onOpen: () => setLooking(j) }, j.id))) }))] })) : null] }), _jsx(Make, { prompt: making, onClose: () => setMaking(null), onMade: (a) => {
157
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", children: t('Scheduled tasks') }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('What runs without you watching: prompts on a schedule, background runs, Studio renders and workflows.') })] }), _jsxs(Button, { size: "sm", shrink: 0, disabled: !signed, onPress: () => setMaking(''), children: [_jsx(Plus, { size: 14 }), t('New task')] })] }), _jsx(Line, { children: problem }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", children: t('Tasks') }), !signed ? (_jsx(Said, { says: t('Sign in to see your scheduled tasks.'), children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: t('Sign in') }) })) : tasks.error && !shownTasks.length ? (_jsx(Said, { says: told(tasks.error, t) })) : tasks.loading && !shownTasks.length ? (_jsx(Said, { says: t('Reading your tasks…') })) : !shownTasks.length ? (_jsx(Said, { says: t('No scheduled tasks yet. Ask Chat or Dev to do something on a schedule, or start one here.'), children: _jsx(Button, { size: "sm", onPress: () => setMaking(''), children: t('New task') }) })) : (_jsx(YStack, { role: "list", "aria-label": t('Your tasks'), borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownTasks.map((a, i) => (_jsx(Task, { a: a, first: i === 0, onOpen: () => edit(a.id), onSwitch: (on) => void flip(a, on), onRun: () => void run(a), onDelete: () => setDeleting(a) }, a.id))) }))] }), signed ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", flex: 1, children: t('Activity') }), _jsx(SizableText, { size: "$1", color: "$soft", children: live ? t('Live') : '' })] }), runs.error && !shownRuns.length ? (_jsx(Said, { says: told(runs.error, t) })) : runs.loading && !shownRuns.length ? (_jsx(Said, { says: t('Reading what ran…') })) : !shownRuns.length ? (_jsx(Said, { says: t("Nothing has run yet. A task's runs, Dev's background runs and Studio's renders show here as they go.") })) : (_jsx(YStack, { role: "list", "aria-label": t('Activity'), borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownRuns.map((j, i) => (_jsx(Ran, { j: j, first: i === 0, now: now, onOpen: () => setLooking(j) }, j.id))) }))] })) : null] }), _jsx(Make, { prompt: making, onClose: () => setMaking(null), onMade: (a) => {
152
158
  setTasks((was) => [a, ...was.filter((x) => x.id !== a.id)]);
153
- setProblem(`Scheduled: ${a.name}.`);
154
- } }), _jsx(Detail, { j: looking, onClose: () => setLooking(null), now: now }), deleting ? (_jsx(Confirm, { what: deleting.name, says: "It stops running, and its schedule and its runs go with it. A run going now is stopped.", open: true, onOpenChange: (o) => !o && setDeleting(null), onYes: async () => {
155
- await remove(t, deleting.id);
159
+ setProblem(t('Scheduled: {name}.', { name: a.name }));
160
+ } }), _jsx(Detail, { j: looking, onClose: () => setLooking(null), now: now }), deleting ? (_jsx(Confirm, { what: deleting.name, says: t('It stops running, and its schedule and its runs go with it. A run going now is stopped.'), open: true, onOpenChange: (o) => !o && setDeleting(null), onYes: async () => {
161
+ await remove(target, deleting.id);
156
162
  setTasks((was) => was.filter((x) => x.id !== deleting.id));
157
163
  setDeleting(null);
158
164
  } })) : null] }));
159
165
  }
160
166
  /** One task: what it is and when it runs next, its switch, and its menu. */
161
167
  function Task({ a, first, onOpen, onSwitch, onRun, onDelete }) {
168
+ const { t } = useI18n();
162
169
  const when = a.draft
163
- ? 'Needs instructions'
164
- : [words(a.schedule), a.enabled && a.next ? `next ${local(a.next)}` : a.schedule.kind !== 'manual' && !a.enabled ? 'paused' : ''].filter(Boolean).join(' · ');
165
- return (_jsxs(XStack, { role: "listitem", items: "center", gap: "$3", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": `Open ${a.name}`, onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", cursor: "pointer", style: { textAlign: 'left' }, children: [_jsx(YStack, { width: 32, height: 32, rounded: "$3", bg: "$raised", items: "center", justify: "center", shrink: 0, "aria-hidden": true, children: _jsx(Clock, { size: 16 }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, shrink: 1, children: a.name }), _jsx(Badge, { children: SOURCE[a.source] }), a.draft ? _jsx(Badge, { children: "Draft" }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: when })] })] }), _jsx(Switch, { checked: a.enabled, disabled: a.draft, onCheckedChange: onSwitch, "aria-label": `${a.name} on` }), _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": `Actions for ${a.name}`, width: 30, height: 30, items: "center", justify: "center", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(MoreHorizontal, { size: 16 }) }), items: [
166
- ...(a.draft ? [] : [{ key: 'run', label: 'Run now', icon: _jsx(Play, { size: 16 }), onSelect: onRun }]),
167
- { key: 'edit', label: 'Edit', icon: _jsx(Pencil, { size: 16 }), onSelect: onOpen },
168
- { key: 'delete', label: 'Delete', icon: _jsx(Trash2, { size: 16 }), onSelect: onDelete },
170
+ ? t('Needs instructions')
171
+ : [words(a.schedule, t), a.enabled && a.next ? t('next {when}', { when: local(a.next) }) : a.schedule.kind !== 'manual' && !a.enabled ? t('paused') : ''].filter(Boolean).join(' · ');
172
+ return (_jsxs(XStack, { role: "listitem", items: "center", gap: "$3", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": t('Open {name}', { name: a.name }), onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [_jsx(YStack, { width: 32, height: 32, rounded: "$3", bg: "$raised", items: "center", justify: "center", shrink: 0, "aria-hidden": true, children: _jsx(Clock, { size: 16 }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, shrink: 1, children: a.name }), _jsx(Badge, { children: t(SOURCE[a.source]) }), a.draft ? _jsx(Badge, { children: t('Draft') }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: when })] })] }), _jsx(Switch, { checked: a.enabled, disabled: a.draft, onCheckedChange: onSwitch, "aria-label": t('{name} on', { name: a.name }) }), _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": t('Actions for {name}', { name: a.name }), width: 30, height: 30, items: "center", justify: "center", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(MoreHorizontal, { size: 16 }) }), items: [
173
+ ...(a.draft ? [] : [{ key: 'run', label: t('Run now'), icon: _jsx(Play, { size: 16 }), onSelect: onRun }]),
174
+ { key: 'edit', label: t('Edit'), icon: _jsx(Pencil, { size: 16 }), onSelect: onOpen },
175
+ { key: 'delete', label: t('Delete'), icon: _jsx(Trash2, { size: 16 }), onSelect: onDelete },
169
176
  ] })] }));
170
177
  }
171
178
  /** One run in Activity. */
172
179
  function Ran({ j, first, now, onOpen }) {
173
- return (_jsx(XStack, { role: "listitem", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { render: "button", "aria-label": `${j.title}: ${SAID[j.state]}`, onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", px: "$3", py: "$2.5", cursor: "pointer", style: { textAlign: 'left' }, children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: j.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: about(j, now) })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: j.state === 'running' && j.progress !== null ? `${Math.round(j.progress * 100)}%` : SAID[j.state] })] }) }));
180
+ const { t } = useI18n();
181
+ return (_jsx(XStack, { role: "listitem", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { render: "button", "aria-label": t('{title}: {state}', { title: j.title, state: t(SAID[j.state]) }), onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", px: "$3", py: "$2.5", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: j.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: about(j, now, t) })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: j.state === 'running' && j.progress !== null ? `${Math.round(j.progress * 100)}%` : t(SAID[j.state]) })] }) }));
174
182
  }
175
183
  /** One run, opened: how it went, its log, what it made and what it cost. */
176
184
  function Detail({ j, onClose, now }) {
185
+ const { t } = useI18n();
177
186
  const host = useHost();
178
- return (_jsx(Sheet, { title: j?.title ?? 'Run', open: Boolean(j), onOpenChange: (o) => !o && onClose(), width: 520, children: j ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsxs(SizableText, { size: "$2", color: "$ink", children: [SAID[j.state], j.status && j.status !== j.state ? ` (${j.status})` : ''] })] }), _jsx(Facts, { rows: [
179
- ['From', SOURCE[j.product]],
180
- ['Started', j.started ? local(j.started) : '—'],
181
- ['Took', lasted(j.started, j.finished, now) || '—'],
182
- ['Cost', j.cost !== null ? dollars(j.cost) : 'Not known yet'],
183
- ] }), j.error ? (_jsx(SizableText, { size: "$2", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: j.error })) : null, j.summary ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: j.summary })) : null, j.outputs.length ? (_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "What it made" }), _jsx(Outputs, { ids: j.outputs })] })) : null, _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", children: [j.session ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.go(j.session), children: [_jsx(ScrollText, { size: 14 }), "Open its log"] })) : null, j.origin ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.open(j.origin), children: [_jsx(ExternalLink, { size: 14 }), "Open where it runs"] })) : null, j.automation ? (_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => host.go(path({ kind: 'automation', id: j.automation })), children: [_jsx(Pencil, { size: 14 }), "Edit the task"] })) : null] })] })) : null }));
187
+ return (_jsx(Sheet, { title: j?.title ?? t('Run'), open: Boolean(j), onOpenChange: (o) => !o && onClose(), width: 520, children: j ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsx(SizableText, { size: "$2", color: "$ink", children: j.status && j.status !== j.state ? t('{state} ({status})', { state: t(SAID[j.state]), status: j.status }) : t(SAID[j.state]) })] }), _jsx(Facts, { rows: [
188
+ [t('From'), t(SOURCE[j.product])],
189
+ [t('Started'), j.started ? local(j.started) : '—'],
190
+ [t('Took'), lasted(j.started, j.finished, now) || '—'],
191
+ [t('Cost'), j.cost !== null ? dollars(j.cost) : t('Not known yet')],
192
+ ] }), j.error ? (_jsx(SizableText, { size: "$2", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: j.error })) : null, j.summary ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: j.summary })) : null, j.outputs.length ? (_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: t('What it made') }), _jsx(Outputs, { ids: j.outputs })] })) : null, _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", children: [j.session ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.go(j.session), children: [_jsx(ScrollText, { size: 14 }), t('Open its log')] })) : null, j.origin ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.open(j.origin), children: [_jsx(ExternalLink, { size: 14 }), t('Open where it runs')] })) : null, j.automation ? (_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => host.go(path({ kind: 'automation', id: j.automation })), children: [_jsx(Pencil, { size: 14 }), t('Edit the task')] })) : null] })] })) : null }));
184
193
  }
185
194
  /** A new task: what it does, when in plain words, and the model it thinks with. */
186
195
  function Make({ prompt, onClose, onMade }) {
196
+ const { t } = useI18n();
187
197
  const host = useHost();
188
- const t = useTarget();
198
+ const target = useTarget();
189
199
  const signed = Boolean(host.person);
190
200
  const open = prompt !== null;
191
201
  const [name, setName] = useState('');
@@ -193,14 +203,15 @@ function Make({ prompt, onClose, onMade }) {
193
203
  const [said, setSaid] = useState('');
194
204
  const [read, setRead] = useState(null);
195
205
  const [reading, setReading] = useState(false);
196
- const [unread, setUnread] = useState('');
206
+ // Why the platform could not read the words: its refusal, said when drawn.
207
+ const [unread, setUnread] = useState(null);
197
208
  const [picking, setPicking] = useState(false);
198
209
  const [picked, setPicked] = useState(() => shaped('daily', { kind: 'manual' }));
199
210
  const [model, setModel] = useState(null);
200
211
  const [busy, setBusy] = useState(false);
201
212
  const [note, setNote] = useState('');
202
- const catalog = useRead(signed && open ? () => models(t) : null, [], [t, signed, open]);
203
- const { limits } = useLimits(signed && open ? (signal) => readLimits(t, signal) : null, t);
213
+ const catalog = useRead(signed && open ? () => models(target) : null, [], [target, signed, open]);
214
+ const { limits } = useLimits(signed && open ? (signal) => readLimits(target, signal) : null, target);
204
215
  // Each opening starts from what it was handed.
205
216
  useEffect(() => {
206
217
  if (prompt === null)
@@ -209,7 +220,7 @@ function Make({ prompt, onClose, onMade }) {
209
220
  setName(titled(prompt));
210
221
  setSaid('');
211
222
  setRead(null);
212
- setUnread('');
223
+ setUnread(null);
213
224
  setPicking(false);
214
225
  setNote('');
215
226
  }, [prompt]);
@@ -218,22 +229,22 @@ function Make({ prompt, onClose, onMade }) {
218
229
  const text = said.trim();
219
230
  if (!open || !text || picking) {
220
231
  setRead(null);
221
- setUnread('');
232
+ setUnread(null);
222
233
  return;
223
234
  }
224
235
  const ctl = new AbortController();
225
236
  const id = setTimeout(() => {
226
237
  setReading(true);
227
- cron(t, text, zone(), { signal: ctl.signal })
238
+ cron(target, text, zone(), { signal: ctl.signal })
228
239
  .then((p) => {
229
240
  setRead(p);
230
- setUnread('');
241
+ setUnread(null);
231
242
  })
232
243
  .catch((e) => {
233
244
  if (ctl.signal.aborted)
234
245
  return;
235
246
  setRead(null);
236
- setUnread(e.message);
247
+ setUnread(e);
237
248
  })
238
249
  .finally(() => !ctl.signal.aborted && setReading(false));
239
250
  }, 450);
@@ -241,34 +252,34 @@ function Make({ prompt, onClose, onMade }) {
241
252
  clearTimeout(id);
242
253
  ctl.abort();
243
254
  };
244
- }, [said, open, picking, t]);
255
+ }, [said, open, picking, target]);
245
256
  const when = picking ? picked : (read?.schedule ?? null);
246
- const wrong = when ? unready(when) : '';
257
+ const wrong = when ? t(unready(when)) : '';
247
258
  const make = async () => {
248
259
  if (!name.trim())
249
- return setNote('Name it first.');
260
+ return setNote(t('Name it first.'));
250
261
  if (!asks.trim())
251
- return setNote('Say what it should do.');
262
+ return setNote(t('Say what it should do.'));
252
263
  if (!when)
253
- return setNote(said.trim() ? 'Say when in other words, or choose instead.' : 'Say when it runs.');
264
+ return setNote(said.trim() ? t('Say when in other words, or choose instead.') : t('Say when it runs.'));
254
265
  if (wrong)
255
266
  return setNote(wrong);
256
267
  setBusy(true);
257
268
  setNote('');
258
269
  try {
259
270
  const d = { name: name.trim(), instructions: asks.trim(), model, schedule: when, permissions: 'ask', notify: true, postTo: [], enabled: true, source: 'dev' };
260
- onMade(await create(t, tidy(d)));
271
+ onMade(await create(target, tidy(d)));
261
272
  onClose();
262
273
  }
263
274
  catch (e) {
264
- setNote(unplanned(e) ? PLAN : e.message);
275
+ setNote(unplanned(e) ? t(PLAN) : told(e, t));
265
276
  }
266
277
  finally {
267
278
  setBusy(false);
268
279
  }
269
280
  };
270
- const heard = useMemo(() => (read ? [read.says || words(read.schedule), read.next ? `next ${local(read.next)}` : ''].filter(Boolean).join(' · ') : ''), [read]);
271
- return (_jsx(Sheet, { title: "New task", open: open, onOpenChange: (o) => !o && !busy && onClose(), width: 560, children: _jsxs(YStack, { gap: "$4", children: [_jsx(Field, { label: "Name", children: _jsx(Input, { value: name, onChangeText: setName, placeholder: "Monday summary", "aria-label": "Task name" }) }), _jsx(Field, { label: "What it does", hint: "Each run is one Dev run in this organization, as you. It reads and proposes; it changes nothing on its own.", children: _jsx(Textarea, { value: asks, onChangeText: setAsks, placeholder: "Summarize what changed in my repositories this week, and what needs me.", "aria-label": "What it does", rows: 5 }) }), _jsx(Field, { label: "When", hint: picking ? undefined : 'In your own words: every weekday at 8am, every 5 minutes, first of the month.', children: picking ? (_jsxs(YStack, { gap: "$2", children: [_jsx(When, { s: picked, onChange: setPicked }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(false), children: "Say it instead" }) })] })) : (_jsxs(YStack, { gap: "$2", children: [_jsx(Input, { value: said, onChangeText: setSaid, placeholder: "Every Monday at 9am", "aria-label": "When it runs" }), _jsx(SizableText, { size: "$1", color: unread ? '$soft' : '$ink', role: "status", minH: 18, children: reading ? 'Reading…' : unread ? `${unread} Choose instead, or say it another way.` : heard }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(true), children: "Choose instead" }) })] })) }), _jsx(Field, { label: "Model", children: _jsx(XStack, { children: _jsx(ModelPicker, { size: "sm", name: "Model", models: catalog.value, scope: "chat", limits: limits, value: model ?? ENSO, onChange: (id) => setModel(id === ENSO ? null : id), loading: catalog.loading, error: catalog.error?.message ?? null }) }) }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || !signed, onPress: () => void make(), children: busy ? 'Creating…' : 'Create' })] })] }) }));
281
+ const heard = useMemo(() => (read ? [read.says || words(read.schedule, t), read.next ? t('next {when}', { when: local(read.next) }) : ''].filter(Boolean).join(' · ') : ''), [read, t]);
282
+ return (_jsx(Sheet, { title: t('New task'), open: open, onOpenChange: (o) => !o && !busy && onClose(), width: 560, children: _jsxs(YStack, { gap: "$4", children: [_jsx(Field, { label: t('Name'), children: _jsx(Input, { value: name, onChangeText: setName, placeholder: t('Monday summary'), "aria-label": t('Task name') }) }), _jsx(Field, { label: t('What it does'), hint: t('Each run is one Dev run in this organization, as you. It reads and proposes; it changes nothing on its own.'), children: _jsx(Textarea, { value: asks, onChangeText: setAsks, placeholder: t('Summarize what changed in my repositories this week, and what needs me.'), "aria-label": t('What it does'), rows: 5 }) }), _jsx(Field, { label: t('When'), hint: picking ? undefined : t('In your own words: every weekday at 8am, every 5 minutes, first of the month.'), children: picking ? (_jsxs(YStack, { gap: "$2", children: [_jsx(When, { s: picked, onChange: setPicked }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(false), children: t('Say it instead') }) })] })) : (_jsxs(YStack, { gap: "$2", children: [_jsx(Input, { value: said, onChangeText: setSaid, placeholder: t('Every Monday at 9am'), "aria-label": t('When it runs') }), _jsx(SizableText, { size: "$1", color: unread ? '$soft' : '$ink', role: "status", minH: 18, children: reading ? t('Reading…') : unread ? `${told(unread, t)} ${t('Choose instead, or say it another way.')}` : heard }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(true), children: t('Choose instead') }) })] })) }), _jsx(Field, { label: t('Model'), children: _jsx(XStack, { children: _jsx(ModelPicker, { size: "sm", name: t('Model'), models: catalog.value, scope: "chat", limits: limits, value: model ?? ENSO, onChange: (id) => setModel(id === ENSO ? null : id), loading: catalog.loading, error: catalog.error ? told(catalog.error, t) : null }) }) }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: busy || !signed, onPress: () => void make(), children: busy ? t('Creating…') : t('Create') })] })] }) }));
272
283
  }
273
284
  function Badge({ children }) {
274
285
  return (_jsx(SizableText, { size: "$1", color: "$soft", px: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: children }));
package/lib/section.d.ts CHANGED
@@ -16,6 +16,7 @@ export declare function useSessions(host: Host): Read<Session[]>;
16
16
  export declare function DevSection({ host, onPick, label, children, }: {
17
17
  host: Host;
18
18
  onPick?: () => void;
19
+ /** The runs' heading; absent, "Runs" in the reader's language. */
19
20
  label?: string;
20
21
  children?: ReactNode;
21
22
  }): import("react").JSX.Element;