@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
package/lib/forge.js CHANGED
@@ -9,8 +9,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
9
9
  * Choosing an issue opens New on the issue's own repository — its GitHub
10
10
  * address, or the forge repository it is filed on, never a board's key — with
11
11
  * the issue as the ask; an issue no project answers to asks which one.
12
+ *
13
+ * The page's words are the reader's language (@hanzo/i18n `t`, the target is
14
+ * `target`); a repository's org, name, branch and description, an issue's
15
+ * title, labels and people, and the forge's own refusals are drawn as given.
12
16
  */
13
17
  import { SizableText, XStack, YStack } from '@hanzo/gui';
18
+ import { phrase } from '@hanzo/i18n';
19
+ import { useI18n } from '@hanzo/i18n/react';
14
20
  import { ArrowRight, Check, ChevronDown, CircleCheck, CircleDot, CircleSlash, ExternalLink, FolderGit2, GitBranch, Github, GitPullRequest, LayoutTemplate, RefreshCw, } from '@hanzogui/lucide-icons-2';
15
21
  import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
16
22
  import { useEffect, useMemo, useState } from 'react';
@@ -32,11 +38,11 @@ function Note({ children }) {
32
38
  return (_jsx(SizableText, { size: "$2", color: "$soft", children: children }));
33
39
  }
34
40
  const STATUS = {
35
- backlog: 'Backlog',
36
- todo: 'To do',
37
- in_progress: 'In progress',
38
- done: 'Done',
39
- canceled: 'Canceled',
41
+ backlog: phrase('Backlog'),
42
+ todo: phrase('To do'),
43
+ in_progress: phrase('In progress'),
44
+ done: phrase('Done'),
45
+ canceled: phrase('Canceled'),
40
46
  };
41
47
  export function Forge({ screen }) {
42
48
  if (screen === 'automations')
@@ -59,7 +65,8 @@ const PAGE = 25;
59
65
  */
60
66
  function Projects() {
61
67
  const host = useHost();
62
- const t = useTarget();
68
+ const target = useTarget();
69
+ const { t } = useI18n();
63
70
  const signed = Boolean(host.person);
64
71
  const [q, setQ] = useState('');
65
72
  const [sort, setSort] = useState('updated');
@@ -68,7 +75,7 @@ function Projects() {
68
75
  useEffect(() => {
69
76
  setPage(0);
70
77
  }, [q, sort]);
71
- const list = useRead(signed ? () => codebases(t) : null, [], [t, signed, landed]);
78
+ const list = useRead(signed ? () => codebases(target) : null, [], [target, signed, landed]);
72
79
  const needle = q.trim().toLowerCase();
73
80
  const pending = readPending(host.org);
74
81
  const pendingKey = pending.map((p) => p.fullName).join('\n');
@@ -79,7 +86,7 @@ function Projects() {
79
86
  if (!signed || waiting.length === 0)
80
87
  return;
81
88
  let stop = false;
82
- grants(t)
89
+ grants(target)
83
90
  .then((g) => {
84
91
  if (stop)
85
92
  return;
@@ -94,7 +101,7 @@ function Projects() {
94
101
  return () => {
95
102
  stop = true;
96
103
  };
97
- }, [signed, t, host.org, pendingKey]);
104
+ }, [signed, target, host.org, pendingKey]);
98
105
  const shown = [
99
106
  ...list.value,
100
107
  ...pending
@@ -129,11 +136,13 @@ function Projects() {
129
136
  const open = (c) => host.go(path({ kind: 'repo', org: c.org || host.org || '', name: c.name }));
130
137
  const link = () => host.go(path({ kind: 'screen', screen: 'sync' }));
131
138
  const template = () => host.go(path({ kind: 'screen', screen: 'templates' }));
132
- return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 240, children: [_jsx(SizableText, { render: "h1", size: "$6", fontWeight: "500", color: "$ink", children: "Projects" }), _jsx(SizableText, { size: "$2", color: "$soft", children: `${host.org ? `${host.org}’s` : 'This organization’s'} repositories on the forge. Open one to work on it.` })] }), _jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsxs(Button, { size: "sm", variant: "outline", onPress: template, children: [_jsx(LayoutTemplate, { size: 14 }), "Create from template"] }), _jsxs(Button, { size: "sm", variant: "primary", onPress: link, children: [_jsx(Github, { size: 14 }), "Link a GitHub repo"] })] })] }), signed ? _jsx(GitHubLink, { onSync: link }) : null, list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: "Sign in to see this organization's projects." })) : list.loading && list.value.length === 0 && pending.length === 0 ? (_jsx(Note, { children: "Reading the forge\u2026" })) : none ? (_jsxs(YStack, { gap: "$3", py: "$2", children: [_jsx(Note, { children: "No projects yet. A project is a repository: link one from GitHub, or start one from a template." }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsx(Choice, { label: "Link a GitHub repo", icon: _jsx(Github, { size: 20 }), says: "Mirror repositories you already have. They stay in step with GitHub, and their issues can come too.", onPress: link }), _jsx(Choice, { label: "Create from template", icon: _jsx(LayoutTemplate, { size: 20 }), says: "Start from a finished product \u2014 a site, an app, a store \u2014 and make it yours.", onPress: template })] })] })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(YStack, { flex: 1, "$max-md": { display: 'none' } }), _jsx(YStack, { width: 320, minW: 0, shrink: 1, "$max-md": { width: 'auto', flex: 1 }, children: _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find a project\u2026", "aria-label": "Find a project" }) })] }), shown.length === 0 ? (_jsx(Note, { children: "Nothing matches." })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", bg: "$panel", children: [_jsxs(XStack, { flex: 1, render: "button", "aria-label": "Sort by name", items: "center", gap: "$1", onPress: () => setSort('name'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Name" }), sort === 'name' ? _jsx(ChevronDown, { size: 12 }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, "$max-md": { display: 'none' }, children: "Branch" }), _jsxs(XStack, { render: "button", "aria-label": "Sort by last updated", items: "center", justify: "flex-end", gap: "$1", width: 120, onPress: () => setSort('updated'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Updated" }), sort === 'updated' ? _jsx(ChevronDown, { size: 12 }) : null] })] }), slice.map((c) => (_jsxs(XStack, { render: "button", "aria-label": `Open ${c.org ? `${c.org}/` : ''}${c.name}`, onPress: () => open(c), style: { textAlign: 'left' }, items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(FolderGit2, { size: 15, color: "$soft" }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsxs(XStack, { minW: 0, items: "baseline", children: [c.org ? (_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, shrink: 0, children: `${c.org}/` })) : null, _jsx(SizableText, { size: "$2", color: "$ink", fontWeight: "500", numberOfLines: 1, shrink: 1, children: c.name })] }), c.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.description })) : null] }), _jsxs(XStack, { width: 120, items: "center", gap: "$1.5", minW: 0, "$max-md": { display: 'none' }, children: [_jsx(GitBranch, { size: 12, color: "$soft" }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.branch })] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, style: { textAlign: 'right' }, children: syncing.has(c.name) ? 'Syncing…' : ago(c.updated) })] }, `${c.org}/${c.name}`))), _jsxs(XStack, { px: "$3", py: "$2", justify: "space-between", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: `Showing ${from}–${to} of ${shown.length}` }), _jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": "Previous page", disabled: safePage === 0, onPress: () => setPage(safePage - 1), children: _jsx(SizableText, { size: "$1", color: safePage === 0 ? '$soft' : '$ink', children: "Prev" }) }), _jsx(SizableText, { size: "$1", color: "$soft", children: `${safePage + 1} / ${pages}` }), _jsx(XStack, { render: "button", "aria-label": "Next page", disabled: safePage >= pages - 1, onPress: () => setPage(safePage + 1), children: _jsx(SizableText, { size: "$1", color: safePage >= pages - 1 ? '$soft' : '$ink', children: "Next" }) })] })] })] }))] }))] }) }));
139
+ return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 240, children: [_jsx(SizableText, { render: "h1", size: "$6", fontWeight: "500", color: "$ink", children: t('Projects') }), _jsx(SizableText, { size: "$2", color: "$soft", children: host.org
140
+ ? t('{org}’s repositories on the forge. Open one to work on it.', { org: host.org })
141
+ : t('This organization’s repositories on the forge. Open one to work on it.') })] }), _jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsxs(Button, { size: "sm", variant: "outline", onPress: template, children: [_jsx(LayoutTemplate, { size: 14 }), t('Create from template')] }), _jsxs(Button, { size: "sm", variant: "primary", onPress: link, children: [_jsx(Github, { size: 14 }), t('Link a GitHub repo')] })] })] }), signed ? _jsx(GitHubLink, { onSync: link }) : null, list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: t("Sign in to see this organization's projects.") })) : list.loading && list.value.length === 0 && pending.length === 0 ? (_jsx(Note, { children: t('Reading the forge…') })) : none ? (_jsxs(YStack, { gap: "$3", py: "$2", children: [_jsx(Note, { children: t('No projects yet. A project is a repository: link one from GitHub, or start one from a template.') }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsx(Choice, { label: t('Link a GitHub repo'), icon: _jsx(Github, { size: 20 }), says: t('Mirror repositories you already have. They stay in step with GitHub, and their issues can come too.'), onPress: link }), _jsx(Choice, { label: t('Create from template'), icon: _jsx(LayoutTemplate, { size: 20 }), says: t('Start from a finished product — a site, an app, a store — and make it yours.'), onPress: template })] })] })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(YStack, { flex: 1, "$max-md": { display: 'none' } }), _jsx(YStack, { width: 320, minW: 0, shrink: 1, "$max-md": { width: 'auto', flex: 1 }, children: _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Find a project…'), "aria-label": t('Find a project') }) })] }), shown.length === 0 ? (_jsx(Note, { children: t('Nothing matches.') })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", bg: "$panel", children: [_jsxs(XStack, { flex: 1, render: "button", "aria-label": t('Sort by name'), items: "center", gap: "$1", onPress: () => setSort('name'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('Name') }), sort === 'name' ? _jsx(ChevronDown, { size: 12 }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, "$max-md": { display: 'none' }, children: t('Branch') }), _jsxs(XStack, { render: "button", "aria-label": t('Sort by last updated'), items: "center", justify: "flex-end", gap: "$1", width: 120, onPress: () => setSort('updated'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('Updated') }), sort === 'updated' ? _jsx(ChevronDown, { size: 12 }) : null] })] }), slice.map((c) => (_jsxs(XStack, { render: "button", "aria-label": t('Open {repo}', { repo: `${c.org ? `${c.org}/` : ''}${c.name}` }), onPress: () => open(c), "$platform-web": { textAlign: 'start' }, items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(FolderGit2, { size: 15, color: "$soft" }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsxs(XStack, { minW: 0, items: "baseline", self: "flex-start", 'data-i18n-ltr': '', children: [c.org ? (_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, shrink: 0, children: `${c.org}/` })) : null, _jsx(SizableText, { size: "$2", color: "$ink", fontWeight: "500", numberOfLines: 1, shrink: 1, children: c.name })] }), c.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.description })) : null] }), _jsxs(XStack, { width: 120, items: "center", gap: "$1.5", minW: 0, "$max-md": { display: 'none' }, children: [_jsx(GitBranch, { size: 12, color: "$soft" }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.branch })] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, text: "end", children: syncing.has(c.name) ? t('Syncing…') : ago(c.updated) })] }, `${c.org}/${c.name}`))), _jsxs(XStack, { px: "$3", py: "$2", justify: "space-between", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('Showing {from}–{to} of {total}', { from, to, total: shown.length }) }), _jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": t('Previous page'), disabled: safePage === 0, onPress: () => setPage(safePage - 1), children: _jsx(SizableText, { size: "$1", color: safePage === 0 ? '$soft' : '$ink', children: t('Prev') }) }), _jsx(SizableText, { size: "$1", color: "$soft", children: `${safePage + 1} / ${pages}` }), _jsx(XStack, { render: "button", "aria-label": t('Next page'), disabled: safePage >= pages - 1, onPress: () => setPage(safePage + 1), children: _jsx(SizableText, { size: "$1", color: safePage >= pages - 1 ? '$soft' : '$ink', children: t('Next') }) })] })] })] }))] }))] }) }));
133
142
  }
134
143
  /** One of the two ways in, as a card you press. */
135
144
  function Choice({ label, icon, says, onPress }) {
136
- return (_jsxs(YStack, { render: "button", "aria-label": label, onPress: onPress, flex: 1, minW: 240, gap: "$2", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", items: "flex-start", hoverStyle: { borderColor: '$edge', bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon, _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$4", color: "$ink", fontWeight: "500", children: label }), _jsx(ArrowRight, { size: 14 })] }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'left' }, children: says })] }));
145
+ return (_jsxs(YStack, { render: "button", "aria-label": label, onPress: onPress, flex: 1, minW: 240, gap: "$2", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", items: "flex-start", hoverStyle: { borderColor: '$edge', bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon, _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$4", color: "$ink", fontWeight: "500", children: label }), _jsx(ArrowRight, { size: 14, 'data-i18n-mirror': '' })] }), _jsx(SizableText, { size: "$2", color: "$soft", text: "start", children: says })] }));
137
146
  }
138
147
  /**
139
148
  * The person's GitHub connection, in one line: who it is connected as and the
@@ -142,11 +151,12 @@ function Choice({ label, icon, says, onPress }) {
142
151
  */
143
152
  function GitHubLink({ onSync }) {
144
153
  const host = useHost();
145
- const t = useTarget();
146
- const me = useRead(() => connection(t), null, [t]);
154
+ const target = useTarget();
155
+ const { t } = useI18n();
156
+ const me = useRead(() => connection(target), null, [target]);
147
157
  const [note, setNote] = useState('');
148
158
  const [busy, setBusy] = useState(false);
149
- useBack(t, true, setNote, me.reload);
159
+ useBack(target, true, setNote, me.reload);
150
160
  const c = me.value;
151
161
  // A connection that cannot be read says nothing here: Sync from GitHub above still works.
152
162
  if (me.error || !c || !c.configured)
@@ -155,18 +165,19 @@ function GitHubLink({ onSync }) {
155
165
  setBusy(true);
156
166
  setNote('');
157
167
  try {
158
- host.open(await connect(t, here()));
168
+ host.open(await connect(target, here()));
159
169
  }
160
170
  catch (e) {
161
171
  setNote(e.message);
162
172
  setBusy(false);
163
173
  }
164
174
  };
165
- return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", children: [_jsx(Github, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 200, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: c.connected ? `GitHub connected as @${c.login}` : 'Connect GitHub to bring your repositories here' }), _jsx(SizableText, { size: "$1", color: "$soft", children: note || (c.connected ? 'Choose repositories to mirror; each stays in step with GitHub.' : 'One step on GitHub, then pick the repositories to sync.') })] }), c.connected ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: onSync, children: [_jsx(RefreshCw, { size: 14 }), "Sync repositories"] })) : (_jsxs(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void link(), children: [_jsx(Github, { size: 14 }), busy ? 'Opening GitHub…' : 'Connect GitHub'] }))] }));
175
+ return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", children: [_jsx(Github, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 200, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: c.connected ? t('GitHub connected as {login}', { login: `@${c.login}` }) : t('Connect GitHub to bring your repositories here') }), _jsx(SizableText, { size: "$1", color: "$soft", children: note || (c.connected ? t('Choose repositories to mirror; each stays in step with GitHub.') : t('One step on GitHub, then pick the repositories to sync.')) })] }), c.connected ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: onSync, children: [_jsx(RefreshCw, { size: 14 }), t('Sync repositories')] })) : (_jsxs(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void link(), children: [_jsx(Github, { size: 14 }), busy ? t('Opening GitHub…') : t('Connect GitHub')] }))] }));
166
176
  }
167
- const KINDS = { all: 'Issues and pull requests', issue: 'Issues', pr: 'Pull requests' };
168
- const ORIGINS = { all: 'Every source', github: 'GitHub', hanzo: 'Hanzo' };
169
- const ORDERS = { newest: 'Newest', oldest: 'Oldest', updated: 'Recently updated' };
177
+ /** The menus' words, marked (`phrase`) and drawn through `t`; GitHub and Hanzo are names, drawn as given. */
178
+ const KINDS = { all: phrase('Issues and pull requests'), issue: phrase('Issues'), pr: phrase('Pull requests') };
179
+ const ORIGINS = { all: phrase('Every source'), github: 'GitHub', hanzo: 'Hanzo' };
180
+ const ORDERS = { newest: phrase('Newest'), oldest: phrase('Oldest'), updated: phrase('Recently updated') };
170
181
  /** Mirrored from GitHub: the task index carried it from GitHub (`extRef` github:…). */
171
182
  const fromGitHub = (i) => i.sources.includes('github');
172
183
  /** Done and canceled are closed; every other column is open work. */
@@ -255,12 +266,15 @@ function useOpen(list, work) {
255
266
  }
256
267
  /** Asks which project an issue is for, from the org's own list: never a free string. */
257
268
  function Which({ issue, list, onPick, onClose }) {
269
+ const { t } = useI18n();
258
270
  const [q, setQ] = useState('');
259
271
  const shown = pickProjects(list.filter((p) => p.forge || p.linked), { q }).slice(0, 50);
260
272
  const named = issue?.repo || issue?.hint || '';
261
- return (_jsx(Dialog, { open: issue !== null, onOpenChange: (o) => (o ? null : onClose()), children: _jsxs(DialogContent, { maxW: 480, gap: "$3", children: [_jsx(DialogTitle, { children: "Which project is this for?" }), _jsx(SizableText, { size: "$2", color: "$soft", children: named
262
- ? `“${named}” is not one of this organization’s projects. Choose the repository the run works on.`
263
- : `This issue names no repository${issue?.board ? `: it is on the ${issue.board} board` : ''}. Choose the one the run works on.` }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find a project\u2026", "aria-label": "Find a project" }), _jsx(YStack, { gap: "$1", maxH: 320, overflow: "scroll", children: shown.length === 0 ? (_jsx(Note, { children: list.length ? 'Nothing matches.' : 'No projects are linked yet.' })) : (shown.map((p) => (_jsxs(XStack, { render: "button", "aria-label": `Work on ${p.owner}/${p.name}`, onPress: () => onPick(p), px: "$2.5", py: "$2", rounded: "$3", items: "center", justify: "flex-start", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, shrink: 0, children: `${p.owner}/` }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, shrink: 1, children: p.name })] }, p.key)))) }), _jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "outline", onPress: onClose, children: "Cancel" }) })] }) }));
273
+ return (_jsx(Dialog, { open: issue !== null, onOpenChange: (o) => (o ? null : onClose()), children: _jsxs(DialogContent, { maxW: 480, gap: "$3", children: [_jsx(DialogTitle, { children: t('Which project is this for?') }), _jsx(SizableText, { size: "$2", color: "$soft", children: named
274
+ ? t('“{name}” is not one of this organization’s projects. Choose the repository the run works on.', { name: named })
275
+ : issue?.board
276
+ ? t('This issue names no repository: it is on the {board} board. Choose the one the run works on.', { board: issue.board })
277
+ : t('This issue names no repository. Choose the one the run works on.') }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Find a project…'), "aria-label": t('Find a project') }), _jsx(YStack, { gap: "$1", maxH: 320, overflow: "scroll", children: shown.length === 0 ? (_jsx(Note, { children: list.length ? t('Nothing matches.') : t('No projects are linked yet.') })) : (shown.map((p) => (_jsx(XStack, { render: "button", "aria-label": t('Work on {repo}', { repo: `${p.owner}/${p.name}` }), onPress: () => onPick(p), px: "$2.5", py: "$2", rounded: "$3", items: "center", justify: "flex-start", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { minW: 0, shrink: 1, 'data-i18n-ltr': '', children: [_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, shrink: 0, children: `${p.owner}/` }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, shrink: 1, children: p.name })] }) }, p.key)))) }), _jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "outline", onPress: onClose, children: t('Cancel') }) })] }) }));
264
278
  }
265
279
  /**
266
280
  * Every project's issues, read the way GitHub reads them: open or closed, one
@@ -271,7 +285,8 @@ function Which({ issue, list, onPick, onClose }) {
271
285
  */
272
286
  function Issues() {
273
287
  const host = useHost();
274
- const t = useTarget();
288
+ const target = useTarget();
289
+ const { t } = useI18n();
275
290
  const signed = Boolean(host.person);
276
291
  const [board, setBoard] = useKept(boardKey(host.org), '');
277
292
  const [state, setState] = useState('open');
@@ -284,10 +299,10 @@ function Issues() {
284
299
  const [pulled, setPulled] = useState(0);
285
300
  const [busy, setBusy] = useState(false);
286
301
  const [said, setSaid] = useState(null);
287
- const list = useRead(signed ? () => everything(t) : null, [], [t, signed, pulled]);
288
- const known = useRead(signed ? () => boards(t) : null, [], [t, signed]);
302
+ const list = useRead(signed ? () => everything(target) : null, [], [target, signed, pulled]);
303
+ const known = useRead(signed ? () => boards(target) : null, [], [target, signed]);
289
304
  // What the org's projects are made from, so an issue is placed on its own repository.
290
- const made = useRead(signed ? () => sources(t) : null, NONE, [t, signed]);
305
+ const made = useRead(signed ? () => sources(target) : null, NONE, [target, signed]);
291
306
  const projects = useMemo(() => assemble({ granted: made.value.hub, linked: made.value.linked, held: made.value.held, sites: made.value.sites, work: list.value }), [made.value, list.value]);
292
307
  const work = useMemo(() => attach(list.value, projects), [list.value, projects]);
293
308
  const { open, finding, dialog } = useOpen(projects, list.value);
@@ -334,12 +349,16 @@ function Issues() {
334
349
  setBusy(true);
335
350
  setSaid(null);
336
351
  try {
337
- const s = await syncIssues(t, board);
338
- const where = board ? title : `${s.repos} ${s.repos === 1 ? 'repository' : 'repositories'}`;
339
- const parts = [`${s.issues} ${s.issues === 1 ? 'issue' : 'issues'} synced from ${where}`, `${s.created} new`, `${s.updated} updated`];
352
+ const s = await syncIssues(target, board);
353
+ const where = board ? title : s.repos === 1 ? t('1 repository') : t('{n} repositories', { n: s.repos });
354
+ const parts = [
355
+ s.issues === 1 ? t('1 issue synced from {where}', { where }) : t('{n} issues synced from {where}', { n: s.issues, where }),
356
+ t('{n} new', { n: s.created }),
357
+ t('{n} updated', { n: s.updated }),
358
+ ];
340
359
  if (s.failed)
341
- parts.push(`${s.failed} failed`);
342
- setSaid({ text: `${parts.join(' · ')}.${s.truncated ? ' More remain — sync again to continue.' : ''}` });
360
+ parts.push(t('{n} failed', { n: s.failed }));
361
+ setSaid({ text: `${parts.join(' · ')}.${s.truncated ? ` ${t('More remain — sync again to continue.')}` : ''}` });
343
362
  setPulled((n) => n + 1);
344
363
  }
345
364
  catch (e) {
@@ -352,7 +371,7 @@ function Issues() {
352
371
  };
353
372
  const link = async () => {
354
373
  try {
355
- host.open(await connect(t, here()));
374
+ host.open(await connect(target, here()));
356
375
  }
357
376
  catch (e) {
358
377
  setSaid({ text: e.message });
@@ -360,19 +379,21 @@ function Issues() {
360
379
  };
361
380
  const chip = (key, name, count) => {
362
381
  const on = key.toLowerCase() === board.toLowerCase();
363
- return (_jsxs(XStack, { render: "button", "aria-label": key ? `Show ${name}` : 'Show every project', "aria-pressed": on, onPress: () => setBoard(key), items: "center", gap: "$2", px: "$2.5", py: "$1.5", rounded: "$3", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, shrink: 0, children: [_jsx(SizableText, { flex: 1, size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, style: { textAlign: 'left' }, children: name }), count ? (_jsx(SizableText, { size: "$1", color: "$soft", children: count })) : null] }, key || '*'));
382
+ return (_jsxs(XStack, { render: "button", "aria-label": key ? t('Show {project}', { project: name }) : t('Show every project'), "aria-pressed": on, onPress: () => setBoard(key), items: "center", gap: "$2", px: "$2.5", py: "$1.5", rounded: "$3", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, shrink: 0, children: [_jsx(SizableText, { flex: 1, size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, text: "start", children: name }), count ? (_jsx(SizableText, { size: "$1", color: "$soft", children: count })) : null] }, key || '*'));
364
383
  };
365
- return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 1120, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 240, children: [_jsx(SizableText, { render: "h1", size: "$6", fontWeight: "500", color: "$ink", children: board ? `Issues · ${title}` : 'Issues' }), _jsx(SizableText, { size: "$2", color: "$soft", children: board ? `Work on ${title}. Choosing an issue starts the next run on its own repository.` : 'Work across every project. Choosing an issue starts the next run on its own repository.' })] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: !signed || busy, onPress: () => void pull(), children: [_jsx(Github, { size: 14 }), busy ? 'Syncing…' : board ? `Sync ${title} from GitHub` : 'Sync all from GitHub'] })] }), said ? (_jsxs(XStack, { items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(Note, { children: said.text }), said.connect ? (_jsxs(Button, { size: "sm", variant: "primary", onPress: () => void link(), children: [_jsx(Github, { size: 14 }), "Connect GitHub"] })) : null] })) : null, _jsxs(XStack, { gap: "$5", items: "flex-start", "$max-md": { flexDirection: 'column', gap: '$3' }, children: [_jsxs(YStack, { render: "nav", "aria-label": "Projects", width: 220, shrink: 0, gap: "$0.5", "$max-md": { width: '100%', flexDirection: 'row', overflow: 'scroll', gap: '$1' }, children: [_jsx(SizableText, { size: "$1", color: "$soft", px: "$2.5", pb: "$1", "$max-md": { display: 'none' }, children: "Projects" }), chip('', 'All projects', work.filter((i) => !closed(i)).length), rows.map((p) => chip(p.key, p.name, p.open))] }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$3", width: "100%", children: [_jsx(Input, { value: q, onChangeText: setQ, placeholder: "Search issues", "aria-label": "Search issues", disabled: !signed }), list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: "Sign in to see this organization's issues." })) : list.loading && list.value.length === 0 ? (_jsx(Note, { children: "Reading the forge\u2026" })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", bg: "$panel", flexWrap: "wrap", children: [_jsxs(XStack, { gap: "$3", items: "center", flex: 1, minW: 0, children: [_jsxs(XStack, { render: "button", "aria-label": "Open issues", "aria-pressed": state === 'open', items: "center", gap: "$1.5", onPress: () => setState('open'), children: [_jsx(CircleDot, { size: 14, color: state === 'open' ? '$ink' : '$soft' }), _jsx(SizableText, { size: "$2", color: state === 'open' ? '$ink' : '$soft', fontWeight: state === 'open' ? '500' : '400', children: `${opened.length} Open` })] }), _jsxs(XStack, { render: "button", "aria-label": "Closed issues", "aria-pressed": state === 'closed', items: "center", gap: "$1.5", onPress: () => setState('closed'), children: [_jsx(Check, { size: 14, color: state === 'closed' ? '$ink' : '$soft' }), _jsx(SizableText, { size: "$2", color: state === 'closed' ? '$ink' : '$soft', fontWeight: state === 'closed' ? '500' : '400', children: `${shut.length} Closed` })] })] }), _jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", shrink: 1, minW: 0, children: [_jsx(Pick, { label: "Label", value: label, options: menu('Any label', labels), onPick: setLabel }), _jsx(Pick, { label: "Assignee", value: assignee, options: menu('Anyone', assignees), onPick: setAssignee }), _jsx(Pick, { label: "Kind", value: kind, options: KINDS, onPick: setKind }), _jsx(Pick, { label: "Source", value: origin, options: ORIGINS, onPick: setOrigin }), _jsx(Pick, { label: "Sort", value: order, options: ORDERS, onPick: setOrder })] })] }), shown.length === 0 ? (_jsxs(YStack, { px: "$3", py: "$6", gap: "$3", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Note, { children: state === 'open' ? (narrowed ? 'No open issues match.' : 'Nothing open.') : 'Nothing closed.' }), list.value.length === 0 ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", justify: "center", children: [_jsxs(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => void pull(), children: [_jsx(Github, { size: 14 }), "Sync from GitHub"] }), _jsx(Button, { size: "sm", onPress: () => host.go(''), children: "Start a new run" })] })) : null] })) : (shown.map((i) => {
384
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 1120, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 240, children: [_jsx(SizableText, { render: "h1", size: "$6", fontWeight: "500", color: "$ink", children: board ? t('Issues · {project}', { project: title }) : t('Issues') }), _jsx(SizableText, { size: "$2", color: "$soft", children: board
385
+ ? t('Work on {project}. Choosing an issue starts the next run on its own repository.', { project: title })
386
+ : t('Work across every project. Choosing an issue starts the next run on its own repository.') })] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: !signed || busy, onPress: () => void pull(), children: [_jsx(Github, { size: 14 }), busy ? t('Syncing…') : board ? t('Sync {project} from GitHub', { project: title }) : t('Sync all from GitHub')] })] }), said ? (_jsxs(XStack, { items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(Note, { children: said.text }), said.connect ? (_jsxs(Button, { size: "sm", variant: "primary", onPress: () => void link(), children: [_jsx(Github, { size: 14 }), t('Connect GitHub')] })) : null] })) : null, _jsxs(XStack, { gap: "$5", items: "flex-start", "$max-md": { flexDirection: 'column', gap: '$3' }, children: [_jsxs(YStack, { render: "nav", "aria-label": t('Projects'), width: 220, shrink: 0, gap: "$0.5", "$max-md": { width: '100%', flexDirection: 'row', overflow: 'scroll', gap: '$1' }, children: [_jsx(SizableText, { size: "$1", color: "$soft", px: "$2.5", pb: "$1", "$max-md": { display: 'none' }, children: t('Projects') }), chip('', t('All projects'), work.filter((i) => !closed(i)).length), rows.map((p) => chip(p.key, p.name, p.open))] }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$3", width: "100%", children: [_jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Search issues'), "aria-label": t('Search issues'), disabled: !signed }), list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: t("Sign in to see this organization's issues.") })) : list.loading && list.value.length === 0 ? (_jsx(Note, { children: t('Reading the forge…') })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", bg: "$panel", flexWrap: "wrap", children: [_jsxs(XStack, { gap: "$3", items: "center", flex: 1, minW: 0, children: [_jsxs(XStack, { render: "button", "aria-label": t('Open issues'), "aria-pressed": state === 'open', items: "center", gap: "$1.5", onPress: () => setState('open'), children: [_jsx(CircleDot, { size: 14, color: state === 'open' ? '$ink' : '$soft' }), _jsx(SizableText, { size: "$2", color: state === 'open' ? '$ink' : '$soft', fontWeight: state === 'open' ? '500' : '400', children: t('{n} Open', { n: opened.length }) })] }), _jsxs(XStack, { render: "button", "aria-label": t('Closed issues'), "aria-pressed": state === 'closed', items: "center", gap: "$1.5", onPress: () => setState('closed'), children: [_jsx(Check, { size: 14, color: state === 'closed' ? '$ink' : '$soft' }), _jsx(SizableText, { size: "$2", color: state === 'closed' ? '$ink' : '$soft', fontWeight: state === 'closed' ? '500' : '400', children: t('{n} Closed', { n: shut.length }) })] })] }), _jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", shrink: 1, minW: 0, children: [_jsx(Pick, { label: t('Label'), value: label, options: menu(t('Any label'), labels), onPick: setLabel }), _jsx(Pick, { label: t('Assignee'), value: assignee, options: menu(t('Anyone'), assignees), onPick: setAssignee }), _jsx(Pick, { label: t('Kind'), value: kind, options: { all: t(KINDS.all), issue: t(KINDS.issue), pr: t(KINDS.pr) }, onPick: setKind }), _jsx(Pick, { label: t('Source'), value: origin, options: { ...ORIGINS, all: t(ORIGINS.all) }, onPick: setOrigin }), _jsx(Pick, { label: t('Sort'), value: order, options: { newest: t(ORDERS.newest), oldest: t(ORDERS.oldest), updated: t(ORDERS.updated) }, onPick: setOrder })] })] }), shown.length === 0 ? (_jsxs(YStack, { px: "$3", py: "$6", gap: "$3", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Note, { children: state === 'open' ? (narrowed ? t('No open issues match.') : t('Nothing open.')) : t('Nothing closed.') }), list.value.length === 0 ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", justify: "center", children: [_jsxs(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => void pull(), children: [_jsx(Github, { size: 14 }), t('Sync from GitHub')] }), _jsx(Button, { size: "sm", onPress: () => host.go(''), children: t('Start a new run') })] })) : null] })) : (shown.map((i) => {
366
387
  const named = ref(i);
367
- return (_jsxs(XStack, { items: "center", gap: "$2", pr: "$3", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": `Build ${named || i.title}`, onPress: () => open(i), disabled: finding === i.key && i.key !== '', flex: 1, minW: 0, items: "flex-start", gap: "$3", pl: "$3", py: "$2.5", focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(YStack, { pt: 2, children: _jsx(Mark, { i: i }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", items: "flex-start", children: [_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", maxW: "100%", children: [_jsx(SizableText, { size: "$3", color: "$ink", fontWeight: "500", numberOfLines: 1, shrink: 1, children: i.title }), STATUS[i.status] && !closed(i) && i.status !== 'todo' ? _jsx(Tag, { children: STATUS[i.status] }) : null, !STATUS[i.status] ? _jsx(Tag, { children: i.status }) : null, i.priority !== 'none' && i.priority ? _jsx(Tag, { children: i.priority }) : null, i.labels.slice(0, 4).map((l) => (_jsx(Tag, { children: l }, l)))] }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [
368
- named || (i.pull ? 'pr' : i.kind),
388
+ return (_jsxs(XStack, { items: "center", gap: "$2", paddingInlineEnd: "$3", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": t('Build {issue}', { issue: named || i.title }), onPress: () => open(i), disabled: finding === i.key && i.key !== '', flex: 1, minW: 0, items: "flex-start", gap: "$3", paddingInlineStart: "$3", py: "$2.5", focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(YStack, { pt: 2, children: _jsx(Mark, { i: i }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", items: "flex-start", children: [_jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", maxW: "100%", children: [_jsx(SizableText, { size: "$3", color: "$ink", fontWeight: "500", numberOfLines: 1, shrink: 1, children: i.title }), STATUS[i.status] && !closed(i) && i.status !== 'todo' ? _jsx(Tag, { children: t(STATUS[i.status]) }) : null, !STATUS[i.status] ? _jsx(Tag, { children: i.status }) : null, i.priority !== 'none' && i.priority ? _jsx(Tag, { children: i.priority }) : null, i.labels.slice(0, 4).map((l) => (_jsx(Tag, { children: l }, l)))] }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [
389
+ named || (i.pull ? t('pr') : i.kind),
369
390
  !board && projectOf(i) && !named.startsWith(projectOf(i)) ? projectOf(i) : '',
370
- i.created ? `opened ${ago(i.created)}` : '',
371
- i.assignees.length ? `assigned to ${i.assignees.join(', ')}` : '',
391
+ i.created ? t('opened {when}', { when: ago(i.created) }) : '',
392
+ i.assignees.length ? t('assigned to {people}', { people: i.assignees.join(', ') }) : '',
372
393
  fromGitHub(i) ? 'GitHub' : '',
373
- finding === i.key && i.key !== '' ? 'opening…' : '',
394
+ finding === i.key && i.key !== '' ? t('opening…') : '',
374
395
  ]
375
396
  .filter(Boolean)
376
- .join(' · ') })] })] }), i.url ? (_jsx(Out, { href: i.url, label: `Open ${named || i.title} on ${fromGitHub(i) ? 'GitHub' : 'the forge'}`, children: _jsx(ExternalLink, { size: 14, opacity: 0.6 }) })) : null] }, i.key || `${i.board}:${i.title}`));
397
+ .join(' · ') })] })] }), i.url ? (_jsx(Out, { href: i.url, label: fromGitHub(i) ? t('Open {issue} on GitHub', { issue: named || i.title }) : t('Open {issue} on the forge', { issue: named || i.title }), children: _jsx(ExternalLink, { size: 14, opacity: 0.6 }) })) : null] }, i.key || `${i.board}:${i.title}`));
377
398
  }))] }))] })] })] }), dialog] }));
378
399
  }
package/lib/gear.d.ts CHANGED
@@ -5,7 +5,8 @@ export type Mode = 'auto' | 'manual' | 'plan';
5
5
  * The modes, one line each. Chat offers Auto and Manual; Dev adds Plan. Manual
6
6
  * is a real pause: before a plugin runs for a message (a search, a site read, a
7
7
  * deck), the person allows it once, always in this conversation, or not, and a
8
- * Dev run is asked for before it starts.
8
+ * Dev run is asked for before it starts. Written in English and marked
9
+ * (`phrase`): the composer draws each word through `t`.
9
10
  */
10
11
  export declare const MODES: readonly {
11
12
  id: Mode;
package/lib/gear.js CHANGED
@@ -15,18 +15,20 @@
15
15
  * Memory, the mode); a skill or a plugin is picked for the conversation it was
16
16
  * picked in.
17
17
  */
18
+ import { phrase } from '@hanzo/i18n';
18
19
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
19
20
  import { call } from './api/call.js';
20
21
  /**
21
22
  * The modes, one line each. Chat offers Auto and Manual; Dev adds Plan. Manual
22
23
  * is a real pause: before a plugin runs for a message (a search, a site read, a
23
24
  * deck), the person allows it once, always in this conversation, or not, and a
24
- * Dev run is asked for before it starts.
25
+ * Dev run is asked for before it starts. Written in English and marked
26
+ * (`phrase`): the composer draws each word through `t`.
25
27
  */
26
28
  export const MODES = [
27
- { id: 'auto', label: 'Auto', note: 'Runs tools on its own; pauses only for anything risky.' },
28
- { id: 'manual', label: 'Manual', note: 'Asks before each tool, connector or site it opens.' },
29
- { id: 'plan', label: 'Plan', note: 'Writes the plan and waits for your go before changing anything.' },
29
+ { id: 'auto', label: phrase('Auto'), note: phrase('Runs tools on its own; pauses only for anything risky.') },
30
+ { id: 'manual', label: phrase('Manual'), note: phrase('Asks before each tool, connector or site it opens.') },
31
+ { id: 'plan', label: phrase('Plan'), note: phrase('Writes the plan and waits for your go before changing anything.') },
30
32
  ];
31
33
  /** The modes each surface offers. */
32
34
  export const CHAT_MODES = ['auto', 'manual'];
package/lib/git.js CHANGED
@@ -5,8 +5,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  * with Merge while it is open, Commits is its branch's history since the base.
6
6
  * While the run works it is read again every little while, because a run
7
7
  * pushes when it finishes a step.
8
+ *
9
+ * Its words are the reader's language (@hanzo/i18n `t`); branches, paths,
10
+ * commit messages, a pull request's title and its reviews are drawn as given.
8
11
  */
9
12
  import { SizableText, XStack, YStack } from '@hanzo/gui';
13
+ import { phrase } from '@hanzo/i18n';
14
+ import { useI18n } from '@hanzo/i18n/react';
10
15
  import { ChevronDown, ChevronRight } from '@hanzogui/lucide-icons-2';
11
16
  import { Button } from '@hanzo/ui';
12
17
  import { useEffect, useState } from 'react';
@@ -19,10 +24,11 @@ const AGAIN = 15_000;
19
24
  const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)', whiteSpace: 'pre' };
20
25
  export function Git({ session, title, live }) {
21
26
  const host = useHost();
22
- const t = useTarget();
27
+ const target = useTarget();
28
+ const { t } = useI18n();
23
29
  const signed = Boolean(host.person);
24
30
  const [view, setView] = useState('diff');
25
- const got = useRead(signed ? () => read(t, session) : null, NONE, [t, session, signed, live]);
31
+ const got = useRead(signed ? () => read(target, session) : null, NONE, [target, session, signed, live]);
26
32
  const { reload } = got;
27
33
  useEffect(() => {
28
34
  if (!live)
@@ -33,15 +39,15 @@ export function Git({ session, title, live }) {
33
39
  const c = got.value;
34
40
  const added = c.files.reduce((n, f) => n + f.additions, 0);
35
41
  const removed = c.files.reduce((n, f) => n + f.deletions, 0);
36
- return (_jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: title }), c.head ? (_jsxs(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [`${c.head} → ${c.base}`, c.files.length ? ` · ${c.files.length} ${c.files.length === 1 ? 'file' : 'files'} +${added} −${removed}` : ''] })) : null] }), _jsx(XStack, { gap: "$1", children: ['diff', 'review', 'commits'].map((v) => (_jsx(XStack, { render: "button", "aria-label": label(v), onPress: () => setView(v), px: "$2.5", py: "$1", rounded: "$2", bg: view === v ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$2", color: view === v ? '$ink' : '$soft', children: label(v) }) }, v))) }), !signed ? (_jsx(Soft, { children: "Sign in to see what this run pushed." })) : got.error ? (_jsx(Soft, { children: got.error.message })) : got.loading && !c.head ? (_jsx(Soft, { children: "Reading the branch\u2026" })) : view === 'diff' ? (c.files.length ? (_jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: c.files.map((f, i) => (_jsx(File, { change: f, first: i === 0 }, f.path))) })) : (_jsx(Soft, { children: "No pushed changes" }))) : view === 'review' ? (c.pull ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", p: "$3", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: `#${c.pull.number} ${c.pull.title}` }), _jsx(SizableText, { size: "$1", color: "$soft", children: c.pull.state === 'open'
42
+ return (_jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: title }), c.head ? (_jsxs(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [`${c.head} → ${c.base}`, c.files.length ? ` · ${c.files.length === 1 ? t('1 file') : t('{n} files', { n: c.files.length })} +${added} −${removed}` : ''] })) : null] }), _jsx(XStack, { gap: "$1", children: ['diff', 'review', 'commits'].map((v) => (_jsx(XStack, { render: "button", "aria-label": t(label(v)), onPress: () => setView(v), px: "$2.5", py: "$1", rounded: "$2", bg: view === v ? '$hover' : 'transparent', children: _jsx(SizableText, { size: "$2", color: view === v ? '$ink' : '$soft', children: t(label(v)) }) }, v))) }), !signed ? (_jsx(Soft, { children: t('Sign in to see what this run pushed.') })) : got.error ? (_jsx(Soft, { children: got.error.message })) : got.loading && !c.head ? (_jsx(Soft, { children: t('Reading the branch…') })) : view === 'diff' ? (c.files.length ? (_jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: c.files.map((f, i) => (_jsx(File, { change: f, first: i === 0 }, f.path))) })) : (_jsx(Soft, { children: t('No pushed changes') }))) : view === 'review' ? (c.pull ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", p: "$3", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: `#${c.pull.number} ${c.pull.title}` }), _jsx(SizableText, { size: "$1", color: "$soft", children: c.pull.state === 'open'
37
43
  ? c.pull.mergeable === false
38
- ? 'Open · conflicts with its base'
44
+ ? t('Open · conflicts with its base')
39
45
  : c.pull.mergeable
40
- ? 'Open · ready to merge'
41
- : 'Open'
46
+ ? t('Open · ready to merge')
47
+ : t('Open')
42
48
  : c.pull.state === 'merged'
43
- ? 'Merged'
44
- : 'Closed' }), c.pull.url.startsWith('https://') ? (_jsx(Out, { href: c.pull.url, label: `Open pull request #${c.pull.number}`, children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: "Open on the forge" }) })) : null, _jsx(Merge, { session: session, open: c.pull.state === 'open', onMerged: reload })] }), c.pull.reviews.length ? (c.pull.reviews.map((r, i) => (_jsxs(YStack, { gap: "$1", pb: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: [r.author || 'Someone', verdict(r.state), when(r.at)].filter(Boolean).join(' · ') }), r.body ? (_jsx(SizableText, { size: "$2", color: "$ink", children: r.body })) : null] }, i)))) : (_jsx(Soft, { children: "No reviews yet" }))] })) : (_jsx(Soft, { children: "No pull request yet. The run opens one when it pushes its changes." }))) : c.commits.length ? (_jsx(YStack, { children: c.commits.map((k) => (_jsxs(XStack, { gap: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: k.sha.slice(0, 7) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 2, children: k.message }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [k.author, when(k.date)].filter(Boolean).join(' · ') })] })] }, k.sha))) })) : (_jsx(Soft, { children: "No pushed commits" }))] }));
49
+ ? t('Merged')
50
+ : t('Closed') }), c.pull.url.startsWith('https://') ? (_jsx(Out, { href: c.pull.url, label: t('Open pull request {pr}', { pr: `#${c.pull.number}` }), children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: t('Open on the forge') }) })) : null, _jsx(Merge, { session: session, open: c.pull.state === 'open', onMerged: reload })] }), c.pull.reviews.length ? (c.pull.reviews.map((r, i) => (_jsxs(YStack, { gap: "$1", pb: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: [r.author || t('Someone'), verdict(r.state, t), when(r.at)].filter(Boolean).join(' · ') }), r.body ? (_jsx(SizableText, { size: "$2", color: "$ink", children: r.body })) : null] }, i)))) : (_jsx(Soft, { children: t('No reviews yet') }))] })) : (_jsx(Soft, { children: t('No pull request yet. The run opens one when it pushes its changes.') }))) : c.commits.length ? (_jsx(YStack, { children: c.commits.map((k) => (_jsxs(XStack, { gap: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: mono, children: k.sha.slice(0, 7) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 2, children: k.message }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [k.author, when(k.date)].filter(Boolean).join(' · ') })] })] }, k.sha))) })) : (_jsx(Soft, { children: t('No pushed commits') }))] }));
45
51
  }
46
52
  /**
47
53
  * Merge for a run's pull request, shown while it is open: `POST …/merge` lands
@@ -51,7 +57,8 @@ export function Git({ session, title, live }) {
51
57
  * what closes the button: the pull request reads merged.
52
58
  */
53
59
  export function Merge({ session, open, onMerged }) {
54
- const t = useTarget();
60
+ const target = useTarget();
61
+ const { t } = useI18n();
55
62
  const [busy, setBusy] = useState(false);
56
63
  const [note, setNote] = useState('');
57
64
  const press = async () => {
@@ -60,8 +67,8 @@ export function Merge({ session, open, onMerged }) {
60
67
  setBusy(true);
61
68
  setNote('');
62
69
  try {
63
- const done = await merge(t, session);
64
- setNote(done.base ? `Merged into ${done.base}` : 'Merged');
70
+ const done = await merge(target, session);
71
+ setNote(done.base ? t('Merged into {branch}', { branch: done.base }) : t('Merged'));
65
72
  onMerged();
66
73
  }
67
74
  catch (e) {
@@ -73,30 +80,33 @@ export function Merge({ session, open, onMerged }) {
73
80
  };
74
81
  if (!open && !note)
75
82
  return null;
76
- return (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [open ? (_jsx(Button, { size: "sm", variant: "outline", "aria-busy": busy, onPress: () => void press(), children: busy ? 'Merging…' : 'Merge' })) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null] }));
83
+ return (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [open ? (_jsx(Button, { size: "sm", variant: "outline", "aria-busy": busy, onPress: () => void press(), children: busy ? t('Merging…') : t('Merge') })) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null] }));
77
84
  }
78
85
  function File({ change: f, first }) {
86
+ const { t } = useI18n();
79
87
  const [open, setOpen] = useState(false);
80
88
  const lines = f.patch ? f.patch.split('\n') : [];
81
- return (_jsxs(YStack, { borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", children: [_jsxs(XStack, { render: "button", "aria-label": `${open ? 'Hide' : 'Show'} ${f.path}`, onPress: () => setOpen(!open), items: "center", gap: "$2", px: "$3", py: "$2", hoverStyle: { bg: '$hover' }, children: [open ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }), _jsx(SizableText, { size: "$1", color: "$soft", width: 12, children: f.status[0].toUpperCase() }), _jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", numberOfLines: 1, style: { textAlign: 'left' }, children: f.from ? `${f.from} → ${f.path}` : f.path }), _jsx(SizableText, { size: "$1", color: "$green10", children: `+${f.additions}` }), _jsx(SizableText, { size: "$1", color: "$red10", children: `−${f.deletions}` })] }), open ? (_jsxs(YStack, { px: "$3", pb: "$2", overflow: "scroll", children: [lines.length ? (_jsx(Patch, { text: f.patch })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: f.truncated ? 'Too large to show here.' : 'Binary file' })), lines.length && f.truncated ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "The rest of this file\u2019s change is too large to show here." })) : null] })) : null] }));
89
+ return (_jsxs(YStack, { borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", children: [_jsxs(XStack, { render: "button", "aria-label": open ? t('Hide {path}', { path: f.path }) : t('Show {path}', { path: f.path }), onPress: () => setOpen(!open), items: "center", gap: "$2", px: "$3", py: "$2", hoverStyle: { bg: '$hover' }, children: [open ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14, 'data-i18n-mirror': '' }), _jsx(SizableText, { size: "$1", color: "$soft", width: 12, children: f.status[0].toUpperCase() }), _jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", numberOfLines: 1, text: "start", children: f.from ? `${f.from} → ${f.path}` : f.path }), _jsx(SizableText, { size: "$1", color: "$green10", children: `+${f.additions}` }), _jsx(SizableText, { size: "$1", color: "$red10", children: `−${f.deletions}` })] }), open ? (_jsxs(YStack, { px: "$3", pb: "$2", overflow: "scroll", children: [lines.length ? (_jsx(Patch, { text: f.patch })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: f.truncated ? t('Too large to show here.') : t('Binary file') })), lines.length && f.truncated ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('The rest of this file’s change is too large to show here.') })) : null] })) : null] }));
82
90
  }
83
91
  /** A unified diff's lines, coloured by what each does: the Git tab's, and a transcript's edit card's. */
84
92
  export function Patch({ text }) {
85
93
  return (_jsx(_Fragment, { children: text.split('\n').map((l, i) => (_jsx(SizableText, { size: "$1", style: mono, color: l.startsWith('@@') ? '$soft' : l.startsWith('+') ? '$green10' : l.startsWith('-') ? '$red10' : '$ink', children: l || ' ' }, i))) }));
86
94
  }
95
+ /** A view's name, marked (`phrase`): drawn through `t`. */
87
96
  function label(v) {
88
- return v === 'diff' ? 'Diff' : v === 'review' ? 'Review' : 'Commits';
97
+ return v === 'diff' ? phrase('Diff') : v === 'review' ? phrase('Review') : phrase('Commits');
89
98
  }
90
- function verdict(state) {
99
+ /** What a reviewer did, in the reader's language; a state the forge names that is not one of these is drawn as given. */
100
+ function verdict(state, t) {
91
101
  switch (state.toUpperCase()) {
92
102
  case 'APPROVED':
93
- return 'approved';
103
+ return t('approved');
94
104
  case 'REQUEST_CHANGES':
95
- return 'asked for changes';
105
+ return t('asked for changes');
96
106
  case 'COMMENT':
97
- return 'commented';
107
+ return t('commented');
98
108
  default:
99
- return state.toLowerCase() || 'reviewed';
109
+ return state.toLowerCase() || t('reviewed');
100
110
  }
101
111
  }
102
112
  function when(iso) {
package/lib/host.d.ts CHANGED
@@ -107,8 +107,10 @@ export interface Host {
107
107
  browser?: Browser;
108
108
  /** The host's sidebar lists Settings' sections and Customize's tabs, so the pages draw neither. */
109
109
  sections?: boolean;
110
- /** The default agents an org may add in one press (Customize › Agents). Absent, none are offered. */
110
+ /** The default agents Customize › Agents offers to start from. Absent, none. */
111
111
  cast?: readonly Cast[];
112
+ /** Every persona Customize › Agents offers to start from, read when Browse opens. Absent, none. */
113
+ personas?: () => Promise<readonly Cast[]>;
112
114
  }
113
115
  export declare function HostProvider({ host, children }: {
114
116
  host: Host;
package/lib/landing.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { type Attached } from './tools.tsx';
2
- /** One vocabulary for the mode, on New and in a workspace. */
2
+ /** One vocabulary for the mode, on New and in a workspace: English, marked (`phrase`), drawn through `t`. */
3
3
  export declare const MODES: readonly [{
4
4
  readonly id: 'build';
5
- readonly label: 'Build';
6
- readonly hint: 'Edits, commits and pushes a branch';
5
+ readonly label: string;
6
+ readonly hint: string;
7
7
  }, {
8
8
  readonly id: 'plan';
9
- readonly label: 'Plan';
10
- readonly hint: 'Plans the change and writes nothing';
9
+ readonly label: string;
10
+ readonly hint: string;
11
11
  }];
12
12
  /**
13
13
  * What was typed here and not sent, kept in this tab.