@hanzo/build 0.2.52 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +103 -49
  2. package/lib/api/billing.d.ts +39 -0
  3. package/lib/api/billing.js +41 -0
  4. package/lib/api/coding.d.ts +4 -1
  5. package/lib/api/coding.js +2 -0
  6. package/lib/api/environment.d.ts +12 -0
  7. package/lib/api/environment.js +39 -1
  8. package/lib/api/github.d.ts +24 -4
  9. package/lib/api/github.js +60 -16
  10. package/lib/api/limits.d.ts +69 -0
  11. package/lib/api/limits.js +86 -0
  12. package/lib/api/links.d.ts +42 -0
  13. package/lib/api/links.js +65 -0
  14. package/lib/api/pref.d.ts +5 -3
  15. package/lib/api/pref.js +0 -3
  16. package/lib/api/turn.d.ts +10 -1
  17. package/lib/api/turn.js +16 -4
  18. package/lib/api/work.d.ts +75 -38
  19. package/lib/api/work.js +132 -44
  20. package/lib/builder.d.ts +0 -10
  21. package/lib/builder.js +12 -52
  22. package/lib/choice.d.ts +21 -0
  23. package/lib/choice.js +40 -1
  24. package/lib/customize/agents.d.ts +1 -1
  25. package/lib/customize/agents.js +21 -19
  26. package/lib/customize/connectors.d.ts +1 -1
  27. package/lib/customize/connectors.js +80 -31
  28. package/lib/customize/index.d.ts +2 -0
  29. package/lib/customize/index.js +23 -19
  30. package/lib/customize/load.d.ts +9 -0
  31. package/lib/customize/load.js +22 -0
  32. package/lib/customize/plugins.d.ts +1 -1
  33. package/lib/customize/plugins.js +21 -19
  34. package/lib/customize/say.d.ts +1 -0
  35. package/lib/customize/say.js +38 -0
  36. package/lib/customize/skills.d.ts +1 -1
  37. package/lib/customize/skills.js +23 -17
  38. package/lib/customize/ui.d.ts +28 -11
  39. package/lib/customize/ui.js +29 -21
  40. package/lib/data.d.ts +13 -0
  41. package/lib/data.js +70 -0
  42. package/lib/desk.d.ts +6 -1
  43. package/lib/desk.js +43 -19
  44. package/lib/environment.d.ts +11 -3
  45. package/lib/environment.js +53 -13
  46. package/lib/foot.js +10 -6
  47. package/lib/forge.js +139 -71
  48. package/lib/index.d.ts +28 -2
  49. package/lib/index.js +26 -2
  50. package/lib/landing.d.ts +0 -10
  51. package/lib/landing.js +70 -38
  52. package/lib/merge.d.ts +223 -0
  53. package/lib/merge.js +380 -0
  54. package/lib/meter.d.ts +31 -0
  55. package/lib/meter.js +45 -0
  56. package/lib/mind.d.ts +60 -0
  57. package/lib/mind.js +187 -0
  58. package/lib/nav.d.ts +11 -0
  59. package/lib/nav.js +75 -0
  60. package/lib/panel.d.ts +45 -0
  61. package/lib/panel.js +115 -0
  62. package/lib/pick.d.ts +8 -0
  63. package/lib/pick.js +18 -1
  64. package/lib/plan.d.ts +78 -0
  65. package/lib/plan.js +133 -0
  66. package/lib/project.js +16 -6
  67. package/lib/prompt.d.ts +75 -0
  68. package/lib/prompt.js +125 -0
  69. package/lib/prose.js +14 -10
  70. package/lib/reply.d.ts +36 -0
  71. package/lib/reply.js +124 -0
  72. package/lib/route.d.ts +16 -0
  73. package/lib/route.js +40 -0
  74. package/lib/run.js +87 -52
  75. package/lib/section.d.ts +1 -1
  76. package/lib/section.js +7 -11
  77. package/lib/settings/billing.js +16 -9
  78. package/lib/settings/code.js +15 -12
  79. package/lib/settings/credits.d.ts +9 -0
  80. package/lib/settings/credits.js +110 -0
  81. package/lib/settings/plan.d.ts +17 -0
  82. package/lib/settings/plan.js +108 -0
  83. package/lib/settings/usage.js +11 -119
  84. package/lib/shelf.js +3 -3
  85. package/lib/standing.d.ts +44 -0
  86. package/lib/standing.js +64 -0
  87. package/lib/sync.js +22 -8
  88. package/lib/tabs.d.ts +53 -0
  89. package/lib/tabs.js +197 -0
  90. package/lib/tools.d.ts +2 -9
  91. package/lib/tools.js +20 -25
  92. package/lib/transcript.d.ts +10 -1
  93. package/lib/transcript.js +25 -10
  94. package/lib/voice.d.ts +2 -13
  95. package/lib/voice.js +7 -73
  96. package/package.json +20 -3
@@ -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
- * Yours is the org's own skills — written here, switched on and off — and the
7
- * catalogue's skills it has added. Discover is the brand's catalogue; adding one
8
- * switches it on for the org, which is all adding is: the platform keeps no copy.
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 { Add, Confirm, day, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, Visitor } from './ui.js';
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 = useRead(signed ? () => authored(t) : null, [], [t, signed]);
29
- const on = useRead(signed ? () => active(t) : null, [], [t, signed]);
30
- const shelf = useRead(view === 'discover' ? () => brand(t) : null, null, [t.api, view]);
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.message);
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(Soft, { children: shelf.error.message })) : !shelf.value ? (_jsx(Soft, { children: "Reading the catalogue\u2026" })) : !found.length ? (_jsx(Soft, { children: all.length ? 'No skill matches.' : 'The catalogue lists no skills.' })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: found.length === all.length ? `${all.length} skills across ${shelf.value.products.length} products` : `${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] }));
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 added = on.value.filter((x) => nameOf(x.name) && !mine.has(nameOf(x.name)) && matches(q, nameOf(x.name), x.description));
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(Soft, { children: error.message })) : (own.loading || on.loading) && !own.value.length && !on.value.length ? (_jsx(Soft, { children: "Reading your skills\u2026" })) : !own.value.length && !added.length && !q ? (_jsx(Soft, { action: _jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Discover skills" }), children: "No skills yet. Write one, or add one from the catalogue." })) : (_jsxs(_Fragment, { children: [_jsx(Part, { title: "Written here", detail: "Your organization\u2019s own. Off keeps it, and keeps it out of every agent\u2019s prompt.", children: ownShown.length ? (_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))) })) : (_jsx(SizableText, { size: "$2", color: "$soft", children: own.value.length ? 'None of yours matches.' : 'None yet.' })) }), added.length ? (_jsx(Part, { title: "Added from the catalogue", detail: "On for every agent that names them.", children: _jsx(Grid, { label: "Added skills", children: added.map((x) => {
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 = useRead(() => document(t, skill.name), '', [t.api, skill.name]);
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.' : 'Adding it switches it on for your organization.' })] })) : null, doc.error ? (_jsx(Soft, { children: doc.error.message })) : doc.loading && !doc.value ? (_jsx(Soft, { children: "Reading 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 }) }))] }));
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.message);
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’s name is its id; save under another name to make a new one.' : 'One lowercase word: letters, digits, _ or -. Agents name it as skill_<name>.', children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Name", disabled: Boolean(skill), autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", hint: "The one line an agent reads to decide whether it needs this 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 the skill 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 ? '' : 'An org admin writes and deletes the organization’s 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 () => {
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] }));
@@ -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 the org has, or what it can add. */
10
- export type View = 'yours' | 'discover';
11
- /** What the shell hands a tab: which half, the search, and the Add dialog's state. */
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 declare function matches(q: string, ...text: string[]): boolean;
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: a plus while it is not yours, a check once it is. */
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 empty, loading, signed-out and refused. */
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
- children: ReactNode;
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;
@@ -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, and the quiet line for empty, loading, signed-out and refused.
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
- /** Whether a row answers a search: every word of it somewhere in the row's text. */
17
- export function matches(q, ...text) {
18
- const words = q.trim().toLowerCase().split(/\s+/).filter(Boolean);
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: a plus while it is not yours, a check once it is. */
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 (_jsx(XStack, { "aria-label": `${name} is added`, width: 32, height: 32, items: "center", justify: "center", shrink: 0, children: _jsx(Check, { size: 16 }) }));
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 (_jsx(Button, { size: "icon-sm", variant: "outline", "aria-label": `Add ${name}`, disabled: busy, onPress: onPress, children: _jsx(Plus, { size: 14 }) }));
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.message);
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 empty, loading, signed-out and refused. */
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", style: { textAlign: 'center' }, children: children }), action] }));
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: "$3", color: "$ink", children: title }), _jsx(SizableText, { size: "$1", color: "$soft", children: detail })] }), children] }));
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) => (_jsx(XStack, { render: "button", "aria-pressed": o.id === value, onPress: () => onChange(o.id), 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.id))) }));
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
- export declare function Desk({ id, repo, branch, base, environment, mode, pr, title, project, sandbox, site, published, events, live, menu, refused, retry, onRetry, onHide, }: {
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 pane beside a run's transcript, as tabs: the Browser showing what the run's
4
- * sandbox serves (or where the run is published), its sandbox's Desktop and
5
- * Terminal, what it produced, its files, what it pushed, and its codebase's
6
- * environment.
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, PanelRightClose, RefreshCcw } from '@hanzogui/lucide-icons-2';
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
- /** The tabs that are a surface of their own, drawn edge to edge. */
26
- const BLEED = ['browser', 'desktop', 'terminal'];
27
- /** How often the sandbox's state, and what it serves, is read while the pane is open. */
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
- return (_jsxs(YStack, { role: "complementary", "aria-label": "Run details", flex: 1, minW: 0, minH: 0, overflow: "hidden", style: { borderRadius: 'inherit' }, children: [_jsxs(XStack, { px: "$2", py: "$2", gap: "$1", borderBottomWidth: 1, borderColor: "$borderColor", items: "center", children: [_jsxs(XStack, { flex: 1, minW: 0, gap: "$1", overflow: "scroll", children: [_jsx(TabButton, { id: "browser", tab: tab, onPick: setTab, children: "Browser" }), _jsx(TabButton, { id: "desktop", tab: tab, onPick: setTab, children: "Desktop" }), _jsx(TabButton, { id: "terminal", tab: tab, onPick: setTab, children: "Terminal" }), _jsx(TabButton, { id: "artifacts", tab: tab, onPick: setTab, children: "Artifacts" }), _jsx(TabButton, { id: "files", tab: tab, onPick: setTab, children: "Files" }), _jsx(TabButton, { id: "git", tab: tab, onPick: setTab, children: "Git" }), _jsx(TabButton, { id: "environment", tab: tab, onPick: setTab, children: "Environment" })] }), _jsx(XStack, { render: "button", "aria-label": "Hide the side pane", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onHide, children: _jsx(PanelRightClose, { size: 16 }) }), _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": "Run actions", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: [...menu, { key: 'details', label: 'Details', icon: _jsx(Info, { size: 16 }), onSelect: () => setTab('environment') }] })] }), sandbox ? _jsx(Held, { box: box, live: live }) : null, _jsxs(YStack, { flex: 1, minH: 0, overflow: bleed ? 'hidden' : 'scroll', px: bleed ? 0 : '$3', py: bleed ? 0 : '$3', children: [tab === 'environment' ? (_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' })] })) : null, tab === 'browser' ? _jsx(Browser, { sandbox: sandbox, held: held, site: site, live: live }, published) : null, tab === 'git' ? _jsx(Git, { session: id, title: title, live: live }) : null, tab === 'desktop' ? _jsx(Door, { which: "screen", sandbox: sandbox, held: held, session: id }) : null, tab === 'terminal' ? (_jsx(Terminal, { id: id, sandbox: sandbox, held: held, lines: lines, live: live, failed: failed, refused: refused, retry: retry, onRetry: onRetry })) : null, tab === 'artifacts' ? (_jsx(Artifacts, { session: id, sandbox: sandbox, repo: repo, branch: branch, mode: mode, project: project, site: site, pr: pr, onEnvironment: () => setTab('environment') })) : null, tab === 'files' ? _jsx(Files, { session: id, repo: repo, branch: branch, sandbox: sandbox, live: held === 'running' }) : null] })] }));
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
  }
@@ -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;