@hanzo/build 0.2.79 → 0.2.81

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 (167) hide show
  1. package/README.md +22 -4
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +36 -35
  5. package/lib/api/agents.js +66 -29
  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/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.d.ts +3 -0
  67. package/lib/customize/agents.js +152 -55
  68. package/lib/customize/connectors.js +62 -48
  69. package/lib/customize/index.js +45 -14
  70. package/lib/customize/plugins.js +34 -22
  71. package/lib/customize/say.d.ts +13 -1
  72. package/lib/customize/say.js +15 -21
  73. package/lib/customize/skills.js +31 -22
  74. package/lib/customize/ui.d.ts +5 -9
  75. package/lib/customize/ui.js +26 -16
  76. package/lib/data.d.ts +1 -1
  77. package/lib/data.js +3 -2
  78. package/lib/desk.js +45 -33
  79. package/lib/door.js +35 -10
  80. package/lib/environment.js +25 -19
  81. package/lib/files.js +43 -34
  82. package/lib/find.js +12 -9
  83. package/lib/foot.js +7 -4
  84. package/lib/forge.js +61 -40
  85. package/lib/gear.d.ts +2 -1
  86. package/lib/gear.js +6 -4
  87. package/lib/git.js +29 -19
  88. package/lib/host.d.ts +3 -1
  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/meter.d.ts +1 -1
  93. package/lib/meter.js +14 -5
  94. package/lib/mic.js +12 -5
  95. package/lib/mind.d.ts +1 -0
  96. package/lib/mind.js +5 -3
  97. package/lib/nav.d.ts +6 -5
  98. package/lib/nav.js +37 -30
  99. package/lib/panel.d.ts +2 -1
  100. package/lib/panel.js +52 -36
  101. package/lib/plan.d.ts +4 -20
  102. package/lib/plan.js +26 -10
  103. package/lib/plans.js +49 -30
  104. package/lib/plugins/design.js +3 -2
  105. package/lib/plugins/index.d.ts +23 -4
  106. package/lib/plugins/index.js +11 -26
  107. package/lib/plugins/pdf.js +2 -1
  108. package/lib/plugins/project.js +6 -3
  109. package/lib/plugins/research.js +3 -2
  110. package/lib/plugins/sketch.js +6 -3
  111. package/lib/plugins/slides.js +3 -2
  112. package/lib/plugins/template.js +6 -3
  113. package/lib/plus.js +25 -13
  114. package/lib/prefs.d.ts +0 -14
  115. package/lib/prefs.js +2 -1
  116. package/lib/project.js +69 -62
  117. package/lib/prompt.d.ts +12 -11
  118. package/lib/prompt.js +38 -20
  119. package/lib/publish.js +17 -12
  120. package/lib/reply.js +6 -3
  121. package/lib/run.js +72 -54
  122. package/lib/say.d.ts +2 -0
  123. package/lib/say.js +7 -0
  124. package/lib/scheduled.js +54 -43
  125. package/lib/section.d.ts +1 -0
  126. package/lib/section.js +8 -2
  127. package/lib/settings/account.d.ts +1 -0
  128. package/lib/settings/account.js +31 -21
  129. package/lib/settings/billing.js +55 -28
  130. package/lib/settings/capabilities.js +26 -15
  131. package/lib/settings/card.js +19 -12
  132. package/lib/settings/code.js +22 -11
  133. package/lib/settings/credits.d.ts +4 -4
  134. package/lib/settings/credits.js +34 -21
  135. package/lib/settings/environments.js +21 -14
  136. package/lib/settings/general.js +26 -14
  137. package/lib/settings/index.js +6 -3
  138. package/lib/settings/integrations.js +42 -28
  139. package/lib/settings/keys.js +37 -19
  140. package/lib/settings/list.d.ts +0 -5
  141. package/lib/settings/list.js +24 -15
  142. package/lib/settings/machines.d.ts +3 -0
  143. package/lib/settings/machines.js +63 -38
  144. package/lib/settings/members.js +23 -14
  145. package/lib/settings/memory.js +12 -6
  146. package/lib/settings/notifications.js +32 -19
  147. package/lib/settings/plan.d.ts +1 -1
  148. package/lib/settings/plan.js +40 -17
  149. package/lib/settings/privacy.js +26 -13
  150. package/lib/settings/ui.d.ts +3 -2
  151. package/lib/settings/ui.js +9 -3
  152. package/lib/settings/usage.js +7 -3
  153. package/lib/shelf.js +18 -15
  154. package/lib/side.d.ts +1 -26
  155. package/lib/side.js +11 -5
  156. package/lib/standing.d.ts +2 -10
  157. package/lib/standing.js +5 -3
  158. package/lib/suggest.d.ts +8 -28
  159. package/lib/suggest.js +39 -4
  160. package/lib/suggestions.js +14 -5
  161. package/lib/summary.d.ts +1 -1
  162. package/lib/summary.js +4 -1
  163. package/lib/sync.js +22 -12
  164. package/lib/tools.d.ts +4 -3
  165. package/lib/tools.js +10 -7
  166. package/lib/transcript.js +10 -8
  167. package/package.json +4 -2
@@ -7,8 +7,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
7
7
  * all adding is: the platform keeps no copy. Yours is the org's own skills —
8
8
  * written here, each with its switch — and the catalogue's skills it has added.
9
9
  * A skill written here is switched on when it is saved.
10
+ *
11
+ * Its words are the reader's language (@hanzo/i18n `t`); a skill's name,
12
+ * description and SKILL.md are the platform's or the org's, drawn as given.
10
13
  */
11
14
  import { SizableText, XStack, YStack } from '@hanzo/gui';
15
+ import { useI18n } from '@hanzo/i18n/react';
12
16
  import { BookOpen } from '@hanzogui/lucide-icons-2';
13
17
  import { Button, Input, Switch, Textarea } from '@hanzo/ui';
14
18
  import { Code } from '@hanzo/ui/chat';
@@ -21,14 +25,15 @@ import { say } from './say.js';
21
25
  import { Add, Confirm, day, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
22
26
  const PAGE = 60;
23
27
  export function Skills({ view, q, adding, onAdding, onView, onCount }) {
28
+ const { t } = useI18n();
24
29
  const host = useHost();
25
- const t = useTarget();
30
+ const target = useTarget();
26
31
  const signed = Boolean(host.person);
27
32
  // What every run in the org loads is an org admin's to change; a member reads it.
28
33
  const may = signed && host.admin;
29
- const own = useLoad(signed ? () => authored(t) : null, [], [t, signed]);
30
- const on = useLoad(signed ? () => active(t) : null, [], [t, signed]);
31
- const shelf = useLoad(view === 'discover' ? () => brand(t) : null, null, [t.api, view]);
34
+ const own = useLoad(signed ? () => authored(target) : null, [], [target, signed]);
35
+ const on = useLoad(signed ? () => active(target) : null, [], [target, signed]);
36
+ const shelf = useLoad(view === 'discover' ? () => brand(target) : null, null, [target.api, view]);
32
37
  const [editing, setEditing] = useState(null);
33
38
  const [reading, setReading] = useState(null);
34
39
  const [busy, setBusy] = useState('');
@@ -43,12 +48,12 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
43
48
  setBusy(name);
44
49
  setNote('');
45
50
  try {
46
- await toggle(t, next ? [tool(name)] : [], next ? [] : [tool(name)]);
51
+ await toggle(target, next ? [tool(name)] : [], next ? [] : [tool(name)]);
47
52
  on.reload();
48
- setNote(next ? `${name} is on` : `${name} is off`);
53
+ setNote(next ? t('{name} is on', { name }) : t('{name} is off', { name }));
49
54
  }
50
55
  catch (e) {
51
- setNote(say(e));
56
+ setNote(say(e, t));
52
57
  }
53
58
  finally {
54
59
  setBusy('');
@@ -64,20 +69,20 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
64
69
  own.reload();
65
70
  on.reload();
66
71
  onView('yours');
67
- setNote(fresh ? `${s.name} is saved and on` : `${s.name} is saved`);
72
+ setNote(fresh ? t('{name} is saved and on', { name: s.name }) : t('{name} is saved', { name: s.name }));
68
73
  }, onDeleted: (name) => {
69
74
  setEditing(null);
70
75
  own.reload();
71
76
  on.reload();
72
- setNote(`${name} is deleted`);
77
+ setNote(t('{name} is deleted', { name }));
73
78
  } })) : null;
74
79
  if (view === 'discover') {
75
80
  const all = shelf.value?.skills ?? [];
76
81
  const found = all.filter((e) => matches(q, e.name, e.description, e.product));
77
- return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title: "From Hanzo", detail: "Add one and every run in your organization can use it.", children: shelf.error && !shelf.value ? (_jsx(Failed, { error: shelf.error, onRetry: shelf.reload })) : !shelf.value ? (_jsx(Soft, { children: "Loading skills\u2026" })) : !found.length ? (_jsx(Soft, { children: all.length ? `No skill matches “${q.trim()}”.` : 'There are no skills to add yet.' })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: found.length === all.length ? `${all.length} skills` : `${found.length} of ${all.length} skills` }), _jsx(Grid, { label: "Skills to add", children: found.slice(0, shown).map((e) => (_jsx(Tile, { title: e.name, detail: e.description, meta: e.product, mark: _jsx(Mark, { name: e.name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading(e), action: may ? _jsx(Add, { name: e.name, added: lit.has(tool(e.name)), busy: busy === e.name, onPress: () => void flip(e.name, true) }) : undefined }, e.name))) }), found.length > shown ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => setShown(shown + PAGE), children: "Show more" }) })) : null] })) }), reader, editor] }));
82
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title: t('From Hanzo'), detail: t('Add one and every run in your organization can use it.'), children: shelf.error && !shelf.value ? (_jsx(Failed, { error: shelf.error, onRetry: shelf.reload })) : !shelf.value ? (_jsx(Soft, { children: t('Loading skills…') })) : !found.length ? (_jsx(Soft, { children: all.length ? t('No skill matches “{q}”.', { q: q.trim() }) : t('There are no skills to add yet.') })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: found.length === all.length ? t('{n} skills', { n: all.length }) : t('{found} of {n} skills', { found: found.length, n: all.length }) }), _jsx(Grid, { label: t('Skills to add'), children: found.slice(0, shown).map((e) => (_jsx(Tile, { title: e.name, detail: e.description, meta: e.product, mark: _jsx(Mark, { name: e.name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading(e), action: may ? _jsx(Add, { name: e.name, added: lit.has(tool(e.name)), busy: busy === e.name, onPress: () => void flip(e.name, true) }) : undefined }, e.name))) }), found.length > shown ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => setShown(shown + PAGE), children: t('Show more') }) })) : null] })) }), reader, editor] }));
78
83
  }
79
84
  if (!signed) {
80
- return _jsx(Visitor, { children: "Sign in to see your skills." });
85
+ return _jsx(Visitor, { children: t('Sign in to see your skills.') });
81
86
  }
82
87
  const ownShown = own.value.filter((s) => matches(q, s.name, s.description));
83
88
  const addedShown = added.filter((x) => matches(q, nameOf(x.name), x.description));
@@ -85,9 +90,11 @@ export function Skills({ view, q, adding, onAdding, onView, onCount }) {
85
90
  return (_jsxs(YStack, { gap: "$5", children: [_jsx(Line, { children: note }), error && !own.value.length && !on.value.length ? (_jsx(Failed, { error: error, onRetry: () => {
86
91
  own.reload();
87
92
  on.reload();
88
- } })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children: "Loading your skills\u2026" })) : !own.value.length && !added.length ? (_jsxs(Empty, { title: "No skills yet", detail: may ? 'Add one from Hanzo, or write your own.' : 'An org admin adds skills. Browse what there is.', children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse skills" }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "Write a skill" })) : null] })) : !ownShown.length && !addedShown.length ? (_jsx(Soft, { children: `No skill of yours matches “${q.trim()}”.` })) : (_jsxs(_Fragment, { children: [ownShown.length ? (_jsx(Part, { title: "Made by your team", detail: "Off keeps a skill but leaves it out of every run.", children: _jsx(Grid, { label: "Your skills", children: ownShown.map((s) => (_jsx(Tile, { title: s.name, detail: s.description || firstLine(s.content), meta: [s.source ? `From ${s.source}` : day(s.created) ? `Saved ${day(s.created)}` : '', s.admitted ? '' : 'Not in runs until an admin saves it'].filter(Boolean).join(' · ') || undefined, mark: _jsx(Mark, { name: s.name }), onOpen: () => setEditing(s), action: may ? (_jsx(Switch, { checked: lit.has(tool(s.name)), disabled: busy === s.name, onCheckedChange: (v) => void flip(s.name, v), "aria-label": `${s.name} on` })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: lit.has(tool(s.name)) ? 'On' : 'Off' })) }, s.id))) }) })) : null, addedShown.length ? (_jsx(Part, { title: "Added from Hanzo", detail: "On for every run in your organization.", children: _jsx(Grid, { label: "Added skills", children: addedShown.map((x) => {
93
+ } })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children: t('Loading your skills…') })) : !own.value.length && !added.length ? (_jsxs(Empty, { title: t('No skills yet'), detail: may ? t('Add one from Hanzo, or write your own.') : t('An org admin adds skills. Browse what there is.'), children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: t('Browse skills') }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: t('Write a skill') })) : null] })) : !ownShown.length && !addedShown.length ? (_jsx(Soft, { children: t('No skill of yours matches “{q}”.', { q: q.trim() }) })) : (_jsxs(_Fragment, { children: [ownShown.length ? (_jsx(Part, { title: t('Made by your team'), detail: t('Off keeps a skill but leaves it out of every run.'), children: _jsx(Grid, { label: t('Your skills'), children: ownShown.map((s) => (_jsx(Tile, { title: s.name, detail: s.description || firstLine(s.content), meta: [s.source ? t('From {source}', { source: s.source }) : day(s.created) ? t('Saved {date}', { date: day(s.created) }) : '', s.admitted ? '' : t('Not in runs until an admin saves it')]
94
+ .filter(Boolean)
95
+ .join(' · ') || undefined, mark: _jsx(Mark, { name: s.name }), onOpen: () => setEditing(s), action: may ? (_jsx(Switch, { checked: lit.has(tool(s.name)), disabled: busy === s.name, onCheckedChange: (v) => void flip(s.name, v), "aria-label": t('{name} on', { name: s.name }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: lit.has(tool(s.name)) ? t('On') : t('Off') })) }, s.id))) }) })) : null, addedShown.length ? (_jsx(Part, { title: t('Added from Hanzo'), detail: t('On for every run in your organization.'), children: _jsx(Grid, { label: t('Added skills'), children: addedShown.map((x) => {
89
96
  const name = nameOf(x.name);
90
- return (_jsx(Tile, { title: name, detail: x.description, mark: _jsx(Mark, { name: name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading({ name, description: x.description, product: '' }), action: may ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: busy === name, onPress: () => void flip(name, false), "aria-label": `Remove ${name}`, children: "Remove" })) : undefined }, x.name));
97
+ return (_jsx(Tile, { title: name, detail: x.description, mark: _jsx(Mark, { name: name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading({ name, description: x.description, product: '' }), action: may ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: busy === name, onPress: () => void flip(name, false), "aria-label": t('Remove {name}', { name }), children: t('Remove') })) : undefined }, x.name));
91
98
  }) }) })) : null] })), reader, editor] }));
92
99
  }
93
100
  const firstLine = (md) => md
@@ -96,13 +103,15 @@ const firstLine = (md) => md
96
103
  .find(Boolean) ?? '';
97
104
  /** One catalogue skill: what it is, its SKILL.md, and adding or removing it. */
98
105
  function Reader({ skill, added, signed, busy, onFlip, onClose, }) {
99
- const t = useTarget();
100
- const doc = useLoad(() => document(t, skill.name), '', [t.api, skill.name]);
101
- return (_jsxs(Sheet, { title: skill.name, open: true, onOpenChange: (o) => !o && onClose(), width: 720, children: [skill.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: skill.description })) : null, signed ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: added ? 'outline' : 'default', disabled: busy, onPress: () => onFlip(!added), children: added ? 'Remove' : 'Add to your skills' }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, children: added ? 'On for your organization.' : 'Every run in your organization can use it once added.' })] })) : null, doc.error ? (_jsx(Failed, { error: doc.error, onRetry: doc.reload })) : doc.loading && !doc.value ? (_jsx(Soft, { children: "Loading SKILL.md\u2026" })) : (_jsx(Code, { language: "SKILL.md", value: doc.value, children: _jsx(SizableText, { size: "$1", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap' }, children: doc.value }) }))] }));
106
+ const { t } = useI18n();
107
+ const target = useTarget();
108
+ const doc = useLoad(() => document(target, skill.name), '', [target.api, skill.name]);
109
+ return (_jsxs(Sheet, { title: skill.name, open: true, onOpenChange: (o) => !o && onClose(), width: 720, children: [skill.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: skill.description })) : null, signed ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: added ? 'outline' : 'default', disabled: busy, onPress: () => onFlip(!added), children: added ? t('Remove') : t('Add to your skills') }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, children: added ? t('On for your organization.') : t('Every run in your organization can use it once added.') })] })) : null, doc.error ? (_jsx(Failed, { error: doc.error, onRetry: doc.reload })) : doc.loading && !doc.value ? (_jsx(Soft, { children: t('Loading SKILL.md…') })) : (_jsx(Code, { language: "SKILL.md", value: doc.value, children: _jsx(SizableText, { size: "$1", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap' }, children: doc.value }) }))] }));
102
110
  }
103
111
  /** Write a skill, or revise one of the org's own. Its name is its id, so a saved skill keeps its name. */
104
112
  function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
105
- const t = useTarget();
113
+ const { t } = useI18n();
114
+ const target = useTarget();
106
115
  const [name, setName] = useState(skill?.name ?? '');
107
116
  const [description, setDescription] = useState(skill?.description ?? '');
108
117
  const [content, setContent] = useState(skill?.content ?? '');
@@ -113,21 +122,21 @@ function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
113
122
  setWorking(true);
114
123
  setNote('');
115
124
  try {
116
- const saved = await write(t, { name: name.trim(), description: description.trim(), content });
125
+ const saved = await write(target, { name: name.trim(), description: description.trim(), content });
117
126
  // A new skill is switched on as it is saved; a revised one keeps its switch.
118
127
  if (!skill)
119
- await toggle(t, [tool(saved.name)]);
128
+ await toggle(target, [tool(saved.name)]);
120
129
  onSaved(saved, !skill);
121
130
  }
122
131
  catch (e) {
123
- setNote(say(e));
132
+ setNote(say(e, t));
124
133
  }
125
134
  finally {
126
135
  setWorking(false);
127
136
  }
128
137
  };
129
- return (_jsxs(Sheet, { title: skill ? skill.name : 'New skill', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (_jsxs(SizableText, { size: "$2", color: "$soft", children: ["Read from ", skill.source, ". The next push of that repository replaces what is saved here."] })) : null, _jsx(Field, { label: "Name", hint: skill ? 'A skill keeps its name. Save under another name to make a new one.' : 'One lowercase word: letters, digits, _ or -.', children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Name", disabled: Boolean(skill), autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", hint: "One line. An agent reads it to decide whether it needs the skill.", children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: "How we triage an incoming issue", "aria-label": "Description" }) }), _jsx(Field, { label: "SKILL.md", hint: "Markdown: what it is for, when to use it, and the steps.", children: _jsx(Textarea, { value: content, onChangeText: setContent, placeholder: '# Triage\n\n1. Read the issue…', "aria-label": "SKILL.md", rows: 14, style: mono }) }), _jsx(Line, { children: note || (may ? '' : 'Only an org admin can write or delete skills.') }), _jsxs(XStack, { gap: "$2", items: "center", children: [skill && may ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Delete" })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: may ? 'Cancel' : 'Close' }), may ? (_jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: "Save" })) : null] }), skill ? (_jsx(Confirm, { what: skill.name, says: "The skill leaves your organization and every agent that names it.", open: asking, onOpenChange: setAsking, onYes: async () => {
130
- await remove(t, skill.id);
138
+ return (_jsxs(Sheet, { title: skill ? skill.name : t('New skill'), open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Read from {source}. The next push of that repository replaces what is saved here.', { source: skill.source }) })) : null, _jsx(Field, { label: t('Name'), hint: skill ? t('A skill keeps its name. Save under another name to make a new one.') : t('One lowercase word: letters, digits, _ or -.'), children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": t('Name'), disabled: Boolean(skill), autoCapitalize: "none" }) }), _jsx(Field, { label: t('Description'), hint: t('One line. An agent reads it to decide whether it needs the skill.'), children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: t('How we triage an incoming issue'), "aria-label": t('Description') }) }), _jsx(Field, { label: "SKILL.md", hint: t('Markdown: what it is for, when to use it, and the steps.'), children: _jsx(Textarea, { value: content, onChangeText: setContent, placeholder: t('# Triage\n\n1. Read the issue…'), "aria-label": "SKILL.md", rows: 14, fontFamily: "$mono" }) }), _jsx(Line, { children: note || (may ? '' : t('Only an org admin can write or delete skills.')) }), _jsxs(XStack, { gap: "$2", items: "center", children: [skill && may ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: t('Delete') })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: may ? t('Cancel') : t('Close') }), may ? (_jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: t('Save') })) : null] }), skill ? (_jsx(Confirm, { what: skill.name, says: t('The skill leaves your organization and every agent that names it.'), open: asking, onOpenChange: setAsking, onYes: async () => {
139
+ await remove(target, skill.id);
131
140
  onDeleted(skill.name);
132
141
  } })) : null] }));
133
142
  }
@@ -2,10 +2,6 @@ import { type ReactNode } from 'react';
2
2
  export declare const mono: {
3
3
  fontFamily: string;
4
4
  };
5
- /** A button that holds a card's text reads from the left, not from the middle a button centres it in. */
6
- export declare const left: {
7
- readonly textAlign: 'left';
8
- };
9
5
  /** Which half of a tab: what there is to browse and add (first), or what the org already has. */
10
6
  export type View = 'discover' | 'yours';
11
7
  /** What the shell hands a tab: which half, the search, the Add dialog's state, and where to say how many are yours. */
@@ -55,13 +51,13 @@ export declare function Featured({ title, detail, meta, mark, action, onOpen }:
55
51
  action?: ReactNode;
56
52
  onOpen: () => void;
57
53
  }): import("react").JSX.Element;
58
- /** A card's add control: Add while it is not yours, Added once it is. */
59
- export declare function Add({ name, added, busy, onPress, label }: {
54
+ /** A card's add control: Add (Use, for a template one is made from) while it is not yours, Added once it is. */
55
+ export declare function Add({ name, added, busy, onPress, use }: {
60
56
  name: string;
61
57
  added: boolean;
62
58
  busy?: boolean;
63
59
  onPress: () => void;
64
- label?: string;
60
+ use?: boolean;
65
61
  }): import("react").JSX.Element;
66
62
  /** A dialog with a title, a close control and a body that scrolls inside the window. */
67
63
  export declare function Sheet({ title, open, onOpenChange, width, children }: {
@@ -71,7 +67,7 @@ export declare function Sheet({ title, open, onOpenChange, width, children }: {
71
67
  width?: number;
72
68
  children: ReactNode;
73
69
  }): import("react").JSX.Element;
74
- /** Asks before a delete; says what goes with it. */
70
+ /** Asks before a delete; says what goes with it. `what` is the thing's name, `says` comes already said. */
75
71
  export declare function Confirm({ what, says, open, onOpenChange, onYes }: {
76
72
  what: string;
77
73
  says: string;
@@ -116,7 +112,7 @@ export declare function Field({ label, hint, children }: {
116
112
  hint?: string;
117
113
  children: ReactNode;
118
114
  }): import("react").JSX.Element;
119
- /** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
115
+ /** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. Labels come already said. */
120
116
  export declare function Choice<T extends string>({ value, options, onChange, label, }: {
121
117
  value: T;
122
118
  options: readonly {
@@ -5,16 +5,19 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  * featured card, a dialog with a title and a scrolling body, a delete that asks
6
6
  * first, the empty state that names the next step, a failure with its retry,
7
7
  * and the quiet line for loading and signed-out.
8
+ *
9
+ * Their own words are the reader's language (@hanzo/i18n `t`); a title, a
10
+ * line or a label a tab hands them comes already said, since it may be a
11
+ * name or a description that is drawn as given.
8
12
  */
9
13
  import { SizableText, XStack, YStack } from '@hanzo/gui';
14
+ import { useI18n } from '@hanzo/i18n/react';
10
15
  import { Check, Plus, X } from '@hanzogui/lucide-icons-2';
11
16
  import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
12
17
  import { useEffect, useState } from 'react';
13
18
  import { useHost } from '../host.js';
14
19
  import { say } from './say.js';
15
20
  export const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
16
- /** A button that holds a card's text reads from the left, not from the middle a button centres it in. */
17
- export const left = { textAlign: 'left' };
18
21
  /** Tells the shell how many are yours, whenever that changes. */
19
22
  export function useCount(n, onCount) {
20
23
  useEffect(() => onCount(n), [n, onCount]);
@@ -37,25 +40,29 @@ export function Mark({ name, logo, icon, size = 32 }) {
37
40
  */
38
41
  export function Tile({ title, detail, meta, mark, action, onOpen, }) {
39
42
  const body = (_jsxs(_Fragment, { children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: title }), detail ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 3, children: detail })) : null, meta ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, opacity: 0.8, children: meta })) : null] })] }));
40
- return (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$2", p: "$3", minW: 0, rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: onOpen ? { borderColor: '$edge' } : undefined, children: [onOpen ? (_jsx(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 0, gap: "$3", items: "flex-start", cursor: "pointer", style: left, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: body })) : (_jsx(XStack, { flex: 1, minW: 0, gap: "$3", items: "flex-start", children: body })), action] }));
43
+ return (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$2", p: "$3", minW: 0, rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: onOpen ? { borderColor: '$edge' } : undefined, children: [onOpen ? (_jsx(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 0, gap: "$3", items: "flex-start", cursor: "pointer", "$platform-web": { textAlign: 'start' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: body })) : (_jsx(XStack, { flex: 1, minW: 0, gap: "$3", items: "flex-start", children: body })), action] }));
41
44
  }
42
45
  /** The one card the shelf puts first, drawn larger. */
43
46
  export function Featured({ title, detail, meta, mark, action, onOpen }) {
44
- return (_jsxs(XStack, { items: "flex-start", gap: "$4", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", rowGap: "$3", children: [_jsxs(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 220, gap: "$4", items: "flex-start", cursor: "pointer", style: left, children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", textTransform: "uppercase", letterSpacing: 1, children: "Featured" }), _jsx(SizableText, { size: "$6", color: "$ink", numberOfLines: 1, children: title }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 3, children: detail }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: meta })] })] }), action] }));
47
+ const { t } = useI18n();
48
+ return (_jsxs(XStack, { items: "flex-start", gap: "$4", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", rowGap: "$3", children: [_jsxs(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 220, gap: "$4", items: "flex-start", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", textTransform: "uppercase", letterSpacing: 1, children: t('Featured') }), _jsx(SizableText, { size: "$6", color: "$ink", numberOfLines: 1, children: title }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 3, children: detail }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: meta })] })] }), action] }));
45
49
  }
46
- /** A card's add control: Add while it is not yours, Added once it is. */
47
- export function Add({ name, added, busy, onPress, label = 'Add' }) {
50
+ /** A card's add control: Add (Use, for a template one is made from) while it is not yours, Added once it is. */
51
+ export function Add({ name, added, busy, onPress, use = false }) {
52
+ const { t } = useI18n();
48
53
  if (added) {
49
- return (_jsxs(XStack, { "aria-label": `${name} is added`, gap: "$1", height: 32, items: "center", shrink: 0, children: [_jsx(Check, { size: 14 }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Added" })] }));
54
+ return (_jsxs(XStack, { "aria-label": t('{name} is added', { name }), gap: "$1", height: 32, items: "center", shrink: 0, children: [_jsx(Check, { size: 14 }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Added') })] }));
50
55
  }
51
- return (_jsxs(Button, { size: "sm", variant: "outline", "aria-label": `${label} ${name}`, disabled: busy, onPress: onPress, children: [_jsx(Plus, { size: 14 }), " ", label] }));
56
+ return (_jsxs(Button, { size: "sm", variant: "outline", "aria-label": use ? t('Use {name}', { name }) : t('Add {name}', { name }), disabled: busy, onPress: onPress, children: [_jsx(Plus, { size: 14 }), " ", use ? t('Use') : t('Add')] }));
52
57
  }
53
58
  /** A dialog with a title, a close control and a body that scrolls inside the window. */
54
59
  export function Sheet({ title, open, onOpenChange, width = 560, children }) {
55
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: width, showCloseButton: false, maxH: "88vh", gap: "$3", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { numberOfLines: 1, flex: 1, children: title }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsx(YStack, { gap: "$3", overflow: "scroll", minH: 0, shrink: 1, children: children })] }) }));
60
+ const { t } = useI18n();
61
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: width, showCloseButton: false, maxH: "88vh", gap: "$3", children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { numberOfLines: 1, flex: 1, children: title }), _jsx(XStack, { render: "button", "aria-label": t('Close'), p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsx(YStack, { gap: "$3", overflow: "scroll", minH: 0, shrink: 1, children: children })] }) }));
56
62
  }
57
- /** Asks before a delete; says what goes with it. */
63
+ /** Asks before a delete; says what goes with it. `what` is the thing's name, `says` comes already said. */
58
64
  export function Confirm({ what, says, open, onOpenChange, onYes }) {
65
+ const { t } = useI18n();
59
66
  const [busy, setBusy] = useState(false);
60
67
  const [note, setNote] = useState('');
61
68
  const yes = async () => {
@@ -66,13 +73,13 @@ export function Confirm({ what, says, open, onOpenChange, onYes }) {
66
73
  onOpenChange(false);
67
74
  }
68
75
  catch (e) {
69
- setNote(say(e));
76
+ setNote(say(e, t));
70
77
  }
71
78
  finally {
72
79
  setBusy(false);
73
80
  }
74
81
  };
75
- return (_jsxs(Sheet, { title: `Delete ${what}?`, open: open, onOpenChange: onOpenChange, width: 420, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onPress: () => void yes(), children: "Delete" })] })] }));
82
+ return (_jsxs(Sheet, { title: t('Delete {name}?', { name: what }), open: open, onOpenChange: onOpenChange, width: 420, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: t('Cancel') }), _jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onPress: () => void yes(), children: t('Delete') })] })] }));
76
83
  }
77
84
  /** The quiet line for loading, a search that matched nothing, and signed-out. */
78
85
  export function Soft({ children, action }) {
@@ -80,7 +87,8 @@ export function Soft({ children, action }) {
80
87
  }
81
88
  /** A read that failed: what went wrong in plain words, and the one control that asks again. */
82
89
  export function Failed({ error, onRetry }) {
83
- return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", role: "alert", children: [_jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: say(error) }), _jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: "Try again" })] }));
90
+ const { t } = useI18n();
91
+ return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", role: "alert", children: [_jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: say(error, t) }), _jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: t('Try again') })] }));
84
92
  }
85
93
  /** Nothing here yet: what this place holds, and the next step, as buttons. */
86
94
  export function Empty({ title, detail, children }) {
@@ -88,8 +96,9 @@ export function Empty({ title, detail, children }) {
88
96
  }
89
97
  /** What a signed-out visitor is told, with the one control that signs them in. */
90
98
  export function Visitor({ children }) {
99
+ const { t } = useI18n();
91
100
  const host = useHost();
92
- return (_jsx(Soft, { action: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }), children: children }));
101
+ return (_jsx(Soft, { action: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: t('Sign in') }), children: children }));
93
102
  }
94
103
  /** What the last action did, said once, where it was done. */
95
104
  export function Line({ children }) {
@@ -105,9 +114,10 @@ export function Part({ title, detail, action, children }) {
105
114
  export function Field({ label, hint, children }) {
106
115
  return (_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), children, hint ? (_jsx(SizableText, { size: "$1", color: "$soft", children: hint })) : null] }));
107
116
  }
108
- /** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
117
+ /** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. Labels come already said. */
109
118
  export function Choice({ value, options, onChange, label, }) {
110
- return (_jsx(XStack, { role: "group", "aria-label": label, p: 2, gap: 2, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", self: "flex-start", shrink: 0, children: options.map((o) => (_jsxs(XStack, { render: "button", "aria-pressed": o.id === value, "aria-label": o.count ? `${o.label}, ${o.count}` : o.label, onPress: () => onChange(o.id), items: "center", gap: "$1.5", px: "$3", py: "$1", rounded: "$2", bg: o.id === value ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: o.id === value ? '$ink' : '$soft', children: o.label }), o.count ? (_jsx(SizableText, { size: "$1", color: "$soft", fontVariant: ['tabular-nums'], children: o.count })) : null] }, o.id))) }));
119
+ const { t } = useI18n();
120
+ return (_jsx(XStack, { role: "group", "aria-label": label, p: 2, gap: 2, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", self: "flex-start", shrink: 0, children: options.map((o) => (_jsxs(XStack, { render: "button", "aria-pressed": o.id === value, "aria-label": o.count ? t('{label}, {count}', { label: o.label, count: o.count }) : o.label, onPress: () => onChange(o.id), items: "center", gap: "$1.5", px: "$3", py: "$1", rounded: "$2", bg: o.id === value ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: o.id === value ? '$ink' : '$soft', children: o.label }), o.count ? (_jsx(SizableText, { size: "$1", color: "$soft", fontVariant: ['tabular-nums'], children: o.count })) : null] }, o.id))) }));
111
121
  }
112
122
  /** A date from Unix seconds, or ''. */
113
123
  export const day = (s) => (s ? new Date(s * 1000).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : '');
package/lib/data.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface Read<T> {
12
12
  export declare function useRead<T>(load: (() => Promise<T>) | null, initial: T, key: unknown[]): Read<T>;
13
13
  /** How long a read is waited on before the screen says it is taking too long. */
14
14
  export declare const WAIT = 15000;
15
- /** A read that did not answer in time. */
15
+ /** A read that did not answer in time. Its message is ours, marked, so a screen drawing it through `t` says it in the reader's language. */
16
16
  export declare class Slow extends Error {
17
17
  constructor();
18
18
  }
package/lib/data.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * signed in is not attempted — the platform would refuse it and the refusal
7
7
  * would read as an outage.
8
8
  */
9
+ import { phrase } from '@hanzo/i18n';
9
10
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
11
  import { places } from './api/places.js';
11
12
  import { projects } from './api/projects.js';
@@ -45,10 +46,10 @@ export function useRead(load, initial, key) {
45
46
  }
46
47
  /** How long a read is waited on before the screen says it is taking too long. */
47
48
  export const WAIT = 15_000;
48
- /** A read that did not answer in time. */
49
+ /** A read that did not answer in time. Its message is ours, marked, so a screen drawing it through `t` says it in the reader's language. */
49
50
  export class Slow extends Error {
50
51
  constructor() {
51
- super('Hanzo is taking too long to answer.');
52
+ super(phrase('Hanzo is taking too long to answer.'));
52
53
  this.name = 'Slow';
53
54
  }
54
55
  }
package/lib/desk.js CHANGED
@@ -8,8 +8,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  * The sandbox outlives the run: kept when the run ends, parked while nobody
9
9
  * watches, retired a day after it parks. The bar under the tabs says which and
10
10
  * suspends or resumes it; a tab that needs it running resumes it (sandbox.ts).
11
+ *
12
+ * Its words are the reader's language (@hanzo/i18n `t`); a repository, branch,
13
+ * port, address, command and the sandbox's own error are drawn as given.
11
14
  */
12
15
  import { SizableText, XStack, YStack } from '@hanzo/gui';
16
+ import { useI18n } from '@hanzo/i18n/react';
13
17
  import { ExternalLink, Info, MoreHorizontal, RefreshCcw } from '@hanzogui/lucide-icons-2';
14
18
  import { Button, DropdownMenu } from '@hanzo/ui';
15
19
  import { PreviewFrame } from '@hanzo/ui/agents';
@@ -38,38 +42,39 @@ export const SEED = {
38
42
  const EVERY = 10_000;
39
43
  const PORTS = 5_000;
40
44
  export function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, side, sheet, onHide, }) {
45
+ const { t: say } = useI18n();
41
46
  const name = repo.split('/').filter(Boolean).pop() || repo;
42
47
  const lines = shell(events);
43
- const failed = failure(events);
48
+ const failed = failure(events, say);
44
49
  const box = useBox(sandbox);
45
50
  // Nothing is said of the sandbox until it has been read.
46
51
  const held = box.value?.state ?? '';
47
- const environ = () => side.open({ kind: 'environment', title: 'Environment' });
52
+ const environ = () => side.open({ kind: 'environment', title: say('Environment') });
48
53
  const kinds = [
49
- { id: 'preview', label: 'Preview', bleed: true, render: () => _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) },
54
+ { id: 'preview', label: say('Preview'), bleed: true, render: () => _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) },
50
55
  {
51
56
  id: 'artifacts',
52
- label: 'Artifacts',
57
+ label: say('Artifacts'),
53
58
  render: () => _jsx(Artifacts, { session: id, sandbox: sandbox, repo: repo, branch: branch, mode: mode, project: project, site: site, pr: pr, onEnvironment: environ }),
54
59
  },
55
- { id: 'files', label: 'Files', render: () => _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) },
56
- { id: 'diff', label: 'Diff', render: () => _jsx(Git, { session: id, title: title, live: live }) },
60
+ { id: 'files', label: say('Files'), render: () => _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) },
61
+ { id: 'diff', label: say('Diff'), render: () => _jsx(Git, { session: id, title: title, live: live }) },
57
62
  {
58
63
  id: 'terminal',
59
- label: 'Terminal',
64
+ label: say('Terminal'),
60
65
  bleed: true,
61
66
  render: () => _jsx(Terminal, { id: id, sandbox: sandbox, held: held, lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry }),
62
67
  },
63
- { id: 'desktop', label: 'Desktop', bleed: true, render: () => _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) },
68
+ { id: 'desktop', label: say('Desktop'), bleed: true, render: () => _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) },
64
69
  {
65
70
  id: 'environment',
66
- label: 'Environment',
67
- render: () => (_jsxs(YStack, { gap: "$2", children: [_jsx(Environment, { repo: name, busy: live && mode === 'setup' }), _jsx(SizableText, { size: "$2", color: "$ink", pt: "$4", children: "This run" }), _jsx(Fact, { label: "Run", value: id }), _jsx(Fact, { label: "Repository", value: name || '—' }), _jsx(Fact, { label: "Branch", value: branch || '—' }), _jsx(Fact, { label: "Base", value: base || '—' }), _jsx(Fact, { label: "Runs on", value: environment || 'sandbox' }), _jsx(Fact, { label: "Sandbox", value: sandbox || '—' }), _jsx(Fact, { label: "Mode", value: mode || 'build' })] })),
71
+ label: say('Environment'),
72
+ render: () => (_jsxs(YStack, { gap: "$2", children: [_jsx(Environment, { repo: name, busy: live && mode === 'setup' }), _jsx(SizableText, { size: "$2", color: "$ink", pt: "$4", children: say('This run') }), _jsx(Fact, { label: say('Run'), value: id }), _jsx(Fact, { label: say('Repository'), value: name || '—' }), _jsx(Fact, { label: say('Branch'), value: branch || '—' }), _jsx(Fact, { label: say('Base'), value: base || '—' }), _jsx(Fact, { label: say('Runs on'), value: environment || 'sandbox' }), _jsx(Fact, { label: say('Sandbox'), value: sandbox || '—' }), _jsx(Fact, { label: say('Mode'), value: mode || 'build' })] })),
68
73
  },
69
74
  ];
70
- return (_jsx(Panel, { side: side, kinds: kinds, sheet: sheet, onHide: onHide, label: "Run details",
75
+ return (_jsx(Panel, { side: side, kinds: kinds, sheet: sheet, onHide: onHide, label: say('Run details'),
71
76
  // An empty tab offers what this run made: its site and its pull request.
72
- suggested: [...(site ? [{ title: name || site, url: site }] : []), ...(pr.href ? [{ title: `Pull request ${pr.label}`, url: pr.href }] : [])], bar: sandbox ? _jsx(Held, { box: box, live: live }) : null, menu: _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": "Run actions", p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: [...menu, { key: 'details', label: 'Details', icon: _jsx(Info, { size: 16 }), onSelect: environ }] }) }));
77
+ suggested: [...(site ? [{ title: name || site, url: site }] : []), ...(pr.href ? [{ title: say('Pull request {pr}', { pr: pr.label }), url: pr.href }] : [])], bar: sandbox ? _jsx(Held, { box: box, live: live }) : null, menu: _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": say('Run actions'), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: [...menu, { key: 'details', label: say('Details'), icon: _jsx(Info, { size: 16 }), onSelect: environ }] }) }));
73
78
  }
74
79
  /**
75
80
  * The run's sandbox as it stands, and what moves it. It is read while the pane is
@@ -118,30 +123,33 @@ const until = (ms) => new Date(ms).toLocaleString(undefined, { weekday: 'short',
118
123
  * A sandbox a run is working in is not suspended from here.
119
124
  */
120
125
  function Held({ box, live }) {
126
+ const { t } = useI18n();
121
127
  const b = box.value;
122
128
  const state = b?.state ?? 'pending';
123
129
  const says = state === 'running'
124
- ? 'Sandbox running'
130
+ ? t('Sandbox running')
125
131
  : state === 'parked'
126
132
  ? b?.ends
127
- ? `Sandbox suspended · its files are kept until ${until(b.ends)}`
128
- : 'Sandbox suspended · its files are kept'
133
+ ? t('Sandbox suspended · its files are kept until {time}', { time: until(b.ends) })
134
+ : t('Sandbox suspended · its files are kept')
129
135
  : state === 'pending'
130
- ? 'Sandbox starting…'
136
+ ? t('Sandbox starting…')
131
137
  : state === 'gone'
132
- ? 'Sandbox ended'
133
- : `Sandbox unavailable${b?.error ? ` · ${b.error}` : ''}`;
134
- return (_jsxs(XStack, { px: "$3", py: "$1.5", gap: "$2", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", "aria-label": "Sandbox", children: [_jsx(YStack, { width: 7, height: 7, rounded: 99, bg: state === 'running' ? '$green10' : state === 'parked' ? '$soft' : '$borderColor' }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, children: box.note || says }), state === 'running' && !live ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: box.busy, onPress: () => box.act('park'), children: "Suspend" })) : state === 'parked' ? (_jsx(Button, { size: "sm", variant: "outline", disabled: box.busy, onPress: () => box.act('wake'), children: box.busy ? 'Resuming…' : 'Resume' })) : null] }));
138
+ ? t('Sandbox ended')
139
+ : b?.error
140
+ ? t('Sandbox unavailable · {error}', { error: b.error })
141
+ : t('Sandbox unavailable');
142
+ return (_jsxs(XStack, { px: "$3", py: "$1.5", gap: "$2", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", "aria-label": t('Sandbox'), children: [_jsx(YStack, { width: 7, height: 7, rounded: 99, bg: state === 'running' ? '$green10' : state === 'parked' ? '$soft' : '$borderColor' }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, children: box.note || says }), state === 'running' && !live ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: box.busy, onPress: () => box.act('park'), children: t('Suspend') })) : state === 'parked' ? (_jsx(Button, { size: "sm", variant: "outline", disabled: box.busy, onPress: () => box.act('wake'), children: box.busy ? t('Resuming…') : t('Resume') })) : null] }));
135
143
  }
136
144
  /** What a preview page says of itself, when it cannot serve. */
137
145
  const PREVIEW = 'hanzo-preview';
138
146
  /** How often a preview is opened again on its own before Retry is the person's to press. */
139
147
  const REMINTS = 2;
140
148
  const REMINT = 30_000;
141
- /** What a preview page's status means to the person looking at it, when it is not reopened. */
149
+ /** What a preview page's status means to the person looking at it, when it is not reopened, in the language of `say`. */
142
150
  const SAYS = {
143
- 401: () => 'This browser keeps the preview’s cookie out of a frame. Open it in a new tab.',
144
- 502: (port) => `Nothing answered on port ${port}. Start the server, then Retry.`,
151
+ 401: (_, say) => `${say('This browser keeps the preview’s cookie out of a frame.')} ${say('Open it in a new tab.')}`,
152
+ 502: (port, say) => `${say('Nothing answered on port {port}.', { port })} ${say('Start the server, then Retry.')}`,
145
153
  };
146
154
  /**
147
155
  * The Browser tab: what the run's sandbox serves, one port at a time, framed at
@@ -151,6 +159,7 @@ const SAYS = {
151
159
  * in a while; any other status is said, and Retry is the person's.
152
160
  */
153
161
  function Browser({ sandbox, held, site, live }) {
162
+ const { t: say } = useI18n();
154
163
  const t = useTarget();
155
164
  const [open, setOpen] = useState([]);
156
165
  const [port, setPort] = useState(0);
@@ -211,32 +220,33 @@ function Browser({ sandbox, held, site, live }) {
211
220
  setAttempt((a) => a + 1);
212
221
  return;
213
222
  }
214
- const say = SAYS[m.status];
215
- if (say || m.status === 409)
216
- setWhy(say ? say(port) : 'The sandbox stopped serving. Retry to open it again.');
223
+ const words = SAYS[m.status];
224
+ if (words || m.status === 409)
225
+ setWhy(words ? words(port, say) : `${say('The sandbox stopped serving.')} ${say('Retry to open it again.')}`);
217
226
  };
218
227
  window.addEventListener('message', hear);
219
228
  return () => window.removeEventListener('message', hear);
220
- }, [src, port]);
229
+ }, [src, port, say]);
221
230
  const tab = () => away(preview(t, sandbox, port)).catch((e) => setWhy(e.message));
222
231
  if (!running)
223
232
  return _jsx(Site, { site: site, live: live, held: held });
224
233
  if (!port)
225
234
  return _jsx(Site, { site: site, live: live, held: held });
226
235
  const host = open.find((p) => p.port === port)?.host ?? '';
227
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": "Reload the page", onPress: () => setAttempt((a) => a + 1), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), open.length > 1 ? (_jsx(XStack, { gap: "$1", children: open.map((p) => (_jsx(XStack, { render: "button", "aria-label": `Port ${p.port}`, onPress: () => setPort(p.port), px: "$2", py: "$1", rounded: "$2", bg: p.port === port ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: p.port === port ? '$ink' : '$soft', children: String(p.port) }) }, p.port))) })) : null, _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: host || `port ${port}` }), _jsx(XStack, { render: "button", "aria-label": "Open in a new tab", onPress: () => void tab(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) })] }), why ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: why }), _jsxs(XStack, { gap: "$2", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAttempt((a) => a + 1), children: "Retry" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => void tab(), children: "Open in a new tab" })] })] })) : src ? (_jsx("iframe", { ref: frame, src: src, title: `What the run serves on port ${port}`, sandbox: "allow-scripts allow-same-origin allow-forms allow-popups", style: { border: 0, width: '100%', height: '100%', display: 'block', background: '#fff' } })) : (_jsx(YStack, { flex: 1, items: "center", justify: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: `Opening port ${port}…` }) }))] }));
236
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": say('Reload the page'), onPress: () => setAttempt((a) => a + 1), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), open.length > 1 ? (_jsx(XStack, { gap: "$1", children: open.map((p) => (_jsx(XStack, { render: "button", "aria-label": say('Port {port}', { port: p.port }), onPress: () => setPort(p.port), px: "$2", py: "$1", rounded: "$2", bg: p.port === port ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: p.port === port ? '$ink' : '$soft', children: String(p.port) }) }, p.port))) })) : null, _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: host || say('port {port}', { port }) }), _jsx(XStack, { render: "button", "aria-label": say('Open in a new tab'), onPress: () => void tab(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) })] }), why ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: why }), _jsxs(XStack, { gap: "$2", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAttempt((a) => a + 1), children: say('Retry') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => void tab(), children: say('Open in a new tab') })] })] })) : src ? (_jsx("iframe", { ref: frame, src: src, title: say('What the run serves on port {port}', { port }), sandbox: "allow-scripts allow-same-origin allow-forms allow-popups", style: { border: 0, width: '100%', height: '100%', display: 'block', background: '#fff' } })) : (_jsx(YStack, { flex: 1, items: "center", justify: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: say('Opening port {port}…', { port }) }) }))] }));
228
237
  }
229
238
  /**
230
239
  * Where the run published its work, framed, with its address, a reload and the
231
240
  * page in a tab of its own; or what keeps its sandbox from serving.
232
241
  */
233
242
  function Site({ site, live, held }) {
243
+ const { t } = useI18n();
234
244
  const frame = useRef(null);
235
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": "Reload the page", onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: site || 'Nothing is serving yet' }), site ? (_jsx(Out, { href: site, label: "Open in a new tab", children: _jsx(XStack, { p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: site, title: "Where this run is published", device: "desktop", empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "Nothing is serving yet" }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: held === 'parked'
236
- ? 'The run’s sandbox is suspended. Resume it to see what it serves.'
245
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1.5", items: "center", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": t('Reload the page'), onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 14 }) }), _jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, style: mono, children: site || t('Nothing is serving yet') }), site ? (_jsx(Out, { href: site, label: t('Open in a new tab'), children: _jsx(XStack, { p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 14 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: site, title: t('Where this run is published'), device: "desktop", empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: t('Nothing is serving yet') }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: held === 'parked'
246
+ ? `${t('The run’s sandbox is suspended.')} ${t('Resume it to see what it serves.')}`
237
247
  : live
238
- ? 'When the run starts a server in its sandbox, the page opens here.'
239
- : 'Nothing listens in this run’s sandbox, and it published no site. Its work is on its branch.' })] }) })] }));
248
+ ? t('When the run starts a server in its sandbox, the page opens here.')
249
+ : `${t('Nothing listens in this run’s sandbox, and it published no site.')} ${t('Its work is on its branch.')}` })] }) })] }));
240
250
  }
241
251
  function Fact({ label, value }) {
242
252
  return (_jsxs(XStack, { gap: "$3", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: "$soft", width: 96, children: label }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 2, children: value })] }));
@@ -248,14 +258,16 @@ const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)', whiteSpace:
248
258
  * shell is a tmux session named for the run, so a reopened tab reattaches to it.
249
259
  */
250
260
  function Terminal({ id, sandbox, held, lines, live, failed, refused, retry, onRetry, }) {
261
+ const { t } = useI18n();
251
262
  const reachable = Boolean(sandbox) && held === 'running';
252
263
  // The shell while there is one, unless the log was asked for.
253
264
  const [log, setLog] = useState(false);
254
265
  const showShell = reachable && !log;
255
- return (_jsxs(YStack, { flex: 1, minH: 0, bg: "$background", children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1", borderBottomWidth: 1, borderColor: "$borderColor", items: "center", children: [reachable ? (_jsx(XStack, { render: "button", "aria-label": "Shell", onPress: () => setLog(false), px: "$2", py: "$1", rounded: "$2", bg: showShell ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: showShell ? '$ink' : '$soft', children: "Shell" }) })) : null, _jsx(XStack, { render: "button", "aria-label": "Agent log", onPress: () => setLog(true), px: "$2", py: "$1", rounded: "$2", bg: showShell ? 'transparent' : '$hover', children: _jsx(SizableText, { size: "$1", color: showShell ? '$soft' : '$ink', children: "Agent log" }) })] }), showShell ? _jsx(Door, { which: "terminal", sandbox: sandbox, held: held, session: `run-${id.replace(/^sess_/, '').slice(0, 12)}` }) : _jsx(Log, { lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry })] }));
266
+ return (_jsxs(YStack, { flex: 1, minH: 0, bg: "$background", children: [_jsxs(XStack, { px: "$2", py: "$1.5", gap: "$1", borderBottomWidth: 1, borderColor: "$borderColor", items: "center", children: [reachable ? (_jsx(XStack, { render: "button", "aria-label": t('Shell'), onPress: () => setLog(false), px: "$2", py: "$1", rounded: "$2", bg: showShell ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$1", color: showShell ? '$ink' : '$soft', children: t('Shell') }) })) : null, _jsx(XStack, { render: "button", "aria-label": t('Agent log'), onPress: () => setLog(true), px: "$2", py: "$1", rounded: "$2", bg: showShell ? 'transparent' : '$hover', children: _jsx(SizableText, { size: "$1", color: showShell ? '$soft' : '$ink', children: t('Agent log') }) })] }), showShell ? _jsx(Door, { which: "terminal", sandbox: sandbox, held: held, session: `run-${id.replace(/^sess_/, '').slice(0, 12)}` }) : _jsx(Log, { lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry })] }));
256
267
  }
257
268
  function Log({ lines, live, failed, refused, retry, onRetry, }) {
269
+ const { t } = useI18n();
258
270
  const prompt = 'workspace';
259
271
  const idle = lines.length === 0 && !live;
260
- return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$3", gap: "$1", children: [idle ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", py: "$8", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: refused || (failed ? `This run stopped before its first command: ${failed}` : 'This run finished without writing a command.') }), refused ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: retry })) : null] })) : (lines.map((line, i) => line.role === 'cmd' ? (_jsxs(XStack, { gap: "$2", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(SizableText, { flex: 1, size: "$1", color: "$ink", style: mono, children: line.text })] }, i)) : (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, children: line.text }, i)))), live ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(YStack, { width: 7, height: 14, bg: "$ink" })] })) : null] }), _jsx(XStack, { px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", color: "$soft", children: "Commands appear here as the run writes them." }) })] }));
272
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$3", gap: "$1", children: [idle ? (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", py: "$8", children: [_jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: refused || (failed ? t('This run stopped before its first command: {reason}', { reason: failed }) : t('This run finished without writing a command.')) }), refused ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: retry })) : null] })) : (lines.map((line, i) => line.role === 'cmd' ? (_jsxs(XStack, { gap: "$2", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(SizableText, { flex: 1, size: "$1", color: "$ink", style: mono, children: line.text })] }, i)) : (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, children: line.text }, i)))), live ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: `${prompt} $` }), _jsx(YStack, { width: 7, height: 14, bg: "$ink" })] })) : null] }), _jsx(XStack, { px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", color: "$soft", children: t('Commands appear here as the run writes them.') }) })] }));
261
273
  }