@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/customize/skills.js
CHANGED
|
@@ -3,9 +3,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* Skills: what the agent knows how to do, each a SKILL.md it reads when an agent
|
|
4
4
|
* names it.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Browse is Hanzo's catalogue; adding one switches it on for the org, which is
|
|
7
|
+
* all adding is: the platform keeps no copy. Yours is the org's own skills —
|
|
8
|
+
* written here, each with its switch — and the catalogue's skills it has added.
|
|
9
9
|
* A skill written here is switched on when it is saved.
|
|
10
10
|
*/
|
|
11
11
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
@@ -15,19 +15,20 @@ import { Code } from '@hanzo/ui/chat';
|
|
|
15
15
|
import { useMemo, useState } from 'react';
|
|
16
16
|
import { active, authored, brand, document, nameOf, remove, tool, write } from '../api/skills.js';
|
|
17
17
|
import { toggle } from '../api/tools.js';
|
|
18
|
-
import { useRead } from '../data.js';
|
|
19
18
|
import { useHost, useTarget } from '../host.js';
|
|
20
|
-
import {
|
|
19
|
+
import { useLoad } from './load.js';
|
|
20
|
+
import { say } from './say.js';
|
|
21
|
+
import { Add, Confirm, day, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
|
|
21
22
|
const PAGE = 60;
|
|
22
|
-
export function Skills({ view, q, adding, onAdding, onView }) {
|
|
23
|
+
export function Skills({ view, q, adding, onAdding, onView, onCount }) {
|
|
23
24
|
const host = useHost();
|
|
24
25
|
const t = useTarget();
|
|
25
26
|
const signed = Boolean(host.person);
|
|
26
27
|
// What every run in the org loads is an org admin's to change; a member reads it.
|
|
27
28
|
const may = signed && host.admin;
|
|
28
|
-
const own =
|
|
29
|
-
const on =
|
|
30
|
-
const shelf =
|
|
29
|
+
const own = useLoad(signed ? () => authored(t) : null, [], [t, signed]);
|
|
30
|
+
const on = useLoad(signed ? () => active(t) : null, [], [t, signed]);
|
|
31
|
+
const shelf = useLoad(view === 'discover' ? () => brand(t) : null, null, [t.api, view]);
|
|
31
32
|
const [editing, setEditing] = useState(null);
|
|
32
33
|
const [reading, setReading] = useState(null);
|
|
33
34
|
const [busy, setBusy] = useState('');
|
|
@@ -35,6 +36,8 @@ export function Skills({ view, q, adding, onAdding, onView }) {
|
|
|
35
36
|
const [shown, setShown] = useState(PAGE);
|
|
36
37
|
const lit = useMemo(() => new Set(on.value.map((x) => x.name)), [on.value]);
|
|
37
38
|
const mine = useMemo(() => new Set(own.value.map((s) => s.name)), [own.value]);
|
|
39
|
+
const added = on.value.filter((x) => nameOf(x.name) && !mine.has(nameOf(x.name)));
|
|
40
|
+
useCount(own.value.length + added.length, onCount);
|
|
38
41
|
/** Switch one skill on or off, then read what is on again. */
|
|
39
42
|
const flip = async (name, next) => {
|
|
40
43
|
setBusy(name);
|
|
@@ -45,7 +48,7 @@ export function Skills({ view, q, adding, onAdding, onView }) {
|
|
|
45
48
|
setNote(next ? `${name} is on` : `${name} is off`);
|
|
46
49
|
}
|
|
47
50
|
catch (e) {
|
|
48
|
-
setNote(e
|
|
51
|
+
setNote(say(e));
|
|
49
52
|
}
|
|
50
53
|
finally {
|
|
51
54
|
setBusy('');
|
|
@@ -71,15 +74,18 @@ export function Skills({ view, q, adding, onAdding, onView }) {
|
|
|
71
74
|
if (view === 'discover') {
|
|
72
75
|
const all = shelf.value?.skills ?? [];
|
|
73
76
|
const found = all.filter((e) => matches(q, e.name, e.description, e.product));
|
|
74
|
-
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), shelf.error && !shelf.value ? (_jsx(
|
|
77
|
+
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title: "From Hanzo", detail: "Add one and every run in your organization can use it.", children: shelf.error && !shelf.value ? (_jsx(Failed, { error: shelf.error, onRetry: shelf.reload })) : !shelf.value ? (_jsx(Soft, { children: "Loading skills\u2026" })) : !found.length ? (_jsx(Soft, { children: all.length ? `No skill matches “${q.trim()}”.` : 'There are no skills to add yet.' })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: found.length === all.length ? `${all.length} skills` : `${found.length} of ${all.length} skills` }), _jsx(Grid, { label: "Skills to add", children: found.slice(0, shown).map((e) => (_jsx(Tile, { title: e.name, detail: e.description, meta: e.product, mark: _jsx(Mark, { name: e.name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading(e), action: may ? _jsx(Add, { name: e.name, added: lit.has(tool(e.name)), busy: busy === e.name, onPress: () => void flip(e.name, true) }) : undefined }, e.name))) }), found.length > shown ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => setShown(shown + PAGE), children: "Show more" }) })) : null] })) }), reader, editor] }));
|
|
75
78
|
}
|
|
76
79
|
if (!signed) {
|
|
77
80
|
return _jsx(Visitor, { children: "Sign in to see your skills." });
|
|
78
81
|
}
|
|
79
82
|
const ownShown = own.value.filter((s) => matches(q, s.name, s.description));
|
|
80
|
-
const
|
|
83
|
+
const addedShown = added.filter((x) => matches(q, nameOf(x.name), x.description));
|
|
81
84
|
const error = own.error ?? on.error;
|
|
82
|
-
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Line, { children: note }), error && !own.value.length && !on.value.length ? (_jsx(
|
|
85
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Line, { children: note }), error && !own.value.length && !on.value.length ? (_jsx(Failed, { error: error, onRetry: () => {
|
|
86
|
+
own.reload();
|
|
87
|
+
on.reload();
|
|
88
|
+
} })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children: "Loading your skills\u2026" })) : !own.value.length && !added.length ? (_jsxs(Empty, { title: "No skills yet", detail: may ? 'Add one from Hanzo, or write your own.' : 'An org admin adds skills. Browse what there is.', children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse skills" }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "Write a skill" })) : null] })) : !ownShown.length && !addedShown.length ? (_jsx(Soft, { children: `No skill of yours matches “${q.trim()}”.` })) : (_jsxs(_Fragment, { children: [ownShown.length ? (_jsx(Part, { title: "Made by your team", detail: "Off keeps a skill but leaves it out of every run.", children: _jsx(Grid, { label: "Your skills", children: ownShown.map((s) => (_jsx(Tile, { title: s.name, detail: s.description || firstLine(s.content), meta: [s.source ? `From ${s.source}` : day(s.created) ? `Saved ${day(s.created)}` : '', s.admitted ? '' : 'Not in runs until an admin saves it'].filter(Boolean).join(' · ') || undefined, mark: _jsx(Mark, { name: s.name }), onOpen: () => setEditing(s), action: may ? (_jsx(Switch, { checked: lit.has(tool(s.name)), disabled: busy === s.name, onCheckedChange: (v) => void flip(s.name, v), "aria-label": `${s.name} on` })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: lit.has(tool(s.name)) ? 'On' : 'Off' })) }, s.id))) }) })) : null, addedShown.length ? (_jsx(Part, { title: "Added from Hanzo", detail: "On for every run in your organization.", children: _jsx(Grid, { label: "Added skills", children: addedShown.map((x) => {
|
|
83
89
|
const name = nameOf(x.name);
|
|
84
90
|
return (_jsx(Tile, { title: name, detail: x.description, mark: _jsx(Mark, { name: name, icon: _jsx(BookOpen, { size: 15 }) }), onOpen: () => setReading({ name, description: x.description, product: '' }), action: may ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: busy === name, onPress: () => void flip(name, false), "aria-label": `Remove ${name}`, children: "Remove" })) : undefined }, x.name));
|
|
85
91
|
}) }) })) : null] })), reader, editor] }));
|
|
@@ -91,8 +97,8 @@ const firstLine = (md) => md
|
|
|
91
97
|
/** One catalogue skill: what it is, its SKILL.md, and adding or removing it. */
|
|
92
98
|
function Reader({ skill, added, signed, busy, onFlip, onClose, }) {
|
|
93
99
|
const t = useTarget();
|
|
94
|
-
const doc =
|
|
95
|
-
return (_jsxs(Sheet, { title: skill.name, open: true, onOpenChange: (o) => !o && onClose(), width: 720, children: [skill.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: skill.description })) : null, signed ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: added ? 'outline' : 'default', disabled: busy, onPress: () => onFlip(!added), children: added ? 'Remove' : 'Add to your skills' }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, children: added ? 'On for your organization.' : '
|
|
100
|
+
const doc = useLoad(() => document(t, skill.name), '', [t.api, skill.name]);
|
|
101
|
+
return (_jsxs(Sheet, { title: skill.name, open: true, onOpenChange: (o) => !o && onClose(), width: 720, children: [skill.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: skill.description })) : null, signed ? (_jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: added ? 'outline' : 'default', disabled: busy, onPress: () => onFlip(!added), children: added ? 'Remove' : 'Add to your skills' }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, children: added ? 'On for your organization.' : 'Every run in your organization can use it once added.' })] })) : null, doc.error ? (_jsx(Failed, { error: doc.error, onRetry: doc.reload })) : doc.loading && !doc.value ? (_jsx(Soft, { children: "Loading SKILL.md\u2026" })) : (_jsx(Code, { language: "SKILL.md", value: doc.value, children: _jsx(SizableText, { size: "$1", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap' }, children: doc.value }) }))] }));
|
|
96
102
|
}
|
|
97
103
|
/** Write a skill, or revise one of the org's own. Its name is its id, so a saved skill keeps its name. */
|
|
98
104
|
function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
|
|
@@ -114,13 +120,13 @@ function Editor({ skill, may, onClose, onSaved, onDeleted, }) {
|
|
|
114
120
|
onSaved(saved, !skill);
|
|
115
121
|
}
|
|
116
122
|
catch (e) {
|
|
117
|
-
setNote(e
|
|
123
|
+
setNote(say(e));
|
|
118
124
|
}
|
|
119
125
|
finally {
|
|
120
126
|
setWorking(false);
|
|
121
127
|
}
|
|
122
128
|
};
|
|
123
|
-
return (_jsxs(Sheet, { title: skill ? skill.name : 'New skill', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (_jsxs(SizableText, { size: "$2", color: "$soft", children: ["Read from ", skill.source, ". The next push of that repository replaces what is saved here."] })) : null, _jsx(Field, { label: "Name", hint: skill ? 'A skill
|
|
129
|
+
return (_jsxs(Sheet, { title: skill ? skill.name : 'New skill', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [skill?.source ? (_jsxs(SizableText, { size: "$2", color: "$soft", children: ["Read from ", skill.source, ". The next push of that repository replaces what is saved here."] })) : null, _jsx(Field, { label: "Name", hint: skill ? 'A skill keeps its name. Save under another name to make a new one.' : 'One lowercase word: letters, digits, _ or -.', children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Name", disabled: Boolean(skill), autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", hint: "One line. An agent reads it to decide whether it needs the skill.", children: _jsx(Input, { value: description, onChangeText: setDescription, placeholder: "How we triage an incoming issue", "aria-label": "Description" }) }), _jsx(Field, { label: "SKILL.md", hint: "Markdown: what it is for, when to use it, and the steps.", children: _jsx(Textarea, { value: content, onChangeText: setContent, placeholder: '# Triage\n\n1. Read the issue…', "aria-label": "SKILL.md", rows: 14, style: mono }) }), _jsx(Line, { children: note || (may ? '' : 'Only an org admin can write or delete skills.') }), _jsxs(XStack, { gap: "$2", items: "center", children: [skill && may ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Delete" })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: may ? 'Cancel' : 'Close' }), may ? (_jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: "Save" })) : null] }), skill ? (_jsx(Confirm, { what: skill.name, says: "The skill leaves your organization and every agent that names it.", open: asking, onOpenChange: setAsking, onYes: async () => {
|
|
124
130
|
await remove(t, skill.id);
|
|
125
131
|
onDeleted(skill.name);
|
|
126
132
|
} })) : null] }));
|
package/lib/customize/ui.d.ts
CHANGED
|
@@ -6,18 +6,21 @@ export declare const mono: {
|
|
|
6
6
|
export declare const left: {
|
|
7
7
|
readonly textAlign: 'left';
|
|
8
8
|
};
|
|
9
|
-
/** Which half of a tab: what
|
|
10
|
-
export type View = '
|
|
11
|
-
/** What the shell hands a tab: which half, the search,
|
|
9
|
+
/** Which half of a tab: what there is to browse and add (first), or what the org already has. */
|
|
10
|
+
export type View = 'discover' | 'yours';
|
|
11
|
+
/** What the shell hands a tab: which half, the search, the Add dialog's state, and where to say how many are yours. */
|
|
12
12
|
export interface Pane {
|
|
13
13
|
view: View;
|
|
14
14
|
q: string;
|
|
15
15
|
adding: boolean;
|
|
16
16
|
onAdding: (open: boolean) => void;
|
|
17
17
|
onView: (v: View) => void;
|
|
18
|
+
onCount: (n: number) => void;
|
|
18
19
|
}
|
|
20
|
+
/** Tells the shell how many are yours, whenever that changes. */
|
|
21
|
+
export declare function useCount(n: number, onCount: (n: number) => void): void;
|
|
19
22
|
/** Whether a row answers a search: every word of it somewhere in the row's text. */
|
|
20
|
-
export
|
|
23
|
+
export { matches } from '../merge.ts';
|
|
21
24
|
/** Cards in as many columns as fit, one column on a phone. */
|
|
22
25
|
export declare function Grid({ children, label }: {
|
|
23
26
|
children: ReactNode;
|
|
@@ -52,12 +55,13 @@ export declare function Featured({ title, detail, meta, mark, action, onOpen }:
|
|
|
52
55
|
action?: ReactNode;
|
|
53
56
|
onOpen: () => void;
|
|
54
57
|
}): import("react").JSX.Element;
|
|
55
|
-
/** A card's add control:
|
|
56
|
-
export declare function Add({ name, added, busy, onPress }: {
|
|
58
|
+
/** A card's add control: Add while it is not yours, Added once it is. */
|
|
59
|
+
export declare function Add({ name, added, busy, onPress, label }: {
|
|
57
60
|
name: string;
|
|
58
61
|
added: boolean;
|
|
59
62
|
busy?: boolean;
|
|
60
63
|
onPress: () => void;
|
|
64
|
+
label?: string;
|
|
61
65
|
}): import("react").JSX.Element;
|
|
62
66
|
/** A dialog with a title, a close control and a body that scrolls inside the window. */
|
|
63
67
|
export declare function Sheet({ title, open, onOpenChange, width, children }: {
|
|
@@ -75,11 +79,22 @@ export declare function Confirm({ what, says, open, onOpenChange, onYes }: {
|
|
|
75
79
|
onOpenChange: (o: boolean) => void;
|
|
76
80
|
onYes: () => Promise<void>;
|
|
77
81
|
}): import("react").JSX.Element;
|
|
78
|
-
/** The quiet line for
|
|
82
|
+
/** The quiet line for loading, a search that matched nothing, and signed-out. */
|
|
79
83
|
export declare function Soft({ children, action }: {
|
|
80
84
|
children: string;
|
|
81
85
|
action?: ReactNode;
|
|
82
86
|
}): import("react").JSX.Element;
|
|
87
|
+
/** A read that failed: what went wrong in plain words, and the one control that asks again. */
|
|
88
|
+
export declare function Failed({ error, onRetry }: {
|
|
89
|
+
error: unknown;
|
|
90
|
+
onRetry: () => void;
|
|
91
|
+
}): import("react").JSX.Element;
|
|
92
|
+
/** Nothing here yet: what this place holds, and the next step, as buttons. */
|
|
93
|
+
export declare function Empty({ title, detail, children }: {
|
|
94
|
+
title: string;
|
|
95
|
+
detail: string;
|
|
96
|
+
children?: ReactNode;
|
|
97
|
+
}): import("react").JSX.Element;
|
|
83
98
|
/** What a signed-out visitor is told, with the one control that signs them in. */
|
|
84
99
|
export declare function Visitor({ children }: {
|
|
85
100
|
children: string;
|
|
@@ -89,10 +104,11 @@ export declare function Line({ children }: {
|
|
|
89
104
|
children: string;
|
|
90
105
|
}): import("react").JSX.Element | null;
|
|
91
106
|
/** A small heading over a run of cards. */
|
|
92
|
-
export declare function Part({ title, detail, children }: {
|
|
107
|
+
export declare function Part({ title, detail, action, children }: {
|
|
93
108
|
title: string;
|
|
94
109
|
detail: string;
|
|
95
|
-
|
|
110
|
+
action?: ReactNode;
|
|
111
|
+
children?: ReactNode;
|
|
96
112
|
}): import("react").JSX.Element;
|
|
97
113
|
/** A labelled control, with a line under it when there is more to say. */
|
|
98
114
|
export declare function Field({ label, hint, children }: {
|
|
@@ -100,12 +116,13 @@ export declare function Field({ label, hint, children }: {
|
|
|
100
116
|
hint?: string;
|
|
101
117
|
children: ReactNode;
|
|
102
118
|
}): import("react").JSX.Element;
|
|
103
|
-
/** Two or three choices side by side; the chosen one is filled. */
|
|
104
|
-
export declare function Choice<T extends string>({ value, options, onChange, label }: {
|
|
119
|
+
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
|
|
120
|
+
export declare function Choice<T extends string>({ value, options, onChange, label, }: {
|
|
105
121
|
value: T;
|
|
106
122
|
options: readonly {
|
|
107
123
|
id: T;
|
|
108
124
|
label: string;
|
|
125
|
+
count?: number;
|
|
109
126
|
}[];
|
|
110
127
|
onChange: (v: T) => void;
|
|
111
128
|
label: string;
|
package/lib/customize/ui.js
CHANGED
|
@@ -3,24 +3,24 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* What every Customize tab is drawn from, so the four read as one page: a grid
|
|
4
4
|
* of cards that stacks on a phone, the card itself with its one action, the
|
|
5
5
|
* featured card, a dialog with a title and a scrolling body, a delete that asks
|
|
6
|
-
* first,
|
|
6
|
+
* first, the empty state that names the next step, a failure with its retry,
|
|
7
|
+
* and the quiet line for loading and signed-out.
|
|
7
8
|
*/
|
|
8
9
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
9
10
|
import { Check, Plus, X } from '@hanzogui/lucide-icons-2';
|
|
10
11
|
import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
11
|
-
import { useState } from 'react';
|
|
12
|
+
import { useEffect, useState } from 'react';
|
|
12
13
|
import { useHost } from '../host.js';
|
|
14
|
+
import { say } from './say.js';
|
|
13
15
|
export const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
|
|
14
16
|
/** A button that holds a card's text reads from the left, not from the middle a button centres it in. */
|
|
15
17
|
export const left = { textAlign: 'left' };
|
|
16
|
-
/**
|
|
17
|
-
export function
|
|
18
|
-
|
|
19
|
-
if (!words.length)
|
|
20
|
-
return true;
|
|
21
|
-
const hay = text.join(' ').toLowerCase();
|
|
22
|
-
return words.every((w) => hay.includes(w));
|
|
18
|
+
/** Tells the shell how many are yours, whenever that changes. */
|
|
19
|
+
export function useCount(n, onCount) {
|
|
20
|
+
useEffect(() => onCount(n), [n, onCount]);
|
|
23
21
|
}
|
|
22
|
+
/** Whether a row answers a search: every word of it somewhere in the row's text. */
|
|
23
|
+
export { matches } from '../merge.js';
|
|
24
24
|
/** Cards in as many columns as fit, one column on a phone. */
|
|
25
25
|
export function Grid({ children, label }) {
|
|
26
26
|
return (_jsx("div", { role: "list", "aria-label": label, style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 280px), 1fr))', gap: 12 }, children: children }));
|
|
@@ -43,12 +43,12 @@ export function Tile({ title, detail, meta, mark, action, onOpen, }) {
|
|
|
43
43
|
export function Featured({ title, detail, meta, mark, action, onOpen }) {
|
|
44
44
|
return (_jsxs(XStack, { items: "flex-start", gap: "$4", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", rowGap: "$3", children: [_jsxs(XStack, { render: "button", "aria-label": title, onPress: onOpen, flex: 1, minW: 220, gap: "$4", items: "flex-start", cursor: "pointer", style: left, children: [mark, _jsxs(YStack, { flex: 1, minW: 0, gap: "$1.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", textTransform: "uppercase", letterSpacing: 1, children: "Featured" }), _jsx(SizableText, { size: "$6", color: "$ink", numberOfLines: 1, children: title }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 3, children: detail }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: meta })] })] }), action] }));
|
|
45
45
|
}
|
|
46
|
-
/** A card's add control:
|
|
47
|
-
export function Add({ name, added, busy, onPress }) {
|
|
46
|
+
/** A card's add control: Add while it is not yours, Added once it is. */
|
|
47
|
+
export function Add({ name, added, busy, onPress, label = 'Add' }) {
|
|
48
48
|
if (added) {
|
|
49
|
-
return (
|
|
49
|
+
return (_jsxs(XStack, { "aria-label": `${name} is added`, gap: "$1", height: 32, items: "center", shrink: 0, children: [_jsx(Check, { size: 14 }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Added" })] }));
|
|
50
50
|
}
|
|
51
|
-
return (
|
|
51
|
+
return (_jsxs(Button, { size: "sm", variant: "outline", "aria-label": `${label} ${name}`, disabled: busy, onPress: onPress, children: [_jsx(Plus, { size: 14 }), " ", label] }));
|
|
52
52
|
}
|
|
53
53
|
/** A dialog with a title, a close control and a body that scrolls inside the window. */
|
|
54
54
|
export function Sheet({ title, open, onOpenChange, width = 560, children }) {
|
|
@@ -66,7 +66,7 @@ export function Confirm({ what, says, open, onOpenChange, onYes }) {
|
|
|
66
66
|
onOpenChange(false);
|
|
67
67
|
}
|
|
68
68
|
catch (e) {
|
|
69
|
-
setNote(e
|
|
69
|
+
setNote(say(e));
|
|
70
70
|
}
|
|
71
71
|
finally {
|
|
72
72
|
setBusy(false);
|
|
@@ -74,9 +74,17 @@ export function Confirm({ what, says, open, onOpenChange, onYes }) {
|
|
|
74
74
|
};
|
|
75
75
|
return (_jsxs(Sheet, { title: `Delete ${what}?`, open: open, onOpenChange: onOpenChange, width: 420, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: busy, onPress: () => void yes(), children: "Delete" })] })] }));
|
|
76
76
|
}
|
|
77
|
-
/** The quiet line for
|
|
77
|
+
/** The quiet line for loading, a search that matched nothing, and signed-out. */
|
|
78
78
|
export function Soft({ children, action }) {
|
|
79
|
-
return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft",
|
|
79
|
+
return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: children }), action] }));
|
|
80
|
+
}
|
|
81
|
+
/** A read that failed: what went wrong in plain words, and the one control that asks again. */
|
|
82
|
+
export function Failed({ error, onRetry }) {
|
|
83
|
+
return (_jsxs(YStack, { py: "$6", items: "center", gap: "$3", role: "alert", children: [_jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: say(error) }), _jsx(Button, { size: "sm", variant: "outline", onPress: onRetry, children: "Try again" })] }));
|
|
84
|
+
}
|
|
85
|
+
/** Nothing here yet: what this place holds, and the next step, as buttons. */
|
|
86
|
+
export function Empty({ title, detail, children }) {
|
|
87
|
+
return (_jsxs(YStack, { py: "$8", px: "$4", items: "center", gap: "$3", rounded: "$4", borderWidth: 1, borderStyle: "dashed", borderColor: "$borderColor", children: [_jsxs(YStack, { items: "center", gap: "$1", maxW: 420, children: [_jsx(SizableText, { size: "$4", color: "$ink", text: "center", children: title }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: detail })] }), children ? (_jsx(XStack, { gap: "$2", flexWrap: "wrap", justify: "center", children: children })) : null] }));
|
|
80
88
|
}
|
|
81
89
|
/** What a signed-out visitor is told, with the one control that signs them in. */
|
|
82
90
|
export function Visitor({ children }) {
|
|
@@ -90,16 +98,16 @@ export function Line({ children }) {
|
|
|
90
98
|
return (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: children }));
|
|
91
99
|
}
|
|
92
100
|
/** A small heading over a run of cards. */
|
|
93
|
-
export function Part({ title, detail, children }) {
|
|
94
|
-
return (_jsxs(YStack, { gap: "$2.5", children: [_jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$
|
|
101
|
+
export function Part({ title, detail, action, children }) {
|
|
102
|
+
return (_jsxs(YStack, { gap: "$2.5", render: "section", "aria-label": title, children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { render: "h2", size: "$4", color: "$ink", children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: detail })] }), action] }), children] }));
|
|
95
103
|
}
|
|
96
104
|
/** A labelled control, with a line under it when there is more to say. */
|
|
97
105
|
export function Field({ label, hint, children }) {
|
|
98
106
|
return (_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), children, hint ? (_jsx(SizableText, { size: "$1", color: "$soft", children: hint })) : null] }));
|
|
99
107
|
}
|
|
100
|
-
/** Two or three choices side by side; the chosen one is filled. */
|
|
101
|
-
export function Choice({ value, options, onChange, label }) {
|
|
102
|
-
return (_jsx(XStack, { role: "group", "aria-label": label, p: 2, gap: 2, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", self: "flex-start", children: options.map((o) => (
|
|
108
|
+
/** Two or three choices side by side; the chosen one is filled. A count, when one is given, follows its label. */
|
|
109
|
+
export function Choice({ value, options, onChange, label, }) {
|
|
110
|
+
return (_jsx(XStack, { role: "group", "aria-label": label, p: 2, gap: 2, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", self: "flex-start", shrink: 0, children: options.map((o) => (_jsxs(XStack, { render: "button", "aria-pressed": o.id === value, "aria-label": o.count ? `${o.label}, ${o.count}` : o.label, onPress: () => onChange(o.id), items: "center", gap: "$1.5", px: "$3", py: "$1", rounded: "$2", bg: o.id === value ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { size: "$2", color: o.id === value ? '$ink' : '$soft', children: o.label }), o.count ? (_jsx(SizableText, { size: "$1", color: "$soft", fontVariant: ['tabular-nums'], children: o.count })) : null] }, o.id))) }));
|
|
103
111
|
}
|
|
104
112
|
/** A date from Unix seconds, or ''. */
|
|
105
113
|
export const day = (s) => (s ? new Date(s * 1000).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) : '');
|
package/lib/data.d.ts
CHANGED
|
@@ -10,6 +10,19 @@ export interface Read<T> {
|
|
|
10
10
|
}
|
|
11
11
|
/** Run `load` whenever `key` changes (and on `reload`), keeping the last good value. */
|
|
12
12
|
export declare function useRead<T>(load: (() => Promise<T>) | null, initial: T, key: unknown[]): Read<T>;
|
|
13
|
+
/** How long a read is waited on before the screen says it is taking too long. */
|
|
14
|
+
export declare const WAIT = 15000;
|
|
15
|
+
/** A read that did not answer in time. */
|
|
16
|
+
export declare class Slow extends Error {
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A read drawn at once from what this page last read under the same `key`, then
|
|
21
|
+
* read again behind it: stale while it revalidates, so moving between tabs is
|
|
22
|
+
* instant and a list never blanks to "reading" on the way back. A read that has
|
|
23
|
+
* not answered after `wait` ms is let go, as `Slow`. A null key reads nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useCached<T>(key: string | null, load: (signal: AbortSignal) => Promise<T>, initial: T, wait?: number): Read<T>;
|
|
13
26
|
/**
|
|
14
27
|
* The org's coding runs, newest first, kept current by the org's own session
|
|
15
28
|
* stream: a status that moves moves its dot, and a run started anywhere appears.
|
package/lib/data.js
CHANGED
|
@@ -43,6 +43,76 @@ export function useRead(load, initial, key) {
|
|
|
43
43
|
}, [...key, tick]);
|
|
44
44
|
return { value, error, loading, reload };
|
|
45
45
|
}
|
|
46
|
+
/** How long a read is waited on before the screen says it is taking too long. */
|
|
47
|
+
export const WAIT = 15_000;
|
|
48
|
+
/** A read that did not answer in time. */
|
|
49
|
+
export class Slow extends Error {
|
|
50
|
+
constructor() {
|
|
51
|
+
super('Hanzo is taking too long to answer.');
|
|
52
|
+
this.name = 'Slow';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** What this page last read, per key, for the life of the page. */
|
|
56
|
+
const seen = new Map();
|
|
57
|
+
/**
|
|
58
|
+
* A read drawn at once from what this page last read under the same `key`, then
|
|
59
|
+
* read again behind it: stale while it revalidates, so moving between tabs is
|
|
60
|
+
* instant and a list never blanks to "reading" on the way back. A read that has
|
|
61
|
+
* not answered after `wait` ms is let go, as `Slow`. A null key reads nothing.
|
|
62
|
+
*/
|
|
63
|
+
export function useCached(key, load, initial, wait = WAIT) {
|
|
64
|
+
const [value, setValue] = useState(() => (key !== null && seen.has(key) ? seen.get(key) : initial));
|
|
65
|
+
const [error, setError] = useState(null);
|
|
66
|
+
const [loading, setLoading] = useState(key !== null);
|
|
67
|
+
const [tick, setTick] = useState(0);
|
|
68
|
+
const reload = useCallback(() => setTick((n) => n + 1), []);
|
|
69
|
+
const loader = useRef(load);
|
|
70
|
+
loader.current = load;
|
|
71
|
+
const was = useRef(key);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
// Another key (another org, another search) never shows the last one's answer or failure.
|
|
74
|
+
if (was.current !== key) {
|
|
75
|
+
was.current = key;
|
|
76
|
+
setValue(key !== null && seen.has(key) ? seen.get(key) : initial);
|
|
77
|
+
setError(null);
|
|
78
|
+
}
|
|
79
|
+
if (key === null) {
|
|
80
|
+
setLoading(false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const ctl = new AbortController();
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
ctl.abort();
|
|
86
|
+
setError(new Slow());
|
|
87
|
+
setLoading(false);
|
|
88
|
+
}, wait);
|
|
89
|
+
setLoading(true);
|
|
90
|
+
loader
|
|
91
|
+
.current(ctl.signal)
|
|
92
|
+
.then((v) => {
|
|
93
|
+
if (ctl.signal.aborted)
|
|
94
|
+
return;
|
|
95
|
+
seen.set(key, v);
|
|
96
|
+
setValue(v);
|
|
97
|
+
setError(null);
|
|
98
|
+
setLoading(false);
|
|
99
|
+
})
|
|
100
|
+
.catch((e) => {
|
|
101
|
+
if (ctl.signal.aborted)
|
|
102
|
+
return;
|
|
103
|
+
setError(e);
|
|
104
|
+
setLoading(false);
|
|
105
|
+
})
|
|
106
|
+
.finally(() => clearTimeout(timer));
|
|
107
|
+
return () => {
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
ctl.abort();
|
|
110
|
+
};
|
|
111
|
+
// `initial` is the empty value, read only when the key changes.
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
113
|
+
}, [key, tick, wait]);
|
|
114
|
+
return { value, error, loading, reload };
|
|
115
|
+
}
|
|
46
116
|
/**
|
|
47
117
|
* The org's coding runs, newest first, kept current by the org's own session
|
|
48
118
|
* stream: a status that moves moves its dot, and a run started anywhere appears.
|
package/lib/desk.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type DropdownMenuProps } from '@hanzo/ui';
|
|
2
2
|
import type { Event } from './api/sessions.ts';
|
|
3
|
-
|
|
3
|
+
import type { Deck, Tabs } from './tabs.ts';
|
|
4
|
+
/** A run's panel before anyone has arranged it: what a run is read by, the terminal chosen. */
|
|
5
|
+
export declare const SEED: Tabs;
|
|
6
|
+
export declare function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, side, sheet, onHide, }: {
|
|
4
7
|
id: string;
|
|
5
8
|
repo: string;
|
|
6
9
|
branch: string;
|
|
@@ -27,5 +30,7 @@ export declare function Desk({ id, repo, branch, base, environment, mode, pr, ti
|
|
|
27
30
|
/** The refusal's one action: Retry, or Sign in when nobody is. */
|
|
28
31
|
retry: string;
|
|
29
32
|
onRetry: () => void;
|
|
33
|
+
side: Deck;
|
|
34
|
+
sheet: boolean;
|
|
30
35
|
onHide: () => void;
|
|
31
36
|
}): import("react").JSX.Element;
|
package/lib/desk.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* The
|
|
4
|
-
* sandbox serves (or where
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* The run's side panel: Dev's kinds of tab in the one panel (panel.tsx) — the
|
|
4
|
+
* Preview of what the run's sandbox serves (or where it is published), its
|
|
5
|
+
* Artifacts, Files, Diff and Terminal, and on asking its Desktop and its
|
|
6
|
+
* codebase's Environment.
|
|
7
7
|
*
|
|
8
8
|
* The sandbox outlives the run: kept when the run ends, parked while nobody
|
|
9
9
|
* watches, retired a day after it parks. The bar under the tabs says which and
|
|
10
10
|
* suspends or resumes it; a tab that needs it running resumes it (sandbox.ts).
|
|
11
11
|
*/
|
|
12
12
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
13
|
-
import { ExternalLink, Info, MoreHorizontal,
|
|
13
|
+
import { ExternalLink, Info, MoreHorizontal, RefreshCcw } from '@hanzogui/lucide-icons-2';
|
|
14
14
|
import { Button, DropdownMenu } from '@hanzo/ui';
|
|
15
15
|
import { PreviewFrame } from '@hanzo/ui/agents';
|
|
16
16
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
@@ -22,24 +22,52 @@ import { Artifacts, Files } from './files.js';
|
|
|
22
22
|
import { Git } from './git.js';
|
|
23
23
|
import { useTarget } from './host.js';
|
|
24
24
|
import { away, Out } from './out.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
import { Panel } from './panel.js';
|
|
26
|
+
/** A run's panel before anyone has arranged it: what a run is read by, the terminal chosen. */
|
|
27
|
+
export const SEED = {
|
|
28
|
+
tabs: [
|
|
29
|
+
{ id: 'preview', kind: 'preview', title: 'Preview' },
|
|
30
|
+
{ id: 'artifacts', kind: 'artifacts', title: 'Artifacts' },
|
|
31
|
+
{ id: 'files', kind: 'files', title: 'Files' },
|
|
32
|
+
{ id: 'diff', kind: 'diff', title: 'Diff' },
|
|
33
|
+
{ id: 'terminal', kind: 'terminal', title: 'Terminal' },
|
|
34
|
+
],
|
|
35
|
+
at: 'terminal',
|
|
36
|
+
};
|
|
37
|
+
/** How often the sandbox's state, and what it serves, is read while the panel is open. */
|
|
28
38
|
const EVERY = 10_000;
|
|
29
39
|
const PORTS = 5_000;
|
|
30
|
-
export function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, onHide, }) {
|
|
31
|
-
// Until a tab is picked it follows the run: a setup run's work IS the
|
|
32
|
-
// environment, a published run opens on its site, any other on its terminal.
|
|
33
|
-
const [picked, setTab] = useState(null);
|
|
34
|
-
const tab = picked ?? (mode === 'setup' ? 'environment' : site ? 'browser' : 'terminal');
|
|
40
|
+
export function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, side, sheet, onHide, }) {
|
|
35
41
|
const name = repo.split('/').filter(Boolean).pop() || repo;
|
|
36
42
|
const lines = shell(events);
|
|
37
43
|
const failed = failure(events);
|
|
38
|
-
const bleed = BLEED.includes(tab);
|
|
39
44
|
const box = useBox(sandbox);
|
|
40
45
|
// Nothing is said of the sandbox until it has been read.
|
|
41
46
|
const held = box.value?.state ?? '';
|
|
42
|
-
|
|
47
|
+
const environ = () => side.open({ kind: 'environment', title: 'Environment' });
|
|
48
|
+
const kinds = [
|
|
49
|
+
{ id: 'preview', label: 'Preview', bleed: true, render: () => _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) },
|
|
50
|
+
{
|
|
51
|
+
id: 'artifacts',
|
|
52
|
+
label: 'Artifacts',
|
|
53
|
+
render: () => _jsx(Artifacts, { session: id, sandbox: sandbox, repo: repo, branch: branch, mode: mode, project: project, site: site, pr: pr, onEnvironment: environ }),
|
|
54
|
+
},
|
|
55
|
+
{ id: 'files', label: 'Files', render: () => _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) },
|
|
56
|
+
{ id: 'diff', label: 'Diff', render: () => _jsx(Git, { session: id, title: title, live: live }) },
|
|
57
|
+
{
|
|
58
|
+
id: 'terminal',
|
|
59
|
+
label: 'Terminal',
|
|
60
|
+
bleed: true,
|
|
61
|
+
render: () => _jsx(Terminal, { id: id, sandbox: sandbox, held: held, lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry }),
|
|
62
|
+
},
|
|
63
|
+
{ id: 'desktop', label: 'Desktop', bleed: true, render: () => _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) },
|
|
64
|
+
{
|
|
65
|
+
id: 'environment',
|
|
66
|
+
label: 'Environment',
|
|
67
|
+
render: () => (_jsxs(YStack, { gap: "$2", children: [_jsx(Environment, { repo: name, busy: live && mode === 'setup' }), _jsx(SizableText, { size: "$2", color: "$ink", pt: "$4", children: "This run" }), _jsx(Fact, { label: "Run", value: id }), _jsx(Fact, { label: "Repository", value: name || '—' }), _jsx(Fact, { label: "Branch", value: branch || '—' }), _jsx(Fact, { label: "Base", value: base || '—' }), _jsx(Fact, { label: "Runs on", value: environment || 'sandbox' }), _jsx(Fact, { label: "Sandbox", value: sandbox || '—' }), _jsx(Fact, { label: "Mode", value: mode || 'build' })] })),
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
return (_jsx(Panel, { side: side, kinds: kinds, sheet: sheet, onHide: onHide, label: "Run details", bar: sandbox ? _jsx(Held, { box: box, live: live }) : null, menu: _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": "Run actions", p: "$1.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: [...menu, { key: 'details', label: 'Details', icon: _jsx(Info, { size: 16 }), onSelect: environ }] }) }));
|
|
43
71
|
}
|
|
44
72
|
/**
|
|
45
73
|
* The run's sandbox as it stands, and what moves it. It is read while the pane is
|
|
@@ -208,10 +236,6 @@ function Site({ site, live, held }) {
|
|
|
208
236
|
? 'When the run starts a server in its sandbox, the page opens here.'
|
|
209
237
|
: 'Nothing listens in this run’s sandbox, and it published no site. Its work is on its branch.' })] }) })] }));
|
|
210
238
|
}
|
|
211
|
-
function TabButton({ id, tab, onPick, children }) {
|
|
212
|
-
const on = tab === id;
|
|
213
|
-
return (_jsx(XStack, { render: "button", "aria-label": children, "aria-pressed": on, onPress: () => onPick(id), px: "$3", py: "$1.5", rounded: 999, shrink: 0, bg: on ? '$edge' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: on ? '$ink' : '$soft', children: children }) }));
|
|
214
|
-
}
|
|
215
239
|
function Fact({ label, value }) {
|
|
216
240
|
return (_jsxs(XStack, { gap: "$3", items: "baseline", children: [_jsx(SizableText, { size: "$1", color: "$soft", width: 96, children: label }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 2, children: value })] }));
|
|
217
241
|
}
|
package/lib/environment.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Home, Homes } from './api/work.ts';
|
|
2
|
+
import { type Read } from './data.ts';
|
|
1
3
|
export declare function Environment({ repo, busy }: {
|
|
2
4
|
repo: string;
|
|
3
5
|
busy: boolean;
|
|
@@ -5,11 +7,17 @@ export declare function Environment({ repo, busy }: {
|
|
|
5
7
|
/**
|
|
6
8
|
* New's offer to set a codebase up. An agent onboards it, or an org admin saves
|
|
7
9
|
* it empty and writes the scripts by hand beside the next run.
|
|
10
|
+
*
|
|
11
|
+
* The repository is chosen from the org's own (the forge's, and every linked
|
|
12
|
+
* repository's copy there), never typed: a name that is not one of them — a
|
|
13
|
+
* board's key handed over as a codebase — is said so and cannot be set up.
|
|
8
14
|
*/
|
|
9
|
-
export declare function SetupDialog({ repo, open, onOpenChange, onStart, onSaved, }: {
|
|
15
|
+
export declare function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved, onRepo, }: {
|
|
10
16
|
repo: string;
|
|
17
|
+
homes: Read<Homes>;
|
|
11
18
|
open: boolean;
|
|
12
19
|
onOpenChange: (o: boolean) => void;
|
|
13
|
-
onStart: () => Promise<void>;
|
|
14
|
-
onSaved: () => void;
|
|
20
|
+
onStart: (repo: string) => Promise<void>;
|
|
21
|
+
onSaved: (repo: string) => void;
|
|
22
|
+
onRepo: (home: Home) => void;
|
|
15
23
|
}): import("react").JSX.Element;
|