@hanzo/rooms 0.1.61 → 0.1.63

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/App.js CHANGED
@@ -23,6 +23,11 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
23
23
  // router and the path, and draws this only for a signed-in reader; what a
24
24
  // stranger sees is the host's.
25
25
  //
26
+ // THE ORGANIZATION COMES FIRST (Orgs.tsx), and the app mounts it itself, so no
27
+ // host draws the app without it: a reader with none makes one and a reader
28
+ // with several picks one. No plan is asked for here: that is the workspace's,
29
+ // behind its own front door.
30
+ //
26
31
  // THE SIDEBAR'S TOGGLE IS AT THE WINDOW'S TOP-LEFT. In a browser tab it leads
27
32
  // the rail. A host that is a window of its own (`bar`) has no browser around
28
33
  // it, so the app draws the window's bar: the toggle, then Back and Forward
@@ -53,6 +58,7 @@ import { useAi } from './lib/ai.js';
53
58
  import { Chat } from './Chat.js';
54
59
  import { Column } from './Column.js';
55
60
  import { isPlans, leave, useDevHost } from './dev.js';
61
+ import { Orgs } from './Orgs.js';
56
62
  import { useRooms } from './host.js';
57
63
  import { useKept } from './kept.js';
58
64
  import { Mark, Rail, Toggle } from './Rail.js';
@@ -88,6 +94,10 @@ const typing = (e) => {
88
94
  /** The builder's Settings, general section. */
89
95
  const SETTINGS = written({ kind: 'settings', section: 'general' });
90
96
  export function App({ bar } = {}) {
97
+ return (_jsx(Orgs, { children: _jsx(Window, { bar: bar }) }));
98
+ }
99
+ /** The window itself, drawn once the organization is settled. */
100
+ function Window({ bar }) {
91
101
  const [drawer, setDrawer] = useState(false);
92
102
  const shut = useCallback(() => setDrawer(false), []);
93
103
  // The sidebar shut, kept per browser under the key the builder's rail kept it under.
package/dist/Chat.js CHANGED
@@ -45,7 +45,8 @@ import { useMedia } from "@hanzo/gui";
45
45
  import { useIam, useIamToken } from "@hanzo/iam/react";
46
46
  import { bearer, hasSession, org } from "./lib/session.js";
47
47
  import { api } from "./lib/api.js";
48
- import { dev } from "./lib/host.js";
48
+ import { app, dev, go } from "./lib/host.js";
49
+ import { useRooms } from "./host.js";
49
50
  import { chats, house, paidPlan, paused, setCreditsAfterAllowance, useLimits } from "./lib/limits.js";
50
51
  import { LimitedBanner } from "@hanzo/ui/product/LimitedBanner";
51
52
  import { WEB } from "./lib/web.js";
@@ -1216,6 +1217,11 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1216
1217
  }, onCancel: () => {
1217
1218
  setAsking(null);
1218
1219
  } }));
1220
+ // Dev, from the Summary's Create file or site: a move inside the app where the
1221
+ // host's app reads the path (hanzo.ai), else Dev on hanzo.ai, as the shell's
1222
+ // Dev place goes from a host without it (hanzo.team).
1223
+ const pathed = useRooms().path !== null;
1224
+ const create = () => (pathed ? go(app("")) : window.location.assign(dev()));
1219
1225
  /** The tabs' browser, as this conversation offers it: the org's sites to start from, and a page asked about, captured or opened in Dev. */
1220
1226
  const browsing = {
1221
1227
  suggested: sites,
@@ -1268,7 +1274,7 @@ function Thread({ placeholder = message(), initial, anonymous = false, heading,
1268
1274
  e.preventDefault();
1269
1275
  // A dropped folder arrives as entries; each file under it goes up with its path.
1270
1276
  void dropped(e.dataTransfer).then((list) => hold(at, list, via));
1271
- }, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: () => window.location.assign(site("/dev")) }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs", ...browsing }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs", ...browsing }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
1277
+ }, children: [dragging ? (_jsx(YStack, { "aria-hidden": true, position: "absolute", t: 0, r: 0, b: 0, l: 0, z: 10, items: "center", justify: "center", bg: "var(--surface-scrim)", borderWidth: 2, borderStyle: "dashed", borderColor: "$borderColor", rounded: "var(--pane-round)", pointerEvents: "none", children: _jsxs(XStack, { items: "center", gap: "$2", px: "$4", py: "$2.5", rounded: "$10", bg: "$background", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(Paperclip, { size: 15, "aria-hidden": true }), _jsx(Text, { fontSize: "$4", color: "$ink", children: "Drop files to attach" })] }) })) : null, _jsxs(XStack, { "data-slot": "conversation-head", height: 52, items: "center", justify: "space-between", gap: "$3", pl: "$4", pr: "$2", shrink: 0, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, flex: 1, children: [mine ? (_jsx(Text, { fontSize: 15, lineHeight: 20, color: "$ink", numberOfLines: 1, shrink: 1, minW: 0, children: mine.title || "Untitled" })) : null, room.length ? (_jsx(XStack, { role: "list", "aria-label": "Agents in this chat", items: "center", gap: "$1.5", minW: 0, overflowX: "auto", "$platform-web": { scrollbarWidth: "none" }, children: room.map((name) => (_jsxs(XStack, { role: "listitem", items: "center", gap: "$1.5", pl: "$1", pr: "$0.5", py: "$0.5", rounded: 999, borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: [_jsx(Face, { src: faces[name]?.avatar ?? (name === withWhom ? portrait : undefined), emoji: faces[name]?.emoji ?? (name === withWhom ? persona?.emoji : undefined), name: name, size: 22 }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$color", numberOfLines: 1, display: "none", $sm: { display: "flex" }, children: name }), _jsx(Box, { render: "button", onClick: () => openAgent(name), "aria-label": `Remove ${name} from this chat`, title: `Remove ${name}`, width: 24, height: 24, rounded: 999, items: "center", justify: "center", hoverStyle: { bg: "$hover" }, children: _jsx(X, { size: 13, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) })] }, name))) })) : null] }), _jsxs(XStack, { "data-slot": "conversation-controls", items: "center", gap: 2, shrink: 0, children: [open ? _jsx(Share, { thread: open }) : null, mine ? (_jsx(ConversationMenu, { threads: alone, active: open, children: _jsx(XStack, { children: _jsx(ThreadMore, { t: mine }) }) })) : null, _jsx(Summary, { at: at, said: said, held: files, deck: deck, onOpen: () => setPanel(true), onCreate: create }), _jsx(PanelToggle, { open: panel, onToggle: () => setPanel(!panel) })] })] }), wide && aside !== null ? (_jsx(Beside, { children: _jsx(Panel, { side: deck, kinds: NONE, onHide: () => setPanel(false), label: "Tabs", ...browsing }) })) : null, !wide && panel ? _jsx(Panel, { side: deck, kinds: NONE, sheet: true, onHide: () => setPanel(false), label: "Tabs", ...browsing }) : null, isFree(model) ? (_jsxs(Text, { fontSize: "$2", color: "$soft", text: "center", maxW: MEASURE, mx: "auto", px: "$4", py: "$2", children: [freeCopy.notice, " ", _jsx(Anchor, { href: site("/legal/privacy"), children: freeCopy.noticeCta })] })) : null, _jsx(ChatSurface, { ...chat, messages: shown, send: guarded,
1272
1278
  // AN EMPTY ROOM CENTRES ITS COMPOSER. While nothing has been said the
1273
1279
  // opening (the heading and the starters) and the composer sit as one
1274
1280
  // group in the middle of the height, with or without the plan bars in
package/dist/Meet.js CHANGED
@@ -282,7 +282,7 @@ export function Meet() {
282
282
  if (transcriber.open)
283
283
  transcriber.toggle();
284
284
  setActiveMeetingRoom(null);
285
- }, children: [_jsx(PhoneOff, { size: 14 }), "Leave Call"] })] })) : (_jsx(XStack, { items: "center", gap: 8, shrink: 1, minW: "auto", children: _jsx(Button, { asChild: true, variant: "linkMuted", size: "sm", px: 0, children: _jsxs("a", { href: "https://meet.hanzo.ai", target: "_blank", rel: "noopener noreferrer", children: ["meet.hanzo.ai", _jsx(ExternalLink, { size: 12 })] }) }) }))] }), activeMeetingRoom ? (_jsxs(XStack, { flex: 1, overflow: "hidden", position: "relative", children: [_jsx(XStack, { flex: 1, minW: "auto", position: "relative", bg: "$sunken", children: meetingView === 'iframe' ? (_jsx(View, { render: _jsx("iframe", { src: `https://meet.hanzo.ai/${activeMeetingRoom}#config.prejoinPageEnabled=false&config.startWithAudioMuted=${!micOn}&config.startWithVideoMuted=${!cameraOn}`, allow: "camera; microphone; display-capture; autoplay; clipboard-write; speaker-selection; screen-wake-lock", title: `Hanzo Meet Room: ${activeMeetingRoom}` }), width: "100%", height: "100%", borderWidth: 0, bg: "$sunken" }, "stage")) : (_jsxs(View, { flex: 1, display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 12, p: 16, overflowY: "auto", bg: "$background", children: [_jsx(YStack, { bg: "$panel", rounded: 12, borderWidth: 1, borderColor: "$borderColor", position: "relative", overflow: "hidden", items: "center", justify: "center", minH: 220, children: _jsxs(YStack, { items: "center", children: [_jsx(YStack, { width: 72, height: 72, rounded: 9999, bg: "$raised", items: "center", justify: "center", mb: 8, children: _jsx(Text, { fontSize: "$8", fontWeight: "700", color: "$ink", children: "You" }) }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Alex (Host)" }), _jsx(Text, { fontSize: "$1", color: micOn ? GOOD : '$soft', children: micOn ? 'Mic Active' : 'Muted' })] }) }), invitedAgents.map((agentName) => {
285
+ }, children: [_jsx(PhoneOff, { size: 14 }), "Leave Call"] })] })) : (_jsx(XStack, { items: "center", gap: 8, shrink: 1, minW: "auto", children: _jsx(Button, { asChild: true, variant: "linkMuted", size: "sm", px: 0, children: _jsxs("a", { href: "https://meet.hanzo.ai", target: "_blank", rel: "noopener noreferrer", children: ["meet.hanzo.ai", _jsx(ExternalLink, { size: 12 })] }) }) }))] }), activeMeetingRoom ? (_jsxs(XStack, { flex: 1, overflow: "hidden", position: "relative", children: [_jsx(XStack, { flex: 1, minW: "auto", position: "relative", bg: "$sunken", children: meetingView === 'iframe' ? (_jsx(View, { render: _jsx("iframe", { src: `https://meet.hanzo.ai/${activeMeetingRoom}#config.prejoinPageEnabled=false&config.startWithAudioMuted=${!micOn}&config.startWithVideoMuted=${!cameraOn}`, allow: "camera; microphone; display-capture; autoplay; clipboard-write; speaker-selection; screen-wake-lock", title: `Hanzo Meet: ${activeMeetingRoom}` }), width: "100%", height: "100%", borderWidth: 0, bg: "$sunken" }, "stage")) : (_jsxs(View, { flex: 1, display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: 12, p: 16, overflowY: "auto", bg: "$background", children: [_jsx(YStack, { bg: "$panel", rounded: 12, borderWidth: 1, borderColor: "$borderColor", position: "relative", overflow: "hidden", items: "center", justify: "center", minH: 220, children: _jsxs(YStack, { items: "center", children: [_jsx(YStack, { width: 72, height: 72, rounded: 9999, bg: "$raised", items: "center", justify: "center", mb: 8, children: _jsx(Text, { fontSize: "$8", fontWeight: "700", color: "$ink", children: "You" }) }), _jsx(Text, { fontSize: "$2", fontWeight: "600", color: "$ink", children: "Alex (Host)" }), _jsx(Text, { fontSize: "$1", color: micOn ? GOOD : '$soft', children: micOn ? 'Mic Active' : 'Muted' })] }) }), invitedAgents.map((agentName) => {
286
286
  const voice = voiceOf(agentName);
287
287
  const isThisSpeaking = (speaking || handsFree.state === 'speaking') && activeCoPilotAgent.toLowerCase() === agentName.toLowerCase();
288
288
  return (_jsxs(YStack, { bg: "$panel", rounded: 12, borderWidth: isThisSpeaking ? 2 : 1, borderColor: isThisSpeaking ? 'var(--state-success)' : '$borderColor', position: "relative", overflow: "hidden", items: "center", justify: "center", p: 20, minH: 220, transition: "200ms", children: [_jsxs(View, { position: "relative", mb: 10, children: [_jsx(Face, { name: agentName, size: 72 }), isThisSpeaking ? (_jsx(View, { position: "absolute", b: -4, r: -4, bg: "var(--popover)", borderWidth: 1, borderColor: "var(--state-success)", rounded: 9999, p: 4, children: _jsx(Volume2, { size: 12, color: GOOD }) })) : null] }), _jsx(Text, { fontSize: "$3", fontWeight: "700", color: "$ink", mb: 2, children: agentName }), _jsxs(Text, { fontSize: "$1", color: "$soft", mb: 8, children: ["Voice: ", voice] }), _jsx(XStack, { items: "center", gap: 6, bg: "$hover", px: 10, py: 4, rounded: 12, children: isThisSpeaking ? (_jsxs(_Fragment, { children: [_jsx(AudioWave, { active: true, color: "var(--state-success)" }), _jsx(Text, { fontSize: "$1", fontWeight: "600", color: GOOD, children: "Speaking in Call" })] })) : (_jsxs(_Fragment, { children: [_jsx(Bot, { size: 11, color: "var(--muted-foreground)" }), _jsx(Text, { fontSize: "$1", fontWeight: "600", color: "$quiet", children: "AI Agent \u00B7 In Call" })] })) })] }, agentName));
package/dist/Orgs.js CHANGED
@@ -4,26 +4,28 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
4
4
  //
5
5
  // An organization owns every agent, credit and member. A signed-in reader who
6
6
  // has none is shown one thing: make one. A reader with several picks the one
7
- // they are working in when they arrive. And an organization whose plan is not
8
- // live is asked for it before any room is drawn. Only past all three does the
9
- // workspace render, and every request under it then carries that organization
10
- // (lib/auth/session.ts `scope`, hooks/useAi.ts).
7
+ // they are working in when they arrive. Past both the app renders, and every
8
+ // request under it then carries that organization (lib/auth/session.ts
9
+ // `scope`, hooks/useAi.ts).
11
10
  //
12
- // FREE IS FOR ONE PERSON. A personal organization on the Free tier opens /chat
13
- // and /dev on every host but hanzo.team; the workspace's own host asks every
14
- // organization for a paid plan. A personal organization starts on Free and may
15
- // upgrade at creation; a team organization is sold the per-seat Team plan, and
16
- // that is the plan the subscribe ask offers. Plans are the catalog's, read live
17
- // and seeded from @hanzo/plans for first paint. Checkout is hanzo.ai/pay in
18
- // this tab; it returns here, and the tier is read again on return.
11
+ // NO PLAN STANDS IN FRONT OF CHAT OR DEV. Off the front door every
12
+ // organization opens, whatever its plan: what it may spend is the API's to
13
+ // meter, per plan, on every call. The per-seat Team plan is the workspace's to
14
+ // ask for, behind its front door, and nowhere else. It used to be asked of
15
+ // every organization that was not personal and not on a live plan, on every
16
+ // host, which put each member of a team on Free, and every account filed in the
17
+ // shared sign-up organization, in front of a wall at hanzo.ai/chat. Plans are
18
+ // the catalog's, read live and seeded from @hanzo/plans for first paint.
19
+ // Checkout is hanzo.ai/pay in this tab; it returns here, and the tier is read
20
+ // again on return.
19
21
  //
20
22
  // THE FRONT DOOR. A workspace rooted at `/` (hanzo.team) draws its host's
21
23
  // Landing for a stranger at Home (`front`) and its host's Signup at /start
22
24
  // (`signup`). Behind either, a reader with no team names one, invites people
23
- // and takes the per-seat plan (`Start`, `Plan`) instead of the create dialog
24
- // and the subscribe ask; a personal organization on Free is offered a team,
25
- // and an open one is sent on (`forward`). Without those props nothing here
26
- // changes.
25
+ // and takes the per-seat plan (`Start`, `Plan`) instead of the create dialog;
26
+ // a team without a live plan is offered that plan, a personal organization on
27
+ // Free is offered a team, and an open one is sent on (`forward`). Behind the
28
+ // door, and only there, nothing opens without a live plan.
27
29
  import { dev } from './lib/host.js';
28
30
  import { useCallback, useEffect, useMemo, useState } from 'react';
29
31
  import { useIam } from '@hanzo/iam/react';
@@ -78,21 +80,19 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
78
80
  // The team being set up in this page. Once its name is taken the setup owns
79
81
  // the screen until checkout, whatever `Orgs` reads of the new organization.
80
82
  const [founding, setFounding] = useState(null);
81
- const { tier, wrong, reload } = useTier(Boolean(isAuthenticated && current), current);
83
+ // The plan is read behind the front door only: nowhere else does it decide anything.
84
+ const { tier, wrong, reload } = useTier(Boolean(door && isAuthenticated && current), current);
82
85
  // A plan is live when billing READ one for this organization and it is not the free rung.
83
86
  const live = Boolean(tier?.tier && tier.tier.name !== FREE_TIER);
84
- // A Free organization off the workspace's host is asked what kind it is, and a
85
- // personal one opens: Free is the plan a person uses the app on alone. Behind
86
- // the front door every organization without a live plan is read for its kind:
87
- // a team is offered the plan, a personal one is offered a team.
88
- const onTeam = workspace(useHost());
89
- const free = door ? Boolean(tier) && !live : !live && !onTeam && tier?.tier?.name === FREE_TIER;
87
+ // Behind the front door every organization without a live plan is read for
88
+ // its kind: a team is offered the plan, a personal one is offered a team.
89
+ const free = door && Boolean(tier) && !live;
90
90
  const personal = usePersonal(current, free);
91
91
  // A SuperAdmin IAM stepped into this organization sees its rooms whatever its
92
92
  // plan: support is there to see what the organization has, and what it spends
93
93
  // is billed to the operator's own books, never the organization's.
94
94
  const supporting = Boolean(current) && assumed() === current;
95
- const open = live || (!door && free && personal === true) || supporting;
95
+ const open = !door || live || supporting;
96
96
  const { client } = useAi();
97
97
  useEffect(() => {
98
98
  try {
@@ -195,34 +195,28 @@ export function Orgs({ children, front = false, forward, signup = false, }) {
195
195
  }
196
196
  if (!current || (members.length > 1 && !picked))
197
197
  return _jsx(Pick, { members: members, onPick: choose });
198
- // NOTHING OPENS WITHOUT A LIVE PLAN. Every branch here used to hand the room
199
- // over when the answer was anything but a plain "free": an error of any kind,
200
- // a read still in flight, a document with no plan in it. Each was a way in
201
- // without paying — a refusal, a slow network, or devtools blocking one request
202
- // all read as permission. The default is now the ask, and the room is the
203
- // exception rather than the fallback. The one exception to the plan is a
204
- // personal organization on Free, off hanzo.team, and its kind is read first.
198
+ if (open)
199
+ return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: _jsx(Held, { veiled: false, children: children }) });
200
+ // BEHIND THE FRONT DOOR NOTHING OPENS WITHOUT A LIVE PLAN. Every branch here
201
+ // used to hand the room over when the answer was anything but a plain
202
+ // "free": an error of any kind, a read still in flight, a document with no
203
+ // plan in it. Each was a way in without paying — a refusal, a slow network,
204
+ // or devtools blocking one request all read as permission. The default is the
205
+ // ask, and the room is the exception rather than the fallback.
205
206
  if (!tier && !wrong)
206
207
  return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
207
208
  if (free && personal === null)
208
209
  return _jsxs(_Fragment, { children: [_jsx(Held, { veiled: true, children: children }), _jsx(Veil, {})] });
209
- if (open)
210
- return forward ? _jsx(Forward, { to: forward }) : _jsx(_Fragment, { children: _jsx(Held, { veiled: false, children: children }) });
211
210
  const swap = () => setPicked(false);
212
- if (door) {
213
- // A billing OUTAGE is ours; the plan is still the condition.
214
- if (!tier)
215
- return _jsx(Plan, { org: current, members: members, onSwitch: swap, outage: outage(wrong), onRetry: reload });
216
- if (personal) {
217
- if (front && Landing)
218
- return _jsx(Landing, { member: true });
219
- return _jsx(Start, { onFounded: setFounding, first: false, members: members, onSwitch: swap });
220
- }
221
- return _jsx(Plan, { org: current, members: members, onSwitch: swap });
211
+ // A billing OUTAGE is ours; the plan is still the condition.
212
+ if (!tier)
213
+ return _jsx(Plan, { org: current, members: members, onSwitch: swap, outage: outage(wrong), onRetry: reload });
214
+ if (personal) {
215
+ if (front && Landing)
216
+ return _jsx(Landing, { member: true });
217
+ return _jsx(Start, { onFounded: setFounding, first: false, members: members, onSwitch: swap });
222
218
  }
223
- // A billing OUTAGE is not a bill: it is ours, and saying "subscribe" to
224
- // someone who already has would be a lie. It still does not open the room.
225
- return _jsx(Subscribe, { org: current, members: members, onSwitch: swap, outage: outage(wrong), onRetry: reload });
219
+ return _jsx(Plan, { org: current, members: members, onSwitch: swap });
226
220
  }
227
221
  /**
228
222
  * A link that asks to arrive signed in.
@@ -459,12 +453,6 @@ function CreateDialog({ onClose }) {
459
453
  function Pick({ members, onPick }) {
460
454
  return (_jsx(Page, { label: "Choose an organization", children: _jsxs(Column, { children: [_jsx(Brand, {}), _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(View, { display: "grid", gap: 10, children: members.map((o) => (_jsx(View, { render: "button", onClick: () => onPick(o), items: "flex-start", px: 16, py: 14, rounded: 12, borderWidth: 1, borderColor: "var(--border)", bg: "transparent", cursor: "pointer", children: _jsx(Text, { fontSize: "$4", color: "var(--foreground)", text: "left", children: o }) }, o))) })] }) }));
461
455
  }
462
- /** The organization exists and its plan is not live: it is offered the per-seat Team plan. */
463
- function Subscribe({ org, members, onSwitch, outage, onRetry, }) {
464
- const { plan, price } = usePlan('team');
465
- const seat = plan?.pricePerUser ? ' / seat' : '';
466
- 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] })] }) }));
467
- }
468
456
  // ── the front door's setup ─────────────────────────────────────────────────────
469
457
  /**
470
458
  * CREATE YOUR TEAM: its name, then who else is in it, then its plan.
package/dist/Rail.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // The app's rail: its products as marks down the window's left edge, in one
4
4
  // order — Chat, Dev, Library, Scheduled tasks, Customize — then More, a menu of
5
5
  // the rest, and the account at the foot. It is how the app moves between Chat
@@ -16,7 +16,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  // The sidebar's toggle leads it (`toggle`) where the host has no bar of its own:
17
17
  // a browser tab. A host that is a window draws the toggle in its bar beside Back
18
18
  // and Forward (App.tsx `bar`), and its rail starts with Chat. On a phone the
19
- // rail lies along the foot of the drawer (`flat`), the account at its end.
19
+ // rail lies along the foot of the drawer (`flat`), the account at its end; the
20
+ // drawer is too narrow for every mark, so Library, Scheduled tasks and
21
+ // Customize lead More there.
20
22
  import { Boxes, Clock, Code2, Ellipsis, FolderGit2, LayoutTemplate, Library, ListTodo, MessageSquare, PanelLeft, Puzzle, Server, SquareTerminal, Users } from 'lucide-react';
21
23
  import { XStack, YStack } from '@hanzo/gui';
22
24
  import { route } from '@hanzo/build';
@@ -69,8 +71,16 @@ export function Toggle({ open, onPress, label }) {
69
71
  export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev, go, onOrg, }) {
70
72
  const here = mode === 'dev' ? raised(at) : null;
71
73
  const to = (p) => () => go(app(p));
72
- // Everything else the app reaches, behind More: Dev's other places, then the products on hosts of their own.
74
+ // Everything else the app reaches, behind More: on a phone the raised places first, then Dev's other places, then the products on hosts of their own.
73
75
  const rest = [
76
+ ...(flat
77
+ ? [
78
+ { key: 'library', label: 'Library', icon: _jsx(Library, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.library) },
79
+ { key: 'scheduled', label: 'Scheduled tasks', icon: _jsx(Clock, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.scheduled) },
80
+ { key: 'customize', label: 'Customize', icon: _jsx(Puzzle, { size: 16, "aria-hidden": true }), onSelect: to(RAISED.customize) },
81
+ { type: 'separator', key: 'raised' },
82
+ ]
83
+ : []),
74
84
  { key: 'projects', label: 'Projects', icon: _jsx(FolderGit2, { size: 16, "aria-hidden": true }), onSelect: to('-/projects') },
75
85
  { key: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16, "aria-hidden": true }), onSelect: to('-/templates') },
76
86
  { key: 'issues', label: 'Issues', icon: _jsx(ListTodo, { size: 16, "aria-hidden": true }), onSelect: to('-/issues') },
@@ -81,5 +91,5 @@ export function Rail({ host, mode, at, open, toggle, flat = false, onChat, onDev
81
91
  { key: 'team', label: 'Team', icon: _jsx(Users, { size: 16, "aria-hidden": true }), onSelect: () => out(WORKSPACE) },
82
92
  ];
83
93
  const Stack = flat ? XStack : YStack;
84
- return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) }), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
94
+ return (_jsxs(Stack, { "data-slot": "rail", render: "nav", "aria-label": "Products", ...(flat ? { pt: '$2', mt: '$2', borderTopWidth: 1, borderColor: '$borderColor', gap: '$1' } : { width: RAIL, flex: 1, pt: '$2', pb: '$2', gap: 8 }), shrink: 0, items: "center", children: [toggle ? (_jsx(YStack, { pb: 8, children: _jsx(Toggle, { open: open, onPress: toggle }) })) : null, _jsx(Mark, { label: "Chat", on: mode === 'chat', onPress: onChat, "data-slot": "rail-chat", children: _jsx(MessageSquare, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Dev", on: mode === 'dev' && !here, onPress: onDev, "data-slot": "rail-dev", children: _jsx(Code2, { size: 18, "aria-hidden": true }) }), flat ? null : (_jsxs(_Fragment, { children: [_jsx(Mark, { label: "Library", on: here === 'library', onPress: to(RAISED.library), "data-slot": "rail-library", children: _jsx(Library, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Scheduled tasks", on: here === 'scheduled', onPress: to(RAISED.scheduled), "data-slot": "rail-scheduled", children: _jsx(Clock, { size: 18, "aria-hidden": true }) }), _jsx(Mark, { label: "Customize", on: here === 'customize', onPress: to(RAISED.customize), "data-slot": "rail-customize", children: _jsx(Puzzle, { size: 18, "aria-hidden": true }) })] })), _jsx(DropdownMenu, { placement: flat ? 'top-start' : 'right-start', minWidth: 220, trigger: _jsx(XStack, { render: "button", "aria-label": "More", ...{ title: 'More' }, "data-slot": "rail-more", width: MARK, height: MARK, rounded: "$4", items: "center", justify: "center", cursor: "pointer", opacity: 0.62, hoverStyle: { bg: '$hover', opacity: 1 }, pressStyle: { bg: '$raised' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, $touchable: { width: 44, height: 44 }, children: _jsx(Ellipsis, { size: 18, "aria-hidden": true }) }), items: rest }), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, above: flat, onOrg: onOrg })] }));
85
95
  }
package/dist/Room.js CHANGED
@@ -33,8 +33,9 @@ export function Room({ mode, front = false, children, }) {
33
33
  if (to)
34
34
  return null;
35
35
  // A session comes first, then the organization: nothing of the workspace is
36
- // drawn until the reader has signed in at hanzo.id, belongs to an
37
- // organization that is paid for, and has picked the one they are working in.
36
+ // drawn until the reader has signed in at hanzo.id and picked the
37
+ // organization they are working in, and on the workspace's own host until
38
+ // that organization's plan is live (`Orgs`).
38
39
  // Going to a room from the apex is going to hanzo.team, in this tab.
39
40
  return (_jsx(Orgs, { front: front, children: _jsx(Workspace, { mode: mode, navigate: (route) => {
40
41
  // A page of another origin — hanzo.ai's from hanzo.team — is a
package/dist/Shell.js CHANGED
@@ -577,7 +577,7 @@ function Team({ filter = "", onPick, navigate, }) {
577
577
  if (rooms.length === 0)
578
578
  return _jsx(Note, { children: "No channels yet." });
579
579
  if (shown.length === 0)
580
- return _jsxs(Note, { children: ["No room matches \u201C", filter, "\u201D."] });
580
+ return _jsxs(Note, { children: ["No channel matches \u201C", filter, "\u201D."] });
581
581
  return (_jsxs(_Fragment, { children: [favourites.length > 0 ? (_jsx(SidebarFolder, { name: "Favorites", defaultOpen: true, children: favourites.map((one) => (_jsx(SidebarItem, { active: teamKey(one) === room, icon: _jsx(Star, { size: 13, "aria-hidden": true }), onPress: () => {
582
582
  openRoom(teamKey(one));
583
583
  navigate?.(roomHref(teamKey(one)));
package/dist/cast.js CHANGED
@@ -74,7 +74,7 @@ export function brief(cast) {
74
74
  }
75
75
  const names = cast.map((one) => one.name);
76
76
  return [
77
- `There are ${cast.length} of you in this room with the user: ${join(names)}. When the user names some of you, those of you answer, each in turn; when the user names nobody, each of you answers. Each of you answers as yourself, in your own voice. Begin each contribution on its own line with your name and a colon, as in "${names[0]}: ...". Never write the user's lines. Who each of you is follows, one section per name.`,
77
+ `There are ${cast.length} of you in this conversation with the user: ${join(names)}. When the user names some of you, those of you answer, each in turn; when the user names nobody, each of you answers. Each of you answers as yourself, in your own voice. Begin each contribution on its own line with your name and a colon, as in "${names[0]}: ...". Never write the user's lines. Who each of you is follows, one section per name.`,
78
78
  ...cast.map((one) => `## ${one.name}\n\n${one.says ?? `${one.name} answers as ${one.name}.`}`),
79
79
  ].join('\n\n');
80
80
  }
package/dist/network.d.ts CHANGED
@@ -39,7 +39,8 @@ export declare function of(channel: string): Net;
39
39
  */
40
40
  export declare const speaker: (channel: string, sender?: string) => string;
41
41
  /** A room is named by its own address, not by whoever spoke in it last: the
42
- * handle for a DM, the network's room id otherwise. */
42
+ * handle for a DM, the network's room id otherwise, and the channel it is on
43
+ * where no id arrived. */
43
44
  export declare function roomName(room: {
44
45
  channel: string;
45
46
  roomId?: string;
package/dist/network.js CHANGED
@@ -184,11 +184,12 @@ export function of(channel) {
184
184
  */
185
185
  export const speaker = (channel, sender) => sender ? `@${sender}` : `Someone on ${of(channel).name}`;
186
186
  /** A room is named by its own address, not by whoever spoke in it last: the
187
- * handle for a DM, the network's room id otherwise. */
187
+ * handle for a DM, the network's room id otherwise, and the channel it is on
188
+ * where no id arrived. */
188
189
  export function roomName(room) {
189
190
  if (room.roomKind === 'dm')
190
191
  return speaker(room.channel, room.last.sender);
191
- return room.roomId ? `#${room.roomId}` : `A room on ${of(room.channel).name}`;
192
+ return room.roomId ? `#${room.roomId}` : `A channel on ${of(room.channel).name}`;
192
193
  }
193
194
  /**
194
195
  * One channel's mark, at whatever size the caller has room for.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
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",