@hanzo/build 0.2.14 → 0.2.16

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/lib/builder.js CHANGED
@@ -28,7 +28,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
28
28
  * left column, never two.
29
29
  */
30
30
  import { SizableText, XStack, YStack } from '@hanzo/gui';
31
- import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, Menu, SlidersHorizontal, Workflow } from '@hanzogui/lucide-icons-2';
31
+ import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, Menu, Search, SlidersHorizontal, Workflow } from '@hanzogui/lucide-icons-2';
32
32
  import { Button } from '@hanzo/ui';
33
33
  import { SessionRail } from '@hanzo/ui/chat';
34
34
  import { HanzoMark } from '@hanzo/ui/product';
@@ -49,7 +49,6 @@ import { Artifacts, Templates } from './shelf.js';
49
49
  import { Find } from './find.js';
50
50
  import { DOCS, Slack, Who } from './foot.js';
51
51
  import { Settings } from './settings/index.js';
52
- import { Where } from './switch.js';
53
52
  function Shell() {
54
53
  const host = useHost();
55
54
  const t = useTarget();
@@ -72,10 +71,11 @@ function Shell() {
72
71
  return list;
73
72
  }, [recents.value, order]);
74
73
  const screen = r.kind === 'screen' ? r.screen : '';
75
- // The top left: the organization switcher once there is an org to switch, the mark before.
76
- // Beside the rail from md up, beside the drawer's button below it — one place at a time.
77
- const switcher = Boolean(host.person && (host.org || (host.memberships?.length ?? 0) > 0));
78
- const top = switcher ? (_jsx(Where, {})) : (_jsx(XStack, { render: "button", "aria-label": "Hanzo", items: "center", onPress: () => go(''), children: _jsx(HanzoMark, { size: 18 }) }));
74
+ // The top left, as on claude.ai: the builder's name, which leads to New, and under
75
+ // it the search that finds a run. The organization is the account's, so it is
76
+ // switched from the account menu at the foot. Beside the rail from md up, beside
77
+ // the drawer's button below it — one place at a time.
78
+ const name = (_jsx(XStack, { render: "button", "aria-label": "Hanzo Build", items: "center", onPress: () => go(''), children: _jsx(SizableText, { size: "$5", fontWeight: "600", color: "$ink", numberOfLines: 1, children: "Hanzo Build" }) }));
79
79
  const links = [
80
80
  { id: 'projects', label: 'Projects', icon: _jsx(Kanban, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'projects' })), active: screen === 'projects' },
81
81
  { id: 'artifacts', label: 'Artifacts', icon: _jsx(Blocks, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
@@ -106,13 +106,14 @@ function Shell() {
106
106
  ];
107
107
  return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, bg: "$background", children: [_jsxs(YStack, { position: "relative", shrink: 0, children: [_jsx(SessionRail, { onNew: () => go(''), fresh: r.kind === 'new', links: links, more: more, recents: rows, active: r.kind === 'run' ? r.id : null, onOpen: (id) => go(id), onSort: () => setOrder(order === 'newest' ? 'running' : 'newest'), sortLabel: order === 'newest' ? 'Show running first' : 'Show newest first', empty: _jsx(SizableText, { size: "$1", color: "$soft", px: "$2", children: !host.person ? 'Sign in to see your runs.' : recents.error ? recents.error.message : recents.loading ? 'Reading…' : 'No runs yet.' }), notice: host.person && !slack ? (_jsx(Slack, { onOpen: () => go(path({ kind: 'settings', section: 'integrations' })), onDismiss: () => setSlack(true) })) : undefined, account: host.person
108
108
  ? {
109
- name: host.person.email || host.person.name,
109
+ // Who, and in which organization, as claude.ai says who and on which plan.
110
+ name: [host.person.name || host.person.email, host.org].filter(Boolean).join(' · '),
110
111
  onPress: () => {
111
112
  setDrawer(false);
112
113
  setMenu(true);
113
114
  },
114
115
  }
115
- : { name: 'Sign in', onPress: () => host.signIn?.() }, onSettings: () => go(path({ kind: 'settings', section: 'general' })), onSearch: () => setFinding(true), collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : switcher ? 80 : 40, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsx(XStack, { position: "absolute", t: 8, l: 8, r: 8, z: 2, items: "center", "$max-md": { display: 'none' }, children: top })), host.person ? _jsx(Who, { open: menu, onOpenChange: setMenu, narrow: collapsed }) : null] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$2", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setDrawer(true), "aria-label": "Open runs", children: _jsx(Menu, { size: 18 }) }), top] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
116
+ : { name: 'Sign in', onPress: () => host.signIn?.() }, collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : 92, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsxs(YStack, { position: "absolute", t: 10, l: 8, r: 8, z: 2, gap: "$2.5", "$max-md": { display: 'none' }, children: [_jsx(XStack, { px: "$2.5", children: name }), _jsxs(XStack, { render: "button", "aria-label": "Search runs", onPress: () => setFinding(true), items: "center", gap: "$2", px: "$2.5", height: 34, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: { bg: '$hover' }, children: [_jsx(Search, { size: 15, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Search" })] })] })), host.person ? _jsx(Who, { open: menu, onOpenChange: setMenu, narrow: collapsed }) : null] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$2", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setDrawer(true), "aria-label": "Open runs", children: _jsx(Menu, { size: 18 }) }), name, _jsx(XStack, { flex: 1 }), _jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setFinding(true), "aria-label": "Search runs", children: _jsx(Search, { size: 18 }) })] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
116
117
  }
117
118
  /** Whichever pane the address names, beside a rail — the builder's or the host's. */
118
119
  function Pane({ onStarted }) {
package/lib/foot.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * The rail's foot: the card that offers Hanzo in Slack, and the menu the account
4
- * row opens — who is signed in, Settings, Usage, the plans, help and logging out.
4
+ * row opens — who is signed in, the organization they act in, Settings, Usage,
5
+ * the plans, help and logging out.
5
6
  * Everything it opens is an address of this page, but help, which is the
6
7
  * documentation.
7
8
  */
8
9
  import { Popover, SizableText, XStack, YStack } from '@hanzo/gui';
9
10
  import { Gauge, LifeBuoy, LogOut, Settings, Slack as Mark, Sparkles, X } from '@hanzogui/lucide-icons-2';
10
- import { MenuRow, MenuRule } from '@hanzo/ui/product';
11
+ import { MenuLabel, MenuRow, MenuRule } from '@hanzo/ui/product';
11
12
  import { useHost } from './host.js';
12
13
  import { path } from './route.js';
13
14
  export const DOCS = 'https://docs.hanzo.ai/docs/dev';
@@ -22,11 +23,17 @@ export function Slack({ onOpen, onDismiss }) {
22
23
  */
23
24
  export function Who({ open, onOpenChange, narrow }) {
24
25
  const host = useHost();
26
+ // The organizations this person acts in; choosing one reloads the page scoped to it.
27
+ const orgs = host.memberships ?? (host.org ? [host.org] : []);
25
28
  const go = (p) => {
26
29
  onOpenChange(false);
27
30
  host.go(p);
28
31
  };
29
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": "Account", items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: "Usage", icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: "View all plans", icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: "Get help", icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
32
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": "Account", items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: "Organization" }), _jsx(YStack, { role: "radiogroup", "aria-label": "Organizations", gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
33
+ onOpenChange(false);
34
+ if (o !== host.org)
35
+ host.chooseOrg?.(o);
36
+ } }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: "Usage", icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: "View all plans", icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: "Get help", icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
30
37
  onOpenChange(false);
31
38
  window.open(DOCS, '_blank', 'noopener,noreferrer');
32
39
  } }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label: "Log out", icon: _jsx(LogOut, { size: 16 }), onPress: () => {
package/lib/landing.js CHANGED
@@ -13,7 +13,7 @@ import { SizableText, XStack, YStack } from '@hanzo/gui';
13
13
  import { Cloud, Monitor } from '@hanzogui/lucide-icons-2';
14
14
  import { Button } from '@hanzo/ui';
15
15
  import { ModeSelect } from '@hanzo/ui/agents';
16
- import { Composer, EmptyPrompt } from '@hanzo/ui/chat';
16
+ import { Composer } from '@hanzo/ui/chat';
17
17
  import { BranchSelect, ChipSelect, FooterLink, HanzoMark, RepoSelect } from '@hanzo/ui/product';
18
18
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
19
19
  import { start, unhonoured } from './api/coding.js';
@@ -165,7 +165,7 @@ export function Landing({ onStarted }) {
165
165
  },
166
166
  }, placeholder: "Codebase", disabled: !signed }, round), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
167
167
  const foot = (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Attach, { files: files, onFiles: setFiles, onNote: setNote }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote }), _jsx(ModeSelect, { modes: MODES, value: kept.mode, onChange: (m) => set({ mode: m }), bg: "transparent", minH: 24, px: "$1.5", self: "center" }), _jsx(XStack, { flex: 1 }), _jsx(ChipSelect, { quiet: true, name: "Model", label: catalog.value.find((m) => m.id === kept.model)?.label ?? 'Enso', chosen: catalog.value.find((m) => m.id === kept.model) ?? null, items: catalog.value, onChange: (m) => set({ model: m.id }), placeholder: "Search models\u2026", placement: "top-end", loading: catalog.loading, error: catalog.error?.message ?? null }), _jsx(ChipSelect, { quiet: true, name: "Effort", label: EFFORTS.find((e) => e.id === kept.effort)?.label ?? 'Medium', chosen: EFFORTS.find((e) => e.id === kept.effort) ?? null, items: EFFORTS, onChange: (e) => set({ effort: e.id }), placement: "top-end", width: 180 })] }));
168
- return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", px: "$4", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, flex: 1, minH: 0, children: [_jsx(EmptyPrompt, { title: prefs.callName ? `What’s up next, ${prefs.callName}?` : 'What’s up next?', mark: _jsx(HanzoMark, { size: 18 }), column: COLUMN }), _jsx(YStack, { flex: 1 }), _jsxs(YStack, { pb: "$2", gap: "$2", children: [env.value && env.value.state !== 'ready' && !place.id ? (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 0, children: env.value.state === 'proposed'
168
+ return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", justify: "center", px: "$4", pb: "$10", overflow: "scroll", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, gap: "$5", children: [_jsxs(XStack, { role: "heading", "aria-level": 1, justify: "center", items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(HanzoMark, { size: 26 }), _jsx(SizableText, { size: "$8", color: "$ink", style: { textAlign: 'center' }, children: prefs.callName ? `What’s up next, ${prefs.callName}?` : 'What’s up next?' })] }), _jsxs(YStack, { gap: "$2", children: [env.value && env.value.state !== 'ready' && !place.id ? (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 0, children: env.value.state === 'proposed'
169
169
  ? `A proposed environment for ${codebase} is waiting for review.`
170
170
  : `${codebase} has no environment yet, so every run starts it bare.` }), env.value.state === 'proposed' && env.value.session ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(env.value.session), children: "Review" })) : (_jsx(Button, { size: "sm", onPress: () => setOffering(true), children: "Set up environment" }))] })) : null, note || unhonoured(kept.mode, place.id) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(kept.mode, place.id) })) : null, _jsx(Composer, { inline: true, value: draft, onChange: setDraft, onSend: () => void send(), busy: busy, placeholder: "Describe a task or ask a question", label: "Describe a task or ask a question", head: head, foot: foot })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) }), codebase ? (_jsx(SetupDialog, { repo: codebase, open: offering, onOpenChange: setOffering, onStart: setup, onSaved: () => {
171
171
  setOffering(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a page (hanzo.build) and as a component (hanzo.ai/dev).",
package/lib/switch.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function Where(): import("react").JSX.Element;
package/lib/switch.js DELETED
@@ -1,39 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /**
3
- * The rail's top left: the organization, and the projects under it.
4
- *
5
- * The account stays at the foot of the rail. Settings opens that account on
6
- * this page. Choosing an organization reloads this origin so every read is
7
- * scoped to it.
8
- */
9
- import { YStack } from '@hanzo/gui';
10
- import { MenuLabel, MenuRow, MenuRule, OrgSwitcher } from '@hanzo/ui/product';
11
- import { projects } from './api/projects.js';
12
- import { useRead } from './data.js';
13
- import { useHost, useTarget } from './host.js';
14
- import { route } from './route.js';
15
- export function Where() {
16
- const host = useHost();
17
- const t = useTarget();
18
- const signed = Boolean(host.person);
19
- const list = useRead(signed ? () => projects(t) : null, [], [t, signed]);
20
- const here = route(host.path);
21
- const current = host.org || '';
22
- const scope = {
23
- currentOrg: () => current,
24
- setCurrentOrg: (next) => host.chooseOrg?.(next),
25
- isScopedAway: () => false,
26
- hasSelectedOrg: () => Boolean(current),
27
- enterOrg: (next) => host.chooseOrg?.(next),
28
- leaveOrg: () => undefined,
29
- switchOrg: (next) => host.chooseOrg?.(next),
30
- };
31
- const rows = () => Promise.resolve((host.memberships ?? (current ? [current] : [])).map((name) => ({ name, displayName: name })));
32
- return (_jsx(OrgSwitcher, { scope: scope, orgs: (page) => (page === 0 ? rows() : Promise.resolve([])), pageSize: 50, search: false, heading: "Organization", lead: true, sub: here.kind === 'project' ? here.slug : undefined, current: current ? { name: current, displayName: current } : undefined, aria: current ? `Organization ${current}` : 'Organization', footer: (close) => (_jsxs(YStack, { gap: "$1", children: [list.value.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: "Project" }), _jsx(YStack, { role: "radiogroup", "aria-label": "Projects", gap: "$1", children: list.value.slice(0, 12).map((p) => (_jsx(MenuRow, { label: p.name || p.slug, active: here.kind === 'project' && here.slug === p.slug, onPress: () => {
33
- close();
34
- host.go(p.slug);
35
- } }, p.slug))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", onPress: () => {
36
- close();
37
- host.go('-/settings');
38
- } })] })) }));
39
- }