@hanzo/build 0.2.52 → 0.2.53
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 +103 -49
- package/lib/api/billing.d.ts +39 -0
- package/lib/api/billing.js +41 -0
- package/lib/api/coding.d.ts +4 -1
- package/lib/api/coding.js +2 -0
- package/lib/api/environment.d.ts +12 -0
- package/lib/api/environment.js +39 -1
- package/lib/api/github.d.ts +24 -4
- package/lib/api/github.js +60 -16
- package/lib/api/limits.d.ts +69 -0
- package/lib/api/limits.js +86 -0
- package/lib/api/links.d.ts +42 -0
- package/lib/api/links.js +65 -0
- package/lib/api/pref.d.ts +5 -3
- package/lib/api/pref.js +0 -3
- package/lib/api/turn.d.ts +10 -1
- package/lib/api/turn.js +16 -4
- package/lib/api/work.d.ts +75 -38
- package/lib/api/work.js +132 -44
- package/lib/builder.d.ts +0 -10
- package/lib/builder.js +12 -52
- package/lib/choice.d.ts +21 -0
- package/lib/choice.js +40 -1
- package/lib/customize/agents.d.ts +1 -1
- package/lib/customize/agents.js +21 -19
- package/lib/customize/connectors.d.ts +1 -1
- package/lib/customize/connectors.js +80 -31
- package/lib/customize/index.d.ts +2 -0
- package/lib/customize/index.js +23 -19
- package/lib/customize/load.d.ts +9 -0
- package/lib/customize/load.js +22 -0
- package/lib/customize/plugins.d.ts +1 -1
- package/lib/customize/plugins.js +21 -19
- package/lib/customize/say.d.ts +1 -0
- package/lib/customize/say.js +38 -0
- package/lib/customize/skills.d.ts +1 -1
- package/lib/customize/skills.js +23 -17
- package/lib/customize/ui.d.ts +28 -11
- package/lib/customize/ui.js +29 -21
- package/lib/data.d.ts +13 -0
- package/lib/data.js +70 -0
- package/lib/desk.d.ts +6 -1
- package/lib/desk.js +43 -19
- package/lib/environment.d.ts +11 -3
- package/lib/environment.js +53 -13
- package/lib/foot.js +10 -6
- package/lib/forge.js +139 -71
- package/lib/index.d.ts +28 -2
- package/lib/index.js +26 -2
- package/lib/landing.d.ts +0 -10
- package/lib/landing.js +70 -38
- package/lib/merge.d.ts +223 -0
- package/lib/merge.js +380 -0
- package/lib/meter.d.ts +31 -0
- package/lib/meter.js +45 -0
- package/lib/mind.d.ts +60 -0
- package/lib/mind.js +187 -0
- package/lib/nav.d.ts +11 -0
- package/lib/nav.js +75 -0
- package/lib/panel.d.ts +45 -0
- package/lib/panel.js +115 -0
- package/lib/pick.d.ts +8 -0
- package/lib/pick.js +18 -1
- package/lib/plan.d.ts +78 -0
- package/lib/plan.js +133 -0
- package/lib/project.js +16 -6
- package/lib/prompt.d.ts +75 -0
- package/lib/prompt.js +125 -0
- package/lib/prose.js +14 -10
- package/lib/reply.d.ts +36 -0
- package/lib/reply.js +124 -0
- package/lib/route.d.ts +16 -0
- package/lib/route.js +40 -0
- package/lib/run.js +87 -52
- package/lib/section.d.ts +1 -1
- package/lib/section.js +7 -11
- package/lib/settings/billing.js +16 -9
- package/lib/settings/code.js +15 -12
- package/lib/settings/credits.d.ts +9 -0
- package/lib/settings/credits.js +110 -0
- package/lib/settings/plan.d.ts +17 -0
- package/lib/settings/plan.js +108 -0
- package/lib/settings/usage.js +11 -119
- package/lib/shelf.js +3 -3
- package/lib/standing.d.ts +44 -0
- package/lib/standing.js +64 -0
- package/lib/sync.js +22 -8
- package/lib/tabs.d.ts +53 -0
- package/lib/tabs.js +197 -0
- package/lib/tools.d.ts +2 -9
- package/lib/tools.js +20 -25
- package/lib/transcript.d.ts +10 -1
- package/lib/transcript.js +25 -10
- package/lib/voice.d.ts +2 -13
- package/lib/voice.js +7 -73
- package/package.json +20 -3
package/lib/environment.js
CHANGED
|
@@ -13,8 +13,9 @@ import { Plus, X } from '@hanzogui/lucide-icons-2';
|
|
|
13
13
|
import { Button, Dialog, DialogContent, DialogTitle, Input, Textarea } from '@hanzo/ui';
|
|
14
14
|
import { useEffect, useState } from 'react';
|
|
15
15
|
import { start } from './api/coding.js';
|
|
16
|
-
import { read, refuse, removeSecret, save, setSecret, SETUP } from './api/environment.js';
|
|
16
|
+
import { plain, read, refuse, removeSecret, save, setSecret, SETUP } from './api/environment.js';
|
|
17
17
|
import { useRead } from './data.js';
|
|
18
|
+
import { matches } from './merge.js';
|
|
18
19
|
import { useHost, useTarget } from './host.js';
|
|
19
20
|
const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
|
|
20
21
|
export function Environment({ repo, busy }) {
|
|
@@ -44,7 +45,7 @@ export function Environment({ repo, busy }) {
|
|
|
44
45
|
return _jsx(Soft, { children: "This run has no codebase, so it has no environment." });
|
|
45
46
|
}
|
|
46
47
|
if (env.error) {
|
|
47
|
-
return _jsx(Soft, { children:
|
|
48
|
+
return _jsx(Soft, { children: `${repo}’s environment could not be read right now.` });
|
|
48
49
|
}
|
|
49
50
|
if (!shown) {
|
|
50
51
|
return _jsx(Soft, { children: "Reading the environment\u2026" });
|
|
@@ -63,7 +64,7 @@ export function Environment({ repo, busy }) {
|
|
|
63
64
|
return true;
|
|
64
65
|
}
|
|
65
66
|
catch (e) {
|
|
66
|
-
setNote(e
|
|
67
|
+
setNote(plain(e, repo, 'save'));
|
|
67
68
|
return false;
|
|
68
69
|
}
|
|
69
70
|
finally {
|
|
@@ -78,7 +79,7 @@ export function Environment({ repo, busy }) {
|
|
|
78
79
|
host.go(run.session);
|
|
79
80
|
}
|
|
80
81
|
catch (e) {
|
|
81
|
-
setNote(e
|
|
82
|
+
setNote(plain(e, repo, 'start'));
|
|
82
83
|
setWorking(false);
|
|
83
84
|
}
|
|
84
85
|
};
|
|
@@ -102,27 +103,66 @@ export function Environment({ repo, busy }) {
|
|
|
102
103
|
/**
|
|
103
104
|
* New's offer to set a codebase up. An agent onboards it, or an org admin saves
|
|
104
105
|
* it empty and writes the scripts by hand beside the next run.
|
|
106
|
+
*
|
|
107
|
+
* The repository is chosen from the org's own (the forge's, and every linked
|
|
108
|
+
* repository's copy there), never typed: a name that is not one of them — a
|
|
109
|
+
* board's key handed over as a codebase — is said so and cannot be set up.
|
|
105
110
|
*/
|
|
106
|
-
export function SetupDialog({ repo, open, onOpenChange, onStart, onSaved, }) {
|
|
111
|
+
export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved, onRepo, }) {
|
|
107
112
|
const host = useHost();
|
|
108
113
|
const t = useTarget();
|
|
109
|
-
const [
|
|
114
|
+
const [chosen, setChosen] = useState(repo);
|
|
115
|
+
const [q, setQ] = useState('');
|
|
116
|
+
const [working, setWorking] = useState('');
|
|
110
117
|
const [note, setNote] = useState('');
|
|
111
|
-
const
|
|
112
|
-
|
|
118
|
+
const [last, setLast] = useState('');
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!open)
|
|
121
|
+
return;
|
|
122
|
+
setChosen(repo);
|
|
123
|
+
setQ('');
|
|
124
|
+
setNote('');
|
|
125
|
+
setLast('');
|
|
126
|
+
}, [open, repo]);
|
|
127
|
+
const list = homes.value.homes;
|
|
128
|
+
const home = list.find((h) => h.name.toLowerCase() === chosen.toLowerCase()) ?? null;
|
|
129
|
+
// A name the whole list does not hold is not the org's; with part of the list unread, the platform decides.
|
|
130
|
+
const stranger = Boolean(chosen) && !home && homes.value.whole && !homes.loading;
|
|
131
|
+
const ready = Boolean(chosen) && !stranger && !homes.loading;
|
|
132
|
+
const shown = list.filter((h) => matches(q, h.label, h.name)).slice(0, 6);
|
|
133
|
+
const run = async (what) => {
|
|
134
|
+
if (!ready)
|
|
135
|
+
return;
|
|
136
|
+
// The org's own spelling of the name, when the list holds it.
|
|
137
|
+
const repo = home?.name ?? chosen;
|
|
138
|
+
setWorking(what);
|
|
139
|
+
setLast(what);
|
|
113
140
|
setNote('');
|
|
114
141
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
if (what === 'save') {
|
|
143
|
+
await save(t, repo, { install: '', start: '' });
|
|
144
|
+
onSaved(repo);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
await onStart(repo);
|
|
148
|
+
onOpenChange(false);
|
|
149
|
+
}
|
|
117
150
|
}
|
|
118
151
|
catch (e) {
|
|
119
|
-
setNote(e
|
|
152
|
+
setNote(plain(e, repo, what));
|
|
120
153
|
}
|
|
121
154
|
finally {
|
|
122
|
-
setWorking(
|
|
155
|
+
setWorking('');
|
|
123
156
|
}
|
|
124
157
|
};
|
|
125
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW:
|
|
158
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: "Set up an environment" }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Repository" }), _jsx(XStack, { px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: stranger ? '$ink' : '$borderColor', items: "center", gap: "$2", "aria-label": "Chosen repository", children: _jsx(SizableText, { size: "$2", color: home || (chosen && !stranger) ? '$ink' : '$soft', numberOfLines: 1, flex: 1, minW: 0, children: home ? home.label : chosen || 'Choose a repository' }) }), stranger ? (_jsx(SizableText, { size: "$1", color: "$ink", role: "alert", children: `${chosen} is not one of this organization’s repositories. Choose one below.` })) : null, _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find a repository\u2026", "aria-label": "Find a repository" }), _jsx(YStack, { gap: 2, "aria-label": "Repositories", children: homes.loading && !list.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Reading the repositories\u2026" })) : homes.error && !list.length ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "The repository list could not be read right now." }), _jsx(Button, { size: "sm", variant: "outline", onPress: homes.reload, children: "Read again" })] })) : shown.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: list.length ? 'Nothing matches.' : 'This organization has no repositories on the forge yet.' })) : (shown.map((h) => {
|
|
159
|
+
const on = h.name.toLowerCase() === chosen.toLowerCase();
|
|
160
|
+
return (_jsx(XStack, { render: "button", "aria-pressed": on, "aria-label": `Choose ${h.label}`, onPress: () => {
|
|
161
|
+
setChosen(h.name);
|
|
162
|
+
setNote('');
|
|
163
|
+
onRepo(h);
|
|
164
|
+
}, px: "$2.5", py: "$1.5", rounded: "$2", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, children: h.label }) }, h.name));
|
|
165
|
+
})) })] }), _jsx(SizableText, { size: "$2", color: "$soft", children: "An agent onboards the codebase: it explores it, writes the install and start scripts, and names the secrets it needs. It takes several minutes. Interrupt it anytime, or take over in the terminal." }), note ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$ink", role: "status", flex: 1, minW: 200, children: note }), last ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working !== '', onPress: () => void run(last), children: "Try again" })) : null] })) : null, _jsxs(XStack, { gap: "$2", justify: "space-between", items: "center", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "secondary", disabled: working !== '' || !ready, onPress: () => void run('save'), children: working === 'save' ? 'Saving…' : 'Skip & save' })) : (_jsx(YStack, {})), _jsx(Button, { size: "sm", variant: "primary", disabled: working !== '' || !ready, onPress: () => void run('start'), children: working === 'start' ? 'Starting the agent…' : 'Start agent' })] })] })] }) }));
|
|
126
166
|
}
|
|
127
167
|
function Script({ label, value, onChange, locked, hint }) {
|
|
128
168
|
return (_jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), _jsx(Textarea, { value: value, onChangeText: onChange, disabled: locked, placeholder: hint, "aria-label": label, rows: 3, style: mono })] }));
|
package/lib/foot.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* The rail's foot: the card that offers Hanzo in Slack, and the menu the account
|
|
4
|
-
* row opens — who is signed in, the
|
|
5
|
-
* the plans, help and
|
|
4
|
+
* row opens — who is signed in, the plan and its meter (`Meter`), the
|
|
5
|
+
* organization they act in, Settings, Usage, Billing, the plans, help and
|
|
6
|
+
* logging out.
|
|
6
7
|
* Everything it opens is an address of this page, but help, which is the
|
|
7
8
|
* documentation.
|
|
8
9
|
*/
|
|
9
10
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
11
|
import { Popover, SizableText, XStack, YStack } from '@hanzo/gui';
|
|
11
|
-
import { Gauge, LifeBuoy, LogOut, Settings, Slack as Mark, Sparkles, X } from '@hanzogui/lucide-icons-2';
|
|
12
|
+
import { CreditCard, Gauge, LifeBuoy, LogOut, Settings, Slack as Mark, Sparkles, X } from '@hanzogui/lucide-icons-2';
|
|
12
13
|
import { MenuLabel, MenuRow, MenuRule } from '@hanzo/ui/product';
|
|
13
|
-
import { useHost } from './host.js';
|
|
14
|
+
import { useHost, useTarget } from './host.js';
|
|
15
|
+
import { Meter } from './meter.js';
|
|
16
|
+
import { useStanding } from './standing.js';
|
|
14
17
|
import { path } from './route.js';
|
|
15
18
|
export const DOCS = 'https://docs.hanzo.ai/docs/dev';
|
|
16
19
|
/** Hanzo in Slack is set up under Settings; the card goes there, and once dismissed stays gone. */
|
|
@@ -52,17 +55,18 @@ export function useWho() {
|
|
|
52
55
|
*/
|
|
53
56
|
export function Who({ open, onOpenChange, narrow }) {
|
|
54
57
|
const host = useHost();
|
|
58
|
+
const standing = useStanding(useTarget(), open);
|
|
55
59
|
// The organizations this person acts in; choosing one reloads the page scoped to it.
|
|
56
60
|
const orgs = host.memberships ?? (host.org ? [host.org] : []);
|
|
57
61
|
const go = (p) => {
|
|
58
62
|
onOpenChange(false);
|
|
59
63
|
host.go(p);
|
|
60
64
|
};
|
|
61
|
-
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": "Account", items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: "Organization" }), _jsx(YStack, { role: "radiogroup", "aria-label": "Organizations", gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
|
|
65
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": "Account", items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), _jsx(MenuRule, {}), _jsx(Meter, { read: standing, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: "Organization" }), _jsx(YStack, { role: "radiogroup", "aria-label": "Organizations", gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
|
|
62
66
|
onOpenChange(false);
|
|
63
67
|
if (o !== host.org)
|
|
64
68
|
host.chooseOrg?.(o);
|
|
65
|
-
} }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: "Usage", icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: "View all plans", icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: "Get help", icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
|
|
69
|
+
} }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: "Usage", icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: "Billing", icon: _jsx(CreditCard, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(MenuRow, { label: "View all plans", icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: "Get help", icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
|
|
66
70
|
onOpenChange(false);
|
|
67
71
|
window.open(DOCS, '_blank', 'noopener,noreferrer');
|
|
68
72
|
} }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label: "Log out", icon: _jsx(LogOut, { size: 16 }), onPress: () => {
|
package/lib/forge.js
CHANGED
|
@@ -4,19 +4,21 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
4
4
|
*
|
|
5
5
|
* A project is a repository, linked from GitHub or started from a template;
|
|
6
6
|
* choosing one opens its workspace, where every run works on it. Issues are
|
|
7
|
-
* the forge's
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* the forge's and the task index's, merged (merge.ts): the same issue read
|
|
8
|
+
* from two boards is one row. A repository's GitHub issues sync onto it.
|
|
9
|
+
* Choosing an issue opens New on the issue's own repository — its GitHub
|
|
10
|
+
* address, or the forge repository it is filed on, never a board's key — with
|
|
11
|
+
* the issue as the ask; an issue no project answers to asks which one.
|
|
10
12
|
*/
|
|
11
13
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
12
14
|
import { ArrowRight, Check, ChevronDown, CircleCheck, CircleDot, CircleSlash, ExternalLink, FolderGit2, GitBranch, Github, GitPullRequest, LayoutTemplate, RefreshCw, } from '@hanzogui/lucide-icons-2';
|
|
13
|
-
import { Button, DropdownMenu, Input } from '@hanzo/ui';
|
|
14
|
-
import { useEffect, useState } from 'react';
|
|
15
|
+
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
16
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
15
17
|
import { ago } from './ago.js';
|
|
16
18
|
import { codebases } from './api/codebases.js';
|
|
17
19
|
import { Automations } from './automations.js';
|
|
18
|
-
import { boards,
|
|
19
|
-
import { boardKey,
|
|
20
|
+
import { boards, everything, sources } from './api/work.js';
|
|
21
|
+
import { boardKey, pinRepo, readPending, writePending } from './choice.js';
|
|
20
22
|
import { useKept, useRead } from './data.js';
|
|
21
23
|
import { useHost, useTarget } from './host.js';
|
|
22
24
|
import { path } from './route.js';
|
|
@@ -25,6 +27,7 @@ import { Sync } from './sync.js';
|
|
|
25
27
|
import { connect, connection, grants, syncIssues } from './api/github.js';
|
|
26
28
|
import { here, useBack } from './back.js';
|
|
27
29
|
import { Out } from './out.js';
|
|
30
|
+
import { ask, assemble, attach, closes, facets, handle, home, keyOf, matches, pickProjects, place } from './merge.js';
|
|
28
31
|
function Note({ children }) {
|
|
29
32
|
return (_jsx(SizableText, { size: "$2", color: "$soft", children: children }));
|
|
30
33
|
}
|
|
@@ -164,34 +167,107 @@ function GitHubLink({ onSync }) {
|
|
|
164
167
|
const KINDS = { all: 'Issues and pull requests', issue: 'Issues', pr: 'Pull requests' };
|
|
165
168
|
const ORIGINS = { all: 'Every source', github: 'GitHub', hanzo: 'Hanzo' };
|
|
166
169
|
const ORDERS = { newest: 'Newest', oldest: 'Oldest', updated: 'Recently updated' };
|
|
167
|
-
/** Mirrored from GitHub:
|
|
168
|
-
const fromGitHub = (
|
|
170
|
+
/** Mirrored from GitHub: the task index carried it from GitHub (`extRef` github:…). */
|
|
171
|
+
const fromGitHub = (i) => i.sources.includes('github');
|
|
172
|
+
/** Done and canceled are closed; every other column is open work. */
|
|
173
|
+
const closed = (i) => i.state === 'closed';
|
|
174
|
+
/** An RFC 3339 time as milliseconds, 0 when there is none. */
|
|
175
|
+
const ms = (iso) => Date.parse(iso) || 0;
|
|
176
|
+
/** The repository an issue is for, once the projects have named it, else the forge's name for it, else its board. */
|
|
177
|
+
const projectOf = (i) => i.repo || i.forge || i.board;
|
|
178
|
+
/**
|
|
179
|
+
* Whether `i` is filed under the project `key`: its board, its forge
|
|
180
|
+
* repository, or its repository by address or by name — either case. A board
|
|
181
|
+
* and the repository it is named for are one project, as the forge files them.
|
|
182
|
+
*/
|
|
183
|
+
function inProject(i, key) {
|
|
184
|
+
const k = key.toLowerCase();
|
|
185
|
+
return [i.board, i.forge, i.repo, i.repo.split('/')[1] ?? ''].some((v) => v !== '' && v.toLowerCase() === k);
|
|
186
|
+
}
|
|
187
|
+
/** How a person refers to an issue: `owner/name#N` on its own repository, the board's handle otherwise, '' with neither. */
|
|
188
|
+
function ref(i) {
|
|
189
|
+
const tag = handle(i);
|
|
190
|
+
const at = i.repo || i.forge;
|
|
191
|
+
return tag.startsWith('#') && at ? `${at}${tag}` : tag;
|
|
192
|
+
}
|
|
169
193
|
/** A dropdown that picks one of `options`, its trigger naming the choice. */
|
|
170
194
|
function Pick({ label, value, options, onPick }) {
|
|
171
195
|
return (_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": label, items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: options[value] }), _jsx(ChevronDown, { size: 12 })] }), items: Object.keys(options).map((k) => ({
|
|
172
|
-
key: k,
|
|
196
|
+
key: k || '*',
|
|
173
197
|
label: options[k],
|
|
174
198
|
icon: k === value ? _jsx(Check, { size: 14 }) : _jsx(YStack, { width: 14 }),
|
|
175
199
|
onSelect: () => onPick(k),
|
|
176
200
|
})) }));
|
|
177
201
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (
|
|
202
|
+
/** The words a list carries as a menu: every one of them, or `all`. */
|
|
203
|
+
const menu = (all, values) => Object.fromEntries([['', all], ...values.map((v) => [v, v])]);
|
|
204
|
+
function Mark({ i }) {
|
|
205
|
+
if (i.pull)
|
|
206
|
+
return _jsx(GitPullRequest, { size: 16, color: closed(i) ? '$soft' : '$ink' });
|
|
207
|
+
if (i.status === 'canceled')
|
|
182
208
|
return _jsx(CircleSlash, { size: 16, color: "$soft" });
|
|
183
|
-
if (
|
|
209
|
+
if (closed(i))
|
|
184
210
|
return _jsx(CircleCheck, { size: 16, color: "$soft" });
|
|
185
211
|
return _jsx(CircleDot, { size: 16, color: "$ink" });
|
|
186
212
|
}
|
|
187
213
|
function Tag({ children }) {
|
|
188
214
|
return (_jsx(XStack, { px: "$2", py: 1, rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: children }) }));
|
|
189
215
|
}
|
|
216
|
+
const NONE = { hub: [], linked: [], held: [], sites: [], owners: [], next: '', unread: [], missing: [] };
|
|
217
|
+
/**
|
|
218
|
+
* Pressing an issue: New, on the issue's own repository, holding the issue. A
|
|
219
|
+
* GitHub issue runs on GitHub, where its pull request closes it; a forge issue
|
|
220
|
+
* on the forge. A repository not among the projects read so far is looked up by
|
|
221
|
+
* its address; an issue no project answers to asks which one it is for — a
|
|
222
|
+
* board's key or a Linear team's is never taken for a repository.
|
|
223
|
+
*/
|
|
224
|
+
function useOpen(list, work) {
|
|
225
|
+
const host = useHost();
|
|
226
|
+
const t = useTarget();
|
|
227
|
+
const [asking, setAsking] = useState(null);
|
|
228
|
+
const [finding, setFinding] = useState('');
|
|
229
|
+
const go = (i, p) => {
|
|
230
|
+
// A project the person picked for an issue that is not its own closes nothing there.
|
|
231
|
+
const own = place(i, [p]) !== null;
|
|
232
|
+
const n = own ? closes(i, p) : 0;
|
|
233
|
+
pinRepo(host.org, p, ask(i, n), n, own ? home(i, p) : undefined);
|
|
234
|
+
setAsking(null);
|
|
235
|
+
host.go('');
|
|
236
|
+
};
|
|
237
|
+
const open = async (i) => {
|
|
238
|
+
const p = place(i, list);
|
|
239
|
+
if (p && (p.forge || p.linked))
|
|
240
|
+
return go(i, p);
|
|
241
|
+
if (i.repo) {
|
|
242
|
+
setFinding(i.key);
|
|
243
|
+
const [owner = '', name = ''] = i.repo.split('/');
|
|
244
|
+
const found = await sources(t, { q: name, owner })
|
|
245
|
+
.then((s) => assemble({ granted: s.hub, linked: s.linked, held: s.held, sites: s.sites, work }).find((x) => x.key === keyOf(owner, name)) ?? null)
|
|
246
|
+
.catch(() => null);
|
|
247
|
+
setFinding('');
|
|
248
|
+
if (found && (found.forge || found.linked))
|
|
249
|
+
return go(i, found);
|
|
250
|
+
}
|
|
251
|
+
setAsking(i);
|
|
252
|
+
};
|
|
253
|
+
const dialog = _jsx(Which, { issue: asking, list: list, onPick: (p) => asking && go(asking, p), onClose: () => setAsking(null) });
|
|
254
|
+
return { open: (i) => void open(i), finding, dialog };
|
|
255
|
+
}
|
|
256
|
+
/** Asks which project an issue is for, from the org's own list: never a free string. */
|
|
257
|
+
function Which({ issue, list, onPick, onClose }) {
|
|
258
|
+
const [q, setQ] = useState('');
|
|
259
|
+
const shown = pickProjects(list.filter((p) => p.forge || p.linked), { q }).slice(0, 50);
|
|
260
|
+
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" }) })] }) }));
|
|
264
|
+
}
|
|
190
265
|
/**
|
|
191
266
|
* Every project's issues, read the way GitHub reads them: open or closed, one
|
|
192
|
-
* project or all of them, narrowed by words, kind, source and
|
|
193
|
-
*
|
|
194
|
-
*
|
|
267
|
+
* project or all of them, narrowed by words, kind, source, label and assignee,
|
|
268
|
+
* and ordered. The same issue on two boards is one row. Each project syncs its
|
|
269
|
+
* own issues from GitHub; choosing an issue starts the next run on the issue's
|
|
270
|
+
* own repository with the issue as the ask.
|
|
195
271
|
*/
|
|
196
272
|
function Issues() {
|
|
197
273
|
const host = useHost();
|
|
@@ -202,12 +278,19 @@ function Issues() {
|
|
|
202
278
|
const [kind, setKind] = useState('all');
|
|
203
279
|
const [origin, setOrigin] = useState('all');
|
|
204
280
|
const [order, setOrder] = useState('newest');
|
|
281
|
+
const [label, setLabel] = useState('');
|
|
282
|
+
const [assignee, setAssignee] = useState('');
|
|
205
283
|
const [q, setQ] = useState('');
|
|
206
284
|
const [pulled, setPulled] = useState(0);
|
|
207
285
|
const [busy, setBusy] = useState(false);
|
|
208
286
|
const [said, setSaid] = useState(null);
|
|
209
|
-
const list = useRead(signed ? () =>
|
|
287
|
+
const list = useRead(signed ? () => everything(t) : null, [], [t, signed, pulled]);
|
|
210
288
|
const known = useRead(signed ? () => boards(t) : null, [], [t, signed]);
|
|
289
|
+
// 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]);
|
|
291
|
+
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
|
+
const work = useMemo(() => attach(list.value, projects), [list.value, projects]);
|
|
293
|
+
const { open, finding, dialog } = useOpen(projects, list.value);
|
|
211
294
|
// Another screen chose the board (the rail's Issues chooses every board) while this one is open.
|
|
212
295
|
useEffect(() => {
|
|
213
296
|
const on = (e) => setBoard(e.detail);
|
|
@@ -215,37 +298,38 @@ function Issues() {
|
|
|
215
298
|
return () => window.removeEventListener('hanzo-board', on);
|
|
216
299
|
}, [setBoard]);
|
|
217
300
|
useEffect(() => setSaid(null), [board]);
|
|
218
|
-
// The projects: every board the forge names, and every repository an issue is filed under.
|
|
219
|
-
const
|
|
301
|
+
// The projects: every board the forge names, and every repository an issue is filed under that no board is named for.
|
|
302
|
+
const column = new Map();
|
|
220
303
|
for (const b of known.value)
|
|
221
|
-
|
|
222
|
-
for (const
|
|
223
|
-
const key = projectOf(
|
|
224
|
-
if (!key)
|
|
225
|
-
|
|
226
|
-
const p = projects.get(key.toLowerCase()) ?? { key, name: key, open: 0 };
|
|
227
|
-
if (!closed(w))
|
|
228
|
-
p.open++;
|
|
229
|
-
projects.set(key.toLowerCase(), p);
|
|
304
|
+
column.set(b.key.toLowerCase(), { key: b.key, name: b.name, open: 0 });
|
|
305
|
+
for (const i of work) {
|
|
306
|
+
const key = projectOf(i);
|
|
307
|
+
if (key && ![...column.values()].some((p) => inProject(i, p.key)))
|
|
308
|
+
column.set(key.toLowerCase(), { key, name: key, open: 0 });
|
|
230
309
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
310
|
+
for (const p of column.values())
|
|
311
|
+
p.open = work.filter((i) => !closed(i) && inProject(i, p.key)).length;
|
|
312
|
+
const rows = [...column.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
313
|
+
const current = board ? column.get(board.toLowerCase()) : undefined;
|
|
233
314
|
const title = current?.name ?? board;
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
.filter((
|
|
238
|
-
.filter((
|
|
239
|
-
.filter((
|
|
240
|
-
|
|
315
|
+
const inBoard = work.filter((i) => !board || inProject(i, board));
|
|
316
|
+
const { labels, assignees } = facets(inBoard);
|
|
317
|
+
const scoped = inBoard
|
|
318
|
+
.filter((i) => kind === 'all' || (kind === 'pr') === i.pull)
|
|
319
|
+
.filter((i) => origin === 'all' || (origin === 'github') === fromGitHub(i))
|
|
320
|
+
.filter((i) => !label || i.labels.some((l) => l.toLowerCase() === label.toLowerCase()))
|
|
321
|
+
.filter((i) => !assignee || i.assignees.some((a) => a.toLowerCase() === assignee.toLowerCase()))
|
|
322
|
+
.filter((i) => matches(q, i.title, ref(i), projectOf(i), ...i.assignees, ...i.labels));
|
|
323
|
+
const opened = scoped.filter((i) => !closed(i));
|
|
241
324
|
const shut = scoped.filter(closed);
|
|
242
325
|
const shown = (state === 'open' ? opened : shut).slice().sort((a, b) => {
|
|
243
326
|
if (order === 'updated')
|
|
244
|
-
return (b.updated || b.created) - (a.updated || a.created);
|
|
327
|
+
return ms(b.updated || b.created) - ms(a.updated || a.created);
|
|
245
328
|
if (order === 'oldest')
|
|
246
|
-
return a.created - b.created;
|
|
247
|
-
return b.created - a.created;
|
|
329
|
+
return ms(a.created) - ms(b.created);
|
|
330
|
+
return ms(b.created) - ms(a.created);
|
|
248
331
|
});
|
|
332
|
+
const narrowed = q.trim() !== '' || kind !== 'all' || origin !== 'all' || label !== '' || assignee !== '';
|
|
249
333
|
const pull = async () => {
|
|
250
334
|
setBusy(true);
|
|
251
335
|
setSaid(null);
|
|
@@ -274,37 +358,21 @@ function Issues() {
|
|
|
274
358
|
setSaid({ text: e.message });
|
|
275
359
|
}
|
|
276
360
|
};
|
|
277
|
-
const open = (w) => {
|
|
278
|
-
const name = w.repo || w.project;
|
|
279
|
-
if (name) {
|
|
280
|
-
pinCodebase(host.org, {
|
|
281
|
-
org: host.org ?? '',
|
|
282
|
-
name,
|
|
283
|
-
description: '',
|
|
284
|
-
branch: 'main',
|
|
285
|
-
public: false,
|
|
286
|
-
clone: '',
|
|
287
|
-
updated: '',
|
|
288
|
-
branches: ['main'],
|
|
289
|
-
}, `${w.identifier ? `${w.identifier} ` : ''}${w.title}`.trim());
|
|
290
|
-
}
|
|
291
|
-
host.go('');
|
|
292
|
-
};
|
|
293
361
|
const chip = (key, name, count) => {
|
|
294
362
|
const on = key.toLowerCase() === board.toLowerCase();
|
|
295
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 || '*'));
|
|
296
364
|
};
|
|
297
|
-
return (
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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) => {
|
|
366
|
+
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),
|
|
369
|
+
!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(', ')}` : '',
|
|
372
|
+
fromGitHub(i) ? 'GitHub' : '',
|
|
373
|
+
finding === i.key && i.key !== '' ? 'opening…' : '',
|
|
374
|
+
]
|
|
375
|
+
.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}`));
|
|
377
|
+
}))] }))] })] })] }), dialog] }));
|
|
310
378
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -9,14 +9,40 @@
|
|
|
9
9
|
* and at the foot the Slack card and the account menu (`Slack`, `Who`), under
|
|
10
10
|
* `HostProvider`. `Grip` sizes that rail: the edge the builder sizes its own
|
|
11
11
|
* chat with.
|
|
12
|
+
*
|
|
13
|
+
* The plan, once for every surface that shows one: `label` names it (`Max` with
|
|
14
|
+
* `20x` beside it), `useStanding` reads where an account stands, `Meter` is the
|
|
15
|
+
* block an account menu leads with, `Plan` the Usage view and `Credits` the
|
|
16
|
+
* Billing one. Each reads as a `Target`, so a host without a builder mounts it.
|
|
17
|
+
* The pure half — naming, shares, the banner's words — is also `@hanzo/build/plan`,
|
|
18
|
+
* which loads in Node without the components.
|
|
12
19
|
*/
|
|
13
|
-
export { Builder
|
|
20
|
+
export { Builder } from './builder.tsx';
|
|
21
|
+
export { nav, NEW, type Group } from './nav.tsx';
|
|
14
22
|
export { DevSection, DOTS, useSessions } from './section.tsx';
|
|
15
23
|
export { HostProvider } from './host.tsx';
|
|
16
24
|
export { Slack, useWho, Who } from './foot.tsx';
|
|
17
25
|
export { Grip, type GripProps } from './grip.tsx';
|
|
18
26
|
export { administers } from './claims.ts';
|
|
27
|
+
export { Meter, Title } from './meter.tsx';
|
|
28
|
+
export { Plan } from './settings/plan.tsx';
|
|
29
|
+
export { Credits, SEPARATE } from './settings/credits.tsx';
|
|
30
|
+
export { useStanding, rows, left, type Standing, type Row } from './standing.ts';
|
|
31
|
+
export { kind, label, said, share, spent, ways, when, type Label, type Share, type State } from './plan.ts';
|
|
32
|
+
export type { Limits } from './api/limits.ts';
|
|
33
|
+
export type { Target } from './api/call.ts';
|
|
19
34
|
export type { Host, Person } from './host.tsx';
|
|
20
35
|
export type { Session, Status } from './api/sessions.ts';
|
|
21
36
|
export type { Read } from './data.ts';
|
|
22
|
-
export { path, route, SESSION, SLUG, type Route, type Screen } from './route.ts';
|
|
37
|
+
export { href, parse, path, PLACES, route, SESSION, SLUG, type Route, type Screen } from './route.ts';
|
|
38
|
+
/**
|
|
39
|
+
* What Chat draws the same way (@hanzo/rooms imports these): the model and
|
|
40
|
+
* effort (`useMind`, one choice kept for both, changed by `Tune`), the composer
|
|
41
|
+
* (`prompt`), the row under an answer (`Reply`), and the side panel (`Panel`
|
|
42
|
+
* over a `Deck` of tabs).
|
|
43
|
+
*/
|
|
44
|
+
export { catalog, EFFORTS, FIRST, keep, kept, nameOf, reasons, useCatalog, useChoice, useMind, type Choice, type Effort, type Mind } from './mind.ts';
|
|
45
|
+
export { cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, Tune, useEar, type PromptOptions } from './prompt.tsx';
|
|
46
|
+
export { artifacts, blob, lead, renders, Reply, Served, speakable, speaker, type Artifact, type Listen } from './reply.tsx';
|
|
47
|
+
export { Page, Panel, PanelToggle, type Kind, type PanelProps } from './panel.tsx';
|
|
48
|
+
export { address, name, PAGE, useDeck, useKey, usePanel, useShown, type Deck, type Tab, type Tabs } from './tabs.ts';
|
package/lib/index.js
CHANGED
|
@@ -9,11 +9,35 @@
|
|
|
9
9
|
* and at the foot the Slack card and the account menu (`Slack`, `Who`), under
|
|
10
10
|
* `HostProvider`. `Grip` sizes that rail: the edge the builder sizes its own
|
|
11
11
|
* chat with.
|
|
12
|
+
*
|
|
13
|
+
* The plan, once for every surface that shows one: `label` names it (`Max` with
|
|
14
|
+
* `20x` beside it), `useStanding` reads where an account stands, `Meter` is the
|
|
15
|
+
* block an account menu leads with, `Plan` the Usage view and `Credits` the
|
|
16
|
+
* Billing one. Each reads as a `Target`, so a host without a builder mounts it.
|
|
17
|
+
* The pure half — naming, shares, the banner's words — is also `@hanzo/build/plan`,
|
|
18
|
+
* which loads in Node without the components.
|
|
12
19
|
*/
|
|
13
|
-
export { Builder
|
|
20
|
+
export { Builder } from './builder.js';
|
|
21
|
+
export { nav, NEW } from './nav.js';
|
|
14
22
|
export { DevSection, DOTS, useSessions } from './section.js';
|
|
15
23
|
export { HostProvider } from './host.js';
|
|
16
24
|
export { Slack, useWho, Who } from './foot.js';
|
|
17
25
|
export { Grip } from './grip.js';
|
|
18
26
|
export { administers } from './claims.js';
|
|
19
|
-
export {
|
|
27
|
+
export { Meter, Title } from './meter.js';
|
|
28
|
+
export { Plan } from './settings/plan.js';
|
|
29
|
+
export { Credits, SEPARATE } from './settings/credits.js';
|
|
30
|
+
export { useStanding, rows, left } from './standing.js';
|
|
31
|
+
export { kind, label, said, share, spent, ways, when } from './plan.js';
|
|
32
|
+
export { href, parse, path, PLACES, route, SESSION, SLUG } from './route.js';
|
|
33
|
+
/**
|
|
34
|
+
* What Chat draws the same way (@hanzo/rooms imports these): the model and
|
|
35
|
+
* effort (`useMind`, one choice kept for both, changed by `Tune`), the composer
|
|
36
|
+
* (`prompt`), the row under an answer (`Reply`), and the side panel (`Panel`
|
|
37
|
+
* over a `Deck` of tabs).
|
|
38
|
+
*/
|
|
39
|
+
export { catalog, EFFORTS, FIRST, keep, kept, nameOf, reasons, useCatalog, useChoice, useMind } from './mind.js';
|
|
40
|
+
export { cut, GAP, MEASURE, measure, Prompt, prompt, ROUND, Tune, useEar } from './prompt.js';
|
|
41
|
+
export { artifacts, blob, lead, renders, Reply, Served, speakable, speaker } from './reply.js';
|
|
42
|
+
export { Page, Panel, PanelToggle } from './panel.js';
|
|
43
|
+
export { address, name, PAGE, useDeck, useKey, usePanel, useShown } from './tabs.js';
|
package/lib/landing.d.ts
CHANGED
|
@@ -9,16 +9,6 @@ export declare const MODES: readonly [{
|
|
|
9
9
|
readonly label: 'Plan';
|
|
10
10
|
readonly hint: 'Plans the change and writes nothing';
|
|
11
11
|
}];
|
|
12
|
-
export declare const EFFORTS: readonly [{
|
|
13
|
-
readonly id: 'low';
|
|
14
|
-
readonly label: 'Low';
|
|
15
|
-
}, {
|
|
16
|
-
readonly id: 'medium';
|
|
17
|
-
readonly label: 'Medium';
|
|
18
|
-
}, {
|
|
19
|
-
readonly id: 'high';
|
|
20
|
-
readonly label: 'High';
|
|
21
|
-
}];
|
|
22
12
|
/**
|
|
23
13
|
* What was typed here and not sent, kept in this tab.
|
|
24
14
|
*
|