@hanzo/build 0.2.51 → 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 +109 -55
- 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 +42 -4
- package/lib/api/github.js +79 -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 -24
- package/lib/api/work.js +132 -27
- package/lib/builder.d.ts +0 -10
- package/lib/builder.js +21 -54
- 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 +255 -92
- package/lib/host.d.ts +1 -1
- package/lib/index.d.ts +28 -2
- package/lib/index.js +26 -2
- package/lib/landing.d.ts +0 -10
- package/lib/landing.js +79 -47
- 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.d.ts +16 -1
- package/lib/project.js +100 -32
- 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 +28 -4
- package/lib/route.js +48 -1
- 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 +23 -9
- 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/forge.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Projects and issues, read from the forge and opened in this window.
|
|
4
4
|
*
|
|
5
|
-
* A
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* A project is a repository, linked from GitHub or started from a template;
|
|
6
|
+
* choosing one opens its workspace, where every run works on it. Issues are
|
|
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.
|
|
8
12
|
*/
|
|
9
13
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
10
|
-
import { ChevronDown, GitBranch,
|
|
11
|
-
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
12
|
-
import { useEffect, useState } from 'react';
|
|
14
|
+
import { ArrowRight, Check, ChevronDown, CircleCheck, CircleDot, CircleSlash, ExternalLink, FolderGit2, GitBranch, Github, GitPullRequest, LayoutTemplate, RefreshCw, } from '@hanzogui/lucide-icons-2';
|
|
15
|
+
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
16
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
13
17
|
import { ago } from './ago.js';
|
|
14
|
-
import { codebases
|
|
18
|
+
import { codebases } from './api/codebases.js';
|
|
15
19
|
import { Automations } from './automations.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { boardKey, pinBoard, pinCodebase, readPending, writePending } from './choice.js';
|
|
20
|
+
import { boards, everything, sources } from './api/work.js';
|
|
21
|
+
import { boardKey, pinRepo, readPending, writePending } from './choice.js';
|
|
19
22
|
import { useKept, useRead } from './data.js';
|
|
20
23
|
import { useHost, useTarget } from './host.js';
|
|
21
24
|
import { path } from './route.js';
|
|
22
25
|
import { Customize } from './customize/index.js';
|
|
23
26
|
import { Sync } from './sync.js';
|
|
24
|
-
import { grants } from './api/github.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
function Row({ label, onPress, children }) {
|
|
29
|
-
return (_jsx(XStack, { render: "button", onPress: onPress, "aria-label": label, items: "center", gap: "$3", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: { borderColor: '$edge', bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: children }));
|
|
30
|
-
}
|
|
27
|
+
import { connect, connection, grants, syncIssues } from './api/github.js';
|
|
28
|
+
import { here, useBack } from './back.js';
|
|
29
|
+
import { Out } from './out.js';
|
|
30
|
+
import { ask, assemble, attach, closes, facets, handle, home, keyOf, matches, pickProjects, place } from './merge.js';
|
|
31
31
|
function Note({ children }) {
|
|
32
32
|
return (_jsx(SizableText, { size: "$2", color: "$soft", children: children }));
|
|
33
33
|
}
|
|
@@ -46,34 +46,34 @@ export function Forge({ screen }) {
|
|
|
46
46
|
// The fleet's native servers are Connectors → Discover now; the old address lands there.
|
|
47
47
|
if (screen === 'mcp')
|
|
48
48
|
return _jsx(Customize, { tab: "connectors", view: "discover" });
|
|
49
|
-
if (screen === 'codebases')
|
|
50
|
-
return _jsx(Codebases, {});
|
|
51
49
|
if (screen === 'projects')
|
|
52
50
|
return _jsx(Projects, {});
|
|
53
51
|
return _jsx(Issues, {});
|
|
54
52
|
}
|
|
55
53
|
const PAGE = 25;
|
|
56
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The organization's projects: its repositories on the forge, which arrive one
|
|
56
|
+
* of two ways — linked from GitHub, or started from a template. A project is a
|
|
57
|
+
* repository; opening one opens its workspace at `<org>/<repo>`, and every run
|
|
58
|
+
* there works on that repository. There is no project without one.
|
|
59
|
+
*/
|
|
60
|
+
function Projects() {
|
|
57
61
|
const host = useHost();
|
|
58
62
|
const t = useTarget();
|
|
59
63
|
const signed = Boolean(host.person);
|
|
60
64
|
const [q, setQ] = useState('');
|
|
61
|
-
const [
|
|
62
|
-
const [sort, setSort] = useState('name');
|
|
63
|
-
const [filter, setFilter] = useState('all');
|
|
65
|
+
const [sort, setSort] = useState('updated');
|
|
64
66
|
const [page, setPage] = useState(0);
|
|
65
67
|
const [landed, setLanded] = useState(0);
|
|
66
68
|
useEffect(() => {
|
|
67
69
|
setPage(0);
|
|
68
|
-
}, [q, sort
|
|
69
|
-
const list = useRead(signed ? () => codebases(t) : null, [], [t, signed,
|
|
70
|
-
// Which codebases have an environment. A codebase absent here has none.
|
|
71
|
-
const envs = useRead(signed ? () => environments(t) : null, [], [t, signed]);
|
|
72
|
-
const envOf = new Map(envs.value.map((e) => [e.repo, e.state]));
|
|
70
|
+
}, [q, sort]);
|
|
71
|
+
const list = useRead(signed ? () => codebases(t) : null, [], [t, signed, landed]);
|
|
73
72
|
const needle = q.trim().toLowerCase();
|
|
74
73
|
const pending = readPending(host.org);
|
|
75
74
|
const pendingKey = pending.map((p) => p.fullName).join('\n');
|
|
76
75
|
const syncing = new Set(pending.map((p) => p.name));
|
|
76
|
+
// A repository asked for from GitHub is listed as syncing until the forge holds it.
|
|
77
77
|
useEffect(() => {
|
|
78
78
|
const waiting = readPending(host.org);
|
|
79
79
|
if (!signed || waiting.length === 0)
|
|
@@ -110,19 +110,12 @@ function Codebases() {
|
|
|
110
110
|
branches: ['main'],
|
|
111
111
|
})),
|
|
112
112
|
]
|
|
113
|
-
.filter((c) => {
|
|
114
|
-
if (filter === 'ready')
|
|
115
|
-
return envOf.get(c.name) === 'ready';
|
|
116
|
-
if (filter === 'syncing')
|
|
117
|
-
return syncing.has(c.name);
|
|
118
|
-
return true;
|
|
119
|
-
})
|
|
120
113
|
.filter((c) => !needle || `${c.org}/${c.name} ${c.description}`.toLowerCase().includes(needle))
|
|
121
114
|
.slice()
|
|
122
115
|
.sort((a, b) => {
|
|
123
116
|
if (sort === 'updated') {
|
|
124
|
-
const d = Date.parse(b.updated) - Date.parse(a.updated);
|
|
125
|
-
if (
|
|
117
|
+
const d = (Date.parse(b.updated) || 0) - (Date.parse(a.updated) || 0);
|
|
118
|
+
if (d !== 0)
|
|
126
119
|
return d;
|
|
127
120
|
}
|
|
128
121
|
return a.name.localeCompare(b.name);
|
|
@@ -132,84 +125,254 @@ function Codebases() {
|
|
|
132
125
|
const slice = shown.slice(safePage * PAGE, safePage * PAGE + PAGE);
|
|
133
126
|
const from = shown.length === 0 ? 0 : safePage * PAGE + 1;
|
|
134
127
|
const to = safePage * PAGE + slice.length;
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
};
|
|
139
|
-
return (
|
|
140
|
-
const row = await create(t, name, description);
|
|
141
|
-
pinCodebase(host.org, row);
|
|
142
|
-
setMaking(false);
|
|
143
|
-
host.go('');
|
|
144
|
-
} })] }));
|
|
128
|
+
const none = !list.loading && !list.error && list.value.length === 0 && pending.length === 0;
|
|
129
|
+
const open = (c) => host.go(path({ kind: 'repo', org: c.org || host.org || '', name: c.name }));
|
|
130
|
+
const link = () => host.go(path({ kind: 'screen', screen: 'sync' }));
|
|
131
|
+
const template = () => host.go(path({ kind: 'screen', screen: 'templates' }));
|
|
132
|
+
return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", flexWrap: "wrap", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 240, children: [_jsx(SizableText, { render: "h1", size: "$6", fontWeight: "500", color: "$ink", children: "Projects" }), _jsx(SizableText, { size: "$2", color: "$soft", children: `${host.org ? `${host.org}’s` : 'This organization’s'} repositories on the forge. Open one to work on it.` })] }), _jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsxs(Button, { size: "sm", variant: "outline", onPress: template, children: [_jsx(LayoutTemplate, { size: 14 }), "Create from template"] }), _jsxs(Button, { size: "sm", variant: "primary", onPress: link, children: [_jsx(Github, { size: 14 }), "Link a GitHub repo"] })] })] }), signed ? _jsx(GitHubLink, { onSync: link }) : null, list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: "Sign in to see this organization's projects." })) : list.loading && list.value.length === 0 && pending.length === 0 ? (_jsx(Note, { children: "Reading the forge\u2026" })) : none ? (_jsxs(YStack, { gap: "$3", py: "$2", children: [_jsx(Note, { children: "No projects yet. A project is a repository: link one from GitHub, or start one from a template." }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", children: [_jsx(Choice, { label: "Link a GitHub repo", icon: _jsx(Github, { size: 20 }), says: "Mirror repositories you already have. They stay in step with GitHub, and their issues can come too.", onPress: link }), _jsx(Choice, { label: "Create from template", icon: _jsx(LayoutTemplate, { size: 20 }), says: "Start from a finished product \u2014 a site, an app, a store \u2014 and make it yours.", onPress: template })] })] })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(YStack, { flex: 1, "$max-md": { display: 'none' } }), _jsx(YStack, { width: 320, minW: 0, shrink: 1, "$max-md": { width: 'auto', flex: 1 }, children: _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find a project\u2026", "aria-label": "Find a project" }) })] }), shown.length === 0 ? (_jsx(Note, { children: "Nothing matches." })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", bg: "$panel", children: [_jsxs(XStack, { flex: 1, render: "button", "aria-label": "Sort by name", items: "center", gap: "$1", onPress: () => setSort('name'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Name" }), sort === 'name' ? _jsx(ChevronDown, { size: 12 }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, "$max-md": { display: 'none' }, children: "Branch" }), _jsxs(XStack, { render: "button", "aria-label": "Sort by last updated", items: "center", justify: "flex-end", gap: "$1", width: 120, onPress: () => setSort('updated'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Updated" }), sort === 'updated' ? _jsx(ChevronDown, { size: 12 }) : null] })] }), slice.map((c) => (_jsxs(XStack, { render: "button", "aria-label": `Open ${c.org ? `${c.org}/` : ''}${c.name}`, onPress: () => open(c), style: { textAlign: 'left' }, items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(FolderGit2, { size: 15, color: "$soft" }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsxs(XStack, { minW: 0, items: "baseline", children: [c.org ? (_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, shrink: 0, children: `${c.org}/` })) : null, _jsx(SizableText, { size: "$2", color: "$ink", fontWeight: "500", numberOfLines: 1, shrink: 1, children: c.name })] }), c.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.description })) : null] }), _jsxs(XStack, { width: 120, items: "center", gap: "$1.5", minW: 0, "$max-md": { display: 'none' }, children: [_jsx(GitBranch, { size: 12, color: "$soft" }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: c.branch })] }), _jsx(SizableText, { size: "$1", color: "$soft", width: 120, style: { textAlign: 'right' }, children: syncing.has(c.name) ? 'Syncing…' : ago(c.updated) })] }, `${c.org}/${c.name}`))), _jsxs(XStack, { px: "$3", py: "$2", justify: "space-between", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: `Showing ${from}–${to} of ${shown.length}` }), _jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": "Previous page", disabled: safePage === 0, onPress: () => setPage(safePage - 1), children: _jsx(SizableText, { size: "$1", color: safePage === 0 ? '$soft' : '$ink', children: "Prev" }) }), _jsx(SizableText, { size: "$1", color: "$soft", children: `${safePage + 1} / ${pages}` }), _jsx(XStack, { render: "button", "aria-label": "Next page", disabled: safePage >= pages - 1, onPress: () => setPage(safePage + 1), children: _jsx(SizableText, { size: "$1", color: safePage >= pages - 1 ? '$soft' : '$ink', children: "Next" }) })] })] })] }))] }))] }) }));
|
|
145
133
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
/** One of the two ways in, as a card you press. */
|
|
135
|
+
function Choice({ label, icon, says, onPress }) {
|
|
136
|
+
return (_jsxs(YStack, { render: "button", "aria-label": label, onPress: onPress, flex: 1, minW: 240, gap: "$2", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", items: "flex-start", hoverStyle: { borderColor: '$edge', bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon, _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$4", color: "$ink", fontWeight: "500", children: label }), _jsx(ArrowRight, { size: 14 })] }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'left' }, children: says })] }));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The person's GitHub connection, in one line: who it is connected as and the
|
|
140
|
+
* way to sync, or the one press that connects it. GitHub returns here, and this
|
|
141
|
+
* page finishes the connect (../back.ts).
|
|
142
|
+
*/
|
|
143
|
+
function GitHubLink({ onSync }) {
|
|
144
|
+
const host = useHost();
|
|
145
|
+
const t = useTarget();
|
|
146
|
+
const me = useRead(() => connection(t), null, [t]);
|
|
147
|
+
const [note, setNote] = useState('');
|
|
149
148
|
const [busy, setBusy] = useState(false);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
}, [open]);
|
|
158
|
-
const submit = async () => {
|
|
149
|
+
useBack(t, true, setNote, me.reload);
|
|
150
|
+
const c = me.value;
|
|
151
|
+
// A connection that cannot be read says nothing here: Sync from GitHub above still works.
|
|
152
|
+
if (me.error || !c || !c.configured)
|
|
153
|
+
return note ? _jsx(Note, { children: note }) : null;
|
|
154
|
+
const link = async () => {
|
|
159
155
|
setBusy(true);
|
|
160
|
-
|
|
156
|
+
setNote('');
|
|
161
157
|
try {
|
|
162
|
-
await
|
|
163
|
-
setName('');
|
|
164
|
-
setDescription('');
|
|
158
|
+
host.open(await connect(t, here()));
|
|
165
159
|
}
|
|
166
160
|
catch (e) {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
161
|
+
setNote(e.message);
|
|
170
162
|
setBusy(false);
|
|
171
163
|
}
|
|
172
164
|
};
|
|
173
|
-
return (
|
|
165
|
+
return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", children: [_jsx(Github, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 200, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: c.connected ? `GitHub connected as @${c.login}` : 'Connect GitHub to bring your repositories here' }), _jsx(SizableText, { size: "$1", color: "$soft", children: note || (c.connected ? 'Choose repositories to mirror; each stays in step with GitHub.' : 'One step on GitHub, then pick the repositories to sync.') })] }), c.connected ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: onSync, children: [_jsx(RefreshCw, { size: 14 }), "Sync repositories"] })) : (_jsxs(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void link(), children: [_jsx(Github, { size: 14 }), busy ? 'Opening GitHub…' : 'Connect GitHub'] }))] }));
|
|
174
166
|
}
|
|
175
|
-
|
|
167
|
+
const KINDS = { all: 'Issues and pull requests', issue: 'Issues', pr: 'Pull requests' };
|
|
168
|
+
const ORIGINS = { all: 'Every source', github: 'GitHub', hanzo: 'Hanzo' };
|
|
169
|
+
const ORDERS = { newest: 'Newest', oldest: 'Oldest', updated: 'Recently updated' };
|
|
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
|
+
}
|
|
193
|
+
/** A dropdown that picks one of `options`, its trigger naming the choice. */
|
|
194
|
+
function Pick({ label, value, options, onPick }) {
|
|
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) => ({
|
|
196
|
+
key: k || '*',
|
|
197
|
+
label: options[k],
|
|
198
|
+
icon: k === value ? _jsx(Check, { size: 14 }) : _jsx(YStack, { width: 14 }),
|
|
199
|
+
onSelect: () => onPick(k),
|
|
200
|
+
})) }));
|
|
201
|
+
}
|
|
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')
|
|
208
|
+
return _jsx(CircleSlash, { size: 16, color: "$soft" });
|
|
209
|
+
if (closed(i))
|
|
210
|
+
return _jsx(CircleCheck, { size: 16, color: "$soft" });
|
|
211
|
+
return _jsx(CircleDot, { size: 16, color: "$ink" });
|
|
212
|
+
}
|
|
213
|
+
function Tag({ children }) {
|
|
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 }) }));
|
|
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) {
|
|
176
225
|
const host = useHost();
|
|
177
226
|
const t = useTarget();
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
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);
|
|
183
252
|
};
|
|
184
|
-
|
|
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 };
|
|
185
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
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Every project's issues, read the way GitHub reads them: open or closed, one
|
|
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.
|
|
271
|
+
*/
|
|
186
272
|
function Issues() {
|
|
187
273
|
const host = useHost();
|
|
188
274
|
const t = useTarget();
|
|
189
275
|
const signed = Boolean(host.person);
|
|
190
276
|
const [board, setBoard] = useKept(boardKey(host.org), '');
|
|
191
|
-
const
|
|
277
|
+
const [state, setState] = useState('open');
|
|
278
|
+
const [kind, setKind] = useState('all');
|
|
279
|
+
const [origin, setOrigin] = useState('all');
|
|
280
|
+
const [order, setOrder] = useState('newest');
|
|
281
|
+
const [label, setLabel] = useState('');
|
|
282
|
+
const [assignee, setAssignee] = useState('');
|
|
283
|
+
const [q, setQ] = useState('');
|
|
284
|
+
const [pulled, setPulled] = useState(0);
|
|
285
|
+
const [busy, setBusy] = useState(false);
|
|
286
|
+
const [said, setSaid] = useState(null);
|
|
287
|
+
const list = useRead(signed ? () => everything(t) : null, [], [t, signed, pulled]);
|
|
288
|
+
const known = useRead(signed ? () => boards(t) : null, [], [t, signed]);
|
|
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);
|
|
192
294
|
// Another screen chose the board (the rail's Issues chooses every board) while this one is open.
|
|
193
295
|
useEffect(() => {
|
|
194
296
|
const on = (e) => setBoard(e.detail);
|
|
195
297
|
window.addEventListener('hanzo-board', on);
|
|
196
298
|
return () => window.removeEventListener('hanzo-board', on);
|
|
197
299
|
}, [setBoard]);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
300
|
+
useEffect(() => setSaid(null), [board]);
|
|
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();
|
|
303
|
+
for (const b of known.value)
|
|
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 });
|
|
309
|
+
}
|
|
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;
|
|
314
|
+
const title = current?.name ?? board;
|
|
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));
|
|
324
|
+
const shut = scoped.filter(closed);
|
|
325
|
+
const shown = (state === 'open' ? opened : shut).slice().sort((a, b) => {
|
|
326
|
+
if (order === 'updated')
|
|
327
|
+
return ms(b.updated || b.created) - ms(a.updated || a.created);
|
|
328
|
+
if (order === 'oldest')
|
|
329
|
+
return ms(a.created) - ms(b.created);
|
|
330
|
+
return ms(b.created) - ms(a.created);
|
|
331
|
+
});
|
|
332
|
+
const narrowed = q.trim() !== '' || kind !== 'all' || origin !== 'all' || label !== '' || assignee !== '';
|
|
333
|
+
const pull = async () => {
|
|
334
|
+
setBusy(true);
|
|
335
|
+
setSaid(null);
|
|
336
|
+
try {
|
|
337
|
+
const s = await syncIssues(t, board);
|
|
338
|
+
const where = board ? title : `${s.repos} ${s.repos === 1 ? 'repository' : 'repositories'}`;
|
|
339
|
+
const parts = [`${s.issues} ${s.issues === 1 ? 'issue' : 'issues'} synced from ${where}`, `${s.created} new`, `${s.updated} updated`];
|
|
340
|
+
if (s.failed)
|
|
341
|
+
parts.push(`${s.failed} failed`);
|
|
342
|
+
setSaid({ text: `${parts.join(' · ')}.${s.truncated ? ' More remain — sync again to continue.' : ''}` });
|
|
343
|
+
setPulled((n) => n + 1);
|
|
211
344
|
}
|
|
212
|
-
|
|
345
|
+
catch (e) {
|
|
346
|
+
const text = e.message;
|
|
347
|
+
setSaid({ text, connect: /not connected/i.test(text) });
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
setBusy(false);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const link = async () => {
|
|
354
|
+
try {
|
|
355
|
+
host.open(await connect(t, here()));
|
|
356
|
+
}
|
|
357
|
+
catch (e) {
|
|
358
|
+
setSaid({ text: e.message });
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
const chip = (key, name, count) => {
|
|
362
|
+
const on = key.toLowerCase() === board.toLowerCase();
|
|
363
|
+
return (_jsxs(XStack, { render: "button", "aria-label": key ? `Show ${name}` : 'Show every project', "aria-pressed": on, onPress: () => setBoard(key), items: "center", gap: "$2", px: "$2.5", py: "$1.5", rounded: "$3", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, shrink: 0, children: [_jsx(SizableText, { flex: 1, size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, style: { textAlign: 'left' }, children: name }), count ? (_jsx(SizableText, { size: "$1", color: "$soft", children: count })) : null] }, key || '*'));
|
|
213
364
|
};
|
|
214
|
-
return (
|
|
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] }));
|
|
215
378
|
}
|
package/lib/host.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface Host {
|
|
|
39
39
|
person: Person | null;
|
|
40
40
|
/** Whether the person administers `org` (its owner or an admin): they publish to main and change its money; anyone else opens a review and reads. */
|
|
41
41
|
admin: boolean;
|
|
42
|
-
/** The address under the mount: '' | a session id | a slug | '-/
|
|
42
|
+
/** The address under the mount, in route.ts's grammar: '' | a session id | `<org>/<repo>` | a slug | '-/projects' | '-/issues' | … */
|
|
43
43
|
path: string;
|
|
44
44
|
/** Move under the mount. */
|
|
45
45
|
go: (path: string, how?: {
|
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
|
*
|