@hanzo/build 0.2.15 → 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,10 @@ 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 builder's name, which leads to New, and under it the
76
- // organization switcher once there is an org to switch. Beside the rail from md
77
- // up, beside the drawer's button below it — one place at a time.
78
- const switcher = Boolean(host.person && (host.org || (host.memberships?.length ?? 0) > 0));
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.
79
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" }) }));
80
79
  const links = [
81
80
  { id: 'projects', label: 'Projects', icon: _jsx(Kanban, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'projects' })), active: screen === 'projects' },
@@ -107,13 +106,14 @@ function Shell() {
107
106
  ];
108
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
109
108
  ? {
110
- 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(' · '),
111
111
  onPress: () => {
112
112
  setDrawer(false);
113
113
  setMenu(true);
114
114
  },
115
115
  }
116
- : { 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 ? 92 : 48, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsxs(YStack, { position: "absolute", t: 10, l: 12, r: 8, z: 2, gap: "$2", "$max-md": { display: 'none' }, children: [name, switcher ? _jsx(Where, {}) : null] })), 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 }), switcher ? _jsx(Where, {}) : null] }), _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); } })] }));
117
117
  }
118
118
  /** Whichever pane the address names, beside a rail — the builder's or the host's. */
119
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.15",
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
- }