@hanzo/rooms 0.1.93 → 0.1.95

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/dist/Chat.js CHANGED
@@ -18,9 +18,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
18
18
  //
19
19
  // The FREE lane is about PRICE, not identity: a free turn spends no balance and
20
20
  // is data-shared in exchange. That choice is made once, at onboarding, where
21
- // the terms are linked. The line under the composer says so for as long as a
22
- // free model answers. Signing in buys the paid models and a history that
23
- // follows you; it is never the price of asking a question.
21
+ // the terms are linked. Nothing under the composer repeats it. Signing in buys
22
+ // the paid models and a history that follows you; it is never the price of
23
+ // asking a question.
24
24
  //
25
25
  // This file used to branch on `isAuthenticated` and hand a visitor a composer
26
26
  // whose only action was a trip to hanzo.id, captioned "Sign in to send". The
@@ -31,7 +31,6 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "r
31
31
  import { billed, detail, familyOf, planRequired, plainly, refused } from "./failure.js";
32
32
  import { Prose } from "./Prose.js";
33
33
  import { useAgent, useAi, useChat, } from "@hanzo/ai/react";
34
- import { isFree } from "@hanzo/ai";
35
34
  import { ASK, Chat as ChatSurface, Failure, Parts, words } from "@hanzo/ui/chat";
36
35
  import { carriedIn, carrying, compose, partsOf, said as told, weigh } from "./lib/attach.js";
37
36
  import { download, dropped, fileOf, pct, readable, refOf, retrieve } from "./lib/files.js";
@@ -70,7 +69,7 @@ import { Upgrade, useFree } from './Upgrade.js';
70
69
  import { enter, LOGIN } from './lib/destination.js';
71
70
  import { first } from './lib/first.js';
72
71
  import { BAD } from "./lib/mix.js";
73
- import { brand, site } from './where.js';
72
+ import { brand } from './where.js';
74
73
  // NO REASONING PANEL HERE. Every assistant turn opened a "Thinking" disclosure
75
74
  // listing seven steps — "Planning CSS extraction for chat styles", "Designing
76
75
  // responsive top bar grid layout" — typed into this file as a literal array.
@@ -466,8 +465,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
466
465
  * `useState(anonymous ? FREE : ENSO)` read the flag ONCE, on the first render
467
466
  * — which is the render before hydration settles, where `anonymous` is always
468
467
  * false. So every visitor opened on ENSO while the composer beside them said
469
- * "Enso Free", and `isFree(model)` was false, so the data-sharing notice
470
- * never appeared. A visitor was sending on the paid pool, because a state
468
+ * "Enso Free". A visitor was sending on the paid pool, because a state
471
469
  * initialiser cannot see a value that arrives later.
472
470
  *
473
471
  * A CHOICE, or the identity. `null` is "nobody has chosen", which is a
@@ -684,8 +682,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
684
682
  // EVERY MODEL IS CHOSEN, never asked for: one the plan does not cover still
685
683
  // picks, and the gateway answers it with the policy's refusal or from Enso,
686
684
  // which the room then shows. A turn the gateway refuses `plan_required`
687
- // opens the upgrade, titled with the model. `isFree` still drives the
688
- // free-lane notice.
685
+ // opens the upgrade, titled with the model.
689
686
  const free = useFree(!anonymous) && !held;
690
687
  const [ask, setAsk] = useState(null);
691
688
  // KEEPING THE CONVERSATION is the reader's account, not this pane's choice:
@@ -1115,8 +1112,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1115
1112
  * no longer allowed to draw. `:focus-within` would say this in CSS; the
1116
1113
  * stylesheet that would hold it cannot reach past gui's own rule, so the
1117
1114
  * state is read where the event is. */
1118
- // A free send goes. The data-share choice was made at onboarding, once,
1119
- // and the line under the composer still says what a free model does.
1115
+ // A free send goes. The data-share choice was made at onboarding, once.
1120
1116
  const guarded = speak;
1121
1117
  /**
1122
1118
  * The draft and the files, sent as one turn.
@@ -1523,5 +1519,5 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1523
1519
  empty: heading === null ? undefined : (_jsxs(YStack, { "data-slot": "opening", items: "center", width: "100%", pt: "$6", pb: "$2", ...(media.sm ? null : { flex: 1, justify: "flex-end" }), children: [room.length > 1 ? (_jsx(XStack, { items: "center", justify: "center", mb: "$4", children: room.map((name, i) => (_jsx(Box, { ml: i ? -16 : 0, rounded: 40, borderWidth: 2, borderColor: "$background", z: room.length - i, children: _jsx(Face, { src: faces[name]?.avatar, emoji: faces[name]?.emoji, name: name, size: 72 }) }, name))) })) : portrait ? (_jsx(Box, { width: 160, height: 160, rounded: 24, overflow: "hidden", borderWidth: 2, borderColor: "var(--white-15)", boxShadow: "0 10px 24px color-mix(in srgb, var(--pure-black) 70%, transparent)", mb: "$4", bg: "var(--neutral-900)", children: _jsx(View, { render: _jsx("img", { src: portrait, alt: withWhom }), width: "100%", height: "100%", objectFit: "cover" }) })) : null, room.length === 1 && !portrait ? (_jsx(Box, { mb: "$3", children: _jsx(Face, { emoji: mate?.emoji ?? persona?.emoji, name: withWhom, size: 72 }) })) : null, _jsx(Text, { render: "h1", fontSize: "$7", lineHeight: "$7", fontWeight: "700", color: "$ink", text: "center", mb: "$1", children: join(room) || heading || (firstName ? `What’s up, ${firstName}?` : "What’s up?") }), room.length === 1 && (mate?.description ?? persona?.description) ? (_jsx(Text, { fontSize: "$3", color: "$soft", text: "center", maxW: 500, mb: "$4", lineHeight: "$4", children: mate?.description ?? persona?.description })) : null] })),
1524
1520
  // The transcript reads at the same measure. `Thread` defaults to 768
1525
1521
  // itself; handing it the column here is what lets Width reach it.
1526
- column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(XStack, { items: "center", justify: "center", py: "$1", px: "$3", gap: "$2", children: isFree(model) ? (_jsxs(Text, { fontSize: "$1", color: "$soft", children: ["Free chat is data-shared to train models.", " ", _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$ink", children: "Privacy" })] })) : (_jsxs(_Fragment, { children: [_jsx(Anchor, { href: site("/legal/terms"), fontSize: "$1", color: "$soft", children: "Terms" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "\u00B7" }), _jsx(Anchor, { href: site("/legal/privacy"), fontSize: "$1", color: "$soft", children: "Privacy" })] })) }), _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
1522
+ column: { maxW: MEASURE }, composer: prompt({ ...composing, field: mention.field }, mention.state) }), _jsx(AgentMaker, {}), carry.dialog, _jsx(Upgrade, { ask: ask, onClose: () => setAsk(null) })] }));
1527
1523
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The bar over a focused page: the organization chooser, the way in, the way to
3
+ * pay, a beta application. One bar for all of them, so none draws its own.
4
+ *
5
+ * The brand at the start (`HanzoLockup`: the mark and `brand()`), going to this
6
+ * host's root; then the host's `back`, where the page has a step to go back to.
7
+ * At the end, once someone is signed in, who it is: the face, the name and the
8
+ * address, with Use another account and Log out. Signed out, nothing but the
9
+ * brand. Under gui's `md` the account folds into its face, a menu of the address
10
+ * and the same two ways out.
11
+ *
12
+ * Use another account writes this page down first (destination.ts
13
+ * `rememberDestination`), so the sign-in that follows comes back to it, as
14
+ * hanzo.ai's /join does. Log out is the account menu's own (lib/signout). The
15
+ * session is IAM's: the host mounts `IamProvider`.
16
+ */
17
+ import type { ReactNode } from 'react';
18
+ export declare function Chrome({ back }: {
19
+ back?: ReactNode;
20
+ }): import("react").JSX.Element;
package/dist/Chrome.js ADDED
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useIam } from '@hanzo/iam/react';
4
+ import { Text, View, XStack, YStack } from '@hanzo/gui';
5
+ import { Avatar, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@hanzo/ui';
6
+ import { HanzoLockup } from '@hanzogui/shell';
7
+ import { rememberDestination } from './lib/destination.js';
8
+ import { named } from './lib/session.js';
9
+ import { useSignOut } from './lib/signout.js';
10
+ import { brand } from './where.js';
11
+ /** The bar's height under the notch: hanzo.ai's masthead's. */
12
+ const HEIGHT = 60;
13
+ /** The notch's inset. gui carries a CSS function to the page unchanged; its types only name custom properties. */
14
+ const NOTCH = 'env(safe-area-inset-top)';
15
+ export function Chrome({ back }) {
16
+ const { user, isAuthenticated } = useIam();
17
+ const signOut = useSignOut();
18
+ const claimed = named();
19
+ const u = user;
20
+ const name = u?.displayName || u?.name || claimed?.name || '';
21
+ const email = u?.email || claimed?.email || '';
22
+ const avatar = typeof u?.avatar === 'string' && u.avatar.startsWith('https://') ? u.avatar : '';
23
+ const who = name || email;
24
+ const other = () => {
25
+ rememberDestination();
26
+ void signOut();
27
+ };
28
+ const out = () => void signOut();
29
+ const signed = isAuthenticated && Boolean(who);
30
+ return (_jsxs(XStack, { render: "header", "data-slot": "masthead", position: "sticky", t: 0, z: "var(--z-header)", shrink: 0, boxSizing: "content-box", height: HEIGHT, pt: NOTCH, px: "var(--page-gutter, 20px)", gap: 12, items: "center", bg: "var(--background)", borderBottomWidth: 1, borderColor: "var(--border)", children: [_jsx(Text, { render: _jsx("a", { href: "/", "aria-label": `${brand()} home` }), display: "flex", items: "center", shrink: 0, color: "var(--foreground)", textDecorationLine: "none", children: _jsx(HanzoLockup, { label: brand(), size: 22 }) }), back, _jsx(View, { flex: 1 }), signed ? (_jsxs(_Fragment, { children: [_jsxs(XStack, { render: "section", "aria-label": "Account", items: "center", gap: 12, minW: 0, "$max-md": { display: 'none' }, children: [_jsx(Avatar, { src: avatar || null, name: who, alt: "" }), _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: other, children: "Use another account" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: out, children: "Log out" })] }), _jsx(View, { display: "none", "$max-md": { display: 'flex' }, children: _jsxs(DropdownMenu, { placement: "bottom-end", offset: 8, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(XStack, { render: "button", "aria-label": `Account: ${who}`, width: 40, height: 40, rounded: 999, items: "center", justify: "center", borderWidth: 0, bg: "transparent", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: _jsx(Avatar, { src: avatar || null, name: who, alt: "" }) }) }), _jsxs(DropdownMenuContent, { minW: 240, maxW: "calc(100vw - 16px)", "aria-labelledby": undefined, "aria-label": "Account", children: [_jsx(DropdownMenuLabel, { children: _jsxs(YStack, { minW: 0, gap: 2, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: who }), name && email ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: email })) : null] }) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onSelect: other, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "Use another account" }) }), _jsx(DropdownMenuItem, { onSelect: out, children: _jsx(Text, { fontSize: "$3", color: "$ink", children: "Log out" }) })] })] }) })] })) : null] }));
31
+ }
package/dist/Orgs.js CHANGED
@@ -52,7 +52,8 @@ import { createOrg, say } from './lib/org.js';
52
52
  import { countSignup } from './lib/intent.js';
53
53
  import { BAD, mix } from './lib/mix.js';
54
54
  import { useRooms } from './host.js';
55
- import { brand, site, where } from './where.js';
55
+ import { Chrome } from './Chrome.js';
56
+ import { site, where } from './where.js';
56
57
  /** A template to set up once the organization is live. */
57
58
  const SETUP = 'hanzo:org:template';
58
59
  /** Checkout for a plan, coming back to this room. */
@@ -292,9 +293,14 @@ const FIELD = {
292
293
  bg: 'var(--background)',
293
294
  color: 'var(--foreground)',
294
295
  };
295
- /** The sheet every screen stands on: the whole viewport, scrolling, its column centred. */
296
+ /**
297
+ * The sheet every screen stands on: the whole viewport, scrolling, its column
298
+ * centred, under the focused pages' one bar (`Chrome`) once someone is signed
299
+ * in. "Signing you in" stands alone.
300
+ */
296
301
  function Page({ label, children }) {
297
- return (_jsx(YStack, { position: "fixed", inset: 0, z: SHEET, overflowY: "auto", bg: "var(--background)", pt: TOP, px: 20, pb: 48, role: "region", "aria-label": label, children: children }));
302
+ const { isAuthenticated } = useIam();
303
+ return (_jsxs(YStack, { position: "fixed", inset: 0, z: SHEET, overflowY: "auto", bg: "var(--background)", role: "region", "aria-label": label, children: [isAuthenticated ? _jsx(Chrome, {}) : null, _jsx(YStack, { pt: TOP, px: 20, pb: 48, children: children })] }));
298
304
  }
299
305
  /** The column a screen's words sit in. */
300
306
  function Column({ children }) {
@@ -331,9 +337,6 @@ function Forward({ to }) {
331
337
  function Step({ at, of }) {
332
338
  return (_jsxs(Text, { render: "p", mb: 10, fontSize: "$2", fontFamily: "$mono", color: "$soft", children: ["Step ", at, " of ", of] }));
333
339
  }
334
- function Brand() {
335
- return (_jsx(Text, { fontSize: "$6", fontWeight: "500", mb: 28, color: "var(--foreground)", children: brand() }));
336
- }
337
340
  /** The free rung's name in the billing catalog. */
338
341
  const FREE_TIER = 'free';
339
342
  /**
@@ -362,7 +365,7 @@ function SignIn({ to }) {
362
365
  function Create() {
363
366
  const [open, setOpen] = useState(false);
364
367
  const [details, setDetails] = useState(false);
365
- return (_jsxs(Page, { label: "Organizations", children: [_jsxs(Column, { children: [_jsx(Brand, {}), _jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 16, flexWrap: "wrap", children: [_jsxs(YStack, { children: [_jsx(Text, { render: "h1", fontSize: "$10", fontWeight: "500", letterSpacing: -0.32, color: "var(--text-primary)", children: "Organizations" }), _jsx(Text, { render: "p", mt: 6, fontSize: "$4", color: muted, children: "You do not belong to one yet." })] }), _jsxs(Button, { variant: "primary", size: "lg", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 16, "aria-hidden": true }), "New organization"] })] }), _jsx(Rule, {}), _jsxs(YStack, { items: "center", py: 28, children: [_jsx(Text, { render: "p", fontSize: "$6", fontWeight: "500", color: "var(--foreground)", text: "center", children: "No organizations yet" }), _jsx(Button, { variant: "linkMuted", size: "sm", mt: 8, onClick: () => setDetails((d) => !d), children: details ? '- Details' : '+ Details' }), details ? (_jsx(Text, { render: "p", mt: 12, maxW: 440, fontSize: "$4", color: muted, lineHeight: 23.25, text: "center", children: "An organization owns every agent, credit and member. Create one and you are its owner." })) : null] }), _jsx(Rule, {})] }), open ? _jsx(CreateDialog, { onClose: () => setOpen(false) }) : null] }));
368
+ return (_jsxs(Page, { label: "Organizations", children: [_jsxs(Column, { children: [_jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 16, flexWrap: "wrap", children: [_jsxs(YStack, { children: [_jsx(Text, { render: "h1", fontSize: "$10", fontWeight: "500", letterSpacing: -0.32, color: "var(--text-primary)", children: "Organizations" }), _jsx(Text, { render: "p", mt: 6, fontSize: "$4", color: muted, children: "You do not belong to one yet." })] }), _jsxs(Button, { variant: "primary", size: "lg", onClick: () => setOpen(true), children: [_jsx(Plus, { size: 16, "aria-hidden": true }), "New organization"] })] }), _jsx(Rule, {}), _jsxs(YStack, { items: "center", py: 28, children: [_jsx(Text, { render: "p", fontSize: "$6", fontWeight: "500", color: "var(--foreground)", text: "center", children: "No organizations yet" }), _jsx(Button, { variant: "linkMuted", size: "sm", mt: 8, onClick: () => setDetails((d) => !d), children: details ? '- Details' : '+ Details' }), details ? (_jsx(Text, { render: "p", mt: 12, maxW: 440, fontSize: "$4", color: muted, lineHeight: 23.25, text: "center", children: "An organization owns every agent, credit and member. Create one and you are its owner." })) : null] }), _jsx(Rule, {})] }), open ? _jsx(CreateDialog, { onClose: () => setOpen(false) }) : null] }));
366
369
  }
367
370
  /** Name it, pick what it runs and its plan: a paid plan goes to checkout, Free goes straight in. */
368
371
  function CreateDialog({ onClose }) {
@@ -580,7 +583,7 @@ function Pick({ members, current, onPick, onClose, }) {
580
583
  document.addEventListener('keydown', key);
581
584
  return () => document.removeEventListener('keydown', key);
582
585
  }, [onClose, founding]);
583
- return (_jsxs(Page, { label: "Choose an organization", children: [_jsxs(Column, { children: [_jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 12, children: [_jsx(Brand, {}), onClose ? (_jsx(Button, { variant: "ghost", size: "icon-sm", mt: -6, mr: -8, onClick: onClose, "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })) : null] }), _jsx(Heading, { title: "Choose an organization", lede: _jsx(_Fragment, { children: "Everything you open \u2014 chats, agents, credit \u2014 is this one's." }) }), _jsx(Rule, {}), _jsx(XStack, { render: "form", onSubmit: (e) => {
586
+ return (_jsxs(Page, { label: "Choose an organization", children: [_jsxs(Column, { children: [_jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 12, children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Heading, { title: "Choose an organization", lede: _jsx(_Fragment, { children: "Everything you open \u2014 chats, agents, credit \u2014 is this one's." }) }) }), onClose ? (_jsx(Button, { variant: "ghost", size: "icon-sm", mr: -8, onClick: onClose, "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })) : null] }), _jsx(Rule, {}), _jsx(XStack, { render: "form", onSubmit: (e) => {
584
587
  e.preventDefault();
585
588
  if (shown[0])
586
589
  onPick(shown[0].name);
@@ -712,7 +715,7 @@ function Found({ onClose }) {
712
715
  function Subscribe({ org, members, onSwitch, }) {
713
716
  const { plan, price } = usePlan('team');
714
717
  const seat = plan?.pricePerUser ? ' / seat' : '';
715
- return (_jsx(Page, { label: "Subscribe", children: _jsxs(Column, { children: [_jsx(Brand, {}), _jsx(Heading, { title: org, lead: 22.5, lede: plan ? `This organization runs on the ${plan.name} plan at ${price}${seat} / month, and nothing in it works until that plan is live.` : 'Its plan is not live yet.' }), _jsx(Rule, {}), _jsxs(XStack, { gap: 10, flexWrap: "wrap", children: [plan ? (_jsx(Button, { asChild: true, variant: "primary", size: "lg", children: _jsx("a", { href: checkout(plan), children: `Subscribe — ${price}${seat} / month` }) })) : null, members.length > 1 ? (_jsx(Button, { size: "lg", onClick: onSwitch, children: "Switch organization" })) : null] })] }) }));
718
+ return (_jsx(Page, { label: "Subscribe", children: _jsxs(Column, { children: [_jsx(Heading, { title: org, lead: 22.5, lede: plan ? `This organization runs on the ${plan.name} plan at ${price}${seat} / month, and nothing in it works until that plan is live.` : 'Its plan is not live yet.' }), _jsx(Rule, {}), _jsxs(XStack, { gap: 10, flexWrap: "wrap", children: [plan ? (_jsx(Button, { asChild: true, variant: "primary", size: "lg", children: _jsx("a", { href: checkout(plan), children: `Subscribe — ${price}${seat} / month` }) })) : null, members.length > 1 ? (_jsx(Button, { size: "lg", onClick: onSwitch, children: "Switch organization" })) : null] })] }) }));
716
719
  }
717
720
  // ── the front door's setup ─────────────────────────────────────────────────────
718
721
  /**
@@ -808,7 +811,7 @@ function Start({ founded = null, onFounded, first, members = [], onSwitch, }) {
808
811
  };
809
812
  if (step === 'plan' && founded)
810
813
  return _jsx(Plan, { org: founded, members: [founded], seats: 1 + sent.filter((s) => s.link).length, setup: true });
811
- return (_jsx(Page, { label: "Create your team", children: _jsxs(Column, { children: [_jsx(Brand, {}), step === 'name' ? (_jsxs(YStack, { render: "form", onSubmit: create, children: [_jsx(Step, { at: 1, of: 3 }), _jsx(Heading, { title: "Create your team", lede: "A team owns its agents, credit and members. You are its owner." }), _jsx(Rule, {}), _jsxs(View, { render: "label", display: "block", children: [_jsx(Text, { display: "block", fontSize: "$4", fontWeight: "500", mb: 8, color: "$ink", children: "Team name" }), _jsx(Text, { render: _jsx("input", { value: name, onChange: (e) => setName(e.target.value), placeholder: "Acme", autoFocus: true }), ...FIELD }), _jsx(Text, { display: "block", fontSize: "$3", color: "$soft", mt: 8, children: "What the rail and every invoice call it." })] }), refused ? (_jsx(Text, { render: "p", role: "alert", mt: 14, fontSize: "$3", color: BAD, children: refused })) : null, _jsxs(XStack, { gap: 10, flexWrap: "wrap", mt: 24, children: [_jsx(Button, { type: "submit", variant: "primary", size: "lg", disabled: busy, children: busy ? 'Creating…' : 'Continue' }), members.length > 1 && onSwitch ? (_jsx(Button, { type: "button", size: "lg", onClick: onSwitch, children: "Switch organization" })) : null] }), _jsx(Rule, {}), _jsxs(Text, { render: "p", fontSize: "$3", color: "$soft", children: ["Just you? Hanzo Chat is free for personal use at", ' ', _jsx(Text, { render: _jsx("a", { href: site('/chat') }), color: "$ink", textDecorationLine: "underline", children: "hanzo.ai/chat" }), "."] })] })) : (_jsxs(YStack, { render: "form", onSubmit: invite, children: [_jsx(Step, { at: 2, of: 3 }), _jsx(Heading, { title: "Invite your teammates", lede: "Each address gets its own single-use link to join. You can skip this and invite people later." }), _jsx(Rule, {}), _jsxs(View, { render: "label", display: "block", children: [_jsx(Text, { display: "block", fontSize: "$4", fontWeight: "500", mb: 8, color: "$ink", children: "Email addresses" }), _jsx(Text, { render: _jsx("textarea", { value: emails, onChange: (e) => setEmails(e.target.value), placeholder: "ada@acme.com, alan@acme.com", rows: 3 }), ...FIELD }), _jsx(Text, { display: "block", fontSize: "$3", color: "$soft", mt: 8, children: "Separate them with commas, spaces or new lines." })] }), sent.length ? (_jsx(YStack, { render: "ul", gap: 8, m: 0, p: 0, mt: 18, "aria-label": "Invitations", children: sent.map((s) => (_jsx(YStack, { render: "li", children: _jsxs(Text, { fontSize: "$3", color: "$soft", wordWrap: "break-word", children: [_jsx(Text, { fontWeight: "500", color: "$ink", children: s.email }), ' ', s.link ? (_jsxs(_Fragment, { children: ["invited. Their link:", ' ', _jsx(Text, { render: _jsx("a", { href: s.link }), color: "$ink", textDecorationLine: "underline", children: s.link })] })) : (_jsx(Text, { role: "alert", color: BAD, children: s.refused }))] }) }, s.email))) })) : null, _jsx(XStack, { gap: 10, flexWrap: "wrap", mt: 24, children: sent.length ? (_jsx(Button, { type: "button", variant: "primary", size: "lg", onClick: () => setStep('plan'), children: "Continue" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { type: "submit", variant: "primary", size: "lg", disabled: busy, children: busy ? 'Inviting…' : emails.trim() ? 'Invite and continue' : 'Continue' }), _jsx(Button, { type: "button", size: "lg", onClick: () => setStep('plan'), children: "Skip for now" })] })) })] }))] }) }));
814
+ return (_jsx(Page, { label: "Create your team", children: _jsx(Column, { children: step === 'name' ? (_jsxs(YStack, { render: "form", onSubmit: create, children: [_jsx(Step, { at: 1, of: 3 }), _jsx(Heading, { title: "Create your team", lede: "A team owns its agents, credit and members. You are its owner." }), _jsx(Rule, {}), _jsxs(View, { render: "label", display: "block", children: [_jsx(Text, { display: "block", fontSize: "$4", fontWeight: "500", mb: 8, color: "$ink", children: "Team name" }), _jsx(Text, { render: _jsx("input", { value: name, onChange: (e) => setName(e.target.value), placeholder: "Acme", autoFocus: true }), ...FIELD }), _jsx(Text, { display: "block", fontSize: "$3", color: "$soft", mt: 8, children: "What the rail and every invoice call it." })] }), refused ? (_jsx(Text, { render: "p", role: "alert", mt: 14, fontSize: "$3", color: BAD, children: refused })) : null, _jsxs(XStack, { gap: 10, flexWrap: "wrap", mt: 24, children: [_jsx(Button, { type: "submit", variant: "primary", size: "lg", disabled: busy, children: busy ? 'Creating…' : 'Continue' }), members.length > 1 && onSwitch ? (_jsx(Button, { type: "button", size: "lg", onClick: onSwitch, children: "Switch organization" })) : null] }), _jsx(Rule, {}), _jsxs(Text, { render: "p", fontSize: "$3", color: "$soft", children: ["Just you? Hanzo Chat is free for personal use at", ' ', _jsx(Text, { render: _jsx("a", { href: site('/chat') }), color: "$ink", textDecorationLine: "underline", children: "hanzo.ai/chat" }), "."] })] })) : (_jsxs(YStack, { render: "form", onSubmit: invite, children: [_jsx(Step, { at: 2, of: 3 }), _jsx(Heading, { title: "Invite your teammates", lede: "Each address gets its own single-use link to join. You can skip this and invite people later." }), _jsx(Rule, {}), _jsxs(View, { render: "label", display: "block", children: [_jsx(Text, { display: "block", fontSize: "$4", fontWeight: "500", mb: 8, color: "$ink", children: "Email addresses" }), _jsx(Text, { render: _jsx("textarea", { value: emails, onChange: (e) => setEmails(e.target.value), placeholder: "ada@acme.com, alan@acme.com", rows: 3 }), ...FIELD }), _jsx(Text, { display: "block", fontSize: "$3", color: "$soft", mt: 8, children: "Separate them with commas, spaces or new lines." })] }), sent.length ? (_jsx(YStack, { render: "ul", gap: 8, m: 0, p: 0, mt: 18, "aria-label": "Invitations", children: sent.map((s) => (_jsx(YStack, { render: "li", children: _jsxs(Text, { fontSize: "$3", color: "$soft", wordWrap: "break-word", children: [_jsx(Text, { fontWeight: "500", color: "$ink", children: s.email }), ' ', s.link ? (_jsxs(_Fragment, { children: ["invited. Their link:", ' ', _jsx(Text, { render: _jsx("a", { href: s.link }), color: "$ink", textDecorationLine: "underline", children: s.link })] })) : (_jsx(Text, { role: "alert", color: BAD, children: s.refused }))] }) }, s.email))) })) : null, _jsx(XStack, { gap: 10, flexWrap: "wrap", mt: 24, children: sent.length ? (_jsx(Button, { type: "button", variant: "primary", size: "lg", onClick: () => setStep('plan'), children: "Continue" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { type: "submit", variant: "primary", size: "lg", disabled: busy, children: busy ? 'Inviting…' : emails.trim() ? 'Invite and continue' : 'Continue' }), _jsx(Button, { type: "button", size: "lg", onClick: () => setStep('plan'), children: "Skip for now" })] })) })] })) }) }));
812
815
  }
813
816
  /** Where a checkout from the setup returns: the rooms' Home, on this origin. */
814
817
  const home = () => `${window.location.origin}${where().home}`;
@@ -835,7 +838,7 @@ function Plan({ org, members, onSwitch, outage, onRetry, seats: wanted = 1, setu
835
838
  ['month', 'Monthly', `${money(monthly)} / seat / month`],
836
839
  ...(yearly ? [['year', 'Annual', `${money(yearly)} / seat, billed once a year`]] : []),
837
840
  ];
838
- return (_jsx(Page, { label: "Choose your plan", children: _jsxs(Column, { children: [_jsx(Brand, {}), setup ? _jsx(Step, { at: 3, of: 3 }) : null, _jsx(Heading, { title: org, lead: 22.5, lede: outage
841
+ return (_jsx(Page, { label: "Choose your plan", children: _jsxs(Column, { children: [setup ? _jsx(Step, { at: 3, of: 3 }) : null, _jsx(Heading, { title: org, lead: 22.5, lede: outage
839
842
  ? 'Billing could not be read just now, so this team cannot open yet. That is ours to fix.'
840
843
  : plan
841
844
  ? `A team runs on the ${plan.name} plan, and nothing in it works until that plan is live.`
package/dist/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export { configure, where, site, brand } from './where.js';
12
12
  export type { Addresses } from './where.js';
13
13
  export { Rooms, Look, useRooms, useLook, useOrg } from './host.js';
14
14
  export type { Router, Link, RoomsProps } from './host.js';
15
+ export { Chrome } from './Chrome.js';
package/dist/index.js CHANGED
@@ -10,3 +10,5 @@
10
10
  */
11
11
  export { configure, where, site, brand } from './where.js';
12
12
  export { Rooms, Look, useRooms, useLook, useOrg } from './host.js';
13
+ // The bar over a focused page (the chooser, the way in, the way to pay): one for every host.
14
+ export { Chrome } from './Chrome.js';
package/dist/lib/ai.d.ts CHANGED
@@ -40,7 +40,7 @@ export declare const base: () => {
40
40
  export interface Ai {
41
41
  client: AiClient | null;
42
42
  model: string;
43
- /** Answering free, so the turn is data-shared and owes the notice. */
43
+ /** Answering on the free pool. The data-share choice was made at onboarding. */
44
44
  free: boolean;
45
45
  /** Mounted client-side. The static export always prerenders the signed-out
46
46
  * page, so nothing interactive may render until this is true. */
package/dist/lib/ai.js CHANGED
@@ -5,8 +5,8 @@
5
5
  // their account like every other turn they run.
6
6
  //
7
7
  // Signed out — the FREE POOL, anonymously. There is no account to bill and none
8
- // is needed: a free turn spends no balance, and what it costs instead is stated
9
- // in the consent block the composer asks before the first send. A widget key, if
8
+ // is needed: a free turn spends no balance. The data-share choice is made
9
+ // once, at onboarding. A widget key, if
10
10
  // one is configured, rides along so the turn attributes to the org that minted
11
11
  // it; without one the turn is simply anonymous. Either way the client EXISTS,
12
12
  // because a chat you cannot send is not a chat.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.93",
3
+ "version": "0.1.95",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",