@hanzo/build 0.2.80 → 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 (165) 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/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.js +78 -31
  67. package/lib/customize/connectors.js +62 -48
  68. package/lib/customize/index.js +43 -10
  69. package/lib/customize/plugins.js +34 -22
  70. package/lib/customize/say.d.ts +13 -1
  71. package/lib/customize/say.js +15 -21
  72. package/lib/customize/skills.js +31 -22
  73. package/lib/customize/ui.d.ts +5 -9
  74. package/lib/customize/ui.js +26 -16
  75. package/lib/data.d.ts +1 -1
  76. package/lib/data.js +3 -2
  77. package/lib/desk.js +45 -33
  78. package/lib/door.js +35 -10
  79. package/lib/environment.js +25 -19
  80. package/lib/files.js +43 -34
  81. package/lib/find.js +12 -9
  82. package/lib/foot.js +7 -4
  83. package/lib/forge.js +61 -40
  84. package/lib/gear.d.ts +2 -1
  85. package/lib/gear.js +6 -4
  86. package/lib/git.js +29 -19
  87. package/lib/landing.d.ts +5 -5
  88. package/lib/landing.js +52 -44
  89. package/lib/library.js +73 -51
  90. package/lib/meter.d.ts +1 -1
  91. package/lib/meter.js +14 -5
  92. package/lib/mic.js +12 -5
  93. package/lib/mind.d.ts +1 -0
  94. package/lib/mind.js +5 -3
  95. package/lib/nav.d.ts +6 -5
  96. package/lib/nav.js +37 -30
  97. package/lib/panel.d.ts +2 -1
  98. package/lib/panel.js +52 -36
  99. package/lib/plan.d.ts +4 -20
  100. package/lib/plan.js +26 -10
  101. package/lib/plans.js +49 -30
  102. package/lib/plugins/design.js +3 -2
  103. package/lib/plugins/index.d.ts +23 -4
  104. package/lib/plugins/index.js +11 -26
  105. package/lib/plugins/pdf.js +2 -1
  106. package/lib/plugins/project.js +6 -3
  107. package/lib/plugins/research.js +3 -2
  108. package/lib/plugins/sketch.js +6 -3
  109. package/lib/plugins/slides.js +3 -2
  110. package/lib/plugins/template.js +6 -3
  111. package/lib/plus.js +25 -13
  112. package/lib/prefs.d.ts +0 -14
  113. package/lib/prefs.js +2 -1
  114. package/lib/project.js +69 -62
  115. package/lib/prompt.d.ts +12 -11
  116. package/lib/prompt.js +38 -20
  117. package/lib/publish.js +17 -12
  118. package/lib/reply.js +6 -3
  119. package/lib/run.js +72 -54
  120. package/lib/say.d.ts +2 -0
  121. package/lib/say.js +7 -0
  122. package/lib/scheduled.js +54 -43
  123. package/lib/section.d.ts +1 -0
  124. package/lib/section.js +8 -2
  125. package/lib/settings/account.d.ts +1 -0
  126. package/lib/settings/account.js +31 -21
  127. package/lib/settings/billing.js +55 -28
  128. package/lib/settings/capabilities.js +26 -15
  129. package/lib/settings/card.js +19 -12
  130. package/lib/settings/code.js +22 -11
  131. package/lib/settings/credits.d.ts +4 -4
  132. package/lib/settings/credits.js +34 -21
  133. package/lib/settings/environments.js +21 -14
  134. package/lib/settings/general.js +26 -14
  135. package/lib/settings/index.js +6 -3
  136. package/lib/settings/integrations.js +42 -28
  137. package/lib/settings/keys.js +37 -19
  138. package/lib/settings/list.d.ts +0 -5
  139. package/lib/settings/list.js +24 -15
  140. package/lib/settings/machines.d.ts +3 -0
  141. package/lib/settings/machines.js +63 -38
  142. package/lib/settings/members.js +23 -14
  143. package/lib/settings/memory.js +12 -6
  144. package/lib/settings/notifications.js +32 -19
  145. package/lib/settings/plan.d.ts +1 -1
  146. package/lib/settings/plan.js +40 -17
  147. package/lib/settings/privacy.js +26 -13
  148. package/lib/settings/ui.d.ts +3 -2
  149. package/lib/settings/ui.js +9 -3
  150. package/lib/settings/usage.js +7 -3
  151. package/lib/shelf.js +18 -15
  152. package/lib/side.d.ts +1 -26
  153. package/lib/side.js +11 -5
  154. package/lib/standing.d.ts +2 -10
  155. package/lib/standing.js +5 -3
  156. package/lib/suggest.d.ts +8 -28
  157. package/lib/suggest.js +39 -4
  158. package/lib/suggestions.js +14 -5
  159. package/lib/summary.d.ts +1 -1
  160. package/lib/summary.js +4 -1
  161. package/lib/sync.js +22 -12
  162. package/lib/tools.d.ts +4 -3
  163. package/lib/tools.js +10 -7
  164. package/lib/transcript.js +10 -8
  165. package/package.json +4 -2
package/lib/project.js CHANGED
@@ -37,8 +37,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
37
37
  *
38
38
  * Derived from the Hanzo App v2 editor (github.com/hanzoai/build-v2), itself
39
39
  * derived from OSW Studio and DeepSite (MIT). See NOTICE.
40
+ *
41
+ * Its words are the reader's language (@hanzo/i18n `t`, the target is
42
+ * `target`); a project's and a run's names, branches, paths and the
43
+ * platform's reasons are drawn as given.
40
44
  */
41
45
  import { SizableText, useMedia, XStack, YStack } from '@hanzo/gui';
46
+ import { useI18n } from '@hanzo/i18n/react';
42
47
  import { Clock, ExternalLink, MousePointerClick, PanelLeft, RefreshCcw, Share2, Upload, } from '@hanzogui/lucide-icons-2';
43
48
  import { Attachments, CHAT, Console, DEVICES, Feedback, FileTabs, FileTree, PageSelect, PreviewFrame, ProjectChip, VIEWS, Views, Workspace, fold, } from '@hanzo/ui/agents';
44
49
  import { Message } from '@hanzo/ui/chat';
@@ -95,25 +100,27 @@ export function slugOf(name) {
95
100
  }
96
101
  /** One run in the conversation: what was asked, and what it came to. */
97
102
  function Turn({ run, open, onOpen, project }) {
98
- const t = useTarget();
103
+ const target = useTarget();
104
+ const { t } = useI18n();
99
105
  const [v, setV] = useState(null);
100
106
  const [note, setNote] = useState('');
101
- return (_jsxs(YStack, { gap: "$2", children: [_jsx(Message, { role: "user", children: _jsx(SizableText, { size: "$2", color: "$ink", children: run.title || 'Untitled run' }) }), _jsx(Message, { role: "assistant", busy: LIVE.has(run.status), actions: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Feedback, { text: run.title, verdict: v, onVerdict: (next) => {
107
+ return (_jsxs(YStack, { gap: "$2", children: [_jsx(Message, { role: "user", children: _jsx(SizableText, { size: "$2", color: "$ink", children: run.title || t('Untitled run') }) }), _jsx(Message, { role: "assistant", busy: LIVE.has(run.status), actions: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Feedback, { text: run.title, verdict: v, onVerdict: (next) => {
102
108
  setV(next);
103
- record(t, run.id, project, next).catch((e) => {
109
+ record(target, run.id, project, next).catch((e) => {
104
110
  setV(null);
105
111
  setNote(e.message);
106
112
  });
107
- } }), note ? (_jsx(SizableText, { size: "$1", color: "$soft", children: note })) : null, !open ? (_jsx(XStack, { render: "button", onPress: onOpen, hitSlop: 8, children: _jsx(SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: "Show steps" }) })) : null] }), children: _jsx(SizableText, { size: "$2", color: "$ink", children: run.status === 'done' ? 'Done.' : run.status === 'error' ? 'This run hit an error.' : run.status === 'stopped' ? 'Stopped.' : 'Working…' }) })] }));
113
+ } }), note ? (_jsx(SizableText, { size: "$1", color: "$soft", children: note })) : null, !open ? (_jsx(XStack, { render: "button", onPress: onOpen, hitSlop: 8, children: _jsx(SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: t('Show steps') }) })) : null] }), children: _jsx(SizableText, { size: "$2", color: "$ink", children: run.status === 'done' ? t('Done.') : run.status === 'error' ? t('This run hit an error.') : run.status === 'stopped' ? t('Stopped.') : t('Working…') }) })] }));
108
114
  }
109
115
  export function Project({ at }) {
110
116
  const host = useHost();
111
- const t = useTarget();
117
+ const target = useTarget();
118
+ const { t } = useI18n();
112
119
  const signed = Boolean(host.person);
113
- const all = useProjects(t, signed);
120
+ const all = useProjects(target, signed);
114
121
  // The repository the address names, and the forge's record of it.
115
122
  const named = 'slug' in at ? null : at;
116
- const forge = useRead(named && signed ? () => one(t, named.name) : null, null, [t, signed, named?.name]);
123
+ const forge = useRead(named && signed ? () => one(target, named.name) : null, null, [target, signed, named?.name]);
117
124
  const project = all.value.find((p) => (named ? siteOf(p, named) : 'slug' in at && p.slug === at.slug)) ?? null;
118
125
  // The site this workspace publishes to: the project's, or the repository's name as one.
119
126
  const slug = project?.slug ?? ('slug' in at ? at.slug : slugOf(named?.name ?? ''));
@@ -135,7 +142,7 @@ export function Project({ at }) {
135
142
  return () => clearInterval(every);
136
143
  }, [building, again]);
137
144
  // When the build began, and why it failed, are its latest deployment's to say.
138
- const failure = useRead(building || failed ? () => deployments(t, slug) : null, [], [t, slug, building, failed, project?.updated]);
145
+ const failure = useRead(building || failed ? () => deployments(target, slug) : null, [], [target, slug, building, failed, project?.updated]);
139
146
  const latest = failure.value[0];
140
147
  const began = latest?.status === 'building' ? latest.created : 0;
141
148
  const ran = began ? Math.max(0, Math.floor(now / 1000) - began) : 0;
@@ -150,7 +157,7 @@ export function Project({ at }) {
150
157
  return;
151
158
  setRedo({ busy: true, error: '' });
152
159
  try {
153
- await declare(t, { repo: clone, ref: home, name: slug, project: slug, mode: 'branch' });
160
+ await declare(target, { repo: clone, ref: home, name: slug, project: slug, mode: 'branch' });
154
161
  setNow(Date.now());
155
162
  again();
156
163
  setRedo({ busy: false, error: '' });
@@ -161,7 +168,7 @@ export function Project({ at }) {
161
168
  };
162
169
  // The site's runs, or the org's when the repository has no site yet.
163
170
  const sited = !named || Boolean(project);
164
- const listed = useRead(signed ? () => list(t, sited ? { kind: 'coding', project: slug, limit: 50 } : { kind: 'coding', limit: 50 }) : null, [], [t, signed, slug, sited]);
171
+ const listed = useRead(signed ? () => list(target, sited ? { kind: 'coding', project: slug, limit: 50 } : { kind: 'coding', limit: 50 }) : null, [], [target, signed, slug, sited]);
165
172
  // Only the runs on this project's own repository: a record can be moved into
166
173
  // any project, and a run's branch is what Files, Code and Publish read.
167
174
  const mine = (r) => (named ? ours(r.repo, `${named.org}/${named.name}`) && Boolean(r.repo) : ours(r.repo, project?.repo ?? ''));
@@ -169,7 +176,7 @@ export function Project({ at }) {
169
176
  const ordered = useMemo(() => [...runs.value].reverse(), [listed.value, project?.repo, named?.org, named?.name]);
170
177
  const [chosen, setChosen] = useState(null);
171
178
  const current = chosen ?? runs.value[0]?.id ?? null;
172
- const run = useRun(t, current);
179
+ const run = useRun(target, current);
173
180
  const end = outcome(run.events);
174
181
  const pr = pull(run.record?.pr ?? '', run.record?.repo ?? '');
175
182
  const running = LIVE.has(run.status || end.status);
@@ -199,15 +206,15 @@ export function Project({ at }) {
199
206
  const [picked, setPicked] = useState([]);
200
207
  const [attached, setAttached] = useState([]);
201
208
  // The model and effort Chat and New share, and dictation into the ask.
202
- const mind = useMind(t);
203
- const voice = useVoice({ speech: useEar(t), language: useSpoken(), ...useDictation(draft, setDraft) });
209
+ const mind = useMind(target);
210
+ const voice = useVoice({ speech: useEar(target), language: useSpoken(), ...useDictation(draft, setDraft) });
204
211
  const [bridge, setBridge] = useState(false);
205
212
  const [editing, setEditing] = useState(false);
206
213
  const [pageLines, setPageLines] = useState([]);
207
214
  const [history, setHistory] = useState(false);
208
215
  const [publish, setPublish] = useState(null);
209
216
  const [switcher, setSwitcher] = useState(false);
210
- const repos = useRead(switcher && signed ? () => codebases(t) : null, [], [t, switcher, signed]);
217
+ const repos = useRead(switcher && signed ? () => codebases(target) : null, [], [target, switcher, signed]);
211
218
  const frame = useRef(null);
212
219
  // What was published may be new files at the same address: the project is read
213
220
  // for its address, and the frame loads again.
@@ -235,7 +242,7 @@ export function Project({ at }) {
235
242
  // eslint-disable-next-line react-hooks/exhaustive-deps
236
243
  }, [current, run.record, end.published]);
237
244
  // Where the open run's pull request stands on the forge: Merge is offered while it is open.
238
- const proposal = useRead(signed && current && pr.href ? () => pushed(t, current).then((c) => ({ run: current, pull: c.pull })) : null, null, [t, signed, current, pr.href]);
245
+ const proposal = useRead(signed && current && pr.href ? () => pushed(target, current).then((c) => ({ run: current, pull: c.pull })) : null, null, [target, signed, current, pr.href]);
239
246
  const proposing = proposal.value?.run === current && proposal.value.pull?.state === 'open';
240
247
  // Files is drawn again after a merge, from what the forge holds now.
241
248
  const [round, setRound] = useState(0);
@@ -255,9 +262,9 @@ export function Project({ at }) {
255
262
  return;
256
263
  setFiles((was) => [...was, { path, readOnly: true }]);
257
264
  try {
258
- const b = await blob(t, repo, ref, path);
265
+ const b = await blob(target, repo, ref, path);
259
266
  const content = b.binary ? '' : b.truncated ? '' : b.text;
260
- const error = b.binary ? 'A binary file — nothing to show as text.' : b.truncated ? 'Past the 1 MiB view cap — clone the repository to read it.' : undefined;
267
+ const error = b.binary ? t('A binary file — nothing to show as text.') : b.truncated ? t('Past the 1 MiB view cap — clone the repository to read it.') : undefined;
261
268
  setFiles((was) => was.map((f) => (f.path === path ? { ...f, content, error } : f)));
262
269
  }
263
270
  catch (e) {
@@ -265,21 +272,21 @@ export function Project({ at }) {
265
272
  }
266
273
  };
267
274
  // Pages: the HTML files at the repository's root, as the page picker's list.
268
- const root = useRead(repo ? () => tree(t, repo, ref, '') : null, [], [t, repo, ref]);
275
+ const root = useRead(repo ? () => tree(target, repo, ref, '') : null, [], [target, repo, ref]);
269
276
  const pages = useMemo(() => [
270
- { id: '/', label: 'Homepage' },
277
+ { id: '/', label: t('Homepage') },
271
278
  ...root.value
272
279
  .filter((e) => !e.dir && /\.html?$/i.test(e.name) && !/^index\.html?$/i.test(e.name))
273
280
  .map((e) => ({ id: `/${e.path}`, label: e.name.replace(/\.html?$/i, '') })),
274
- ], [root.value]);
281
+ ], [root.value, t]);
275
282
  // A project taken from a starter, not published and not being, shows the
276
283
  // starter's own live page, which is what the copy is until its first publish.
277
284
  const forked = project && !project.live && !building && !failed ? project.forked : '';
278
- const starters = useRead(forked ? () => templates(t) : null, [], [t.api, forked]);
285
+ const starters = useRead(forked ? () => templates(target) : null, [], [target.api, forked]);
279
286
  const starter = forked ? starters.value.find((s) => s.slug === forked && s.demo) : undefined;
280
287
  const shown = project?.live || starter?.demo || '';
281
288
  const src = shown ? new URL(page, shown).toString() : null;
282
- const why = failure.value[0]?.message || failure.error?.message || (failure.loading ? '' : 'Its build ended without saying why.');
289
+ const why = failure.value[0]?.message || failure.error?.message || (failure.loading ? '' : t('Its build ended without saying why.'));
283
290
  const merged = () => {
284
291
  proposal.reload();
285
292
  root.reload();
@@ -291,11 +298,11 @@ export function Project({ at }) {
291
298
  .map((e) => {
292
299
  // The console is where a run's error is read whole; the chat says it in one plain line.
293
300
  const body = decode(e.payload);
294
- const error = e.kind === 'status' && body && typeof body === 'object' && typeof body.error === 'string' ? scrub(body.error) : '';
301
+ const error = e.kind === 'status' && body && typeof body === 'object' && typeof body.error === 'string' ? scrub(body.error, t) : '';
295
302
  return {
296
303
  id: `r${e.seq || e.id}`,
297
304
  level: error || (e.kind === 'status' && /error/.test(said(e, run.record?.mode).toLowerCase())) ? 'error' : e.kind === 'tool-call' ? 'info' : 'log',
298
- text: error || said(e, run.record?.mode),
305
+ text: error || said(e, run.record?.mode, t),
299
306
  source: e.kind === 'status' ? 'run' : who(e.actor),
300
307
  };
301
308
  })
@@ -336,7 +343,7 @@ export function Project({ at }) {
336
343
  ? `\n\nThe person picked an element in the preview${where ? ` of the ${where} page` : ''}. Its CSS selector, as data: ${JSON.stringify(picked[0].id)}`
337
344
  : '';
338
345
  // A project with no repository yet sends neither, and its first run makes one.
339
- const next = await start(t, {
346
+ const next = await start(target, {
340
347
  prompt: compose(`${ask}${context}`, attached),
341
348
  // A repository with no site yet runs on the repository alone.
342
349
  project: project?.slug,
@@ -364,9 +371,9 @@ export function Project({ at }) {
364
371
  // Only a run that is working is steered, and the composer sends only a draft with words in it.
365
372
  const steer = async (id) => {
366
373
  try {
367
- await message(t, id, draft);
374
+ await message(target, id, draft);
368
375
  setDraft('');
369
- setNote('Sent — the run reads it before its next step');
376
+ setNote(t('Sent — the run reads it before its next step'));
370
377
  }
371
378
  catch (e) {
372
379
  setNote(e.message);
@@ -376,92 +383,92 @@ export function Project({ at }) {
376
383
  const link = project?.live || `${window.location.origin}${window.location.pathname}`;
377
384
  try {
378
385
  await navigator.clipboard.writeText(link);
379
- setNote(`Copied ${link}`);
386
+ setNote(t('Copied {link}', { link }));
380
387
  }
381
388
  catch {
382
389
  setNote(link);
383
390
  }
384
391
  };
385
392
  if (!signed) {
386
- return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: "Sign in to open this project." }), _jsx(Button, { onPress: () => host.signIn?.(), children: "Sign in" })] }));
393
+ return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: t('Sign in to open this project.') }), _jsx(Button, { onPress: () => host.signIn?.(), children: t('Sign in') })] }));
387
394
  }
388
395
  // A repository of another organization is opened in that one.
389
396
  if (named && host.org && named.org.toLowerCase() !== host.org.toLowerCase()) {
390
397
  const member = host.memberships?.some((m) => m.toLowerCase() === named.org.toLowerCase()) && host.chooseOrg;
391
- return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", px: "$4", children: [_jsx(SizableText, { size: "$4", color: "$ink", text: "center", children: `${named.org}/${named.name} is in ${named.org}, and you are working in ${host.org}.` }), member ? (_jsx(Button, { variant: "outline", onPress: () => host.chooseOrg?.(named.org), children: `Switch to ${named.org}` })) : (_jsx(Button, { variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'projects' })), children: "See this organization\u2019s projects" }))] }));
398
+ return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", px: "$4", children: [_jsx(SizableText, { size: "$4", color: "$ink", text: "center", children: t('{repo} is in {org}, and you are working in {current}.', { repo: `${named.org}/${named.name}`, org: named.org, current: host.org }) }), member ? (_jsx(Button, { variant: "outline", onPress: () => host.chooseOrg?.(named.org), children: t('Switch to {org}', { org: named.org }) })) : (_jsx(Button, { variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'projects' })), children: t('See this organization’s projects') }))] }));
392
399
  }
393
400
  if (named ? !forge.loading && !forge.value : !all.loading && !project && !all.error) {
394
401
  return (_jsxs(YStack, { flex: 1, items: "center", justify: "center", gap: "$3", px: "$4", children: [_jsx(SizableText, { size: "$4", color: "$ink", text: "center", children: named
395
402
  ? forge.error && !/not found|404/i.test(forge.error.message)
396
403
  ? forge.error.message
397
- : `${named.org} has no repository named ${named.name}.`
404
+ : t('{org} has no repository named {name}.', { org: named.org, name: named.name })
398
405
  : host.org
399
- ? `${host.org} has no project named ${slug}.`
400
- : `There is no project named ${slug}.` }), _jsx(Button, { variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: named ? 'projects' : 'artifacts' })), children: named ? 'See this organization’s projects' : 'See what this organization has built' })] }));
406
+ ? t('{org} has no project named {name}.', { org: host.org, name: slug })
407
+ : t('There is no project named {name}.', { name: slug }) }), _jsx(Button, { variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: named ? 'projects' : 'artifacts' })), children: named ? t('See this organization’s projects') : t('See what this organization has built') })] }));
401
408
  }
402
409
  const chat = (_jsxs(YStack, { "data-slot": "project-chat", flex: 1, minH: 0, position: "relative", children: [_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", pt: "$2", pb: "$4", gap: "$4", $md: { pr: 0 }, children: ordered.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$ink", children: runs.loading
403
- ? 'Reading this project’s runs…'
410
+ ? t('Reading this project’s runs…')
404
411
  : project?.live
405
- ? `${project.name} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.`
412
+ ? t('{name} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.', { name: project.name })
406
413
  : stalled
407
- ? `${project?.name} has been publishing for ${Math.floor(ran / 60)} minutes without finishing. Publish it again from the preview, or say what to change and it gets built.`
414
+ ? t('{name} has been publishing for {minutes} minutes without finishing. Publish it again from the preview, or say what to change and it gets built.', { name: project?.name ?? '', minutes: Math.floor(ran / 60) })
408
415
  : failed
409
- ? `${project?.name} could not be published. Publish it again from the preview, or say what to change and it gets built.`
416
+ ? t('{name} could not be published. Publish it again from the preview, or say what to change and it gets built.', { name: project?.name ?? '' })
410
417
  : building
411
- ? `${project?.name} is being published — it appears in the preview when its build finishes. Say what to change and it gets built.`
418
+ ? t('{name} is being published — it appears in the preview when its build finishes. Say what to change and it gets built.', { name: project?.name ?? '' })
412
419
  : starter
413
- ? `${project?.name} is loaded — the preview shows the ${starter.title} starter until your copy is published. Say what to change and it gets built.`
420
+ ? t('{name} is loaded — the preview shows the {starter} starter until your copy is published. Say what to change and it gets built.', { name: project?.name ?? '', starter: starter.title })
414
421
  : project
415
- ? `${project.name} is loaded and nothing is published yet. Say what to change and it gets built.`
422
+ ? t('{name} is loaded and nothing is published yet. Say what to change and it gets built.', { name: project.name })
416
423
  : named
417
- ? `${named.org}/${named.name} is open on ${home}. Say what to change and it gets built there.`
418
- : `${slug} is loaded. Say what to change and it gets built.` })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", pl: "$2", borderLeftWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: [_jsxs(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: ["Pull request ", pr.label] }), _jsx(ExternalLink, { size: 11 })] }), _jsx(Merge, { session: r.id, open: proposing, onMerged: merged })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { "data-slot": "project-ask", px: "$3", pb: "$3", gap: "$2", minW: 0, $md: { pr: 0 }, children: [note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: () => void (current && running ? steer(current) : send()), onStop: current
419
- ? () => void stop(t, current).then(() => setNote('Stop requested — the run keeps its work on its branch'), (e) => setNote(e.message))
420
- : undefined, busy: busy || (running && !draft.trim()), placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", plus: { upload: () => pick(attached, setAttached, setNote) }, mode: running ? null : { value: mode === 'plan' ? 'plan' : 'auto', set: (m) => setMode(m === 'plan' ? 'plan' : 'build'), items: ['auto', 'plan'] }, mind: running ? null : mind, voice: voice })] }), wide && !shut ? (_jsx(Gutter, { side: "left", span: across, floor: CHAT_FLOOR, ceil: most, reset: CHAT_WIDTH, onSpan: setSplit, label: "Resize the chat", gap: GUTTER })) : null] }));
424
+ ? t('{repo} is open on {branch}. Say what to change and it gets built there.', { repo: `${named.org}/${named.name}`, branch: home })
425
+ : t('{name} is loaded. Say what to change and it gets built.', { name: slug }) })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", paddingInlineStart: "$2", borderInlineStartWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode, t) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(Out, { href: pr.href, label: t('Open pull request {pr}', { pr: pr.label }), children: [_jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: t('Pull request {pr}', { pr: pr.label }) }), _jsx(ExternalLink, { size: 11 })] }), _jsx(Merge, { session: r.id, open: proposing, onMerged: merged })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { "data-slot": "project-ask", px: "$3", pb: "$3", gap: "$2", minW: 0, $md: { pr: 0 }, children: [note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: () => void (current && running ? steer(current) : send()), onStop: current
426
+ ? () => void stop(target, current).then(() => setNote(t('Stop requested — the run keeps its work on its branch')), (e) => setNote(e.message))
427
+ : undefined, busy: busy || (running && !draft.trim()), placeholder: running ? t('Steer this run') : t('Ask Hanzo for edits'), label: t('Ask Hanzo for edits'), plus: { upload: () => pick(attached, setAttached, setNote, t) }, mode: running ? null : { value: mode === 'plan' ? 'plan' : 'auto', set: (m) => setMode(m === 'plan' ? 'plan' : 'build'), items: ['auto', 'plan'] }, mind: running ? null : mind, voice: voice })] }), wide && !shut ? (_jsx(Gutter, { side: "left", span: across, floor: CHAT_FLOOR, ceil: most, reset: CHAT_WIDTH, onSpan: setSplit, label: t('Resize the chat'), gap: GUTTER })) : null] }));
421
428
  // The preview stays loaded while another view is open, so coming back to it
422
429
  // is the page as it was left.
423
- const preview = (_jsxs(YStack, { "data-slot": "preview", flex: 1, minH: 0, minW: 0, gap: "$2", display: view === 'preview' ? 'flex' : 'none', children: [_jsxs(XStack, { "data-slot": "preview-bar", items: "center", gap: "$1.5", minW: 0, shrink: 0, "$max-md": { px: '$2' }, children: [_jsx(XStack, { render: "button", "aria-label": "Reload the preview", onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 16 }) }), _jsx(PageSelect, { pages: pages, value: page, onChange: setPage, shrink: 1 }), _jsx(XStack, { flex: 1 }), _jsx(Views, { views: DEVICES, value: device, onChange: (d) => setDevice(d), label: "Device", labels: "none" }), src ? (_jsx(Out, { href: src, label: "Open in a new tab", children: _jsx(XStack, { p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: src, title: `${title} preview`, device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: stalled
424
- ? 'Publishing is taking too long'
430
+ const preview = (_jsxs(YStack, { "data-slot": "preview", flex: 1, minH: 0, minW: 0, gap: "$2", display: view === 'preview' ? 'flex' : 'none', children: [_jsxs(XStack, { "data-slot": "preview-bar", items: "center", gap: "$1.5", minW: 0, shrink: 0, "$max-md": { px: '$2' }, children: [_jsx(XStack, { render: "button", "aria-label": t('Reload the preview'), onPress: () => frame.current?.reload(), p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(RefreshCcw, { size: 16 }) }), _jsx(PageSelect, { pages: pages, value: page, onChange: setPage, shrink: 1 }), _jsx(XStack, { flex: 1 }), _jsx(Views, { views: DEVICES, value: device, onChange: (d) => setDevice(d), label: t('Device'), labels: "none" }), src ? (_jsx(Out, { href: src, label: t('Open in a new tab'), children: _jsx(XStack, { p: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(ExternalLink, { size: 16 }) }) })) : null] }), _jsx(PreviewFrame, { ref: frame, src: src, title: t('{title} preview', { title }), device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: stalled
431
+ ? t('Publishing is taking too long')
425
432
  : building
426
433
  ? project?.forked
427
- ? 'Publishing your copy…'
428
- : 'Publishing…'
434
+ ? t('Publishing your copy…')
435
+ : t('Publishing…')
429
436
  : failed
430
- ? 'Publishing failed'
431
- : 'Nothing deployed yet' }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: stalled
432
- ? `Its build started ${Math.floor(ran / 60)} minutes ago and has not finished.`
437
+ ? t('Publishing failed')
438
+ : t('Nothing deployed yet') }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: stalled
439
+ ? t('Its build started {minutes} minutes ago and has not finished.', { minutes: Math.floor(ran / 60) })
433
440
  : building
434
- ? 'Its page appears here when the build finishes.'
441
+ ? t('Its page appears here when the build finishes.')
435
442
  : failed
436
443
  ? why
437
- : 'Publish this project and its page appears here.' }), (stalled || failed) && clone ? (_jsx(Button, { variant: "outline", disabled: redo.busy, onPress: () => void retry(), children: "Publish again" })) : null, redo.error ? (_jsx(SizableText, { size: "$2", color: "$soft", text: "center", role: "alert", children: redo.error })) : null] }), toolbar: bridge ? (_jsx(XStack, { render: "button", "aria-pressed": editing, "aria-label": editing ? 'Stop picking elements' : 'Pick an element to edit', onPress: () => {
444
+ : t('Publish this project and its page appears here.') }), (stalled || failed) && clone ? (_jsx(Button, { variant: "outline", disabled: redo.busy, onPress: () => void retry(), children: t('Publish again') })) : null, redo.error ? (_jsx(SizableText, { size: "$2", color: "$soft", text: "center", role: "alert", children: redo.error })) : null] }), toolbar: bridge ? (_jsx(XStack, { render: "button", "aria-pressed": editing, "aria-label": editing ? t('Stop picking elements') : t('Pick an element to edit'), onPress: () => {
438
445
  const next = !editing;
439
446
  setEditing(next);
440
447
  frame.current?.post({ type: 'preview:editable', active: next });
441
448
  }, p: "$2", rounded: "$10", children: _jsx(MousePointerClick, { size: 16 }) })) : undefined })] }));
442
- const body = view === 'preview' ? null : view === 'files' ? (repo && root.error ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$4", role: "status", children: `Nothing to show for ${repo} at ${ref}: the forge answered “${root.error.message}”.` })) : repo ? (_jsx(FileTree, { load: async (dir) => (await tree(t, repo, ref, dir)).map((e) => ({ path: e.path, kind: e.dir ? 'dir' : 'file' })), value: file, onSelect: (p) => void openFile(p), label: `${repo} at ${ref}` }, round)) : (_jsx(SizableText, { size: "$2", color: "$soft", p: "$4", children: "This project has no repository yet. Its first run creates one." }))) : view === 'code' ? (_jsx(FileTabs, { files: files, value: file, onSelect: setFile, onClose: (p) => {
449
+ const body = view === 'preview' ? null : view === 'files' ? (repo && root.error ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$4", role: "status", children: t('Nothing to show for {repo} at {ref}: the forge answered “{reason}”.', { repo, ref, reason: root.error.message }) })) : repo ? (_jsx(FileTree, { load: async (dir) => (await tree(target, repo, ref, dir)).map((e) => ({ path: e.path, kind: e.dir ? 'dir' : 'file' })), value: file, onSelect: (p) => void openFile(p), label: t('{repo} at {ref}', { repo, ref }) }, round)) : (_jsx(SizableText, { size: "$2", color: "$soft", p: "$4", children: t('This project has no repository yet. Its first run creates one.') }))) : view === 'code' ? (_jsx(FileTabs, { files: files, value: file, onSelect: setFile, onClose: (p) => {
443
450
  setFiles((was) => was.filter((f) => f.path !== p));
444
451
  if (file === p)
445
452
  setFile(null);
446
- }, empty: _jsxs(SizableText, { size: "$2", color: "$soft", p: "$4", children: ["Open a file from Files. Code here is read at ", ref, "; ask in the chat to change it."] }) })) : (_jsx(YStack, { p: "$4", gap: "$2", children: picked.length ? (picked.map((p) => (_jsxs(SizableText, { size: "$2", color: "$ink", children: [p.label, " \u2014 ", p.id] }, p.id)))) : (_jsx(SizableText, { size: "$2", color: "$soft", children: bridge
447
- ? 'Pick an element in the preview to see it here and attach it to your next ask.'
448
- : 'Layers are read through the preview bridge, and this page does not run it. Its elements cannot be listed from here.' })) }));
449
- return (_jsxs(_Fragment, { children: [_jsxs(Workspace, { start: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, shrink: 1, children: [_jsx(XStack, { render: "button", "aria-label": "All runs", onPress: () => host.go(''), p: "$1", shrink: 0, children: _jsx(HanzoMark, { size: 20 }) }), _jsx(ProjectChip, { name: title, onPress: () => setSwitcher(true) }), _jsx(XStack, { render: "button", "aria-label": "History", onPress: () => setHistory(true), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(Clock, { size: 16 }) }), _jsx(XStack, { render: "button", "aria-label": shut ? 'Show the chat' : 'Hide the chat', "aria-pressed": !shut, onPress: () => (cramped ? setAsked(!asked) : setCollapsed(!collapsed)), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, "$max-md": { display: 'none' }, children: _jsx(PanelLeft, { size: 16 }) })] }), middle: _jsx(Views, { views: [CHAT, ...VIEWS], value: pane === 'chat' && !wide ? 'chat' : view, onChange: (v) => {
453
+ }, empty: _jsx(SizableText, { size: "$2", color: "$soft", p: "$4", children: t('Open a file from Files. Code here is read at {ref}; ask in the chat to change it.', { ref }) }) })) : (_jsx(YStack, { p: "$4", gap: "$2", children: picked.length ? (picked.map((p) => (_jsxs(SizableText, { size: "$2", color: "$ink", children: [p.label, " \u2014 ", p.id] }, p.id)))) : (_jsx(SizableText, { size: "$2", color: "$soft", children: bridge
454
+ ? t('Pick an element in the preview to see it here and attach it to your next ask.')
455
+ : t('Layers are read through the preview bridge, and this page does not run it. Its elements cannot be listed from here.') })) }));
456
+ return (_jsxs(_Fragment, { children: [_jsxs(Workspace, { start: _jsxs(XStack, { items: "center", gap: "$2", minW: 0, shrink: 1, children: [_jsx(XStack, { render: "button", "aria-label": t('All runs'), onPress: () => host.go(''), p: "$1", shrink: 0, children: _jsx(HanzoMark, { size: 20 }) }), _jsx(ProjectChip, { name: title, onPress: () => setSwitcher(true) }), _jsx(XStack, { render: "button", "aria-label": t('History'), onPress: () => setHistory(true), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, children: _jsx(Clock, { size: 16 }) }), _jsx(XStack, { render: "button", "aria-label": shut ? t('Show the chat') : t('Hide the chat'), "aria-pressed": !shut, onPress: () => (cramped ? setAsked(!asked) : setCollapsed(!collapsed)), p: "$1.5", rounded: "$3", shrink: 0, hoverStyle: { bg: '$hover' }, "$max-md": { display: 'none' }, children: _jsx(PanelLeft, { size: 16 }) })] }), middle: _jsx(Views, { views: [CHAT, ...VIEWS], value: pane === 'chat' && !wide ? 'chat' : view, onChange: (v) => {
450
457
  if (v === 'chat')
451
458
  return setPane('chat');
452
459
  setView(v);
453
460
  setPane('view');
454
- }, label: "View" }), end: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { display: "none", $md: { display: 'flex' }, children: _jsxs(Button, { variant: "outline", size: "sm", onPress: () => void share(), children: [_jsx(Share2, { size: 14 }), " Share"] }) }), _jsxs(Button, { size: "sm", "aria-label": "Publish", disabled: !clone || !slug, onPress: () => setPublish({ repo: clone, title, ref, name: slug, project: slug }), children: [_jsx(Upload, { size: 14 }), _jsx(SizableText, { size: "$2", color: "inherit", "$max-md": { display: 'none' }, children: "Publish" })] })] }), chat: chat, dock: _jsx(Console, { lines: lines, height: dock, onHeight: setDock, onClear: () => setPageLines([]), status: run.status || end.status || undefined, label: "Console" }), collapsed: shut, pane: pane, width: across, onLayout: (e) => setRoom(e.nativeEvent.layout.width), children: [preview, body] }), _jsx(Dialog, { open: history, onOpenChange: setHistory, children: _jsxs(DialogContent, { maxW: 480, children: [_jsxs(DialogTitle, { children: ["Runs on ", title] }), _jsx(YStack, { gap: "$1", children: runs.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "No runs yet." })) : (runs.value.map((r) => (_jsxs(XStack, { render: "button", onPress: () => {
461
+ }, label: t('View') }), end: _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { display: "none", $md: { display: 'flex' }, children: _jsxs(Button, { variant: "outline", size: "sm", onPress: () => void share(), children: [_jsx(Share2, { size: 14 }), " ", t('Share')] }) }), _jsxs(Button, { size: "sm", "aria-label": t('Publish'), disabled: !clone || !slug, onPress: () => setPublish({ repo: clone, title, ref, name: slug, project: slug }), children: [_jsx(Upload, { size: 14 }), _jsx(SizableText, { size: "$2", color: "inherit", "$max-md": { display: 'none' }, children: t('Publish') })] })] }), chat: chat, dock: _jsx(Console, { lines: lines, height: dock, onHeight: setDock, onClear: () => setPageLines([]), status: run.status || end.status || undefined, label: t('Console') }), collapsed: shut, pane: pane, width: across, onLayout: (e) => setRoom(e.nativeEvent.layout.width), children: [preview, body] }), _jsx(Dialog, { open: history, onOpenChange: setHistory, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: t('Runs on {title}', { title }) }), _jsx(YStack, { gap: "$1", children: runs.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('No runs yet.') })) : (runs.value.map((r) => (_jsxs(XStack, { render: "button", onPress: () => {
455
462
  setChosen(r.id);
456
463
  setHistory(false);
457
- }, justify: "space-between", px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, flex: 1, children: r.title || 'Untitled run' }), _jsx(SizableText, { size: "$1", color: "$soft", children: r.status })] }, r.id)))) })] }) }), _jsx(Dialog, { open: switcher, onOpenChange: setSwitcher, children: _jsxs(DialogContent, { maxW: 420, children: [_jsx(DialogTitle, { children: "Projects" }), _jsx(YStack, { gap: "$1", maxH: 420, overflow: "scroll", children: repos.error ? (_jsx(SizableText, { size: "$2", color: "$soft", children: repos.error.message })) : repos.loading && repos.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "Reading the forge\u2026" })) : (repos.value.map((c) => {
464
+ }, justify: "space-between", px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, flex: 1, children: r.title || t('Untitled run') }), _jsx(SizableText, { size: "$1", color: "$soft", children: r.status })] }, r.id)))) })] }) }), _jsx(Dialog, { open: switcher, onOpenChange: setSwitcher, children: _jsxs(DialogContent, { maxW: 420, children: [_jsx(DialogTitle, { children: t('Projects') }), _jsx(YStack, { gap: "$1", maxH: 420, overflow: "scroll", children: repos.error ? (_jsx(SizableText, { size: "$2", color: "$soft", children: repos.error.message })) : repos.loading && repos.value.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Reading the forge…') })) : (repos.value.map((c) => {
458
465
  const here = Boolean(named) && c.name === named?.name;
459
- return (_jsx(XStack, { render: "button", "aria-label": `Open ${c.org}/${c.name}`, "aria-current": here ? 'page' : undefined, onPress: () => {
466
+ return (_jsx(XStack, { render: "button", "aria-label": t('Open {repo}', { repo: `${c.org}/${c.name}` }), "aria-current": here ? 'page' : undefined, onPress: () => {
460
467
  setSwitcher(false);
461
468
  host.go(path({ kind: 'repo', org: c.org || host.org || '', name: c.name }));
462
469
  }, px: "$2", py: "$1.5", rounded: "$3", bg: here ? '$hover' : undefined, hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: c.name }) }, `${c.org}/${c.name}`));
463
470
  })) }), _jsx(Button, { variant: "outline", size: "sm", onPress: () => {
464
471
  setSwitcher(false);
465
472
  host.go(path({ kind: 'screen', screen: 'projects' }));
466
- }, children: "All projects" })] }) }), _jsx(Publish, { source: publish, onClose: () => setPublish(null), onLive: fresh })] }));
473
+ }, children: t('All projects') })] }) }), _jsx(Publish, { source: publish, onClose: () => setPublish(null), onLive: fresh })] }));
467
474
  }
package/lib/prompt.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Say } from '@hanzo/i18n/react';
1
2
  import { type ComposerProps } from '@hanzo/ui/chat';
2
3
  import { type PauseSource } from '@hanzo/ui/models';
3
4
  import { type Machine, type Speech } from '@hanzo/voice';
@@ -19,7 +20,7 @@ export declare const measure: {
19
20
  /** The round controls' box, px, and a phone's, a finger's width. */
20
21
  export declare const ROUND = 32;
21
22
  /** What every Composer placeholder says, unless a surface names who is listening. */
22
- export declare const ASK = "Type / for skills";
23
+ export declare const ASK: string;
23
24
  /**
24
25
  * The frame as a pane of its own, floating on the pane it is drawn in: the
25
26
  * `--pane-*` cut on design's first paper rung.
@@ -107,23 +108,23 @@ export declare function trigger(value: string): {
107
108
  query: string;
108
109
  at: number;
109
110
  } | null;
110
- /** @hanzo/ui `Composer` props for the one composer. */
111
- export declare function prompt(o: PromptOptions, state?: {
111
+ /** What `useMention` hands `prompt`: the lit row of a `/` or `@` list, and the reader's `t`. */
112
+ export interface Mentioned {
112
113
  index: number;
113
114
  closed: string | null;
114
- }): ComposerProps;
115
+ t?: Say;
116
+ }
117
+ /** @hanzo/ui `Composer` props for the one composer. */
118
+ export declare function prompt(o: PromptOptions, state?: Mentioned): ComposerProps;
115
119
  /** Ctrl+U (⌘U) uploads, while a composer with an upload is on the page. */
116
120
  export declare function useUpload(upload: (() => void) | undefined): void;
117
121
  /**
118
- * The composer's own state — which row of a `/` or `@` list is lit — and the
119
- * keys that move it. A surface that hands `prompt` to `Chat` calls this and
120
- * passes its answer through; `<Prompt>` does it for itself.
122
+ * The composer's own state — which row of a `/` or `@` list is lit, and the
123
+ * reader's `t` — and the keys that move it. A surface that hands `prompt` to
124
+ * `Chat` calls this and passes its answer through; `<Prompt>` does it for itself.
121
125
  */
122
126
  export declare function useMention(o: PromptOptions): {
123
- state: {
124
- index: number;
125
- closed: string | null;
126
- };
127
+ state: Mentioned;
127
128
  field: ComposerProps['field'];
128
129
  };
129
130
  /** The composer drawn on its own. */