@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.
- package/README.md +22 -4
- package/lib/activity.d.ts +3 -7
- package/lib/activity.js +18 -5
- package/lib/api/agents.d.ts +36 -35
- package/lib/api/agents.js +66 -29
- package/lib/api/auto.d.ts +3 -23
- package/lib/api/auto.js +39 -22
- package/lib/api/billing.d.ts +0 -35
- package/lib/api/billing.js +6 -5
- package/lib/api/call.d.ts +16 -8
- package/lib/api/call.js +32 -1
- package/lib/api/capabilities.d.ts +0 -11
- package/lib/api/capabilities.js +2 -1
- package/lib/api/codebases.d.ts +0 -10
- package/lib/api/codebases.js +3 -2
- package/lib/api/coding.d.ts +3 -32
- package/lib/api/coding.js +13 -7
- package/lib/api/connectors.d.ts +1 -17
- package/lib/api/connectors.js +5 -4
- package/lib/api/environment.d.ts +6 -20
- package/lib/api/environment.js +22 -19
- package/lib/api/github.d.ts +0 -22
- package/lib/api/github.js +3 -2
- package/lib/api/keys.d.ts +0 -14
- package/lib/api/keys.js +4 -3
- package/lib/api/library.d.ts +3 -17
- package/lib/api/library.js +23 -22
- package/lib/api/machines.d.ts +0 -15
- package/lib/api/machines.js +6 -5
- package/lib/api/mcp.d.ts +0 -9
- package/lib/api/mcp.js +3 -2
- package/lib/api/members.d.ts +0 -15
- package/lib/api/members.js +4 -3
- package/lib/api/memory.d.ts +0 -11
- package/lib/api/memory.js +2 -1
- package/lib/api/places.d.ts +1 -9
- package/lib/api/places.js +12 -1
- package/lib/api/plugins.d.ts +1 -16
- package/lib/api/plugins.js +4 -3
- package/lib/api/profile.d.ts +0 -10
- package/lib/api/profile.js +5 -4
- package/lib/api/projects.d.ts +0 -14
- package/lib/api/projects.js +2 -1
- package/lib/api/provider.d.ts +2 -14
- package/lib/api/provider.js +12 -10
- package/lib/api/sandbox.d.ts +0 -17
- package/lib/api/sandbox.js +5 -4
- package/lib/api/sessions.d.ts +0 -17
- package/lib/api/sessions.js +5 -4
- package/lib/api/skills.d.ts +1 -15
- package/lib/api/skills.js +5 -4
- package/lib/api/studio.d.ts +0 -14
- package/lib/api/studio.js +6 -5
- package/lib/api/turn.d.ts +6 -28
- package/lib/api/turn.js +105 -49
- package/lib/api/verdict.d.ts +0 -9
- package/lib/api/verdict.js +2 -1
- package/lib/api/webhooks.d.ts +1 -14
- package/lib/api/webhooks.js +5 -4
- package/lib/ask.js +7 -4
- package/lib/automations.d.ts +5 -4
- package/lib/automations.js +95 -74
- package/lib/back.js +10 -4
- package/lib/builder.js +7 -5
- package/lib/carry.js +20 -16
- package/lib/customize/agents.d.ts +3 -0
- package/lib/customize/agents.js +152 -55
- package/lib/customize/connectors.js +62 -48
- package/lib/customize/index.js +45 -14
- package/lib/customize/plugins.js +34 -22
- package/lib/customize/say.d.ts +13 -1
- package/lib/customize/say.js +15 -21
- package/lib/customize/skills.js +31 -22
- package/lib/customize/ui.d.ts +5 -9
- package/lib/customize/ui.js +26 -16
- package/lib/data.d.ts +1 -1
- package/lib/data.js +3 -2
- package/lib/desk.js +45 -33
- package/lib/door.js +35 -10
- package/lib/environment.js +25 -19
- package/lib/files.js +43 -34
- package/lib/find.js +12 -9
- package/lib/foot.js +7 -4
- package/lib/forge.js +61 -40
- package/lib/gear.d.ts +2 -1
- package/lib/gear.js +6 -4
- package/lib/git.js +29 -19
- package/lib/host.d.ts +3 -1
- package/lib/landing.d.ts +5 -5
- package/lib/landing.js +52 -44
- package/lib/library.js +73 -51
- package/lib/meter.d.ts +1 -1
- package/lib/meter.js +14 -5
- package/lib/mic.js +12 -5
- package/lib/mind.d.ts +1 -0
- package/lib/mind.js +5 -3
- package/lib/nav.d.ts +6 -5
- package/lib/nav.js +37 -30
- package/lib/panel.d.ts +2 -1
- package/lib/panel.js +52 -36
- package/lib/plan.d.ts +4 -20
- package/lib/plan.js +26 -10
- package/lib/plans.js +49 -30
- package/lib/plugins/design.js +3 -2
- package/lib/plugins/index.d.ts +23 -4
- package/lib/plugins/index.js +11 -26
- package/lib/plugins/pdf.js +2 -1
- package/lib/plugins/project.js +6 -3
- package/lib/plugins/research.js +3 -2
- package/lib/plugins/sketch.js +6 -3
- package/lib/plugins/slides.js +3 -2
- package/lib/plugins/template.js +6 -3
- package/lib/plus.js +25 -13
- package/lib/prefs.d.ts +0 -14
- package/lib/prefs.js +2 -1
- package/lib/project.js +69 -62
- package/lib/prompt.d.ts +12 -11
- package/lib/prompt.js +38 -20
- package/lib/publish.js +17 -12
- package/lib/reply.js +6 -3
- package/lib/run.js +72 -54
- package/lib/say.d.ts +2 -0
- package/lib/say.js +7 -0
- package/lib/scheduled.js +54 -43
- package/lib/section.d.ts +1 -0
- package/lib/section.js +8 -2
- package/lib/settings/account.d.ts +1 -0
- package/lib/settings/account.js +31 -21
- package/lib/settings/billing.js +55 -28
- package/lib/settings/capabilities.js +26 -15
- package/lib/settings/card.js +19 -12
- package/lib/settings/code.js +22 -11
- package/lib/settings/credits.d.ts +4 -4
- package/lib/settings/credits.js +34 -21
- package/lib/settings/environments.js +21 -14
- package/lib/settings/general.js +26 -14
- package/lib/settings/index.js +6 -3
- package/lib/settings/integrations.js +42 -28
- package/lib/settings/keys.js +37 -19
- package/lib/settings/list.d.ts +0 -5
- package/lib/settings/list.js +24 -15
- package/lib/settings/machines.d.ts +3 -0
- package/lib/settings/machines.js +63 -38
- package/lib/settings/members.js +23 -14
- package/lib/settings/memory.js +12 -6
- package/lib/settings/notifications.js +32 -19
- package/lib/settings/plan.d.ts +1 -1
- package/lib/settings/plan.js +40 -17
- package/lib/settings/privacy.js +26 -13
- package/lib/settings/ui.d.ts +3 -2
- package/lib/settings/ui.js +9 -3
- package/lib/settings/usage.js +7 -3
- package/lib/shelf.js +18 -15
- package/lib/side.d.ts +1 -26
- package/lib/side.js +11 -5
- package/lib/standing.d.ts +2 -10
- package/lib/standing.js +5 -3
- package/lib/suggest.d.ts +8 -28
- package/lib/suggest.js +39 -4
- package/lib/suggestions.js +14 -5
- package/lib/summary.d.ts +1 -1
- package/lib/summary.js +4 -1
- package/lib/sync.js +22 -12
- package/lib/tools.d.ts +4 -3
- package/lib/tools.js +10 -7
- package/lib/transcript.js +10 -8
- 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
|
|
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(
|
|
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(
|
|
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,
|
|
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:
|
|
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",
|
|
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
|
|
146
|
-
const
|
|
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(
|
|
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(
|
|
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 ?
|
|
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
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
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:
|
|
262
|
-
?
|
|
263
|
-
:
|
|
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
|
|
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(
|
|
288
|
-
const known = useRead(signed ? () => boards(
|
|
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(
|
|
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(
|
|
338
|
-
const where = board ? title :
|
|
339
|
-
const parts = [
|
|
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(
|
|
342
|
-
setSaid({ text: `${parts.join(' · ')}.${s.truncated ? '
|
|
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(
|
|
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 ?
|
|
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 ?
|
|
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",
|
|
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 ?
|
|
371
|
-
i.assignees.length ?
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
64
|
-
setNote(done.base ?
|
|
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":
|
|
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
|
-
|
|
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
|
|
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:
|
|
6
|
-
readonly hint:
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly hint: string;
|
|
7
7
|
}, {
|
|
8
8
|
readonly id: 'plan';
|
|
9
|
-
readonly label:
|
|
10
|
-
readonly hint:
|
|
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.
|